Source code for meta_package_manager.managers.homebrew

# -*- coding: utf-8 -*-
#
# Copyright (c) 2016-2017 Kevin Deldycke <kevin@deldycke.com>
#                         and contributors.
# All Rights Reserved.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

from __future__ import (
    absolute_import,
    division,
    print_function,
    unicode_literals
)

import json
import re

from boltons.cacheutils import cachedproperty
from packaging.version import parse as parse_version

from ..base import PackageManager
from ..platform import MACOS


[docs]class Homebrew(PackageManager): cli_path = '/usr/local/bin/brew' platforms = frozenset([MACOS]) requirement = '>= 1.0.*' id = "brew"
[docs] def get_version(self): """ Fetch version from ``brew --version`` output.""" return self.run( [self.cli_path] + self.cli_args + ['--version']).split()[1]
[docs] def sync(self): super(Homebrew, self).sync() self.run([self.cli_path] + self.cli_args + ['update'])
@cachedproperty def installed(self): """ Fetch installed packages from ``brew list`` output. .. note:: This method is shared by ``brew`` and ``cask``, only that the latter adds its ``cask`` subcommand to the CLI call. Raw CLI output samples: .. code-block:: shell-session $ brew list --versions ack 2.14 apg 2.2.3 audacity (!) 2.1.2 apple-gcc42 4.2.1-5666.3 atk 2.22.0 bash 4.4.5 bash-completion 1.3_1 boost 1.63.0 c-ares 1.12.0 graphviz 2.40.1 2.40.20161221.0239 quicklook-json latest .. code-block:: shell-session $ brew cask list --versions aerial 1.2beta5 android-file-transfer latest audacity (!) 2.1.2 bitbar 1.9.2 firefox 49.0.1 flux 37.7 gimp 2.8.18-x86_64 java 1.8.0_112-b16 tunnelblick 3.6.8_build_4625 3.6.9_build_4685 virtualbox 5.1.8-111374 5.1.10-112026 .. todo Use the ``removed`` variable to detect removed packages (which are reported with a ``(!)`` flag). See: https://github.com/caskroom/homebrew-cask/blob/master/doc /reporting_bugs/uninstall_wrongly_reports_cask_as_not_installed.md and https://github.com/kdeldycke/meta-package-manager/issues/17 . """ installed = {} output = self.run( [self.cli_path] + self.cli_args + ['list', '--versions']) if output: regexp = re.compile(r'(\S+)( \(!\))? (.+)') for pkg_info in output.split('\n'): if not pkg_info: continue package_id, removed, versions = regexp.match(pkg_info).groups() # Guess latest installed version. versions = set(versions.split()) # Discard generic 'latest' symbolic version if others are # available. if len(versions) > 1: versions.discard('latest') # Parse versions to avoid lexicographic sorting gotchas. version = None if versions: _, version = max([(parse_version(v), v) for v in versions]) installed[package_id] = { 'id': package_id, 'name': package_id, 'installed_version': version} return installed @cachedproperty def outdated(self): """ Fetch outdated packages from ``brew outdated`` output. Raw CLI output samples: .. code-block:: shell-session $ brew outdated --json=v1 [ { "name": "cassandra", "installed_versions": [ "3.5" ], "current_version": "3.7" }, { "name": "vim", "installed_versions": [ "7.4.1967" ], "current_version": "7.4.1993" }, { "name": "youtube-dl", "installed_versions": [ "2016.07.06" ], "current_version": "2016.07.09.1" } ] """ outdated = {} # List available updates. output = self.run( [self.cli_path] + self.cli_args + ['outdated', '--json=v1']) if output: for pkg_info in json.loads(output): # Parse versions to avoid lexicographic sorting gotchas. version = None versions = set(pkg_info['installed_versions']) if versions: _, version = max([(parse_version(v), v) for v in versions]) package_id = pkg_info['name'] outdated[package_id] = { 'id': package_id, 'name': package_id, 'installed_version': version, 'latest_version': pkg_info['current_version']} return outdated
[docs] def upgrade_cli(self, package_id=None): cmd = [self.cli_path] + self.cli_args + ['upgrade', '--cleanup'] if package_id: cmd.append(package_id) return cmd
[docs] def upgrade_all_cli(self): return self.upgrade_cli()
[docs]class HomebrewCask(Homebrew): """ Cask is now part of Homebrew's core and extend it. """ cli_args = ['cask'] # 'cask install' doesn't upgrade to the latest package version, we need to # call 'cask reinstall' instead since 1.1.0. requirement = '>= 1.1.*' id = "cask" name = "Homebrew Cask" # Call homebrew own update: `brew cask update` is just an alias to # `brew update` and is deprecated. See: # https://github.com/kdeldycke/meta-package-manager/issues/28 sync = Homebrew().sync @cachedproperty def outdated(self): """ Search for outdated packages among installed one. Cask doesn't provides an ``outdated`` subcommand contrary to ``brew``. We have no other alternative but to fetch the list of currently installed packages and inspect them one by one to search for outdated ones. Also, `brew cask list` is `not reliable <https://github.com/caskroom /homebrew-cask/blob/master/doc/reporting_bugs /brew_cask_list_shows_wrong_information.md>`_. Raw CLI output samples: .. code-block:: shell-session $ brew cask info aerial aerial: 1.2beta5 https://github.com/JohnCoates/Aerial /usr/local/Caskroom/aerial/1.2beta5 (18 files, 6.6M) From: https://github.com/(...)/blob/master/Casks/aerial.rb ==> Name Aerial Screensaver ==> Artifacts Aerial.saver (screen_saver) .. code-block:: shell-session $ brew cask info firefox firefox: 50.0.1 https://www.mozilla.org/firefox/ /usr/local/Caskroom/firefox/49.0.1 (107 files, 185.3M) From: https://github.com/(...)/blob/master/Casks/firefox.rb ==> Name Mozilla Firefox ==> Artifacts Firefox.app (app) .. code-block:: shell-session $ brew cask info virtualbox virtualbox: 5.1.10-112026 https://www.virtualbox.org /usr/local/Caskroom/virtualbox/5.1.8-111374 (3 files, 88.8M) /usr/local/Caskroom/virtualbox/5.1.10-112026 (3 files, 89.3M) From: https://github.com/(...)/blob/master/Casks/virtualbox.rb ==> Name Oracle VirtualBox ==> Artifacts VirtualBox.pkg (pkg) .. code-block:: shell-session $ brew cask info prey prey: 1.6.3 https://preyproject.com/ Not installed From: https://github.com/(...)/blob/master/Casks/prey.rb ==> Name Prey ==> Artifacts prey-mac-1.6.3-x86.pkg (pkg) ==> Caveats Prey requires your API key, found in the bottom-left corner of the Prey web account Settings page, to complete installation. The API key may be set as an environment variable as follows: API_KEY="abcdef123456" brew cask install prey .. code-block:: shell-session $ brew cask info ubersicht ubersicht: 1.0.44 http://tracesof.net/uebersicht/ Not installed From: https://github.com/(...)/blob/master/Casks/ubersicht.rb ==> Name Übersicht ==> Artifacts Übersicht.app (app) .. code-block:: shell-session $ brew cask info xld xld: 2016.10.07 http://tmkk.undo.jp/xld/index_e.html /usr/local/Caskroom/xld/2016.10.07 (45 files, 221.6K) From: https://github.com/(...)/blob/master/Casks/xld.rb ==> Names X Lossless Decoder XLD ==> Artifacts XLD.app (app) """ outdated = {} for installed_pkg in self.installed.values(): package_id = installed_pkg['id'] version = installed_pkg['installed_version'] # Inspect the package closer to evaluate its state. output = self.run([ self.cli_path] + self.cli_args + ['info', package_id]) latest_version = output.split('\n')[0].split(' ')[1] # Skip already installed packages. if version == latest_version: continue # Casks are allowed to provide several names. See: # https://github.com/kdeldycke/meta-package-manager/issues/26 . package_names = re.search( '==> Names?(.*?)(==>|$)', output, re.DOTALL).groups( )[0].strip().split('\n') # Choose the longuest name as canonical. package_name = max(package_names) outdated[package_id] = { 'id': package_id, 'name': package_name, 'installed_version': version, 'latest_version': latest_version} return outdated
[docs] def upgrade_cli(self, package_id): """ Install a package. .. todo:: Wait for https://github.com/caskroom/homebrew-cask/issues/22647 so we can force a cleanup in one go, as we do above with vanilla Homebrew. """ return [self.cli_path] + self.cli_args + ['reinstall', package_id]
[docs] def upgrade_all_cli(self): """ Cask has no way to upgrade all outdated packages. See: https://github.com/caskroom/homebrew-cask/issues/4678 """ raise NotImplementedError