Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Upgrade PyQt5 to PyQt6. #2279

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
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
47 changes: 25 additions & 22 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-20.04, ubuntu-latest, macos-11, macos-latest, windows-2019, windows-latest]
python-version: ['3.5', '3.6', '3.7', '3.8']
exclude:
# Python 3.5 and 3.6 not available in the latest Ubuntu runners
- os: ubuntu-latest
python-version: '3.5'
- os: ubuntu-latest
python-version: '3.6'
python-version: ['3.8', '3.9', '3.10', '3.11']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
python-version: ['3.8', '3.9', '3.10', '3.11']
python-version: ['3.8', '3.9', '3.10', '3.11', ‘3.12’]

fail-fast: false
runs-on: ${{ matrix.os }}
name: Test Py ${{ matrix.python-version }} - ${{ matrix.os }}
Expand All @@ -39,26 +33,32 @@ jobs:
pip freeze
- name: Prepare Ubuntu
if: runner.os == 'Linux'
# This is needed for PyQt6 to not crash with missing: libEGL.so.1 libGL.so.1 libglib-2.0.so.0
run: |
sudo apt-get update
sudo apt-get install -y libxkbcommon-x11-0 xvfb
sudo apt update
sudo apt install -y libegl1 libgl1 libglib2.0-0
- name: Install Mu dependencies
run: |
pip install .[dev]
pip list
timeout-minutes: 10
- name: Run tests
if: runner.os == 'Linux'
run: xvfb-run make check
run: QT_QPA_PLATFORM=offscreen python make.py check
timeout-minutes: 5
- name: Run tests
if: runner.os != 'Linux'
run: python make.py check
timeout-minutes: 5

test-arm:
# Missing Qt6 versions of: python3-pyqt5.qsci python3-pyqt5.qtchart
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has this situation improved?

# Using pip --use-deprecated=legacy-resolver, as it takes too long, revisit after updating dep versions
# Also had to force install cryptography, pyzmq, and cffi from piwheels, or it tries to build them from source
# Should also revisit if we need all the extra apt packages
if: false
runs-on: ubuntu-latest
name: Test Py 3.7 - arm-debian-buster
name: Test Py 3.10 - arm-debian-bookworm
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is bookworm’s default Python 3.10 or 3.11?

steps:
- uses: actions/checkout@v3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Set up QEMU
Expand All @@ -68,39 +68,42 @@ jobs:
image: tonistiigi/binfmt:latest
platforms: 'linux/arm64,linux/arm/v7,linux/arm/v6'
- name: Check Debian image info
uses: docker://multiarch/debian-debootstrap:armhf-buster
uses: docker://arm32v7/debian:bookworm
with:
args: /bin/bash -c "uname -a && cat /etc/os-release"
args: bash -c "uname -a && cat /etc/os-release"
# This is testing armv7, we could create a matrix for armv8 (aarch64) as well
- name: Install dependencies and run tests
uses: docker://multiarch/debian-debootstrap:armhf-buster
uses: docker://arm32v7/debian:bookworm
with:
args: >
bash -c "
apt-get update &&
apt-get install -y python3 python3-pip python3-virtualenv &&
apt-get install -y python3-pyqt5 python3-pyqt5.qsci python3-pyqt5.qtserialport python3-pyqt5.qtsvg python3-pyqt5.qtchart &&
apt-get install -y libxmlsec1-dev libxml2 libxml2-dev libxkbcommon-x11-0 libatlas-base-dev &&
apt-get install -y git xvfb &&
apt update &&
apt install -y python3 python3-pip python3-virtualenv &&
apt install -y python3-pyqt6 python3-pyqt6.qtserialport python3-pyqt6.qtsvg &&
apt install -y libxmlsec1-dev libxml2 libxml2-dev libxkbcommon-x11-0 libatlas-base-dev &&
python3 -m virtualenv venv --python=python3 --system-site-packages &&
source venv/bin/activate &&
python -c \"import platform, struct, sys; print(platform.machine(), struct.calcsize('P') * 8, sys.version)\" &&
python -m pip --version &&
python -m pip config set global.extra-index-url https://www.piwheels.org/simple &&
python -m pip config list &&
python -m pip list &&
python -m pip install .[dev] &&
python -m pip install cryptography pyzmq cffi --python-version 3.9 --only-binary \":all:\" --no-deps --target venv/lib/python3.10/site-packages/ &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line creates a Py3.9 binary and puts into the Py3.10 site packages?

python -m pip install .[dev] --use-deprecated=legacy-resolver &&
python -m pip list &&
QT_QPA_PLATFORM=\"offscreen\" &&
xvfb-run python make.py check &&
python make.py check &&
echo 'Finished successfully! :)'
"

test-pios:
# Pi OS images don't have python3-pyqt6 packages, so will have to enable and update in the future
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has this situation improved?

