Add pyproject.toml support for Python projects

This patch adds support for the standard Python project configuration
file: pyproject.toml. It is intended to replace *.pyproject files.

Task-number: QTCREATORBUG-22492
Task-number: PYSIDE-2714
Change-Id: I783ee7b49be23c61b0a501ef9bbd722cc652ad14
Reviewed-by: hjk <hjk@qt.io>
Reviewed-by: Jaime Resano <Jaime.RESANO-AISA@qt.io>
This commit is contained in:
Jaime Resano
2025-02-13 17:30:45 +01:00
committed by Jaime Resano
parent 69b9d6cb82
commit 2384da479c
13 changed files with 430 additions and 36 deletions

View File

@@ -4,6 +4,7 @@ add_qtc_plugin(Python
SOURCES SOURCES
../../libs/3rdparty/toml11/toml.hpp ../../libs/3rdparty/toml11/toml.hpp
pipsupport.cpp pipsupport.h pipsupport.cpp pipsupport.h
pyprojecttoml.cpp pyprojecttoml.h
pyside.cpp pyside.h pyside.cpp pyside.h
pythonbuildconfiguration.cpp pythonbuildconfiguration.h pythonbuildconfiguration.cpp pythonbuildconfiguration.h
pysideuicextracompiler.cpp pysideuicextracompiler.h pysideuicextracompiler.cpp pysideuicextracompiler.h

View File

@@ -49,6 +49,11 @@
" <comment>Qt Creator Python project file</comment>", " <comment>Qt Creator Python project file</comment>",
" <glob pattern='*.pyproject'/>", " <glob pattern='*.pyproject'/>",
" </mime-type>", " </mime-type>",
" <mime-type type='text/x-python-pyproject-toml'>",
" <sub-class-of type='application/toml'/>",
" <comment>Python TOML project file</comment>",
" <glob pattern='pyproject.toml'/>",
" </mime-type>",
" <mime-type type='text/x-python3'>", " <mime-type type='text/x-python3'>",
" <comment>Python3 script</comment>", " <comment>Python3 script</comment>",
" <sub-class-of type='text/x-python'/>", " <sub-class-of type='text/x-python'/>",

View File

@@ -0,0 +1,269 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "pyprojecttoml.h"
#include "pythontr.h"
#include <3rdparty/toml11/toml.hpp>
#include <QString>
#include <QStringList>
using namespace Utils;
namespace Python::Internal {
PyProjectTomlError PyProjectTomlError::ParseError(const std::string &description, int line)
{
return PyProjectTomlError(
PyProjectTomlErrorType::ParsingError,
Tr::tr("Parsing error: %1").arg(QString::fromUtf8(description)),
line);
}
PyProjectTomlError PyProjectTomlError::TypeError(
const std::string &nodeName,
const std::string &expectedType,
const std::string &actualType,
int line)
{
return PyProjectTomlError(
PyProjectTomlErrorType::TypeError,
Tr::tr("Type error: \"%1\" must be a \"%2\", not a \"%3\"")
.arg(QString::fromUtf8(nodeName))
.arg(QString::fromUtf8(expectedType))
.arg(QString::fromUtf8(actualType)),
line);
}
PyProjectTomlError PyProjectTomlError::MissingNodeError(
const std::string &nodeName, const std::string &key, int line)
{
return PyProjectTomlError(
PyProjectTomlErrorType::MissingNode,
Tr::tr("Missing node error: \"%1\" table must contain a \"%2\" node")
.arg(QString::fromUtf8(nodeName))
.arg(QString::fromUtf8(key)),
line);
}
PyProjectTomlError PyProjectTomlError::EmptyNodeError(const std::string &nodeName, int line)
{
return PyProjectTomlError(
PyProjectTomlErrorType::EmptyNode,
Tr::tr("Node \"%1\" is empty").arg(QString::fromUtf8(nodeName)),
line);
}
PyProjectTomlError PyProjectTomlError::FileNotFoundError(const std::string &filePath, int line)
{
return PyProjectTomlError(
PyProjectTomlErrorType::FileNotFound,
Tr::tr("File \"%1\" does not exist.").arg(QString::fromUtf8(filePath)),
line);
}
/*!
\brief Returns the value associated with a given key from a TOML table node, if it exists.
The value can be a TOML table, array or primitive value.
\tparam ExpectedType The type of the value to fetch. Must be a TOML value type.
\param expectedTypeName The name of the expected type
\param tableName The name of the table to fetch the value from
\param table The table to fetch the value from
\param key The key to fetch the value from. May be a dotted key.
\return The value if found, otherwise an error string
\note The \a expectedTypeName and \a tableName parameters are only used for error reporting
*/
template<typename ExpectedType>
expected<ExpectedType, PyProjectTomlError> getNodeByKey(
const std::string expectedTypeName,
const std::string tableName,
const toml::ordered_value &table,
const std::string key)
{
// Edge case: Do not show the the root table errors in a specific line
int nodeLine = tableName != "root" ? static_cast<int>(table.location().first_line_number())
: -1;
if (!table.is_table()) {
return make_unexpected(PyProjectTomlError::TypeError(
tableName, "table", toml::to_string(table.type()), nodeLine));
}
try {
const auto &node = toml::find(table, key);
return getNodeValue<ExpectedType>(expectedTypeName, key, node);
} catch (const std::out_of_range &) {
return make_unexpected(PyProjectTomlError::MissingNodeError(tableName, key, nodeLine));
}
}
/*!
\brief Fetches a value of certain type from a TOML node by key if existing
\tparam ExpectedType The type of the value to fetch. Must be a TOML primitive value type.
\param expectedTypeName The name of the expected type
\param nodeName The name of the node to fetch the value from
\param node The node to fetch the value from
\return The value if found, otherwise an error string
\note The \a expectedTypeName and \a nodeName parameters are only used for error reporting
*/
template<typename ExpectedType>
expected<ExpectedType, PyProjectTomlError> getNodeValue(
const std::string expectedTypeName, const std::string nodeName, const toml::ordered_value &node)
{
auto nodeLine = static_cast<int>(node.location().first_line_number());
if (node.is_empty())
return make_unexpected(PyProjectTomlError::EmptyNodeError(nodeName, nodeLine));
if constexpr (std::is_same_v<ExpectedType, toml::table>) {
if (!node.is_table())
return make_unexpected(PyProjectTomlError::TypeError(
nodeName, "table", toml::to_string(node.type()), nodeLine));
return node.as_table();
}
if constexpr (std::is_same_v<ExpectedType, toml::array>) {
if (!node.is_array())
return make_unexpected(PyProjectTomlError::TypeError(
nodeName, "array", toml::to_string(node.type()), nodeLine));
return node.as_array();
}
try {
return toml::get<ExpectedType>(node);
} catch (const toml::type_error &) {
return make_unexpected(PyProjectTomlError::TypeError(
nodeName, expectedTypeName, toml::to_string(node.type()), nodeLine));
}
}
/*
\brief Parses the given pyproject.toml file and returns a PyProjectTomlParseResult
\param pyProjectTomlPath The path to the pyproject.toml file
\returns A PyProjectTomlParseResult containing the errors found and the project information
*/
PyProjectTomlParseResult parsePyProjectToml(const FilePath &pyProjectTomlPath)
{
PyProjectTomlParseResult result;
const expected_str<QByteArray> fileContentsResult = pyProjectTomlPath.fileContents();
if (!fileContentsResult) {
result.errors << PyProjectTomlError::FileNotFoundError(
pyProjectTomlPath.toUserOutput().toStdString(), -1);
return result;
}
QString pyProjectTomlContent = QString::fromUtf8(fileContentsResult.value());
toml::ordered_value rootTable;
try {
rootTable = toml::parse_str<toml::ordered_type_config>(pyProjectTomlContent.toStdString());
} catch (const toml::syntax_error &syntaxErrors) {
for (const auto &error : syntaxErrors.errors()) {
auto errorLine = static_cast<int>(error.locations().at(0).first.first_line_number());
result.errors << PyProjectTomlError::ParseError(error.title(), errorLine);
}
return result;
}
auto projectTable = getNodeByKey<toml::ordered_value>("table", "root", rootTable, "project");
if (!projectTable) {
result.errors << projectTable.error();
return result;
}
auto projectName = getNodeByKey<std::string>("table", "project", projectTable.value(), "name");
if (!projectName) {
result.errors << projectName.error();
} else {
result.projectName = QString::fromUtf8(projectName.value());
}
auto toolTable = getNodeByKey<toml::ordered_value>("table", "root", rootTable, "tool");
if (!toolTable) {
result.errors << toolTable.error();
return result;
}
auto pysideTable
= getNodeByKey<toml::ordered_value>("table", "tool", toolTable.value(), "pyside6-project");
if (!pysideTable) {
result.errors << pysideTable.error();
return result;
}
auto files
= getNodeByKey<toml::ordered_array>("array", "pyside6-project", pysideTable.value(), "files");
if (!files) {
result.errors << files.error();
return result;
}
const auto &filesArray = files.value();
result.projectFiles.reserve(filesArray.size());
for (const auto &fileNode : filesArray) {
auto possibleFile = getNodeValue<std::string>("string", "file", fileNode);
if (!possibleFile) {
result.errors << possibleFile.error();
continue;
}
auto file = QString::fromUtf8(possibleFile.value());
auto filePath = pyProjectTomlPath.parentDir().pathAppended(file);
if (!filePath.exists()) {
auto line = static_cast<int>(fileNode.location().first_line_number());
result.errors << PyProjectTomlError::FileNotFoundError(possibleFile.value(), line);
continue;
}
result.projectFiles.append(file);
}
return result;
}
/*!
\brief Given an existing pyproject.toml file, update it with the given \a projectFiles.
\return If successful, returns the new contents of the file. Otherwise, returns an error.
*/
expected_str<QString> updatePyProjectTomlContent(
const QString &pyProjectTomlContent, const QStringList &projectFiles)
{
toml::ordered_value rootTable;
try {
rootTable = toml::parse_str<toml::ordered_type_config>(pyProjectTomlContent.toStdString());
} catch (const toml::exception &error) {
return make_unexpected(QString::fromUtf8(error.what()));
}
auto &toolTable = rootTable["tool"];
if (!toolTable.is_table()) {
toolTable = toml::ordered_table{};
}
auto &pysideTable = toolTable.as_table()["pyside6-project"];
if (!pysideTable.is_table()) {
pysideTable = toml::ordered_table{};
}
std::vector<std::string> filesArray;
filesArray.reserve(projectFiles.size());
std::transform(
projectFiles.begin(),
projectFiles.end(),
std::back_inserter(filesArray),
[](const QString &file) { return file.toStdString(); });
std::sort(filesArray.begin(), filesArray.end());
pysideTable["files"] = std::move(filesArray);
auto result = QString::fromUtf8(toml::format(rootTable));
// For some reason, the TOML library adds two trailing newlines.
while (result.endsWith("\n\n")) {
result.chop(1);
}
return result;
}
} // namespace Python::Internal

View File

@@ -0,0 +1,71 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <3rdparty/toml11/toml.hpp>
#include <projectexplorer/buildsystem.h>
namespace Python::Internal {
enum class PyProjectTomlErrorType { ParsingError, MissingNode, TypeError, EmptyNode, FileNotFound };
/*
\brief Class representing a generic error found in a pyproject.toml file
Not intended to be constructed directly, but rather calling one of the static factory methods
to create an instance of this class with the appropriate \a description format.
\sa PyProjectTomlParseResult
*/
struct PyProjectTomlError
{
PyProjectTomlErrorType type;
QString description;
int line;
PyProjectTomlError(PyProjectTomlErrorType type, const QString &description, int line = -1)
: type(type)
, description(description)
, line(line)
{}
bool operator==(const PyProjectTomlError &other) const
{
return type == other.type && description == other.description && line == other.line;
}
static PyProjectTomlError ParseError(const std::string &description, int line = -1);
static PyProjectTomlError TypeError(
const std::string &nodeName,
const std::string &expectedTypeName,
const std::string &actualType,
int line = -1);
static PyProjectTomlError MissingNodeError(
const std::string &nodeName, const std::string &key, int line = -1);
static PyProjectTomlError EmptyNodeError(const std::string &nodeName, int line = -1);
static PyProjectTomlError FileNotFoundError(const std::string &filePath, int line = -1);
};
struct PyProjectTomlParseResult
{
QList<PyProjectTomlError> errors;
QString projectName;
QStringList projectFiles;
};
template<typename ExpectedType>
Utils::expected<ExpectedType, PyProjectTomlError> getNodeByKey(
const std::string expectedTypeName,
const std::string tableName,
const toml::ordered_value &table,
const std::string key);
template<typename ExpectedType>
Utils::expected<ExpectedType, PyProjectTomlError> getNodeValue(
const std::string expectedTypeName, const std::string nodeName, const toml::ordered_value &node);
PyProjectTomlParseResult parsePyProjectToml(const Utils::FilePath &pyProjectTomlPath);
Utils::expected_str<QString> updatePyProjectTomlContent(
const QString &pyProjectTomlContent, const QStringList &projectFiles);
} // namespace Python::Internal

View File

@@ -21,6 +21,8 @@ QtcPlugin {
"../../libs/3rdparty/toml11/toml.hpp", "../../libs/3rdparty/toml11/toml.hpp",
"pipsupport.cpp", "pipsupport.cpp",
"pipsupport.h", "pipsupport.h",
"pyprojecttoml.cpp",
"pyprojecttoml.h",
"pyside.cpp", "pyside.cpp",
"pyside.h", "pyside.h",
"pythonbuildconfiguration.cpp", "pythonbuildconfiguration.cpp",

View File

@@ -391,7 +391,8 @@ public:
{ {
registerBuildConfiguration<PythonBuildConfiguration>("Python.PySideBuildConfiguration"); registerBuildConfiguration<PythonBuildConfiguration>("Python.PySideBuildConfiguration");
setSupportedProjectType(PythonProjectId); setSupportedProjectType(PythonProjectId);
setSupportedProjectMimeTypeName(Constants::C_PY_PROJECT_MIME_TYPE); setSupportedProjectMimeTypeNames(
{Constants::C_PY_PROJECT_MIME_TYPE, Constants::C_PY_PROJECT_MIME_TYPE_TOML});
setBuildGenerator([](const Kit *k, const FilePath &projectPath, bool forSetup) { setBuildGenerator([](const Kit *k, const FilePath &projectPath, bool forSetup) {
if (std::optional<Interpreter> python = PythonKitAspect::python(k)) { if (std::optional<Interpreter> python = PythonKitAspect::python(k)) {
BuildInfo base; BuildInfo base;

View File

@@ -3,6 +3,7 @@
#include "pythonbuildsystem.h" #include "pythonbuildsystem.h"
#include "pyprojecttoml.h"
#include "pythonbuildconfiguration.h" #include "pythonbuildconfiguration.h"
#include "pythonconstants.h" #include "pythonconstants.h"
#include "pythonproject.h" #include "pythonproject.h"
@@ -10,9 +11,11 @@
#include <coreplugin/documentmanager.h> #include <coreplugin/documentmanager.h>
#include <coreplugin/messagemanager.h> #include <coreplugin/messagemanager.h>
#include <coreplugin/textdocument.h>
#include <projectexplorer/projectexplorerconstants.h> #include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/target.h> #include <projectexplorer/target.h>
#include <projectexplorer/taskhub.h>
#include <qmljs/qmljsmodelmanagerinterface.h> #include <qmljs/qmljsmodelmanagerinterface.h>
@@ -216,28 +219,50 @@ void PythonBuildSystem::triggerParsing()
emitBuildSystemUpdated(); emitBuildSystemUpdated();
} }
/*!
\brief Saves the build system configuration in the corresponding project file.
Currently, three project file formats are supported: pyproject.toml, *.pyproject and the legacy
*.pyqtc file.
\returns true if the save was successful, false otherwise.
*/
bool PythonBuildSystem::save() bool PythonBuildSystem::save()
{ {
const FilePath filePath = projectFilePath(); const FilePath filePath = projectFilePath();
const QStringList rawList = Utils::transform(m_files, &FileEntry::rawEntry); const QStringList projectFiles = Utils::transform(m_files, &FileEntry::rawEntry);
const FileChangeBlocker changeGuard(filePath); const FileChangeBlocker changeGuard(filePath);
QByteArray newContents; QByteArray newContents;
// New project file if (filePath.fileName() == "pyproject.toml") {
if (filePath.endsWith(".pyproject")) { Core::BaseTextDocument projectFile;
expected_str<QByteArray> contents = filePath.fileContents(); QString pyProjectTomlContent;
if (contents) { QString readingError;
QJsonDocument doc = QJsonDocument::fromJson(*contents); auto result = projectFile.read(filePath, &pyProjectTomlContent, &readingError);
QJsonObject project = doc.object(); if (result != TextFileFormat::ReadSuccess) {
project["files"] = QJsonArray::fromStringList(rawList); MessageManager::writeDisrupting(readingError);
doc.setObject(project); return false;
newContents = doc.toJson();
} else {
MessageManager::writeDisrupting(contents.error());
} }
} else { // Old project file auto newPyProjectToml = updatePyProjectTomlContent(pyProjectTomlContent, projectFiles);
newContents = rawList.join('\n').toUtf8(); if (!newPyProjectToml) {
MessageManager::writeDisrupting(newPyProjectToml.error());
return false;
}
newContents = newPyProjectToml.value().toUtf8();
} else if (filePath.endsWith(".pyproject")) {
// *.pyproject project file
expected_str<QByteArray> contents = filePath.fileContents();
if (!contents) {
MessageManager::writeDisrupting(contents.error());
return false;
}
QJsonDocument doc = QJsonDocument::fromJson(*contents);
QJsonObject project = doc.object();
project["files"] = QJsonArray::fromStringList(projectFiles);
doc.setObject(project);
newContents = doc.toJson();
} else {
// Old project file
newContents = projectFiles.join('\n').toUtf8();
} }
const expected_str<qint64> writeResult = filePath.writeFileContents(newContents); const expected_str<qint64> writeResult = filePath.writeFileContents(newContents);
@@ -326,14 +351,14 @@ void PythonBuildSystem::parse()
QStringList qmlImportPaths; QStringList qmlImportPaths;
const FilePath filePath = projectFilePath(); const FilePath filePath = projectFilePath();
// The PySide project file is JSON based QString errorMessage;
if (filePath.endsWith(".pyproject")) { if (filePath.endsWith(".pyproject")) {
QString errorMessage; // The PySide .pyproject file is JSON based
files = readLinesJson(filePath, &errorMessage); files = readLinesJson(filePath, &errorMessage);
if (!errorMessage.isEmpty()) if (!errorMessage.isEmpty()) {
MessageManager::writeFlashing(errorMessage); MessageManager::writeFlashing(errorMessage);
errorMessage.clear();
errorMessage.clear(); }
qmlImportPaths = readImportPathsJson(filePath, &errorMessage); qmlImportPaths = readImportPathsJson(filePath, &errorMessage);
if (!errorMessage.isEmpty()) if (!errorMessage.isEmpty())
MessageManager::writeFlashing(errorMessage); MessageManager::writeFlashing(errorMessage);
@@ -341,6 +366,20 @@ void PythonBuildSystem::parse()
// To keep compatibility with PyQt we keep the compatibility with plain // To keep compatibility with PyQt we keep the compatibility with plain
// text files as project files. // text files as project files.
files = readLines(filePath); files = readLines(filePath);
} else if (filePath.fileName() == "pyproject.toml") {
auto pyProjectTomlParseResult = parsePyProjectToml(filePath);
TaskHub::clearTasks(ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM);
for (const PyProjectTomlError &error : pyProjectTomlParseResult.errors) {
TaskHub::addTask(
BuildSystemTask(Task::TaskType::Error, error.description, filePath, error.line));
}
if (!pyProjectTomlParseResult.projectName.isEmpty()) {
project()->setDisplayName(pyProjectTomlParseResult.projectName);
}
files = pyProjectTomlParseResult.projectFiles;
} }
m_files = processEntries(files); m_files = processEntries(files);

View File

@@ -34,7 +34,7 @@ const char C_PY3_MIMETYPE[] = "text/x-python3";
const char C_PY_MIME_ICON[] = "text-x-python"; const char C_PY_MIME_ICON[] = "text-x-python";
const char C_PY_PROJECT_MIME_TYPE[] = "text/x-python-project"; const char C_PY_PROJECT_MIME_TYPE[] = "text/x-python-project";
const char C_PY_PROJECT_MIME_TYPE_LEGACY[] = "text/x-pyqt-project"; const char C_PY_PROJECT_MIME_TYPE_LEGACY[] = "text/x-pyqt-project";
const char C_PY_PROJECT_MIME_TYPE_TOML[] = "text/x-python-pyproject-toml";
} // namespace Constants } // namespace Constants
} // namespace Python } // namespace Python

View File

@@ -146,12 +146,12 @@ void PythonEditorWidget::updateInterpretersSelector()
}; };
const FilePath documentPath = textDocument()->filePath(); const FilePath documentPath = textDocument()->filePath();
Project *project = Utils::findOrDefault(ProjectManager::projects(), const auto isPythonProject = [documentPath](Project *project) {
[documentPath](Project *project) { return project->isKnownFile(documentPath) && (
return project->mimeType() project->mimeType() == Constants::C_PY_PROJECT_MIME_TYPE ||
== Constants::C_PY_PROJECT_MIME_TYPE project->mimeType() == Constants::C_PY_PROJECT_MIME_TYPE_TOML);
&& project->isKnownFile(documentPath); };
}); Project *project = Utils::findOrDefault(ProjectManager::projects(), isPythonProject);
if (project) { if (project) {
auto interpretersGroup = new QActionGroup(menu); auto interpretersGroup = new QActionGroup(menu);

View File

@@ -92,8 +92,8 @@ class PythonPlugin final : public ExtensionSystem::IPlugin
setupPipSupport(this); setupPipSupport(this);
KitManager::setIrrelevantAspects(KitManager::irrelevantAspects() KitManager::setIrrelevantAspects(
+ QSet<Id>{PythonKitAspect::id()}); KitManager::irrelevantAspects() + QSet<Id>{PythonKitAspect::id()});
const auto issuesGenerator = [](const Kit *k) -> Tasks { const auto issuesGenerator = [](const Kit *k) -> Tasks {
if (!PythonKitAspect::python(k)) if (!PythonKitAspect::python(k))
@@ -106,6 +106,8 @@ class PythonPlugin final : public ExtensionSystem::IPlugin
Constants::C_PY_PROJECT_MIME_TYPE, issuesGenerator); Constants::C_PY_PROJECT_MIME_TYPE, issuesGenerator);
ProjectManager::registerProjectType<PythonProject>( ProjectManager::registerProjectType<PythonProject>(
Constants::C_PY_PROJECT_MIME_TYPE_LEGACY, issuesGenerator); Constants::C_PY_PROJECT_MIME_TYPE_LEGACY, issuesGenerator);
ProjectManager::registerProjectType<PythonProject>(
Constants::C_PY_PROJECT_MIME_TYPE_TOML, issuesGenerator);
auto oldHighlighter = Utils::Text::codeHighlighter(); auto oldHighlighter = Utils::Text::codeHighlighter();
Utils::Text::setCodeHighlighter( Utils::Text::setCodeHighlighter(
@@ -134,6 +136,6 @@ class PythonPlugin final : public ExtensionSystem::IPlugin
} }
}; };
} // Python::Internal } // namespace Python::Internal
#include "pythonplugin.moc" #include "pythonplugin.moc"

View File

@@ -18,7 +18,7 @@ using namespace Utils;
namespace Python::Internal { namespace Python::Internal {
PythonProject::PythonProject(const FilePath &fileName) PythonProject::PythonProject(const FilePath &fileName)
: Project(Constants::C_PY_PROJECT_MIME_TYPE, fileName) : Project(Constants::C_PY_PROJECT_MIME_TYPE_TOML, fileName)
{ {
setId(PythonProjectId); setId(PythonProjectId);
setProjectLanguages(Context(ProjectExplorer::Constants::PYTHON_LANGUAGE_ID)); setProjectLanguages(Context(ProjectExplorer::Constants::PYTHON_LANGUAGE_ID));
@@ -45,4 +45,4 @@ QString PythonFileNode::displayName() const
return m_displayName; return m_displayName;
} }
} // Python::Internal } // namespace Python::Internal

View File

@@ -192,10 +192,13 @@ void setupPythonDebugWorker()
void setupPythonOutputParser() void setupPythonOutputParser()
{ {
addOutputParserFactory([](Target *t) -> OutputLineParser * { addOutputParserFactory([](Target *t) -> OutputLineParser * {
if (t && t->project()->mimeType() == Constants::C_PY_PROJECT_MIME_TYPE) if (!t)
return nullptr;
if (t->project()->mimeType() == Constants::C_PY_PROJECT_MIME_TYPE
|| t->project()->mimeType() == Constants::C_PY_PROJECT_MIME_TYPE_TOML)
return new PythonOutputLineParser; return new PythonOutputLineParser;
return nullptr; return nullptr;
}); });
} }
} // Python::Internal } // namespace Python::Internal

View File

@@ -44,7 +44,8 @@ FilePath detectPython(const FilePath &documentPath)
FilePaths dirs = Environment::systemEnvironment().path(); FilePaths dirs = Environment::systemEnvironment().path();
if (project && project->mimeType() == Constants::C_PY_PROJECT_MIME_TYPE) { if (project && (project->mimeType() == Constants::C_PY_PROJECT_MIME_TYPE
|| project->mimeType() == Constants::C_PY_PROJECT_MIME_TYPE_TOML)) {
if (auto bc = qobject_cast<PythonBuildConfiguration *>(project->activeBuildConfiguration())) if (auto bc = qobject_cast<PythonBuildConfiguration *>(project->activeBuildConfiguration()))
return bc->python(); return bc->python();
if (const std::optional<Interpreter> python = PythonKitAspect::python(project->activeKit())) if (const std::optional<Interpreter> python = PythonKitAspect::python(project->activeKit()))
@@ -248,4 +249,4 @@ QString pythonVersion(const FilePath &python)
return QString(); return QString();
} }
} // Python::Internal } // namespace Python::Internal