Skip to content

Instantly share code, notes, and snippets.

@Tenzer
Last active December 11, 2020 10:19
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Tenzer/0b76ef1c4e9bf146b003 to your computer and use it in GitHub Desktop.
Save Tenzer/0b76ef1c4e9bf146b003 to your computer and use it in GitHub Desktop.
Cask upgrade

Cask-Upgrade

Small utility to help you manage packages installed via Homebrew Cask as it currently can't upgrade installed packages for you or clean up old versions.

The script will ask you if you want to upgrade applications which has newer versions available. Downloads from previous versions of applications will be removed when an application is updated, like what Homebrew has been doing since version 2.0.0. This behaviour can be disabled by setting the environment variable HOMEBREW_NO_INSTALL_CLEANUP.

Installation

Just download the Python file below, make it executable with chmod +x cask-upgrade.py and run it. It doesn't have any dependencies outside of Python, which is installed by default on OS X.

Example Output

[jeppe@reventon ~]$ cask-upgrade
The following upgrades are available:
- minikube: 0.34.0 -> 0.34.1
- tableplus: 1.5,190 -> 1.6,194 (*)

(*) = May have auto updated

Upgrade? y

==> Satisfying dependencies
All Formula dependencies satisfied.
==> Downloading https://storage.googleapis.com/minikube/releases/v0.34.1/minikube-darwin-amd64
######################################################################## 100.0%
==> Verifying SHA-256 checksum for Cask 'minikube'.
==> Installing Cask minikube
==> Linking Binary 'minikube-darwin-amd64' to '/usr/local/bin/minikube'.
🍺  minikube was successfully installed!

==> Satisfying dependencies
==> Downloading https://s3.amazonaws.com/tableplus-osx-builds/194/TablePlus.dmg
######################################################################## 100.0%
==> Verifying SHA-256 checksum for Cask 'tableplus'.
==> Installing Cask tableplus
Warning: It seems there is already an App at '/Applications/TablePlus.app'; overwriting.
==> Removing App '/Applications/TablePlus.app'.
==> Moving App 'TablePlus.app' to '/Applications/TablePlus.app'.
🍺  tableplus was successfully installed!

Cleaning up downloads:
Removing: /Users/jeppe/Library/Caches/Homebrew/Cask/tableplus--1.5,190.dmg... (27.5MB)
Removing: /Users/jeppe/Library/Caches/Homebrew/Cask/minikube--0.34.0... (38.1MB)
#!/usr/bin/env python
from __future__ import print_function
import os
import re
import subprocess
from distutils.version import LooseVersion
from shutil import rmtree
INSTALLED_PATH = '/usr/local/Caskroom'
METADATA_ROOT_PATH = os.path.join(os.getenv('HOMEBREW_PREFIX', '/usr/local'), 'Homebrew/Library/Taps')
METADATA_PATHS = []
def main():
populate_metadata_paths()
if not METADATA_PATHS:
print('Error: No "Casks" folders were found underneath {}, are you sure you have Homebrew-Cask installed?'.format(METADATA_ROOT_PATH))
exit(1)
outdated_applications = get_outdated_applications()
if not outdated_applications:
exit(0)
auto_updating_apps = False
print('The following upgrades are available:')
for application in sorted(outdated_applications):
data = outdated_applications[application]
if data['auto_updates']:
print('- {}: {} -> {} (*)'.format(application, data['installed'], data['latest']))
auto_updating_apps = True
else:
print('- {}: {} -> {}'.format(application, data['installed'], data['latest']))
if auto_updating_apps:
print('\n(*) = May have auto updated\n')
if not confirmed('Upgrade? '):
exit(0)
apps_upgraded = False
for application in sorted(outdated_applications):
data = outdated_applications[application]
print()
upgrade(application)
apps_upgraded = True
if apps_upgraded and not os.getenv('HOMEBREW_NO_INSTALL_CLEANUP'):
subprocess.call(['brew', 'cleanup'] + outdated_applications.keys())
def populate_metadata_paths():
for root, directories, _ in os.walk(METADATA_ROOT_PATH):
if root == METADATA_ROOT_PATH:
# We don't care about the folders in the top level directory
continue
if 'Casks' in directories:
METADATA_PATHS.append(os.path.join(root, 'Casks'))
if '.git' in directories:
# Don't proceed any further if we found the top of the repository
directories = []
def get_outdated_applications():
outdated_applications = {}
for application in os.listdir(INSTALLED_PATH):
if not os.path.isdir(os.path.join(INSTALLED_PATH, application)):
continue
latest_installed_version, old_installed_versions = get_installed_versions(application)
if not latest_installed_version:
continue
latest_version, auto_updates = get_latest_version_and_auto_update(application)
if not latest_version:
continue
if latest_version > latest_installed_version:
outdated_applications.update({
application: {
'installed': latest_installed_version,
'latest': latest_version,
'auto_updates': auto_updates,
'old_versions': old_installed_versions,
}
})
return outdated_applications
def get_installed_versions(application):
versions = os.listdir(os.path.join(INSTALLED_PATH, application))
versions = [LooseVersion(version) for version in versions if version != '.metadata']
if versions:
versions.sort(reverse=True)
return versions[0], versions[1:]
return False, False
def get_latest_version_and_auto_update(application):
for directory in METADATA_PATHS:
metadata_path = os.path.join(directory, '{}.rb'.format(application))
if not os.path.isfile(metadata_path):
continue
with open(metadata_path, 'r') as metadata_file:
metadata = metadata_file.read()
versions = re.findall(r'^\W*version (\S+)', metadata, re.MULTILINE)
if not versions:
continue
latest_version = None
for version in versions:
version = LooseVersion(version.strip('\'":'))
if special_snowflake(application, version):
continue
if version.vstring == 'latest':
latest_version = version
break
if not latest_version or version > latest_version:
latest_version = version
auto_updates = re.search(r'^\W*auto_updates true\W', metadata, re.MULTILINE)
return latest_version, bool(auto_updates)
return False, False
def special_snowflake(application, version):
if application == 'docker' and version.version[0] == 18:
# Docker for Mac changed versioning scheme and went from 18.x to 2.x
return True
return False
def confirmed(message):
try:
# Python 2
ask = raw_input
except NameError:
# Python 3
ask = input
while True:
response = ask(message).strip().lower()
if response in {'y', 'yes'}:
return True
if response in {'n', 'no'}:
return False
def upgrade(application):
# '--greedy' is necessary to upgrade auto updating casks
subprocess.call(['brew', 'upgrade', '--cask', '--greedy', application])
if __name__ == '__main__':
main()
@lijunle
Copy link

lijunle commented Nov 22, 2016

Awesome script! I hack on line 125 to always return True. Thank you!

@Tenzer
Copy link
Author

Tenzer commented May 8, 2017

@lijunle I have made an update to the script now, so it will list all updates available before doing anything, and only ask for confirmation once before upgrading everything found, perhaps that will make it easier for you?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment