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