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

PR: Migrate the Spyder update mechanism to the conda-based standalone application #20900

Closed
wants to merge 51 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
ab2d85b
Revert "Auxiliary commit to revert individual files from 3dd22c43522b…
mrclary May 15, 2023
6c42ae0
Update message boxes now that updates will always be >6.0.0
mrclary Sep 28, 2023
66a04dd
Convert update mechanism to conda-based installers.
mrclary May 2, 2023
70e1252
Add logging messages
mrclary May 4, 2023
cada5fc
Fix issue where releases could be empty if current version is non-sta…
mrclary May 4, 2023
bf652a6
First check for updates from github:
mrclary May 4, 2023
09583ae
Create single install method.
mrclary May 7, 2023
edaad51
Fix issue where update status was not set when error occurs on startup.
mrclary May 5, 2023
13ecda0
Do not set status to CHECKING while waiting for timer at startup. Ins…
mrclary May 5, 2023
88fab90
Update unit tests
mrclary May 5, 2023
4ede59a
Allow update with conda
mrclary May 6, 2023
9fd0c73
Include all releases, unless current version is stable
mrclary May 6, 2023
83fb3f5
Allow installing after download complete
mrclary May 6, 2023
fee61b6
Fix PermissionError Operation not permitted. os.remove cannot remove …
mrclary May 6, 2023
05d4e81
Alert user to download error and do not proceed with installation
mrclary May 8, 2023
744e9de
Set application status to NO_STATUS if error.
mrclary May 8, 2023
c7c4e99
Use x86_64 for Windows machine architecture. Fix this later
mrclary May 8, 2023
562e43d
Fix "TypeError: _progress_reporter() missing 1 required positional ar…
mrclary May 8, 2023
4e26b02
Improve debug logging for errors in workers
mrclary May 8, 2023
e827776
Rename start_installation -> start_download
mrclary May 9, 2023
7359034
Rename cancel_installation -> cancel_download
mrclary May 9, 2023
5adffc3
Rename install -> start_installation
mrclary May 9, 2023
60f3aa0
Rename UpdateInstallation -> UpdateDownload; _installation_widget -> …
mrclary May 9, 2023
70a9167
Rename _change_update_installation_status -> _change_update_download_…
mrclary May 10, 2023
612da33
If proceeding with install, request install on close and quit Spyder
mrclary Jun 2, 2023
482a282
Use update/install shell scripts.
mrclary Sep 28, 2023
11e1125
Prevent the Application plugin container's on_close method from being…
mrclary Jun 3, 2023
9eba260
Fix download url to resolve any release
mrclary Jun 26, 2023
ac54ff6
Fix issue where update dialog menu could attempt download instead of …
mrclary Jun 27, 2023
48684a9
Debug logging message shows available versions.
mrclary Sep 20, 2023
e56cf51
Set application_update_status for all distributions
mrclary Sep 20, 2023
a1575fd
Refactor check_updates on startup
mrclary Sep 20, 2023
76382f2
Move download error message box to UpdateInstallerDialog.
mrclary Sep 20, 2023
e07c8b3
Fix resetting startup
mrclary Sep 28, 2023
9a6b9ee
Fix update status.
mrclary Sep 28, 2023
5f94290
Fix extraneous argument to set_status_pending
mrclary Sep 28, 2023
8919baf
Restrict channel to conda-forge on install
mrclary Sep 28, 2023
f19d4c5
Search conda-forge instead of anaconda's main channel.
mrclary Sep 28, 2023
a89ac0c
Refactor the resetting of WorkerUpdates attributes.
mrclary Sep 28, 2023
b750b79
Move the try...except clauses in WorkerUpdates.start to WorkerUpdates…
mrclary Sep 28, 2023
630c9c1
pep8
mrclary Sep 28, 2023
c30f566
Refactor _check_updates_ready for better clarity in the flow control.
mrclary Sep 29, 2023
6c58869
Add header to confirm_installation message box
mrclary Sep 29, 2023
a81d5f0
Update download message.
mrclary Sep 29, 2023
980b4e3
Use conda-forge channeldata.json instead of conda search.
mrclary Sep 29, 2023
9a44cfb
Change cancel download message.
mrclary Sep 29, 2023
583254c
Use conda-forge channeldata.json to get releases for conda runtime en…
mrclary Sep 29, 2023
c7b7a51
Simplify WorkerUpdates.
mrclary Sep 29, 2023
c872d69
Use __version__ instead of WorkerUpdates.version
mrclary Sep 30, 2023
93c7d77
Refactor installer UIs to install.py.
mrclary Sep 30, 2023
6ef4b68
Create user preference to determine whether to consider unstable rele…
mrclary Sep 30, 2023
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
Prev Previous commit
Next Next commit
Allow update with conda
  • Loading branch information
