|
#!/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 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?