Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ target_sources(
${CMAKE_CURRENT_LIST_DIR}/AboutDialog.h
${CMAKE_CURRENT_LIST_DIR}/ColorScheme.cpp
${CMAKE_CURRENT_LIST_DIR}/ColorScheme.h
${CMAKE_CURRENT_LIST_DIR}/CreateVenvForm.cpp
${CMAKE_CURRENT_LIST_DIR}/CreateVenvForm.h
${CMAKE_CURRENT_LIST_DIR}/FileRunner.cpp
${CMAKE_CURRENT_LIST_DIR}/FileRunner.h
${CMAKE_CURRENT_LIST_DIR}/PackageManager.cpp
Expand All @@ -25,8 +27,8 @@ target_sources(
${CMAKE_CURRENT_LIST_DIR}/PythonPlugin.h
${CMAKE_CURRENT_LIST_DIR}/PythonPluginManager.cpp
${CMAKE_CURRENT_LIST_DIR}/PythonPluginManager.h
${CMAKE_CURRENT_LIST_DIR}/PythonRuntimeSettings.cpp
${CMAKE_CURRENT_LIST_DIR}/PythonRuntimeSettings.h
${CMAKE_CURRENT_LIST_DIR}/PythonRuntimeSettings.cpp
${CMAKE_CURRENT_LIST_DIR}/PythonRuntimeSettings.h
${CMAKE_CURRENT_LIST_DIR}/PythonRepl.cpp
${CMAKE_CURRENT_LIST_DIR}/PythonRepl.h
${CMAKE_CURRENT_LIST_DIR}/Resources.h
Expand Down
28 changes: 28 additions & 0 deletions src/CreateVenvForm.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#include "CreateVenvForm.h"
#include "ui_CreateVenvForm.h"

#include <QFileDialog>

CreateVenvForm::CreateVenvForm(QWidget *parent) : QDialog(parent), ui(new Ui::CreateVenvForm)
{
ui->setupUi(this);

connect(ui->locationBtn, &QPushButton::clicked, this, &CreateVenvForm::promptForLocation);
}

CreateVenvForm::~CreateVenvForm()
{
delete ui;
}

QString CreateVenvForm::path() const
{
return QString("%1/%2").arg(ui->locationEdit->text(), ui->nameEdit->text());
}

void CreateVenvForm::promptForLocation()
{
QString selectedDir = QFileDialog::getExistingDirectory(
this, "Python Environment Root", ui->locationEdit->text());
ui->locationEdit->setText(selectedDir);
}
26 changes: 26 additions & 0 deletions src/CreateVenvForm.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#ifndef CREATEVENVFORM_H
#define CREATEVENVFORM_H

#include <QDialog>

namespace Ui
{
class CreateVenvForm;
}

class CreateVenvForm : public QDialog
{
Q_OBJECT

public:
explicit CreateVenvForm(QWidget *parent = nullptr);
~CreateVenvForm();

QString path() const;

private:
void promptForLocation();
Ui::CreateVenvForm *ui;
};

#endif // CREATEVENVFORM_H
236 changes: 48 additions & 188 deletions src/PythonConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,11 @@
#include "Utilities.h"

#include <QApplication>
#include <QDebug>
#include <QDir>
#include <QMessageBox>
#include <QProcess>
#include <QStringView>
#include <QVector>
#include <QtGlobal>

#if !defined(USE_EMBEDDED_MODULES) && defined(Q_OS_WINDOWS)
static QString WindowsBundledSitePackagesPath()
{
return QDir::listSeparator() + QApplication::applicationDirPath() +
"/plugins/Python/Lib/site-packages";
}
#endif

//================================================================================

Version::Version(const QString &versionStr) : Version()
Expand Down Expand Up @@ -72,71 +61,6 @@ static Version GetPythonExeVersion(QProcess &pythonProcess)
}
return Version{};
}
//================================================================================

struct PyVenvCfg
{
PyVenvCfg() = default;

static PyVenvCfg FromFile(const QString &path);

QString home{};
bool includeSystemSitesPackages{};
Version version;
};

