Cask upgrade

3 min read Original article ↗
#!/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()