forked from qt-creator/qt-creator
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:
committed by
Jaime Resano
parent
69b9d6cb82
commit
2384da479c
@@ -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
|
||||||
|
@@ -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'/>",
|
||||||
|
269
src/plugins/python/pyprojecttoml.cpp
Normal file
269
src/plugins/python/pyprojecttoml.cpp
Normal 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
|
71
src/plugins/python/pyprojecttoml.h
Normal file
71
src/plugins/python/pyprojecttoml.h
Normal 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
|
@@ -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",
|
||||||
|
@@ -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;
|
||||||
|
@@ -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;
|
||||||
|
QString pyProjectTomlContent;
|
||||||
|
QString readingError;
|
||||||
|
auto result = projectFile.read(filePath, &pyProjectTomlContent, &readingError);
|
||||||
|
if (result != TextFileFormat::ReadSuccess) {
|
||||||
|
MessageManager::writeDisrupting(readingError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto newPyProjectToml = updatePyProjectTomlContent(pyProjectTomlContent, projectFiles);
|
||||||
|
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();
|
expected_str<QByteArray> contents = filePath.fileContents();
|
||||||
if (contents) {
|
if (!contents) {
|
||||||
|
MessageManager::writeDisrupting(contents.error());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(*contents);
|
QJsonDocument doc = QJsonDocument::fromJson(*contents);
|
||||||
QJsonObject project = doc.object();
|
QJsonObject project = doc.object();
|
||||||
project["files"] = QJsonArray::fromStringList(rawList);
|
project["files"] = QJsonArray::fromStringList(projectFiles);
|
||||||
doc.setObject(project);
|
doc.setObject(project);
|
||||||
newContents = doc.toJson();
|
newContents = doc.toJson();
|
||||||
} else {
|
} else {
|
||||||
MessageManager::writeDisrupting(contents.error());
|
// Old project file
|
||||||
}
|
newContents = projectFiles.join('\n').toUtf8();
|
||||||
} else { // Old project file
|
|
||||||
newContents = rawList.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
|
|
||||||
if (filePath.endsWith(".pyproject")) {
|
|
||||||
QString errorMessage;
|
QString errorMessage;
|
||||||
|
if (filePath.endsWith(".pyproject")) {
|
||||||
|
// 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);
|
||||||
|
@@ -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
|
||||||
|
@@ -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);
|
||||||
|
@@ -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"
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user