1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "debuggeritemmanager.h"
27 #include "debuggeritem.h"
28 #include "debuggerkitinformation.h"
29
30 #include <coreplugin/dialogs/ioptionspage.h>
31 #include <coreplugin/icore.h>
32
33 #include <extensionsystem/pluginmanager.h>
34
35 #include <projectexplorer/devicesupport/devicemanager.h>
36 #include <projectexplorer/projectexplorerconstants.h>
37 #include <projectexplorer/projectexplorericons.h>
38
39 #include <utils/algorithm.h>
40 #include <utils/detailswidget.h>
41 #include <utils/environment.h>
42 #include <utils/fileutils.h>
43 #include <utils/hostosinfo.h>
44 #include <utils/pathchooser.h>
45 #include <utils/persistentsettings.h>
46 #include <utils/qtcassert.h>
47 #include <utils/qtcprocess.h>
48 #include <utils/treemodel.h>
49 #include <utils/winutils.h>
50
51 #include <QCoreApplication>
52 #include <QDebug>
53 #include <QDir>
54 #include <QFileInfo>
55 #include <QFormLayout>
56 #include <QHeaderView>
57 #include <QLabel>
58 #include <QLineEdit>
59 #include <QObject>
60 #include <QPointer>
61 #include <QPushButton>
62 #include <QTreeView>
63 #include <QWidget>
64
65 using namespace Debugger::Internal;
66 using namespace Core;
67 using namespace ProjectExplorer;
68 using namespace Utils;
69
70 namespace Debugger {
71 namespace Internal {
72
73 const char DEBUGGER_COUNT_KEY[] = "DebuggerItem.Count";
74 const char DEBUGGER_DATA_KEY[] = "DebuggerItem.";
75 const char DEBUGGER_FILE_VERSION_KEY[] = "Version";
76 const char DEBUGGER_FILENAME[] = "debuggers.xml";
77 const char debuggingToolsWikiLinkC[] = "http://wiki.qt.io/Qt_Creator_Windows_Debugging";
78
79 class DebuggerItemModel;
80
81 class DebuggerItemManagerPrivate
82 {
83 Q_DECLARE_TR_FUNCTIONS(Debugger::DebuggerItemManager)
84 public:
85 DebuggerItemManagerPrivate();
86 ~DebuggerItemManagerPrivate();
87
88 void restoreDebuggers();
89 void saveDebuggers();
90
91 void addDebugger(const DebuggerItem &item);
92 QVariant registerDebugger(const DebuggerItem &item);
93 void readDebuggers(const FilePath &fileName, bool isSystem);
94 void autoDetectCdbDebuggers();
95 void autoDetectGdbOrLldbDebuggers(const FilePath &deviceRoot,
96 const QString &detectionSource,
97 QString *logMessage = nullptr);
98 void autoDetectUvscDebuggers();
99 QString uniqueDisplayName(const QString &base);
100
101 PersistentSettingsWriter m_writer;
102 DebuggerItemModel *m_model = nullptr;
103 IOptionsPage *m_optionsPage = nullptr;
104 };
105
106 static DebuggerItemManagerPrivate *d = nullptr;
107
108 // -----------------------------------------------------------------------
109 // DebuggerItemConfigWidget
110 // -----------------------------------------------------------------------
111
112 class DebuggerItemConfigWidget : public QWidget
113 {
114 Q_DECLARE_TR_FUNCTIONS(Debugger::DebuggerItemManager)
115
116 public:
117 explicit DebuggerItemConfigWidget();
118 void load(const DebuggerItem *item);
119 void store() const;
120
121 private:
122 void binaryPathHasChanged();
123 DebuggerItem item() const;
124 void setAbis(const QStringList &abiNames);
125
126 QLineEdit *m_displayNameLineEdit;
127 QLineEdit *m_typeLineEdit;
128 QLabel *m_cdbLabel;
129 QLineEdit *m_versionLabel;
130 PathChooser *m_binaryChooser;
131 PathChooser *m_workingDirectoryChooser;
132 QLineEdit *m_abis;
133 bool m_autodetected = false;
134 DebuggerEngineType m_engineType = NoEngineType;
135 QVariant m_id;
136 };
137
138 // --------------------------------------------------------------------------
139 // DebuggerTreeItem
140 // --------------------------------------------------------------------------
141
142 class DebuggerTreeItem : public TreeItem
143 {
144 public:
DebuggerTreeItem(const DebuggerItem & item,bool changed)145 DebuggerTreeItem(const DebuggerItem &item, bool changed)
146 : m_item(item), m_orig(item), m_added(changed), m_changed(changed)
147 {}
148
data(int column,int role) const149 QVariant data(int column, int role) const override
150 {
151 switch (role) {
152 case Qt::DisplayRole:
153 switch (column) {
154 case 0: return m_item.displayName();
155 case 1: return m_item.command().toUserOutput();
156 case 2: return m_item.engineTypeName();
157 }
158 break;
159
160 case Qt::FontRole: {
161 QFont font;
162 if (m_changed)
163 font.setBold(true);
164 if (m_removed)
165 font.setStrikeOut(true);
166 return font;
167 }
168
169 case Qt::DecorationRole:
170 if (column == 0)
171 return m_item.decoration();
172 break;
173
174 case Qt::ToolTipRole:
175 return m_item.validityMessage();
176 }
177 return QVariant();
178 }
179
180 DebuggerItem m_item; // Displayed, possibly unapplied data.
181 DebuggerItem m_orig; // Stored original data.
182 bool m_added;
183 bool m_changed;
184 bool m_removed = false;
185 };
186
187 // --------------------------------------------------------------------------
188 // DebuggerItemModel
189 // --------------------------------------------------------------------------
190
191 class DebuggerItemModel : public TreeModel<TreeItem, StaticTreeItem, DebuggerTreeItem>
192 {
193 Q_DECLARE_TR_FUNCTIONS(Debugger::DebuggerOptionsPage)
194
195 public:
196 DebuggerItemModel();
197
198 QModelIndex lastIndex() const;
199 void setCurrentIndex(const QModelIndex &index);
200 void addDebugger(const DebuggerItem &item, bool changed = false);
201 void updateDebugger(const DebuggerItem &item);
202 void apply();
203 void cancel();
204 DebuggerTreeItem *currentTreeItem();
205
206 QPersistentModelIndex m_currentIndex;
207 };
208
209 template <typename Predicate>
forAllDebuggers(const Predicate & pred)210 void forAllDebuggers(const Predicate &pred)
211 {
212 d->m_model->forItemsAtLevel<2>([pred](DebuggerTreeItem *titem) {
213 pred(titem->m_item);
214 });
215 }
216
217 template <typename Predicate>
findDebugger(const Predicate & pred)218 const DebuggerItem *findDebugger(const Predicate &pred)
219 {
220 DebuggerTreeItem *titem = d->m_model->findItemAtLevel<2>([pred](DebuggerTreeItem *titem) {
221 return pred(titem->m_item);
222 });
223 return titem ? &titem->m_item : nullptr;
224 }
225
DebuggerItemModel()226 DebuggerItemModel::DebuggerItemModel()
227 {
228 setHeader({tr("Name"), tr("Path"), tr("Type")});
229 rootItem()->appendChild(
230 new StaticTreeItem({ProjectExplorer::Constants::msgAutoDetected()},
231 {ProjectExplorer::Constants::msgAutoDetectedToolTip()}));
232 rootItem()->appendChild(new StaticTreeItem(ProjectExplorer::Constants::msgManual()));
233 }
234
addDebugger(const DebuggerItem & item,bool changed)235 void DebuggerItemModel::addDebugger(const DebuggerItem &item, bool changed)
236 {
237 QTC_ASSERT(item.id().isValid(), return);
238 int group = item.isAutoDetected() ? 0 : 1;
239 rootItem()->childAt(group)->appendChild(new DebuggerTreeItem(item, changed));
240 }
241
updateDebugger(const DebuggerItem & item)242 void DebuggerItemModel::updateDebugger(const DebuggerItem &item)
243 {
244 auto matcher = [item](DebuggerTreeItem *n) { return n->m_item.m_id == item.id(); };
245 DebuggerTreeItem *treeItem = findItemAtLevel<2>(matcher);
246 QTC_ASSERT(treeItem, return);
247
248 TreeItem *parent = treeItem->parent();
249 QTC_ASSERT(parent, return);
250
251 treeItem->m_changed = treeItem->m_orig != item;
252 treeItem->m_item = item;
253 treeItem->update(); // Notify views.
254 }
255
lastIndex() const256 QModelIndex DebuggerItemModel::lastIndex() const
257 {
258 TreeItem *manualGroup = rootItem()->lastChild();
259 TreeItem *lastItem = manualGroup->lastChild();
260 return lastItem ? indexForItem(lastItem) : QModelIndex();
261 }
262
apply()263 void DebuggerItemModel::apply()
264 {
265 QList<DebuggerTreeItem *> toRemove;
266 forItemsAtLevel<2>([&toRemove](DebuggerTreeItem *titem) {
267 titem->m_added = false;
268 if (titem->m_changed) {
269 titem->m_changed = false;
270 titem->m_orig = titem->m_item;
271 }
272 if (titem->m_removed)
273 toRemove.append(titem);
274 });
275 for (DebuggerTreeItem *titem : toRemove)
276 destroyItem(titem);
277 }
278
cancel()279 void DebuggerItemModel::cancel()
280 {
281 QList<DebuggerTreeItem *> toRemove;
282 forItemsAtLevel<2>([&toRemove](DebuggerTreeItem *titem) {
283 titem->m_removed = false;
284 if (titem->m_changed) {
285 titem->m_changed = false;
286 titem->m_item = titem->m_orig;
287 }
288 if (titem->m_added)
289 toRemove.append(titem);
290 });
291 for (DebuggerTreeItem *titem : toRemove)
292 destroyItem(titem);
293 }
294
setCurrentIndex(const QModelIndex & index)295 void DebuggerItemModel::setCurrentIndex(const QModelIndex &index)
296 {
297 m_currentIndex = index;
298 }
299
currentTreeItem()300 DebuggerTreeItem *DebuggerItemModel::currentTreeItem()
301 {
302 TreeItem *treeItem = itemForIndex(m_currentIndex);
303 return treeItem && treeItem->level() == 2 ? static_cast<DebuggerTreeItem *>(treeItem) : nullptr;
304 }
305
DebuggerItemConfigWidget()306 DebuggerItemConfigWidget::DebuggerItemConfigWidget()
307 {
308 m_displayNameLineEdit = new QLineEdit(this);
309
310 m_typeLineEdit = new QLineEdit(this);
311 m_typeLineEdit->setEnabled(false);
312
313 m_binaryChooser = new PathChooser(this);
314 m_binaryChooser->setExpectedKind(PathChooser::ExistingCommand);
315 m_binaryChooser->setMinimumWidth(400);
316 m_binaryChooser->setHistoryCompleter("DebuggerPaths");
317 m_binaryChooser->setValidationFunction([this](FancyLineEdit *edit, QString *errorMessage) {
318 if (!m_binaryChooser->defaultValidationFunction()(edit, errorMessage))
319 return false;
320 DebuggerItem item;
321 item.setCommand(m_binaryChooser->filePath());
322 errorMessage->clear();
323 item.reinitializeFromFile({}, errorMessage);
324 return errorMessage->isEmpty();
325 });
326
327 m_workingDirectoryChooser = new PathChooser(this);
328 m_workingDirectoryChooser->setExpectedKind(PathChooser::Directory);
329 m_workingDirectoryChooser->setMinimumWidth(400);
330 m_workingDirectoryChooser->setHistoryCompleter("DebuggerPaths");
331
332 m_cdbLabel = new QLabel(this);
333 m_cdbLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
334 m_cdbLabel->setOpenExternalLinks(true);
335
336 m_versionLabel = new QLineEdit(this);
337 m_versionLabel->setPlaceholderText(tr("Unknown"));
338 m_versionLabel->setEnabled(false);
339
340 m_abis = new QLineEdit(this);
341 m_abis->setEnabled(false);
342
343 auto formLayout = new QFormLayout(this);
344 formLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
345 formLayout->addRow(new QLabel(tr("Name:")), m_displayNameLineEdit);
346 formLayout->addRow(m_cdbLabel);
347 formLayout->addRow(new QLabel(tr("Path:")), m_binaryChooser);
348 formLayout->addRow(new QLabel(tr("Type:")), m_typeLineEdit);
349 formLayout->addRow(new QLabel(tr("ABIs:")), m_abis);
350 formLayout->addRow(new QLabel(tr("Version:")), m_versionLabel);
351 formLayout->addRow(new QLabel(tr("Working directory:")), m_workingDirectoryChooser);
352
353 connect(m_binaryChooser, &PathChooser::pathChanged,
354 this, &DebuggerItemConfigWidget::binaryPathHasChanged);
355 connect(m_workingDirectoryChooser, &PathChooser::pathChanged,
356 this, &DebuggerItemConfigWidget::store);
357 connect(m_displayNameLineEdit, &QLineEdit::textChanged,
358 this, &DebuggerItemConfigWidget::store);
359 }
360
item() const361 DebuggerItem DebuggerItemConfigWidget::item() const
362 {
363 DebuggerItem item(m_id);
364 item.setUnexpandedDisplayName(m_displayNameLineEdit->text());
365 item.setCommand(m_binaryChooser->filePath());
366 item.setWorkingDirectory(m_workingDirectoryChooser->filePath());
367 item.setAutoDetected(m_autodetected);
368 Abis abiList;
369 const QStringList abis = m_abis->text().split(QRegularExpression("[^A-Za-z0-9-_]+"));
370 for (const QString &a : abis) {
371 if (a.isNull())
372 continue;
373 abiList << Abi::fromString(a);
374 }
375 item.setAbis(abiList);
376 item.setVersion(m_versionLabel->text());
377 item.setEngineType(m_engineType);
378 return item;
379 }
380
store() const381 void DebuggerItemConfigWidget::store() const
382 {
383 if (!m_id.isNull())
384 d->m_model->updateDebugger(item());
385 }
386
setAbis(const QStringList & abiNames)387 void DebuggerItemConfigWidget::setAbis(const QStringList &abiNames)
388 {
389 m_abis->setText(abiNames.join(", "));
390 }
391
load(const DebuggerItem * item)392 void DebuggerItemConfigWidget::load(const DebuggerItem *item)
393 {
394 m_id = QVariant(); // reset Id to avoid intermediate signal handling
395 if (!item)
396 return;
397
398 // Set values:
399 m_autodetected = item->isAutoDetected();
400
401 m_displayNameLineEdit->setEnabled(!item->isAutoDetected());
402 m_displayNameLineEdit->setText(item->unexpandedDisplayName());
403
404 m_typeLineEdit->setText(item->engineTypeName());
405
406 m_binaryChooser->setReadOnly(item->isAutoDetected());
407 m_binaryChooser->setFilePath(item->command());
408
409 m_workingDirectoryChooser->setReadOnly(item->isAutoDetected());
410 m_workingDirectoryChooser->setFilePath(item->workingDirectory());
411
412 QString text;
413 QString versionCommand;
414 if (item->engineType() == CdbEngineType) {
415 const bool is64bit = is64BitWindowsSystem();
416 const QString versionString = is64bit ? tr("64-bit version") : tr("32-bit version");
417 //: Label text for path configuration. %2 is "x-bit version".
418 text = "<html><body><p>"
419 + tr("Specify the path to the "
420 "<a href=\"%1\">Windows Console Debugger executable</a>"
421 " (%2) here.").arg(QLatin1String(debuggingToolsWikiLinkC), versionString)
422 + "</p></body></html>";
423 versionCommand = "-version";
424 } else {
425 versionCommand = "--version";
426 }
427
428 m_cdbLabel->setText(text);
429 m_cdbLabel->setVisible(!text.isEmpty());
430 m_binaryChooser->setCommandVersionArguments(QStringList(versionCommand));
431 m_versionLabel->setText(item->version());
432 setAbis(item->abiNames());
433 m_engineType = item->engineType();
434 m_id = item->id();
435 }
436
binaryPathHasChanged()437 void DebuggerItemConfigWidget::binaryPathHasChanged()
438 {
439 // Ignore change if this is no valid DebuggerItem
440 if (!m_id.isValid())
441 return;
442
443 DebuggerItem tmp;
444 if (m_binaryChooser->filePath().isExecutableFile()) {
445 tmp = item();
446 tmp.reinitializeFromFile();
447 }
448
449 setAbis(tmp.abiNames());
450 m_versionLabel->setText(tmp.version());
451 m_engineType = tmp.engineType();
452 m_typeLineEdit->setText(tmp.engineTypeName());
453
454 store();
455 }
456
457 // --------------------------------------------------------------------------
458 // DebuggerConfigWidget
459 // --------------------------------------------------------------------------
460
461 class DebuggerConfigWidget : public IOptionsPageWidget
462 {
463 Q_DECLARE_TR_FUNCTIONS(Debugger::DebuggerOptionsPage)
464 public:
DebuggerConfigWidget()465 DebuggerConfigWidget()
466 {
467 m_addButton = new QPushButton(tr("Add"), this);
468
469 m_cloneButton = new QPushButton(tr("Clone"), this);
470 m_cloneButton->setEnabled(false);
471
472 m_delButton = new QPushButton(this);
473 m_delButton->setEnabled(false);
474
475 m_container = new DetailsWidget(this);
476 m_container->setState(DetailsWidget::NoSummary);
477 m_container->setVisible(false);
478
479 m_debuggerView = new QTreeView(this);
480 m_debuggerView->setModel(d->m_model);
481 m_debuggerView->setUniformRowHeights(true);
482 m_debuggerView->setSelectionMode(QAbstractItemView::SingleSelection);
483 m_debuggerView->setSelectionBehavior(QAbstractItemView::SelectRows);
484 m_debuggerView->expandAll();
485
486 auto header = m_debuggerView->header();
487 header->setStretchLastSection(false);
488 header->setSectionResizeMode(0, QHeaderView::ResizeToContents);
489 header->setSectionResizeMode(1, QHeaderView::ResizeToContents);
490 header->setSectionResizeMode(2, QHeaderView::Stretch);
491
492 auto buttonLayout = new QVBoxLayout();
493 buttonLayout->setSpacing(6);
494 buttonLayout->setContentsMargins(0, 0, 0, 0);
495 buttonLayout->addWidget(m_addButton);
496 buttonLayout->addWidget(m_cloneButton);
497 buttonLayout->addWidget(m_delButton);
498 buttonLayout->addItem(new QSpacerItem(10, 40, QSizePolicy::Minimum, QSizePolicy::Expanding));
499
500 auto verticalLayout = new QVBoxLayout();
501 verticalLayout->addWidget(m_debuggerView);
502 verticalLayout->addWidget(m_container);
503
504 auto horizontalLayout = new QHBoxLayout(this);
505 horizontalLayout->addLayout(verticalLayout);
506 horizontalLayout->addLayout(buttonLayout);
507
508 connect(m_debuggerView->selectionModel(), &QItemSelectionModel::currentChanged,
509 this, &DebuggerConfigWidget::currentDebuggerChanged, Qt::QueuedConnection);
510
511 connect(m_addButton, &QAbstractButton::clicked,
512 this, &DebuggerConfigWidget::addDebugger, Qt::QueuedConnection);
513 connect(m_cloneButton, &QAbstractButton::clicked,
514 this, &DebuggerConfigWidget::cloneDebugger, Qt::QueuedConnection);
515 connect(m_delButton, &QAbstractButton::clicked,
516 this, &DebuggerConfigWidget::removeDebugger, Qt::QueuedConnection);
517
518 m_itemConfigWidget = new DebuggerItemConfigWidget;
519 m_container->setWidget(m_itemConfigWidget);
520 updateButtons();
521 }
522
apply()523 void apply() final
524 {
525 m_itemConfigWidget->store();
526 d->m_model->apply();
527 }
528
finish()529 void finish() final
530 {
531 d->m_model->cancel();
532 }
533
534 void cloneDebugger();
535 void addDebugger();
536 void removeDebugger();
537 void currentDebuggerChanged(const QModelIndex &newCurrent);
538 void updateButtons();
539
540 QTreeView *m_debuggerView;
541 QPushButton *m_addButton;
542 QPushButton *m_cloneButton;
543 QPushButton *m_delButton;
544 DetailsWidget *m_container;
545 DebuggerItemConfigWidget *m_itemConfigWidget;
546 };
547
cloneDebugger()548 void DebuggerConfigWidget::cloneDebugger()
549 {
550 DebuggerTreeItem *treeItem = d->m_model->currentTreeItem();
551 if (!treeItem)
552 return;
553
554 DebuggerItem *item = &treeItem->m_item;
555 DebuggerItem newItem;
556 newItem.createId();
557 newItem.setCommand(item->command());
558 newItem.setUnexpandedDisplayName(d->uniqueDisplayName(tr("Clone of %1").arg(item->displayName())));
559 newItem.reinitializeFromFile();
560 newItem.setAutoDetected(false);
561 d->m_model->addDebugger(newItem, true);
562 m_debuggerView->setCurrentIndex(d->m_model->lastIndex());
563 }
564
addDebugger()565 void DebuggerConfigWidget::addDebugger()
566 {
567 DebuggerItem item;
568 item.createId();
569 item.setEngineType(NoEngineType);
570 item.setUnexpandedDisplayName(d->uniqueDisplayName(tr("New Debugger")));
571 item.setAutoDetected(false);
572 d->m_model->addDebugger(item, true);
573 m_debuggerView->setCurrentIndex(d->m_model->lastIndex());
574 }
575
removeDebugger()576 void DebuggerConfigWidget::removeDebugger()
577 {
578 DebuggerTreeItem *treeItem = d->m_model->currentTreeItem();
579 QTC_ASSERT(treeItem, return);
580 treeItem->m_removed = !treeItem->m_removed;
581 treeItem->update();
582 updateButtons();
583 }
584
currentDebuggerChanged(const QModelIndex & newCurrent)585 void DebuggerConfigWidget::currentDebuggerChanged(const QModelIndex &newCurrent)
586 {
587 d->m_model->setCurrentIndex(newCurrent);
588 updateButtons();
589 }
590
updateButtons()591 void DebuggerConfigWidget::updateButtons()
592 {
593 DebuggerTreeItem *titem = d->m_model->currentTreeItem();
594 DebuggerItem *item = titem ? &titem->m_item : nullptr;
595
596 m_itemConfigWidget->load(item);
597 m_container->setVisible(item != nullptr);
598 m_cloneButton->setEnabled(item && item->isValid() && item->canClone());
599 m_delButton->setEnabled(item && !item->isAutoDetected());
600 m_delButton->setText(item && titem->m_removed ? tr("Restore") : tr("Remove"));
601 }
602
603 // --------------------------------------------------------------------------
604 // DebuggerOptionsPage
605 // --------------------------------------------------------------------------
606
607 class DebuggerOptionsPage : public Core::IOptionsPage
608 {
609 Q_DECLARE_TR_FUNCTIONS(Debugger::DebuggerOptionsPage)
610
611 public:
DebuggerOptionsPage()612 DebuggerOptionsPage() {
613 setId(ProjectExplorer::Constants::DEBUGGER_SETTINGS_PAGE_ID);
614 setDisplayName(tr("Debuggers"));
615 setCategory(ProjectExplorer::Constants::KITS_SETTINGS_CATEGORY);
616 setWidgetCreator([] { return new DebuggerConfigWidget; });
617 }
618 };
619
autoDetectCdbDebuggers()620 void DebuggerItemManagerPrivate::autoDetectCdbDebuggers()
621 {
622 FilePaths cdbs;
623
624 const QStringList programDirs = {
625 QString::fromLocal8Bit(qgetenv("ProgramFiles")),
626 QString::fromLocal8Bit(qgetenv("ProgramFiles(x86)")),
627 QString::fromLocal8Bit(qgetenv("ProgramW6432"))
628 };
629
630 QFileInfoList kitFolders;
631
632 for (const QString &dirName : programDirs) {
633 if (dirName.isEmpty())
634 continue;
635 const QDir dir(dirName);
636 // Windows SDK's starting from version 8 live in
637 // "ProgramDir\Windows Kits\<version>"
638 const QString windowsKitsFolderName = "Windows Kits";
639 if (dir.exists(windowsKitsFolderName)) {
640 QDir windowKitsFolder = dir;
641 if (windowKitsFolder.cd(windowsKitsFolderName)) {
642 // Check in reverse order (latest first)
643 kitFolders.append(windowKitsFolder.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot,
644 QDir::Time | QDir::Reversed));
645 }
646 }
647
648 // Pre Windows SDK 8: Check 'Debugging Tools for Windows'
649 for (const QFileInfo &fi : dir.entryInfoList({"Debugging Tools for Windows*"},
650 QDir::Dirs | QDir::NoDotAndDotDot)) {
651 const FilePath filePath = FilePath::fromFileInfo(fi).pathAppended("cdb.exe");
652 if (!cdbs.contains(filePath))
653 cdbs.append(filePath);
654 }
655 }
656
657
658 constexpr char RootVal[] = "KitsRoot";
659 constexpr char RootVal81[] = "KitsRoot81";
660 constexpr char RootVal10[] = "KitsRoot10";
661 const QSettings installedRoots(
662 "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots",
663 QSettings::NativeFormat);
664 for (auto rootVal : {RootVal, RootVal81, RootVal10}) {
665 QFileInfo root(installedRoots.value(QLatin1String(rootVal)).toString());
666 if (root.exists() && !kitFolders.contains(root))
667 kitFolders.append(root);
668 }
669
670 for (const QFileInfo &kitFolderFi : kitFolders) {
671 const QString path = kitFolderFi.absoluteFilePath();
672 const QFileInfo cdb32(path + "/Debuggers/x86/cdb.exe");
673 if (cdb32.isExecutable())
674 cdbs.append(FilePath::fromString(cdb32.absoluteFilePath()));
675 const QFileInfo cdb64(path + "/Debuggers/x64/cdb.exe");
676 if (cdb64.isExecutable())
677 cdbs.append(FilePath::fromString(cdb64.absoluteFilePath()));
678 }
679
680 for (const FilePath &cdb : qAsConst(cdbs)) {
681 if (DebuggerItemManager::findByCommand(cdb))
682 continue;
683 DebuggerItem item;
684 item.createId();
685 item.setAutoDetected(true);
686 item.setAbis(Abi::abisOfBinary(cdb));
687 item.setCommand(cdb);
688 item.setEngineType(CdbEngineType);
689 item.setUnexpandedDisplayName(uniqueDisplayName(tr("Auto-detected CDB at %1").arg(cdb.toUserOutput())));
690 item.reinitializeFromFile(); // collect version number
691 m_model->addDebugger(item);
692 }
693 }
694
searchGdbPathsFromRegistry()695 static Utils::FilePaths searchGdbPathsFromRegistry()
696 {
697 if (!HostOsInfo::isWindowsHost())
698 return {};
699
700 // Registry token for the "GNU Tools for ARM Embedded Processors".
701 static const char kRegistryToken[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\" \
702 "Windows\\CurrentVersion\\Uninstall\\";
703
704 Utils::FilePaths searchPaths;
705
706 QSettings registry(kRegistryToken, QSettings::NativeFormat);
707 const auto productGroups = registry.childGroups();
708 for (const QString &productKey : productGroups) {
709 if (!productKey.startsWith("GNU Tools for ARM Embedded Processors"))
710 continue;
711 registry.beginGroup(productKey);
712 QString uninstallFilePath = registry.value("UninstallString").toString();
713 if (uninstallFilePath.startsWith(QLatin1Char('"')))
714 uninstallFilePath.remove(0, 1);
715 if (uninstallFilePath.endsWith(QLatin1Char('"')))
716 uninstallFilePath.remove(uninstallFilePath.size() - 1, 1);
717 registry.endGroup();
718
719 const QString toolkitRootPath = QFileInfo(uninstallFilePath).path();
720 const QString toolchainPath = toolkitRootPath + QLatin1String("/bin");
721 searchPaths.push_back(FilePath::fromString(toolchainPath));
722 }
723
724 return searchPaths;
725 }
726
autoDetectGdbOrLldbDebuggers(const FilePath & deviceRoot,const QString & detectionSource,QString * logMessage)727 void DebuggerItemManagerPrivate::autoDetectGdbOrLldbDebuggers(const FilePath &deviceRoot,
728 const QString &detectionSource,
729 QString *logMessage)
730 {
731 const QStringList filters = {"gdb-i686-pc-mingw32", "gdb-i686-pc-mingw32.exe", "gdb",
732 "gdb.exe", "lldb", "lldb.exe", "lldb-[1-9]*",
733 "arm-none-eabi-gdb-py.exe"};
734
735 // DebuggerItem result;
736 // result.setAutoDetected(true);
737 // result.setDisplayName(tr("Auto-detected for Tool Chain %1").arg(tc->displayName()));
738 /*
739 // Check suggestions from the SDK.
740 Environment env = Environment::systemEnvironment();
741 if (tc) {
742 tc->addToEnvironment(env); // Find MinGW gdb in toolchain environment.
743 QString path = tc->suggestedDebugger().toString(); // Won't compile
744 if (!path.isEmpty()) {
745 const QFileInfo fi(path);
746 if (!fi.isAbsolute())
747 path = env.searchInPath(path);
748 result.command = FileName::fromString(path);
749 result.engineType = engineTypeFromBinary(path);
750 return maybeAddDebugger(result, false);
751 }
752 }
753 */
754
755 IDevice::ConstPtr device = DeviceManager::deviceForPath(deviceRoot);
756 QTC_ASSERT(device, return);
757
758 FilePaths suspects;
759
760 if (device->osType() == OsTypeMac) {
761 QtcProcess proc;
762 proc.setTimeoutS(2);
763 proc.setCommand({"xcrun", {"--find", "lldb"}});
764 proc.runBlocking();
765 // FIXME:
766 if (proc.result() == QtcProcess::FinishedWithSuccess) {
767 QString lPath = proc.allOutput().trimmed();
768 if (!lPath.isEmpty()) {
769 const QFileInfo fi(lPath);
770 if (fi.exists() && fi.isExecutable() && !fi.isDir())
771 suspects.append(FilePath::fromString(fi.absoluteFilePath()));
772 }
773 }
774 }
775
776 FilePaths paths = device->systemEnvironment().path();
777 if (!deviceRoot.needsDevice())
778 paths.append(searchGdbPathsFromRegistry());
779
780 paths = Utils::filteredUnique(paths);
781
782 for (const FilePath &path : paths) {
783 const FilePath globalPath = path.onDevice(deviceRoot);
784 suspects.append(device->directoryEntries(globalPath, filters, QDir::Files | QDir::Executable));
785 }
786
787 QStringList logMessages{tr("Searching debuggers...")};
788 for (const FilePath &command : qAsConst(suspects)) {
789 const auto commandMatches = [command](const DebuggerTreeItem *titem) {
790 return titem->m_item.command() == command;
791 };
792 if (DebuggerTreeItem *existingItem = m_model->findItemAtLevel<2>(commandMatches)) {
793 if (command.lastModified() != existingItem->m_item.lastModified())
794 existingItem->m_item.reinitializeFromFile();
795 continue;
796 }
797 DebuggerItem item;
798 item.createId();
799 item.setDetectionSource(detectionSource);
800 // Intentionally set items with non-empty source as manual for now to
801 // give the user a chance to remove them. FIXME: Think of a better way.
802 item.setAutoDetected(detectionSource.isEmpty());
803 item.setCommand(command);
804 item.reinitializeFromFile();
805 if (item.engineType() == NoEngineType)
806 continue;
807 //: %1: Debugger engine type (GDB, LLDB, CDB...), %2: Path
808 const QString name = detectionSource.isEmpty() ? tr("System %1 at %2") : tr("Detected %1 at %2");
809 item.setUnexpandedDisplayName(name.arg(item.engineTypeName()).arg(command.toUserOutput()));
810 m_model->addDebugger(item);
811 logMessages.append(tr("Found: \"%1\"").arg(command.toUserOutput()));
812 }
813 if (logMessage)
814 *logMessage = logMessages.join('\n');
815 }
816
autoDetectUvscDebuggers()817 void DebuggerItemManagerPrivate::autoDetectUvscDebuggers()
818 {
819 if (!HostOsInfo::isWindowsHost())
820 return;
821
822 // Registry token for the "KEIL uVision" instance.
823 static const char kRegistryToken[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\" \
824 "Windows\\CurrentVersion\\Uninstall\\Keil \u00B5Vision4";
825
826 QSettings registry(QLatin1String(kRegistryToken), QSettings::NativeFormat);
827 const auto productGroups = registry.childGroups();
828 for (const QString &productKey : productGroups) {
829 if (!productKey.startsWith("App"))
830 continue;
831 registry.beginGroup(productKey);
832 const QDir rootPath(registry.value("Directory").toString());
833 registry.endGroup();
834 const FilePath uVision = FilePath::fromString(
835 rootPath.absoluteFilePath("UV4/UV4.exe"));
836 if (!uVision.exists())
837 continue;
838 if (DebuggerItemManager::findByCommand(uVision))
839 continue;
840
841 QString errorMsg;
842 const QString uVisionVersion = winGetDLLVersion(
843 WinDLLFileVersion, uVision.toString(), &errorMsg);
844
845 DebuggerItem item;
846 item.createId();
847 item.setAutoDetected(true);
848 item.setCommand(uVision);
849 item.setVersion(uVisionVersion);
850 item.setEngineType(UvscEngineType);
851 item.setUnexpandedDisplayName(
852 uniqueDisplayName(tr("Auto-detected uVision at %1")
853 .arg(uVision.toUserOutput())));
854 m_model->addDebugger(item);
855 }
856 }
857
userSettingsFileName()858 static FilePath userSettingsFileName()
859 {
860 return ICore::userResourcePath(DEBUGGER_FILENAME);
861 }
862
DebuggerItemManagerPrivate()863 DebuggerItemManagerPrivate::DebuggerItemManagerPrivate()
864 : m_writer(userSettingsFileName(), "QtCreatorDebuggers")
865 {
866 d = this;
867 m_model = new DebuggerItemModel;
868 m_optionsPage = new DebuggerOptionsPage;
869 ExtensionSystem::PluginManager::addObject(m_optionsPage);
870 restoreDebuggers();
871 }
872
~DebuggerItemManagerPrivate()873 DebuggerItemManagerPrivate::~DebuggerItemManagerPrivate()
874 {
875 ExtensionSystem::PluginManager::removeObject(m_optionsPage);
876 delete m_optionsPage;
877 delete m_model;
878 }
879
uniqueDisplayName(const QString & base)880 QString DebuggerItemManagerPrivate::uniqueDisplayName(const QString &base)
881 {
882 const DebuggerItem *item = findDebugger([base](const DebuggerItem &item) {
883 return item.unexpandedDisplayName() == base;
884 });
885 return item ? uniqueDisplayName(base + " (1)") : base;
886 }
887
registerDebugger(const DebuggerItem & item)888 QVariant DebuggerItemManagerPrivate::registerDebugger(const DebuggerItem &item)
889 {
890 // Try re-using existing item first.
891 DebuggerTreeItem *titem = m_model->findItemAtLevel<2>([item](DebuggerTreeItem *titem) {
892 const DebuggerItem &d = titem->m_item;
893 return d.command() == item.command()
894 && d.isAutoDetected() == item.isAutoDetected()
895 && d.engineType() == item.engineType()
896 && d.unexpandedDisplayName() == item.unexpandedDisplayName()
897 && d.abis() == item.abis();
898 });
899 if (titem)
900 return titem->m_item.id();
901
902 // If item already has an id, use it. Otherwise, create a new id.
903 DebuggerItem di = item;
904 if (!di.id().isValid())
905 di.createId();
906
907 m_model->addDebugger(di);
908 return di.id();
909 }
910
readDebuggers(const FilePath & fileName,bool isSystem)911 void DebuggerItemManagerPrivate::readDebuggers(const FilePath &fileName, bool isSystem)
912 {
913 PersistentSettingsReader reader;
914 if (!reader.load(fileName))
915 return;
916 QVariantMap data = reader.restoreValues();
917
918 // Check version
919 int version = data.value(DEBUGGER_FILE_VERSION_KEY, 0).toInt();
920 if (version < 1)
921 return;
922
923 int count = data.value(DEBUGGER_COUNT_KEY, 0).toInt();
924 for (int i = 0; i < count; ++i) {
925 const QString key = DEBUGGER_DATA_KEY + QString::number(i);
926 if (!data.contains(key))
927 continue;
928 const QVariantMap dbMap = data.value(key).toMap();
929 DebuggerItem item(dbMap);
930 if (isSystem) {
931 item.setAutoDetected(true);
932 // SDK debuggers are always considered to be up-to-date, so no need to recheck them.
933 } else {
934 // User settings.
935 if (item.isAutoDetected()) {
936 if (!item.isValid() || item.engineType() == NoEngineType) {
937 qWarning() << QString("DebuggerItem \"%1\" (%2) read from \"%3\" dropped since it is not valid.")
938 .arg(item.command().toUserOutput(), item.id().toString(), fileName.toUserOutput());
939 continue;
940 }
941 if (!item.command().toFileInfo().isExecutable()) {
942 qWarning() << QString("DebuggerItem \"%1\" (%2) read from \"%3\" dropped since the command is not executable.")
943 .arg(item.command().toUserOutput(), item.id().toString(), fileName.toUserOutput());
944 continue;
945 }
946 }
947
948 }
949 registerDebugger(item);
950 }
951 }
952
restoreDebuggers()953 void DebuggerItemManagerPrivate::restoreDebuggers()
954 {
955 // Read debuggers from SDK
956 readDebuggers(ICore::installerResourcePath(DEBUGGER_FILENAME), true);
957
958 // Read all debuggers from user file.
959 readDebuggers(userSettingsFileName(), false);
960
961 // Auto detect current.
962 autoDetectCdbDebuggers();
963 autoDetectGdbOrLldbDebuggers({}, {});
964 autoDetectUvscDebuggers();
965 }
966
saveDebuggers()967 void DebuggerItemManagerPrivate::saveDebuggers()
968 {
969 QVariantMap data;
970 data.insert(DEBUGGER_FILE_VERSION_KEY, 1);
971
972 int count = 0;
973 forAllDebuggers([&count, &data](DebuggerItem &item) {
974 if (item.isValid() && item.engineType() != NoEngineType) {
975 QVariantMap tmp = item.toMap();
976 if (!tmp.isEmpty()) {
977 data.insert(DEBUGGER_DATA_KEY + QString::number(count), tmp);
978 ++count;
979 }
980 }
981 });
982 data.insert(DEBUGGER_COUNT_KEY, count);
983 m_writer.save(data, ICore::dialogParent());
984
985 // Do not save default debuggers as they are set by the SDK.
986 }
987
988 } // namespace Internal
989
990 // --------------------------------------------------------------------------
991 // DebuggerItemManager
992 // --------------------------------------------------------------------------
993
DebuggerItemManager()994 DebuggerItemManager::DebuggerItemManager()
995 {
996 new DebuggerItemManagerPrivate;
997 connect(ICore::instance(), &ICore::saveSettingsRequested,
998 this, [] { d->saveDebuggers(); });
999 }
1000
~DebuggerItemManager()1001 DebuggerItemManager::~DebuggerItemManager()
1002 {
1003 delete d;
1004 }
1005
debuggers()1006 const QList<DebuggerItem> DebuggerItemManager::debuggers()
1007 {
1008 QList<DebuggerItem> result;
1009 forAllDebuggers([&result](const DebuggerItem &item) { result.append(item); });
1010 return result;
1011 }
1012
findByCommand(const FilePath & command)1013 const DebuggerItem *DebuggerItemManager::findByCommand(const FilePath &command)
1014 {
1015 return findDebugger([command](const DebuggerItem &item) {
1016 return item.command() == command;
1017 });
1018 }
1019
findById(const QVariant & id)1020 const DebuggerItem *DebuggerItemManager::findById(const QVariant &id)
1021 {
1022 return findDebugger([id](const DebuggerItem &item) {
1023 return item.id() == id;
1024 });
1025 }
1026
findByEngineType(DebuggerEngineType engineType)1027 const DebuggerItem *DebuggerItemManager::findByEngineType(DebuggerEngineType engineType)
1028 {
1029 return findDebugger([engineType](const DebuggerItem &item) {
1030 return item.engineType() == engineType;
1031 });
1032 }
1033
registerDebugger(const DebuggerItem & item)1034 QVariant DebuggerItemManager::registerDebugger(const DebuggerItem &item)
1035 {
1036 return d->registerDebugger(item);
1037 }
1038
deregisterDebugger(const QVariant & id)1039 void DebuggerItemManager::deregisterDebugger(const QVariant &id)
1040 {
1041 d->m_model->forItemsAtLevel<2>([id](DebuggerTreeItem *titem) {
1042 if (titem->m_item.id() == id)
1043 d->m_model->destroyItem(titem);
1044 });
1045 }
1046
autoDetectDebuggersForDevice(const FilePath & deviceRoot,const QString & detectionSource,QString * logMessage)1047 void DebuggerItemManager::autoDetectDebuggersForDevice(const FilePath &deviceRoot,
1048 const QString &detectionSource,
1049 QString *logMessage)
1050 {
1051 d->autoDetectGdbOrLldbDebuggers(deviceRoot, detectionSource, logMessage);
1052 }
1053
removeDetectedDebuggers(const QString & detectionSource,QString * logMessage)1054 void DebuggerItemManager::removeDetectedDebuggers(const QString &detectionSource,
1055 QString *logMessage)
1056 {
1057 QStringList logMessages{tr("Removing debugger entries...")};
1058 d->m_model->forItemsAtLevel<2>([detectionSource, &logMessages](DebuggerTreeItem *titem) {
1059 if (titem->m_item.detectionSource() == detectionSource) {
1060 logMessages.append(tr("Removed \"%1\"").arg(titem->m_item.displayName()));
1061 d->m_model->destroyItem(titem);
1062 return;
1063 }
1064 // FIXME: These items appeared in early docker development. Ok to remove for Creator 7.0.
1065 FilePath filePath = titem->m_item.command();
1066 if (filePath.scheme() + ':' + filePath.host() == detectionSource) {
1067 logMessages.append(tr("Removed \"%1\"").arg(titem->m_item.displayName()));
1068 d->m_model->destroyItem(titem);
1069 }
1070 });
1071 if (logMessage)
1072 *logMessage = logMessages.join('\n');
1073 }
1074
listDetectedDebuggers(const QString & detectionSource,QString * logMessage)1075 void DebuggerItemManager::listDetectedDebuggers(const QString &detectionSource, QString *logMessage)
1076 {
1077 QTC_ASSERT(logMessage, return);
1078 QStringList logMessages{tr("Debuggers:")};
1079 d->m_model->forItemsAtLevel<2>([detectionSource, &logMessages](DebuggerTreeItem *titem) {
1080 if (titem->m_item.detectionSource() == detectionSource)
1081 logMessages.append(titem->m_item.displayName());
1082 });
1083 *logMessage = logMessages.join('\n');
1084 }
1085
1086 } // namespace Debugger
1087