mrclary committed Sep 29, 2023
commit 4ede59a96322ce1fd2eb65912ac6ffc8354f86e8
29 changes: 17 additions & 12 deletions spyder/plugins/application/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,7 @@ def __init__(self, name, plugin, parent=None):
self.current_dpi = None
self.dpi_messagebox = None

# Keep track of the downloaded installer executable for updates
self.installer_path = None
self.install_on_close = False

# ---- PluginMainContainer API
# -------------------------------------------------------------------------
Expand All @@ -116,7 +115,7 @@ def setup(self):
(self.application_update_status.sig_check_for_updates_requested
.connect(self.check_updates))
(self.application_update_status.sig_install_on_close_requested
.connect(self.set_installer_path))
.connect(self.set_install_on_close))
self.application_update_status.set_no_status()
self.give_updates_feedback = False
self.thread_updates = None
Expand Down Expand Up @@ -238,9 +237,8 @@ def on_close(self):
self.dependencies_thread.wait()

# Run installer after Spyder is closed
if self.installer_path:
self.application_update_status.installer.install(
self.installer_path)
if self.install_on_close:
self.application_update_status.install()

@Slot()
def show_about(self):
Expand Down Expand Up @@ -308,6 +306,9 @@ def _check_updates_ready(self):
box.set_check_visible(False)
box.exec_()
elif update_available:
self.application_update_status.save_latest_release(
latest_release, update_from_github)

# Update using our installers
box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
box.setDefaultButton(QMessageBox.Yes)
Expand All @@ -334,8 +335,12 @@ def _check_updates_ready(self):
box.setText(msg)
box.exec_()
if box.result() == QMessageBox.Yes:
self.application_update_status.start_installation(
latest_release=latest_release)
if update_from_github:
# Start download
self.application_update_status.start_installation()
else:
# Confirm installation
self.application_update_status.confirm_installation()

# Manual update
if box.result() == QMessageBox.No and not is_conda_based_app():
Expand Down Expand Up @@ -426,10 +431,10 @@ def check_updates(self, startup=False):
# Otherwise, start immediately
self.thread_updates.start()

@Slot(str)
def set_installer_path(self, installer_path):
"""Set installer executable path to be run when closing."""
self.installer_path = installer_path
@Slot(bool)
def set_install_on_close(self, install_on_close):
"""Set whether installer should be run when closing."""
self.install_on_close = install_on_close

# ---- Dependencies
# -------------------------------------------------------------------------
Expand Down
79 changes: 50 additions & 29 deletions spyder/plugins/application/widgets/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from spyder import __version__
from spyder.api.translations import _
from spyder.config.base import is_conda_based_app
from spyder.utils.conda import find_conda
from spyder.utils.icon_manager import ima
from spyder.workers.updates import WorkerDownloadInstaller

Expand Down Expand Up @@ -135,28 +136,29 @@ class UpdateInstallerDialog(QDialog):
Latest release version detected.
"""

sig_install_on_close_requested = Signal(str)
sig_install_on_close_requested = Signal(bool)
"""
Signal to request running the downloaded installer on close.