PyVenvCfg PyVenvCfg::FromFile(const QString &path)
{
PyVenvCfg cfg{};

QFile cfgFile(path);
if (cfgFile.open(QIODevice::ReadOnly | QIODevice::Text))
{
while (!cfgFile.atEnd())
{
QString line = cfgFile.readLine();
QStringList v = line.split("=");

if (v.size() == 2)
{
QString name = v[0].simplified();
QString value = v[1].simplified();

if (name == "home")
{
cfg.home = value;
}
else if (name == "include-system-site-packages")
{
cfg.includeSystemSitesPackages = (value == "true");
}
else if (name == "version")
{
cfg.version = Version(value);
}
}
}
}

return cfg;
}

//================================================================================

bool PythonConfigPaths::isSet() const
{
return m_pythonHome != nullptr && m_pythonPath != nullptr;
}

const wchar_t *PythonConfigPaths::pythonHome() const
{
return m_pythonHome.get();
}

const wchar_t *PythonConfigPaths::pythonPath() const
{
return m_pythonPath.get();
}

//================================================================================

Expand Down Expand Up @@ -198,102 +122,80 @@ void PythonConfig::initFromLocation(const QString &prefix)
if (!envRoot.exists())
{
m_pythonHome = QString();
m_pythonPath = QString();
m_type = Type::Unknown;
return;
}

m_pythonHome = envRoot.path();

if (envRoot.exists("pyvenv.cfg"))
{
QString pythonExePath = PathToPythonExecutableInEnv(Type::Venv, prefix);
initFromPythonExecutable(pythonExePath);
if (m_pythonHome.isEmpty() && m_pythonPath.isEmpty())
{
qDebug() << "Failed to get paths info from python executable at (venv)"
<< pythonExePath;
initVenv(envRoot.path());
}
else
{
m_type = Type::Venv;
}
m_type = Type::Venv;
}
else if (envRoot.exists("conda-meta"))
{
QString pythonExePath = PathToPythonExecutableInEnv(Type::Conda, prefix);
initFromPythonExecutable(pythonExePath);
if (m_pythonHome.isEmpty() && m_pythonPath.isEmpty())
{
qDebug() << "Failed to get paths info from python executable at (conda)"
<< pythonExePath;
initCondaEnv(envRoot.path());
}
else
{
m_type = Type::Conda;
}
m_type = Type::Conda;
}
else
#if defined(Q_OS_MACOS)
{
QString pythonExePath = PathToPythonExecutableInEnv(Type::Unknown, prefix);
initFromPythonExecutable(pythonExePath);
if (m_pythonHome.isEmpty() && m_pythonPath.isEmpty())
{
qDebug() << "Failed to get paths info from python executable at (bundled)"
<< pythonExePath;
initVenv(envRoot.path());
}
else
{
m_type = Type::Unknown;
}
}
#else
{
m_pythonHome = envRoot.path();
m_pythonPath = QString("%1/DLLs;%1/lib;%1/Lib/site-packages;").arg(m_pythonHome);
m_type = Type::Unknown;

#if !defined(USE_EMBEDDED_MODULES) && defined(Q_OS_WINDOWS)
m_pythonPath.append(WindowsBundledSitePackagesPath());
#endif
}
#endif
}

void PythonConfig::initCondaEnv(const QString &condaPrefix)
QString PythonConfig::pythonExecutable() const
{
m_type = Type::Conda;
m_pythonHome = condaPrefix;
m_pythonPath = QString("%1/DLLs;%1/lib;%1/Lib/site-packages;").arg(condaPrefix);

#if !defined(USE_EMBEDDED_MODULES) && defined(Q_OS_WINDOWS)
m_pythonPath.append(WindowsBundledSitePackagesPath());
#endif
return PathToPythonExecutableInEnv(m_type, m_pythonHome);
}

