Source code for meta_package_manager.managers.homebrew

# -*- coding: utf-8 -*-
#
# Copyright (c) 2016-2018 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.

import re

from boltons.cacheutils import cachedproperty

import simplejson as json
from packaging.version import parse as parse_version

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


[docs]class Homebrew(PackageManager): platforms = frozenset([MACOS]) # Vanilla brew and cask CLIs now shares the same version. # 1.7.4 is the first release to deprecate dedicated cask versionning. requirement = '>= 1.7.4' id = "brew"
[docs] def get_version(self): """ Fetch version from ``brew --version`` output. Raw CLI output samples: .. code-block:: shell-session $ brew --version Homebrew 1.8.6-124-g6cd4c31 Homebrew/homebrew-core (git revision 533d; last commit 2018-12-28) Homebrew/homebrew-cask (git revision 5095b; last commit 2018-12-28) """ output = self.run([self.cli_path] + self.cli_args + ['--version']) if output: return output.split()[1].split('-')[0]
@cachedproperty 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'): match = regexp.match(pkg_info) if match: package_id, removed, versions = match.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
[docs] def search(self, query): """ Fetch matching packages from ``brew search`` output. Raw CLI output samples: .. code-block:: shell-session $ brew search sed ==> Formulae gnu-sed āœ” libxdg-basedir minised ==> Casks eclipse-dsl marsedit focused physicseditor google-adwords-editor prefs-editor licensed subclassed-mnemosyne .. todo Tag search results between vanilla formulae and casks. """ matches = {} output = self.run([self.cli_path] + self.cli_args + [ 'search', query]) if output: lines = [ l for l in output.split('\n') if l and not l.startswith('==>')] for package_id in re.compile(r'[\sāœ”]+').split(' '.join(lines)): if package_id: matches[package_id] = { 'id': package_id, 'name': package_id, 'latest_version': None, 'exact': self.exact_match(query, package_id)} return matches
@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'] 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. """ id = "cask" name = "Homebrew Cask" cli_name = "brew" @cachedproperty def outdated(self): """ Search for outdated packages among installed one. Raw CLI output samples: .. code-block:: shell-session $ brew cask outdated google-play-music-desktop-player (4.4.0) != 4.4.1 .. code-block:: shell-session $ brew cask outdated --verbose java (9.0.1,11) != 10,46:76eac37278c24557a3c4199677f19b62 prey (1.7.2) != 1.7.3 qlvideo (1.90) != 1.91 virtualbox (5.2.4-119785) != 5.2.8,121009 .. code-block:: shell-session $ brew cask outdated --greedy --verbose android-file-transfer (latest) != latest atom (1.19.3) != 1.19.4 dropbox (latest) != latest google-chrome (latest) != latest google-drive (latest) != latest google-play-music-desktop-player (4.4.0) != 4.4.1 karabiner-elements (0.90.92) != 0.91.13 osxfuse (3.5.6) != 3.6.3 qlimagesize (latest) != latest qlrest (latest) != latest quicklook-json (latest) != latest steam (latest) != latest """ outdated = {} # Build up the list of CLI options. options = ['--verbose'] # Includes auto-update packages or not. if not self.ignore_auto_updates: options.append('--greedy') # List available updates. output = self.run( [self.cli_path, 'cask'] + self.cli_args + ['outdated'] + options) if output: regexp = re.compile(r'(\S+) \((.*)\) != (.*)') for outdated_pkg in output.strip().split('\n'): package_id, version, latest_version = regexp.match( outdated_pkg).groups() # Skip packages in undetermined state. if version == latest_version: continue outdated[package_id] = { 'id': package_id, 'name': package_id, 'installed_version': version, 'latest_version': latest_version} return outdated
[docs] def upgrade_cli(self, package_id=None): """ 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. """ cmd = [self.cli_path, 'cask'] + self.cli_args + ['upgrade'] if package_id: cmd.append(package_id) return cmd
[docs] def upgrade_all_cli(self): return self.upgrade_cli()