if: false
name: Test PiOS ${{ matrix.docker-tag }}
runs-on: ubuntu-latest
strategy:
matrix:
docker-tag: ['stretch-2018-03-13', 'buster-2021-05-28', 'buster-legacy-2022-04-07']
docker-tag: ['buster-2021-05-28', 'buster-legacy-2022-04-07']
fail-fast: false
services:
rpios:
Expand Down
19 changes: 10 additions & 9 deletions mu/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@
import webbrowser
import base64

from PyQt5.QtCore import (
from PyQt6.QtCore import (
Qt,
QEventLoop,
QThread,
QObject,
pyqtSignal,
QSharedMemory,
)
from PyQt5.QtWidgets import QApplication, QSplashScreen
from PyQt6.QtWidgets import QApplication, QSplashScreen

from . import i18n
from .virtual_environment import venv, logger as vlogger
Expand Down Expand Up @@ -366,16 +366,16 @@ def run():

# Images (such as toolbar icons) aren't scaled nicely on retina/4k displays
# unless this flag is set
os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1"
if hasattr(Qt, "AA_EnableHighDpiScaling"):
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
# os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1"
# if hasattr(Qt, "AA_EnableHighDpiScaling"):
# QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
# QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)

# An issue in PyQt5 v5.13.2 to v5.15.1 makes PyQt5 application
# An issue in PyQt5 v5.13.2 to v5.15.1 makes PyQt6 application
# hang on Mac OS 11 (Big Sur)
# Setting this environment variable fixes the problem.
# See issue #1147 for more information
os.environ["QT_MAC_WANTS_LAYER"] = "1"
# os.environ["QT_MAC_WANTS_LAYER"] = "1"

# In Wayland for AppImage to launch it needs QT_QPA_PLATFORM set
# But only touch it if unset, useful for CI to configure it to "offscreen"
Expand Down Expand Up @@ -462,7 +462,8 @@ def load_theme(theme):
editor.restore_session(sys.argv[1:])

# Save the exit code for sys.exit call below.
exit_status = app.exec_()
exit_status = app.exec()

# Clean up the shared memory used to signal an app instance is running
_shared_memory.release()

Expand Down
2 changes: 1 addition & 1 deletion mu/debugger/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import time
import logging
import os.path
from PyQt5.QtCore import QObject, QThread, pyqtSignal
from PyQt6.QtCore import QObject, QThread, pyqtSignal


logger = logging.getLogger(__name__)
Expand Down
2 changes: 1 addition & 1 deletion mu/i18n.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os
import gettext

from PyQt5.QtCore import QLocale
from PyQt6.QtCore import QLocale

# DEBUG/TRANSLATE: override the language code here (e.g. to Chinese).
# language_code = 'zh'
Expand Down
11 changes: 5 additions & 6 deletions mu/interface/dialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@
"""
import os
import logging
import sys
from PyQt6.QtCore import QSize, QProcess, QTimer, Qt

from PyQt5.QtCore import QSize, QProcess, QTimer, Qt
from PyQt5.QtWidgets import (
from PyQt6.QtWidgets import (
QHBoxLayout,
QVBoxLayout,
QGridLayout,
Expand All @@ -40,7 +39,7 @@
QGroupBox,
QComboBox,
)
from PyQt5.QtGui import QTextCursor
from PyQt6.QtGui import QTextCursor
from mu.resources import load_icon
from mu.interface.widgets import DeviceSelector
from ..virtual_environment import venv
Expand Down Expand Up @@ -492,11 +491,11 @@ def run_esptool(self):
self.process.readyReadStandardError.connect(self.read_process)
self.process.readyReadStandardOutput.connect(self.read_process)
self.process.finished.connect(self.esptool_finished)
self.process.error.connect(self.esptool_error)
self.process.errorOccurred.connect(self.esptool_error)

command = self.commands.pop(0)
self.log_text_area.appendPlainText(command + "\n")
self.process.start(command)
self.process.startCommand(command)

def esptool_error(self, error_num):
self.log_text_area.appendPlainText(
Expand Down
28 changes: 18 additions & 10 deletions mu/interface/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@
import logging
import os.path
from collections import defaultdict
from PyQt5.Qsci import (
from PyQt6.Qsci import (
QsciScintilla,
QsciLexerPython,
QsciLexerHTML,
QsciAPIs,
QsciLexerCSS,
)
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtWidgets import QApplication
from PyQt6.QtCore import Qt, pyqtSignal
from PyQt6.QtWidgets import QApplication
from mu.interface.themes import Font, DayTheme
from mu.logic import NEWLINE

Expand Down Expand Up @@ -192,12 +192,14 @@ def configure(self):
self.setEdgeColumn(79)
self.setMarginLineNumbers(0, True)
self.setMarginWidth(0, 50)
self.setBraceMatching(QsciScintilla.SloppyBraceMatch)
self.setBraceMatching(QsciScintilla.BraceMatch.SloppyBraceMatch)
self.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0)
self.set_theme()
# Markers and indicators
self.setMarginSensitivity(0, True)
self.markerDefine(self.Circle, self.BREAKPOINT_MARKER)
self.markerDefine(
QsciScintilla.MarkerSymbol.Circle, self.BREAKPOINT_MARKER
)
self.setMarginSensitivity(1, True)
# Additional dummy margin to prevent accidental breakpoint toggles when
# trying to position the edit cursor to the left of the first column,
Expand All @@ -211,14 +213,20 @@ def configure(self):
self.setIndicatorDrawUnder(True)
for type_ in self.check_indicators:
self.indicatorDefine(
self.SquiggleIndicator, self.check_indicators[type_]["id"]
QsciScintilla.IndicatorStyle.SquiggleIndicator,
self.check_indicators[type_]["id"],
)
for type_ in self.search_indicators:
self.indicatorDefine(
self.StraightBoxIndicator, self.search_indicators[type_]["id"]
QsciScintilla.IndicatorStyle.StraightBoxIndicator,
self.search_indicators[type_]["id"],
)
self.indicatorDefine(self.FullBoxIndicator, self.DEBUG_INDICATOR)
self.setAnnotationDisplay(self.AnnotationBoxed)
self.indicatorDefine(
QsciScintilla.IndicatorStyle.FullBoxIndicator, self.DEBUG_INDICATOR
)
self.setAnnotationDisplay(
QsciScintilla.AnnotationDisplay.AnnotationBoxed
)
self.selectionChanged.connect(self.selection_change_listener)
self.set_zoom()

Expand Down Expand Up @@ -259,7 +267,7 @@ def set_theme(self, theme=DayTheme):
theme.BreakpointMarker, self.BREAKPOINT_MARKER
)
self.setAutoCompletionThreshold(2)
self.setAutoCompletionSource(QsciScintilla.AcsAll)
self.setAutoCompletionSource(QsciScintilla.AutoCompletionSource.AcsAll)
self.setLexer(self.lexer)
self.setMarginsBackgroundColor(theme.Margin)
self.setMarginsForegroundColor(theme.Caret)
Expand Down
27 changes: 15 additions & 12 deletions mu/interface/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,9 @@
import sys
import logging
import os.path
from PyQt5.QtCore import QSize, Qt, pyqtSignal, QTimer, QThread
from PyQt5.QtWidgets import (
from PyQt6.QtCore import QSize, Qt, pyqtSignal, QTimer, QThread
from PyQt6.QtWidgets import (
QToolBar,
QAction,
QDesktopWidget,
QWidget,
QVBoxLayout,
QTabWidget,
Expand All @@ -33,14 +31,19 @@
QMainWindow,
QStatusBar,
QDockWidget,
QShortcut,
QApplication,
QTabBar,
QPushButton,
QHBoxLayout,
QProgressDialog,
)
from PyQt5.QtGui import QKeySequence, QStandardItemModel, QCursor
from PyQt6.QtGui import (
QKeySequence,
QStandardItemModel,
QCursor,
QAction,
QShortcut,
)
from mu import __version__
from mu.interface.dialogs import (
ModeSelector,
Expand Down Expand Up @@ -85,7 +88,7 @@ def __init__(self, parent):
super().__init__(parent)
self.setMovable(False)
self.setIconSize(QSize(64, 64))
self.setToolButtonStyle(3)
self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
self.setContextMenuPolicy(Qt.PreventContextMenu)
self.setObjectName("StandardToolBar")
self.reset()
Expand Down Expand Up @@ -557,7 +560,7 @@ def on_context_menu(self):
menu.insertAction(actions[0], copy_to_repl)
menu.insertSeparator(actions[0])
# Display menu.
menu.exec_(QCursor.pos())
menu.exec(QCursor.pos())

def copy_to_repl(self):
"""
Expand Down Expand Up @@ -1093,7 +1096,7 @@ def screen_size(self):
"""
Returns an (width, height) tuple with the screen geometry.
"""
screen = QDesktopWidget().screenGeometry()
screen = self.screen().availableGeometry()
return screen.width(), screen.height()

def size_window(self, x=None, y=None, w=None, h=None):
Expand Down Expand Up @@ -1394,9 +1397,9 @@ def upload_to_python_anywhere(
self.worker.finished.connect(self.worker.deleteLater)
self.worker.finished.connect(self.handle_python_anywhere_complete)
self.upload_thread.finished.connect(self.upload_thread.deleteLater)
self.worker.error.connect(self.handle_python_anywhere_error)
self.worker.error.connect(self.upload_thread.quit)
self.worker.error.connect(self.worker.deleteLater)
self.worker.errorOccurred.connect(self.handle_python_anywhere_error)
self.worker.errorOccurred.connect(self.upload_thread.quit)
self.worker.errorOccurred.connect(self.worker.deleteLater)
self.upload_thread.start()

def handle_python_anywhere_complete(self, domain):
Expand Down
Loading