Parameters
----------
installer_path: str
Path to the installer executable.
install_on_close: bool
Whether to install on close.
"""

def __init__(self, parent):
self.cancelled = False
self.status = NO_STATUS
self.download_thread = None
self.download_worker = None
self.latest_release = None
self.update_from_github = None
self.installer_path = None

super().__init__(parent)
self.setWindowFlags(Qt.Dialog | Qt.MSWindowsFixedSizeDialogHint)
self._parent = parent
self._installation_widget = UpdateInstallation(self)
self.latest_release_version = ""

# Layout
installer_layout = QVBoxLayout()
Expand Down Expand Up @@ -190,18 +192,20 @@ def setup(self):
self._installation_widget.setVisible(True)
self.adjustSize()

def save_latest_release(self, latest_release_version):
self.latest_release_version = latest_release_version
def save_latest_release(self, latest_release, update_from_github):
self.latest_release = latest_release
self.update_from_github = update_from_github

def start_installation(self, latest_release_version):
def start_installation(self):
"""Start downloading the update and set downloading status."""
self.latest_release_version = latest_release_version
self.cancelled = False
self._change_update_installation_status(
status=DOWNLOADING_INSTALLER)
self.download_thread = QThread(None)
self.download_worker = WorkerDownloadInstaller(
self, self.latest_release_version)
self, self.latest_release)
self.download_worker.sig_ready.connect(
lambda: self._change_update_installation_status(DOWNLOAD_FINISHED))
self.download_worker.sig_ready.connect(self.confirm_installation)
self.download_worker.sig_ready.connect(self.download_thread.quit)
self.download_worker.sig_download_progress.connect(
Expand Down Expand Up @@ -239,18 +243,22 @@ def continue_installation(self):
reply.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
reply.exec_()
if reply.result() == QMessageBox.Yes:
self.start_installation(self.latest_release_version)
self.start_installation()
else:
self._change_update_installation_status(status=PENDING)

def confirm_installation(self, installer_path):
def confirm_installation(self):
"""
Ask users if they want to proceed with the installer execution.
"""
if self.cancelled:
return
self._change_update_installation_status(status=DOWNLOAD_FINISHED)
self.installer_path = installer_path

self.installer_path = None
# Get data from WorkerDownload
if self.download_worker:
self.installer_path = self.download_worker.installer_path

msg_box = QMessageBox(
icon=QMessageBox.Question,
text=_("Would you like to proceed with the installation?<br><br>"),
Expand All @@ -271,27 +279,40 @@ def confirm_installation(self, installer_path):

if msg_box.clickedButton() == yes_button:
self._change_update_installation_status(status=INSTALLING)
self.install(installer_path)
self.install()
self._change_update_installation_status(status=PENDING)
elif msg_box.clickedButton() == after_closing_button:
self.sig_install_on_close_requested.emit(self.installer_path)
self.sig_install_on_close_requested.emit(True)
self._change_update_installation_status(status=PENDING)
else:
self._change_update_installation_status(status=PENDING)

def install(self, installer_path):
"""Install from downloaded installer."""
if os.name == 'nt':
cmd = 'start'
elif sys.platform == 'darwin':
cmd = 'open'
else:
cmd = 'gnome-terminal --window -- sh'
if os.path.exists(installer_path):
subprocess.Popen(
' '.join([cmd, installer_path]),
shell=True
)
def install(self):
"""Install from downloaded installer or update through conda."""
if (
self.update_from_github
and self.installer_path is not None
and os.path.exists(self.installer_path)
):
# TODO: Close Spyder
# TODO: Uninstall existing version
# Run downloaded installer
if os.name == 'nt':
cmd = 'start'
elif sys.platform == 'darwin':
cmd = 'open'
else:
cmd = 'gnome-terminal --window -- sh'
subprocess.Popen(' '.join([cmd, self.installer_path]), shell=True)
elif (
not self.update_from_github
and self.latest_release is not None
):
# Update with conda
# TODO: Restart Spyder
cmd = [find_conda(), 'install', '-p', sys.prefix,
f'spyder={self.latest_release}']
subprocess.Popen(' '.join(cmd), shell=True)

def finish_installation(self):
"""Handle finished installation."""
Expand All @@ -317,7 +338,7 @@ def _change_update_installation_status(self, status=NO_STATUS):
elif status == FINISHED or status == PENDING:
self.finish_installation()
self.sig_installation_status.emit(
self.status, self.latest_release_version)
self.status, self.latest_release)

def _cancel_download(self):
self._change_update_installation_status(status=CANCELLED)
Expand Down
20 changes: 14 additions & 6 deletions spyder/plugins/application/widgets/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ class ApplicationUpdateStatus(StatusBarWidget):
Signal to request checking for updates.
"""

sig_install_on_close_requested = Signal(str)
sig_install_on_close_requested = Signal(bool)
"""
Signal to request running the downloaded installer on close.

Parameters
----------
installer_path: str
Path to instal
install_on_close: bool
Whether to install on close.
"""

CUSTOM_WIDGET_CLASS = QLabel
Expand Down Expand Up @@ -120,8 +120,17 @@ def get_tooltip(self):
def get_icon(self):
return ima.icon('spyder_about')

def start_installation(self, latest_release):
self.installer.start_installation(latest_release)
def save_latest_release(self, latest_release, update_from_github):
self.installer.save_latest_release(latest_release, update_from_github)

def start_installation(self):
self.installer.start_installation()

def confirm_installation(self):
self.installer.confirm_installation()

def install(self):
self.installer.install()

def set_download_progress(self, current_value, total):
percentage_progress = 0
Expand All @@ -131,7 +140,6 @@ def set_download_progress(self, current_value, total):

def set_status_pending(self, latest_release):
self.set_value(PENDING)
self.installer.save_latest_release(latest_release)

def set_status_checking(self):
self.set_value(CHECKING)
Expand Down
13 changes: 3 additions & 10 deletions spyder/workers/updates.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,15 +172,8 @@ class WorkerDownloadInstaller(QObject):
and Linux without blocking the Spyder user interface.
"""

sig_ready = Signal(str)
"""
Signal to inform that the worker has finished successfully.

Parameters
----------
installer_path: str
Path where the downloaded installer is located.
"""
sig_ready = Signal()
"""Signal to inform that the worker has finished successfully."""

sig_download_progress = Signal(int, int)
"""
Expand Down Expand Up @@ -265,6 +258,6 @@ def start(self):
error_msg = _('Unable to download the installer.')
self.error = error_msg
try:
self.sig_ready.emit(self.installer_path)
self.sig_ready.emit()
except RuntimeError:
pass