diff options
| author | David Schulz <david.schulz@qt.io> | 2023-11-09 08:28:12 +0100 |
|---|---|---|
| committer | David Schulz <david.schulz@qt.io> | 2023-11-14 11:03:24 +0000 |
| commit | f6e403b219d779e358396a57960d607ff557652e (patch) | |
| tree | f04d78943878fc6b66aa021bcdcc32cfb6494d41 /src/plugins/python/pythonproject.cpp | |
| parent | 5acce4bd7f80784e253f63a3e8aaafb7ad756adb (diff) | |
Python: split buildsystem and project into separate files
Change-Id: Ic226ff6685cb6657a5e83258aab151754d80b3bf
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
Diffstat (limited to 'src/plugins/python/pythonproject.cpp')
| -rw-r--r-- | src/plugins/python/pythonproject.cpp | 425 |
1 files changed, 15 insertions, 410 deletions
diff --git a/src/plugins/python/pythonproject.cpp b/src/plugins/python/pythonproject.cpp index 036d5e7ce4b..c259b088406 100644 --- a/src/plugins/python/pythonproject.cpp +++ b/src/plugins/python/pythonproject.cpp @@ -3,33 +3,12 @@ #include "pythonproject.h" +#include "pythonbuildsystem.h" #include "pythonconstants.h" -#include "pythontr.h" -#include <projectexplorer/buildsystem.h> -#include <projectexplorer/buildtargetinfo.h> -#include <projectexplorer/kitmanager.h> -#include <projectexplorer/projectexplorerconstants.h> -#include <projectexplorer/projectnodes.h> -#include <projectexplorer/target.h> - -#include <QJsonArray> -#include <QJsonDocument> -#include <QJsonObject> -#include <QProcessEnvironment> -#include <QRegularExpression> -#include <QTimer> - -#include <coreplugin/documentmanager.h> #include <coreplugin/icontext.h> -#include <coreplugin/icore.h> -#include <coreplugin/messagemanager.h> - -#include <qmljs/qmljsmodelmanagerinterface.h> -#include <utils/algorithm.h> -#include <utils/fileutils.h> -#include <utils/mimeutils.h> +#include <projectexplorer/projectexplorerconstants.h> using namespace Core; using namespace ProjectExplorer; @@ -37,155 +16,6 @@ using namespace Utils; namespace Python::Internal { -class PythonBuildSystem : public BuildSystem -{ -public: - explicit PythonBuildSystem(Target *target); - - bool supportsAction(Node *context, ProjectAction action, const Node *node) const override; - bool addFiles(Node *, const FilePaths &filePaths, FilePaths *) override; - RemovedFilesFromProject removeFiles(Node *, const FilePaths &filePaths, FilePaths *) override; - bool deleteFiles(Node *, const FilePaths &) override; - bool renameFile(Node *, - const FilePath &oldFilePath, - const FilePath &newFilePath) override; - QString name() const override { return QLatin1String("python"); } - - void parse(); - bool save(); - - bool writePyProjectFile(const FilePath &filePath, QString &content, - const QStringList &rawList, QString *errorMessage); - - void triggerParsing() final; - -private: - struct FileEntry { - QString rawEntry; - FilePath filePath; - }; - QList<FileEntry> processEntries(const QStringList &paths) const; - - QList<FileEntry> m_files; - QList<FileEntry> m_qmlImportPaths; -}; - -/** - * @brief Provides displayName relative to project node - */ -class PythonFileNode : public FileNode -{ -public: - PythonFileNode(const FilePath &filePath, const QString &nodeDisplayName, - FileType fileType = FileType::Source) - : FileNode(filePath, fileType) - , m_displayName(nodeDisplayName) - {} - - QString displayName() const override { return m_displayName; } -private: - QString m_displayName; -}; - -static QJsonObject readObjJson(const FilePath &projectFile, QString *errorMessage) -{ - const expected_str<QByteArray> fileContentsResult = projectFile.fileContents(); - if (!fileContentsResult) { - *errorMessage = fileContentsResult.error(); - return {}; - } - - const QByteArray content = *fileContentsResult; - - // This assumes the project file is formed with only one field called - // 'files' that has a list associated of the files to include in the project. - if (content.isEmpty()) { - *errorMessage = Tr::tr("Unable to read \"%1\": The file is empty.") - .arg(projectFile.toUserOutput()); - return QJsonObject(); - } - - QJsonParseError error; - const QJsonDocument doc = QJsonDocument::fromJson(content, &error); - if (doc.isNull()) { - const int line = content.left(error.offset).count('\n') + 1; - *errorMessage = Tr::tr("Unable to parse \"%1\":%2: %3") - .arg(projectFile.toUserOutput()).arg(line) - .arg(error.errorString()); - return QJsonObject(); - } - - return doc.object(); -} - -static QStringList readLines(const FilePath &projectFile) -{ - QSet<QString> visited; - QStringList lines; - - const expected_str<QByteArray> contents = projectFile.fileContents(); - if (contents) { - QTextStream stream(contents.value()); - - while (true) { - const QString line = stream.readLine(); - if (line.isNull()) - break; - if (!Utils::insert(visited, line)) - continue; - lines.append(line); - } - } - - return lines; -} - -static QStringList readLinesJson(const FilePath &projectFile, QString *errorMessage) -{ - QSet<QString> visited; - QStringList lines; - - const QJsonObject obj = readObjJson(projectFile, errorMessage); - for (const QJsonValue &file : obj.value("files").toArray()) { - const QString fileName = file.toString(); - if (Utils::insert(visited, fileName)) - lines.append(fileName); - } - - return lines; -} - -static QStringList readImportPathsJson(const FilePath &projectFile, QString *errorMessage) -{ - QStringList importPaths; - - const QJsonObject obj = readObjJson(projectFile, errorMessage); - if (obj.contains("qmlImportPaths")) { - const QJsonValue dirs = obj.value("qmlImportPaths"); - const QJsonArray dirs_array = dirs.toArray(); - - QSet<QString> visited; - - for (const auto &dir : dirs_array) - visited.insert(dir.toString()); - - importPaths.append(Utils::toList(visited)); - } - - return importPaths; -} - -class PythonProjectNode : public ProjectNode -{ -public: - PythonProjectNode(const FilePath &path) - : ProjectNode(path) - { - setDisplayName(path.completeBaseName()); - setAddFileFilter("*.py"); - } -}; - PythonProject::PythonProject(const FilePath &fileName) : Project(Constants::C_PY_MIMETYPE, fileName) { @@ -196,229 +26,6 @@ PythonProject::PythonProject(const FilePath &fileName) setBuildSystemCreator([](Target *t) { return new PythonBuildSystem(t); }); } -static FileType getFileType(const FilePath &f) -{ - if (f.endsWith(".py")) - return FileType::Source; - if (f.endsWith(".qrc")) - return FileType::Resource; - if (f.endsWith(".ui")) - return FileType::Form; - if (f.endsWith(".qml") || f.endsWith(".js")) - return FileType::QML; - return Node::fileTypeForFileName(f); -} - -void PythonBuildSystem::triggerParsing() -{ - ParseGuard guard = guardParsingRun(); - parse(); - - QList<BuildTargetInfo> appTargets; - - auto newRoot = std::make_unique<PythonProjectNode>(projectDirectory()); - - const FilePath projectFile = projectFilePath(); - const QString displayName = projectFile.relativePathFrom(projectDirectory()).toUserOutput(); - newRoot->addNestedNode( - std::make_unique<PythonFileNode>(projectFile, displayName, FileType::Project)); - - for (const FileEntry &entry : std::as_const(m_files)) { - const QString displayName = entry.filePath.relativePathFrom(projectDirectory()).toUserOutput(); - const FileType fileType = getFileType(entry.filePath); - - newRoot->addNestedNode(std::make_unique<PythonFileNode>(entry.filePath, displayName, fileType)); - const MimeType mt = mimeTypeForFile(entry.filePath, MimeMatchMode::MatchExtension); - if (mt.matchesName(Constants::C_PY_MIMETYPE) || mt.matchesName(Constants::C_PY3_MIMETYPE) - || mt.matchesName(Constants::C_PY_GUI_MIMETYPE)) { - BuildTargetInfo bti; - bti.displayName = displayName; - bti.buildKey = entry.filePath.toString(); - bti.targetFilePath = entry.filePath; - bti.projectFilePath = projectFile; - bti.isQtcRunnable = entry.filePath.fileName() == "main.py"; - appTargets.append(bti); - } - } - setRootProjectNode(std::move(newRoot)); - - setApplicationTargets(appTargets); - - auto modelManager = QmlJS::ModelManagerInterface::instance(); - if (modelManager) { - const auto hiddenRccFolders = project()->files(Project::HiddenRccFolders); - auto projectInfo = modelManager->defaultProjectInfoForProject(project(), hiddenRccFolders); - - for (const FileEntry &importPath : std::as_const(m_qmlImportPaths)) { - if (!importPath.filePath.isEmpty()) - projectInfo.importPaths.maybeInsert(importPath.filePath, QmlJS::Dialect::Qml); - } - - modelManager->updateProjectInfo(projectInfo, project()); - } - - guard.markAsSuccess(); - - emitBuildSystemUpdated(); -} - -bool PythonBuildSystem::save() -{ - const FilePath filePath = projectFilePath(); - const QStringList rawList = Utils::transform(m_files, &FileEntry::rawEntry); - const FileChangeBlocker changeGuarg(filePath); - bool result = false; - - QByteArray newContents; - - // New project file - if (filePath.endsWith(".pyproject")) { - expected_str<QByteArray> contents = filePath.fileContents(); - if (contents) { - QJsonDocument doc = QJsonDocument::fromJson(*contents); - QJsonObject project = doc.object(); - project["files"] = QJsonArray::fromStringList(rawList); - doc.setObject(project); - newContents = doc.toJson(); - } else { - MessageManager::writeDisrupting(contents.error()); - } - } else { // Old project file - newContents = rawList.join('\n').toUtf8(); - } - - const expected_str<qint64> writeResult = filePath.writeFileContents(newContents); - if (writeResult) - result = true; - else - MessageManager::writeDisrupting(writeResult.error()); - - return result; -} - -bool PythonBuildSystem::addFiles(Node *, const FilePaths &filePaths, FilePaths *) -{ - const Utils::FilePath projectDir = projectDirectory(); - - auto comp = [](const FileEntry &left, const FileEntry &right) { - return left.rawEntry < right.rawEntry; - }; - - const bool isSorted = std::is_sorted(m_files.begin(), m_files.end(), comp); - - for (const FilePath &filePath : filePaths) { - if (!projectDir.isSameDevice(filePath)) - return false; - m_files.append(FileEntry{filePath.relativePathFrom(projectDir).toString(), filePath}); - } - - if (isSorted) - std::sort(m_files.begin(), m_files.end(), comp); - - return save(); -} - -RemovedFilesFromProject PythonBuildSystem::removeFiles(Node *, const FilePaths &filePaths, FilePaths *) -{ - - for (const FilePath &filePath : filePaths) { - Utils::eraseOne(m_files, - [filePath](const FileEntry &entry) { return filePath == entry.filePath; }); - } - - return save() ? RemovedFilesFromProject::Ok : RemovedFilesFromProject::Error; -} - -bool PythonBuildSystem::deleteFiles(Node *, const FilePaths &) -{ - return true; -} - -bool PythonBuildSystem::renameFile(Node *, const FilePath &oldFilePath, const FilePath &newFilePath) -{ - for (FileEntry &entry : m_files) { - if (entry.filePath == oldFilePath) { - entry.filePath = newFilePath; - entry.rawEntry = newFilePath.relativeChildPath(projectDirectory()).toString(); - break; - } - } - - return save(); -} - -void PythonBuildSystem::parse() -{ - m_files.clear(); - m_qmlImportPaths.clear(); - - QStringList files; - QStringList qmlImportPaths; - - const FilePath filePath = projectFilePath(); - // The PySide project file is JSON based - if (filePath.endsWith(".pyproject")) { - QString errorMessage; - files = readLinesJson(filePath, &errorMessage); - if (!errorMessage.isEmpty()) - MessageManager::writeFlashing(errorMessage); - - errorMessage.clear(); - qmlImportPaths = readImportPathsJson(filePath, &errorMessage); - if (!errorMessage.isEmpty()) - MessageManager::writeFlashing(errorMessage); - } else if (filePath.endsWith(".pyqtc")) { - // To keep compatibility with PyQt we keep the compatibility with plain - // text files as project files. - files = readLines(filePath); - } - - m_files = processEntries(files); - m_qmlImportPaths = processEntries(qmlImportPaths); -} - -/** - * Expands environment variables in the given \a string when they are written - * like $$(VARIABLE). - */ -static void expandEnvironmentVariables(const Environment &env, QString &string) -{ - const QRegularExpression candidate("\\$\\$\\((.+)\\)"); - - QRegularExpressionMatch match; - int index = string.indexOf(candidate, 0, &match); - while (index != -1) { - const QString value = env.value(match.captured(1)); - - string.replace(index, match.capturedLength(), value); - index += value.length(); - - index = string.indexOf(candidate, index, &match); - } -} - -/** - * Expands environment variables and converts the path from relative to the - * project to an absolute path for all given raw paths - */ -QList<PythonBuildSystem::FileEntry> PythonBuildSystem::processEntries( - const QStringList &rawPaths) const -{ - QList<FileEntry> processed; - const FilePath projectDir = projectDirectory(); - const Environment env = projectDirectory().deviceEnvironment(); - - for (const QString &rawPath : rawPaths) { - FilePath resolvedPath; - QString path = rawPath.trimmed(); - if (!path.isEmpty()) { - expandEnvironmentVariables(env, path); - resolvedPath = projectDir.resolvePath(path); - } - processed << FileEntry{rawPath, resolvedPath}; - } - return processed; -} Project::RestoreResult PythonProject::fromMap(const Store &map, QString *errorMessage) { @@ -431,25 +38,23 @@ Project::RestoreResult PythonProject::fromMap(const Store &map, QString *errorMe return res; } -PythonBuildSystem::PythonBuildSystem(Target *target) - : BuildSystem(target) +PythonProjectNode::PythonProjectNode(const FilePath &path) + : ProjectNode(path) { - connect(target->project(), &Project::projectFileIsDirty, this, [this] { triggerParsing(); }); - triggerParsing(); + setDisplayName(path.completeBaseName()); + setAddFileFilter("*.py"); } -bool PythonBuildSystem::supportsAction(Node *context, ProjectAction action, const Node *node) const +PythonFileNode::PythonFileNode(const FilePath &filePath, + const QString &nodeDisplayName, + FileType fileType) + : FileNode(filePath, fileType) + , m_displayName(nodeDisplayName) +{} + +QString PythonFileNode::displayName() const { - if (node->asFileNode()) { - return action == ProjectAction::Rename - || action == ProjectAction::RemoveFile; - } - if (node->isFolderNodeType() || node->isProjectNodeType()) { - return action == ProjectAction::AddNewFile - || action == ProjectAction::RemoveFile - || action == ProjectAction::AddExistingFile; - } - return BuildSystem::supportsAction(context, action, node); + return m_displayName; } } // Python::Internal |