void PythonConfig::initVenv(const QString &venvPrefix)
ResolvedPythonPaths PythonConfig::resolvePaths() const
{
PyVenvCfg cfg = PyVenvCfg::FromFile(QString("%1/pyvenv.cfg").arg(venvPrefix));
QProcess pythonProcess;
preparePythonProcess(pythonProcess);
// -I runs the interpreter in isolated mode so the reported values match what
// the embedded (also isolated) interpreter should use: standard library +
// the environment's site-packages, without user-site or ambient PYTHON*.
// The four prefixes are printed first (one per line), then the search paths.
pythonProcess.setArguments({"-I",
"-c",
"import sys; "
"print(sys.prefix); print(sys.exec_prefix); "
"print(sys.base_prefix); print(sys.base_exec_prefix); "
"print(chr(10).join(sys.path))"});
pythonProcess.start(QIODevice::ReadOnly);
pythonProcess.waitForFinished();

const QString output = QString::fromUtf8(pythonProcess.readAllStandardOutput());

QStringList lines;
for (const QString &line : output.split('\n'))
{
const QString trimmed = line.trimmed();
if (!trimmed.isEmpty())
{
lines.append(trimmed);
}
}

m_type = Type::Venv;
m_pythonHome = venvPrefix;
m_pythonPath = QString("%1/Lib/site-packages;%3/DLLS;%3/lib").arg(venvPrefix, cfg.home);
if (cfg.includeSystemSitesPackages)
// 4 prefixes + at least one search path.
if (lines.size() < 5)
{
m_pythonPath.append(QString("%1/Lib/site-packages").arg(cfg.home));
plgWarning() << "Could not query Python paths from " << pythonExecutable() << ": got '"
<< output << "'";
return {};
}

#if !defined(USE_EMBEDDED_MODULES) && defined(Q_OS_WINDOWS)
m_pythonPath.append(WindowsBundledSitePackagesPath());
#endif
ResolvedPythonPaths paths;
paths.prefix = lines.takeFirst();
paths.execPrefix = lines.takeFirst();
paths.basePrefix = lines.takeFirst();
paths.baseExecPrefix = lines.takeFirst();
paths.moduleSearchPaths = lines;
return paths;
}

void PythonConfig::preparePythonProcess(QProcess &pythonProcess) const
{
const QString pythonExePath = PathToPythonExecutableInEnv(type(), m_pythonHome);
pythonProcess.setProgram(pythonExePath);
pythonProcess.setProgram(pythonExecutable());

// Conda env have SSL related libraries stored in a part that is not
// in the path of the python exe, we have to add it ourselves.
Expand All @@ -307,14 +209,6 @@ void PythonConfig::preparePythonProcess(QProcess &pythonProcess) const
}
}

PythonConfigPaths PythonConfig::pythonCompatiblePaths() const
{
PythonConfigPaths paths;
paths.m_pythonHome.reset(QStringToWcharArray(m_pythonHome));
paths.m_pythonPath.reset(QStringToWcharArray(m_pythonPath));
return paths;
}

Version PythonConfig::getVersion() const
{
QProcess pythonProcess;
Expand Down Expand Up @@ -366,52 +260,18 @@ PythonConfig PythonConfig::fromContainingEnvironment()
QString root = qEnvironmentVariable("CONDA_PREFIX");
if (!root.isEmpty())
{
const QString pythonExePath = PathToPythonExecutableInEnv(Type::Conda, root);
config.initFromPythonExecutable(pythonExePath);
config.m_pythonHome = root;
config.m_type = Type::Conda;
return config;
}

root = qEnvironmentVariable("VIRTUAL_ENV");
if (!root.isEmpty())
{
const QString pythonExePath = PathToPythonExecutableInEnv(Type::Venv, root);
config.initFromPythonExecutable(pythonExePath);
config.m_pythonHome = root;
config.m_type = Type::Venv;
return config;
}

return config;
}

void PythonConfig::initFromPythonExecutable(const QString &pythonExecutable)
{
m_type = Type::Unknown;

const QString pythonPathScript = QStringLiteral(
"import os;import sys;print(os.pathsep.join(sys.path[1:]));print(sys.prefix, end='')");

QProcess pythonProcess;
pythonProcess.setProgram(pythonExecutable);
pythonProcess.setArguments({"-c", pythonPathScript});
pythonProcess.start(QIODevice::ReadOnly);
pythonProcess.waitForFinished();

const QString result = QString::fromUtf8(pythonProcess.readAllStandardOutput());

QStringList pathsAndHome = result.split('\n');

if (pathsAndHome.size() != 2)
{
plgWarning() << "'" << result << "' could not be parsed as a list if paths and a home path."
<< "Expected 2 strings found " << pathsAndHome.size();
return;
}

m_pythonPath = pathsAndHome.takeFirst();
m_pythonHome = pathsAndHome.takeFirst();

#if !defined(USE_EMBEDDED_MODULES) && defined(Q_OS_WINDOWS)
m_pythonPath.append(WindowsBundledSitePackagesPath());
#endif
}
Loading
Loading