2023-10-16 10:21:50 +02:00
|
|
|
// Copyright (C) 2023 The Qt Company Ltd.
|
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
|
|
|
|
|
|
#include "pythonkitaspect.h"
|
|
|
|
|
|
|
|
|
|
#include "pythonconstants.h"
|
|
|
|
|
#include "pythonsettings.h"
|
|
|
|
|
#include "pythontr.h"
|
2023-11-22 11:09:41 +01:00
|
|
|
#include "pythonutils.h"
|
2023-10-16 10:21:50 +02:00
|
|
|
|
|
|
|
|
#include <projectexplorer/kitmanager.h>
|
|
|
|
|
|
|
|
|
|
#include <utils/guard.h>
|
|
|
|
|
#include <utils/layoutbuilder.h>
|
2023-11-22 11:09:41 +01:00
|
|
|
#include <utils/process.h>
|
2023-10-16 10:21:50 +02:00
|
|
|
|
|
|
|
|
#include <QComboBox>
|
|
|
|
|
|
|
|
|
|
using namespace ProjectExplorer;
|
|
|
|
|
using namespace Utils;
|
|
|
|
|
|
|
|
|
|
namespace Python {
|
|
|
|
|
|
|
|
|
|
using namespace Internal;
|
|
|
|
|
|
|
|
|
|
class PythonKitAspectImpl final : public KitAspect
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
PythonKitAspectImpl(Kit *kit, const KitAspectFactory *kitInfo)
|
|
|
|
|
: KitAspect(kit, kitInfo)
|
|
|
|
|
{
|
2023-08-22 10:25:30 +02:00
|
|
|
setManagingPage(Constants::C_PYTHONOPTIONS_PAGE_ID);
|
|
|
|
|
|
2023-10-16 10:21:50 +02:00
|
|
|
m_comboBox = createSubWidget<QComboBox>();
|
|
|
|
|
m_comboBox->setSizePolicy(QSizePolicy::Ignored, m_comboBox->sizePolicy().verticalPolicy());
|
|
|
|
|
|
|
|
|
|
refresh();
|
|
|
|
|
m_comboBox->setToolTip(kitInfo->description());
|
|
|
|
|
connect(m_comboBox, &QComboBox::currentIndexChanged, this, [this] {
|
|
|
|
|
if (m_ignoreChanges.isLocked())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
PythonKitAspect::setPython(m_kit, m_comboBox->currentData().toString());
|
|
|
|
|
});
|
2023-12-12 14:40:37 +01:00
|
|
|
connect(PythonSettings::instance(),
|
|
|
|
|
&PythonSettings::interpretersChanged,
|
|
|
|
|
this,
|
|
|
|
|
&PythonKitAspectImpl::refresh);
|
2023-10-16 10:21:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void makeReadOnly() override
|
|
|
|
|
{
|
|
|
|
|
m_comboBox->setEnabled(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void refresh() override
|
|
|
|
|
{
|
|
|
|
|
const GuardLocker locker(m_ignoreChanges);
|
|
|
|
|
m_comboBox->clear();
|
|
|
|
|
m_comboBox->addItem(Tr::tr("None"), QString());
|
|
|
|
|
|
|
|
|
|
for (const Interpreter &interpreter : PythonSettings::interpreters())
|
|
|
|
|
m_comboBox->addItem(interpreter.name, interpreter.id);
|
|
|
|
|
|
|
|
|
|
updateComboBox(PythonKitAspect::python(m_kit));
|
2023-12-12 14:40:37 +01:00
|
|
|
emit changed(); // we need to emit changed here to update changes in the macro expander
|
2023-10-16 10:21:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void updateComboBox(const std::optional<Interpreter> &python)
|
|
|
|
|
{
|
|
|
|
|
const int index = python ? std::max(m_comboBox->findData(python->id), 0) : 0;
|
|
|
|
|
m_comboBox->setCurrentIndex(index);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
void addToLayoutImpl(Layouting::LayoutItem &parent) override
|
|
|
|
|
{
|
|
|
|
|
addMutableAction(m_comboBox);
|
|
|
|
|
parent.addItem(m_comboBox);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
Guard m_ignoreChanges;
|
|
|
|
|
QComboBox *m_comboBox = nullptr;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class PythonKitAspectFactory : public KitAspectFactory
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
PythonKitAspectFactory()
|
|
|
|
|
{
|
|
|
|
|
setId(PythonKitAspect::id());
|
|
|
|
|
setDisplayName(Tr::tr("Python"));
|
|
|
|
|
setDescription(Tr::tr("The interpreter used for Python based projects."));
|
|
|
|
|
setPriority(10000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Tasks validate(const Kit *k) const override
|
|
|
|
|
{
|
2023-11-22 11:09:41 +01:00
|
|
|
Tasks result;
|
2023-10-16 10:21:50 +02:00
|
|
|
const std::optional<Interpreter> python = PythonKitAspect::python(k);
|
|
|
|
|
if (!python)
|
2023-11-22 11:09:41 +01:00
|
|
|
return result;
|
2023-10-16 10:21:50 +02:00
|
|
|
const FilePath path = python->command;
|
|
|
|
|
if (path.needsDevice())
|
2023-11-22 11:09:41 +01:00
|
|
|
return result;
|
|
|
|
|
if (path.isEmpty()) {
|
|
|
|
|
result << BuildSystemTask(Task::Error, Tr::tr("No Python setup."));
|
|
|
|
|
} else if (!path.exists()) {
|
|
|
|
|
result << BuildSystemTask(Task::Error,
|
|
|
|
|
Tr::tr("Python %1 not found.").arg(path.toUserOutput()));
|
|
|
|
|
} else if (!path.isExecutableFile()) {
|
|
|
|
|
result << BuildSystemTask(Task::Error,
|
|
|
|
|
Tr::tr("Python %1 not executable.").arg(path.toUserOutput()));
|
|
|
|
|
} else {
|
|
|
|
|
if (!pipIsUsable(path)) {
|
|
|
|
|
result << BuildSystemTask(
|
|
|
|
|
Task::Warning,
|
|
|
|
|
Tr::tr("Python %1 does not contain a usable pip. Pip is used to install python "
|
|
|
|
|
"packages from the Python Package Index, like PySide and the python "
|
|
|
|
|
"language server. If you want to use any of that functionality "
|
|
|
|
|
"ensure pip is installed for that python.")
|
|
|
|
|
.arg(path.toUserOutput()));
|
|
|
|
|
}
|
|
|
|
|
if (!venvIsUsable(path)) {
|
|
|
|
|
result << BuildSystemTask(
|
|
|
|
|
Task::Warning,
|
|
|
|
|
Tr::tr("Python %1 does not contain a usable venv. venv is the recommended way "
|
|
|
|
|
"to isolate a development environment for a project from the globally "
|
|
|
|
|
"installed python.")
|
|
|
|
|
.arg(path.toUserOutput()));
|
|
|
|
|
}
|
2023-10-16 10:21:50 +02:00
|
|
|
}
|
2023-11-22 11:09:41 +01:00
|
|
|
return result;
|
2023-10-16 10:21:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ItemList toUserOutput(const Kit *k) const override
|
|
|
|
|
{
|
|
|
|
|
if (const std::optional<Interpreter> python = PythonKitAspect::python(k)) {
|
|
|
|
|
return {{displayName(),
|
|
|
|
|
QString("%1 (%2)").arg(python->name).arg(python->command.toUserOutput())}};
|
|
|
|
|
}
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
KitAspect *createKitAspect(Kit *k) const override { return new PythonKitAspectImpl(k, this); }
|
2023-11-01 15:05:03 +01:00
|
|
|
|
|
|
|
|
QSet<Id> availableFeatures(const Kit *k) const override
|
|
|
|
|
{
|
|
|
|
|
if (k->isAspectRelevant(PythonKitAspect::id()) && PythonKitAspect::python(k))
|
|
|
|
|
return {PythonKitAspect::id()};
|
|
|
|
|
return {};
|
|
|
|
|
}
|
2023-12-12 14:40:37 +01:00
|
|
|
|
|
|
|
|
void addToMacroExpander(Kit *kit, MacroExpander *expander) const override
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(kit, return);
|
|
|
|
|
expander->registerVariable("Python:Name",
|
|
|
|
|
Tr::tr("Name of Python Interpreter"),
|
|
|
|
|
[kit]() -> QString {
|
|
|
|
|
if (auto python = PythonKitAspect::python(kit))
|
|
|
|
|
return python->name;
|
|
|
|
|
return {};
|
|
|
|
|
});
|
|
|
|
|
expander->registerVariable("Python:Path",
|
|
|
|
|
Tr::tr("Path to Python Interpreter"),
|
|
|
|
|
[kit]() -> QString {
|
|
|
|
|
if (auto python = PythonKitAspect::python(kit))
|
|
|
|
|
return python->command.toUserOutput();
|
|
|
|
|
return {};
|
|
|
|
|
});
|
|
|
|
|
}
|
2023-10-16 10:21:50 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
std::optional<Interpreter> PythonKitAspect::python(const Kit *kit)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(kit, return {});
|
|
|
|
|
const QString interpreterId = kit->value(id()).toString();
|
|
|
|
|
if (interpreterId.isEmpty())
|
|
|
|
|
return {};
|
|
|
|
|
if (const Interpreter interpreter = PythonSettings::interpreter(interpreterId); interpreter.id == interpreterId)
|
|
|
|
|
return interpreter;
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PythonKitAspect::setPython(Kit *k, const QString &interpreterId)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(k, return);
|
|
|
|
|
k->setValue(PythonKitAspect::id(), interpreterId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Id PythonKitAspect::id()
|
|
|
|
|
{
|
|
|
|
|
return Constants::PYTHON_TOOLKIT_ASPECT_ID;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const PythonKitAspectFactory thePythonToolKitAspectFactory;
|
|
|
|
|
|
|
|
|
|
} // namespace Python
|