Skip to content

Commit

Permalink
Refactor missing package installation
Browse files Browse the repository at this point in the history
Fixes #1155

This commit re-uses existing `PackageTaskRunner` functionality to
create a list of missing packages and install them. The benefit is more
detailed logging and increased re-usability of existing code.

This commit provides the following major changes in behavior:

1. It does no longer track renamed packages. The `installed_packages`
   setting must contain up-to-date package names.

   It might be surprising to see packages being installed, which are
   not in the list of `installed_packages`. Maybe private packages are
   named like ones from official channel used to be in the past.
   If renaming is to be taken into account each entry would need to be
   checked for being available first.

2. It overwrites existing unmanaged packages.

   The `installed_packages` setting normally contains packages, which
   have been installed via and are managed by Package Control.

   Any package in that list can be considered as "to be managed".

   Custom unmanaged packages should not appear in that list as the only
   way they get there was a user adding them manually.

   Hence it can be considered safe to take the `installed_packages` and
   ask ST to install them and overwrite possibly existing unmanaged
   packages, as it is likely enough for them to be overrides.

   Note: git/hg repositories are not treated as overrides nor are they
         treated as managed packages.

3. All remaining missing packages are removed from `installed_packages`
   once all available packages have been installed in order to prevent
   ST from re-trying after each start.
  • Loading branch information
deathaxe committed Mar 20, 2023
1 parent 89a8f23 commit a9d5e97
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 50 deletions.
54 changes: 12 additions & 42 deletions package_control/package_cleanup.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,50 +388,20 @@ def install_missing_packages(self, found_packages):
return

installed_packages = self.manager.installed_packages()
missing_packages = installed_packages - found_packages
if not missing_packages:
return

# Fetch a list of available (and renamed) packages and abort
# if there are none available for installation.
# An empty list indicates connection or configuration problems.
available_packages = set(self.manager.list_available_packages())
if not available_packages:
return

# Detect renamed packages and prepare batched install with new names.
# Update `installed_packages` setting to remove old names without loosing something
# in case installation fails.
renamed_packages = self.manager.settings.get('renamed_packages', {})
renamed_packages = {renamed_packages.get(p, p) for p in missing_packages}
if renamed_packages != missing_packages:
self.manager.update_installed_packages(add=renamed_packages, remove=missing_packages)

# Make sure not to overwrite existing packages after renaming is applied.
missing_packages = renamed_packages - found_packages
if not missing_packages:
return

console_write(
'Installing %s missing package%s...',
(len(missing_packages), 's' if len(missing_packages) != 1 else '')
tasks = self.create_package_tasks(
actions=(self.INSTALL, self.OVERWRITE),
include_packages=installed_packages,
found_packages=found_packages
)

reenable_packages = self.disable_packages({self.INSTALL: missing_packages})
time.sleep(0.7)

try:
for package_name in missing_packages:
result = self.manager.install_package(package_name)

# re-enable if upgrade is not deferred to next start
if result is None and package_name in reenable_packages:
reenable_packages.remove(package_name)

finally:
if reenable_packages:
time.sleep(0.7)
self.reenable_packages({self.INSTALL: reenable_packages})
if tasks:
self.run_install_tasks(tasks, package_kind='missing')

# Drop remaining missing packages, which seem no longer available upstream,
# to avoid trying again and again each time ST starts.
missing_packages = installed_packages - set(self.manager.list_packages())
if missing_packages:
self.manager.update_installed_packages(remove=missing_packages)

def remove_orphaned_packages(self, found_packages):
"""
Expand Down
30 changes: 22 additions & 8 deletions package_control/package_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ def remove_packages(self, packages, progress=None, package_kind=''):
time.sleep(0.7)
self.reenable_packages({self.REMOVE: packages - deffered})

def run_install_tasks(self, tasks, progress=None):
def run_install_tasks(self, tasks, progress=None, package_kind=''):
"""
Execute specified package install tasks
Expand All @@ -284,13 +284,20 @@ def run_install_tasks(self, tasks, progress=None):
:param progress:
An ``ActivityIndicator`` object to use for status information.
:param package_kind:
A unicode string with an additional package attribute.
(e.g.: `missing`, ...)
"""

if package_kind:
package_kind += ' '

num_packages = len(tasks)
if num_packages == 1:
message = 'Installing package {}'.format(tasks[0].package_name)
message = 'Installing {}package {}'.format(package_kind, tasks[0].package_name)
else:
message = 'Installing {} packages...'.format(num_packages)
message = 'Installing {} {}packages...'.format(num_packages, package_kind)
console_write(message)

if progress:
Expand All @@ -306,7 +313,7 @@ def run_install_tasks(self, tasks, progress=None):
try:
for task in tasks:
if progress:
progress.set_label('Installing package {}'.format(task.package_name))
progress.set_label('Installing {}package {}'.format(package_kind, task.package_name))
result = self.manager.install_package(task.package_name)
if result is True:
num_success += 1
Expand All @@ -317,10 +324,11 @@ def run_install_tasks(self, tasks, progress=None):
if num_packages == 1:
message = 'Package {} successfully installed'.format(tasks[0].package_name)
elif num_packages == num_success:
message = 'All packages successfully installed'
message = 'All {}packages successfully installed'.format(package_kind)
console_write(message)
else:
message = '{} of {} packages successfully installed'.format(num_success, num_packages)
message = '{} of {} {}packages successfully installed'.format(
num_success, num_packages, package_kind)
console_write(message)

if progress:
Expand Down Expand Up @@ -417,7 +425,7 @@ def run_upgrade_tasks(self, tasks, progress=None):

return update_completed

def create_package_tasks(self, actions, include_packages=None, ignore_packages=None):
def create_package_tasks(self, actions, include_packages=None, ignore_packages=None, found_packages=None):
"""
Makes tasks.
Expand All @@ -436,6 +444,11 @@ def create_package_tasks(self, actions, include_packages=None, ignore_packages=N
:param ignore_packages:
A list/set of package names that should not be returned in the list
:param found_packages:
A list/set of package names found on filesystem to be used for task creation.
It primarily exists to re-use existing data for optimization purposes.
If ``None`` is provided, a list is created internally.
:return:
A list of ``PackageInstallTask`` objects on success.
``False``, if no packages are available upstream, most likely a connection error.
Expand All @@ -447,7 +460,8 @@ def create_package_tasks(self, actions, include_packages=None, ignore_packages=N
if not available_packages:
return False

found_packages = self.manager.list_packages()
if found_packages is None:
found_packages = self.manager.list_packages()
renamed_packages = self.manager.settings.get('renamed_packages', {})

# VCS package updates
Expand Down

0 comments on commit a9d5e97

Please sign in to comment.