diff --git a/dvc/updater.py b/dvc/updater.py index 21251e4dab..e7710a9b59 100644 --- a/dvc/updater.py +++ b/dvc/updater.py @@ -1,16 +1,19 @@ from __future__ import unicode_literals -import sys +import logging import os +import sys import time -import logging + import colorama from packaging import version from dvc import __version__ -from dvc.lock import Lock, LockError -from dvc.utils import is_binary, boxify, env2bool - +from dvc.lock import Lock +from dvc.lock import LockError +from dvc.utils import boxify +from dvc.utils import env2bool +from dvc.utils import is_binary logger = logging.getLogger(__name__) @@ -24,9 +27,8 @@ class Updater(object): # pragma: no cover def __init__(self, dvc_dir): self.dvc_dir = dvc_dir self.updater_file = os.path.join(dvc_dir, self.UPDATER_FILE) - self.lock = Lock( - self.updater_file + ".lock", tmp_dir=os.path.join(dvc_dir, "tmp") - ) + self.lock = Lock(self.updater_file + ".lock", + tmp_dir=os.path.join(dvc_dir, "tmp")) self.current = version.parse(__version__).base_version def _is_outdated_file(self): @@ -103,53 +105,66 @@ def _notify(self): message = ( "Update available {red}{current}{reset} -> {green}{latest}{reset}" - + "\n" - + self._get_update_instructions() - ).format( - red=colorama.Fore.RED, - reset=colorama.Fore.RESET, - green=colorama.Fore.GREEN, - yellow=colorama.Fore.YELLOW, - blue=colorama.Fore.BLUE, - current=self.current, - latest=self.latest, - ) + + "\n" + self._get_update_instructions()).format( + red=colorama.Fore.RED, + reset=colorama.Fore.RESET, + green=colorama.Fore.GREEN, + yellow=colorama.Fore.YELLOW, + blue=colorama.Fore.BLUE, + current=self.current, + latest=self.latest, + ) logger.info(boxify(message, border_color="yellow")) def _get_update_instructions(self): instructions = { - "pip": "Run {yellow}pip{reset} install dvc {blue}--upgrade{reset}", - "yum": "Run {yellow}yum{reset} update dvc", - "yay": "Run {yellow}yay{reset} {blue}-S{reset} dvc", - "formula": "Run {yellow}brew{reset} upgrade dvc", - "cask": "Run {yellow}brew cask{reset} upgrade dvc", - "apt": ( - "Run {yellow}apt-get{reset} install" - " {blue}--only-upgrade{reset} dvc" - ), - "binary": ( - "To upgrade follow this steps:\n" - "1. Uninstall dvc binary\n" - "2. Go to {blue}https://dvc.org{reset}\n" - "3. Download and install new binary" - ), - None: ( - "Find the latest release at\n{blue}" - "https://github.com/iterative/dvc/releases/latest" - "{reset}" - ), + "pip": + "Run {yellow}pip{reset} install dvc {blue}--upgrade{reset}", + "yum": + "Run {yellow}yum{reset} update dvc", + "yay": + "Run {yellow}yay{reset} {blue}-S{reset} dvc", + "formula": + "Run {yellow}brew{reset} upgrade dvc", + "cask": + "Run {yellow}brew cask{reset} upgrade dvc", + "apt": ("Run {yellow}apt-get{reset} install" + " {blue}--only-upgrade{reset} dvc"), + "binary": ("To upgrade follow this steps:\n" + "1. Uninstall dvc binary\n" + "2. Go to {blue}https://dvc.org{reset}\n" + "3. Download and install new binary"), + "conda": + "Run {yellow}conda{reset} {update}update{reset} dvc", + None: ("Find the latest release at\n{blue}" + "https://github.com/iterative/dvc/releases/latest" + "{reset}"), } package_manager = self._get_package_manager() return instructions[package_manager] + @staticmethod + def _get_dvc_path(system): + if system in ("linux", "darwin"): + output = os.popen("which dvc") + else: + output = os.popen("where dvc") + + return output.read().lower() + + @staticmethod + def _is_conda(path): + return "conda" in path + def _get_linux(self): import distro if not is_binary(): - return "pip" + dvc_path = self._get_dvc_path("linux") + return "conda" if self._is_conda(dvc_path) else "pip" package_managers = { "rhel": "yum", @@ -164,23 +179,29 @@ def _get_linux(self): return package_managers.get(distro.id()) def _get_darwin(self): - if not is_binary(): - if __file__.startswith("/usr/local/Cellar"): - return "formula" - else: - return "pip" + if is_binary(): + return None + + package_manager = None - # NOTE: both pkg and cask put dvc binary into /usr/local/bin, - # so in order to know which method of installation was used, - # we need to actually call `brew cask` - ret = os.system("brew cask ls dvc") - if ret == 0: - return "cask" + if __file__.startswith("/usr/local/Cellar"): + package_manager = "formula" - return None + dvc_path = self._get_dvc_path("darwin") + if self._is_conda(dvc_path): + package_manager = "conda" + + return package_manager or "pip" def _get_windows(self): - return None if is_binary() else "pip" + if is_binary(): + return None + + dvc_path = self._get_dvc_path("windows") + if self._is_conda(dvc_path): + return "conda" + + return "pip" def _get_package_manager(self): import platform diff --git a/dvc/utils/pkg.py b/dvc/utils/pkg.py new file mode 100644 index 0000000000..f080d1e1dc --- /dev/null +++ b/dvc/utils/pkg.py @@ -0,0 +1,67 @@ +from dvc.utils import is_binary + + +def is_conda(): + try: + from .build import PKG # patched during conda package build + + return PKG == "conda" + except ImportError: + return False + + +def get_linux(): + import distro + + if is_conda(): + return "conda" + + if not is_binary(): + return "pip" + + package_managers = { + "rhel": "yum", + "centos": "yum", + "fedora": "yum", + "amazon": "yum", + "opensuse": "yum", + "ubuntu": "apt", + "debian": "apt", + } + + return package_managers.get(distro.id()) + + +def get_darwin(): + if is_conda(): + return "conda" + if not is_binary(): + if __file__.startswith("/usr/local/Cellar"): + return "formula" + else: + return "pip" + return None + + +def get_windows(): + if is_conda(): + return "conda" + return None if is_binary() else "pip" + + +def get_package_manager(): + import platform + from dvc.exceptions import DvcException + + m = { + "Windows": get_windows(), + "Darwin": get_darwin(), + "Linux": get_linux(), + } + + system = platform.system() + func = m.get(system) + if func is None: + raise DvcException("not supported system '{}'".format(system)) + + return func diff --git a/tests/func/test_updater.py b/tests/func/test_updater.py index 3cd2758041..2876b6f836 100644 --- a/tests/func/test_updater.py +++ b/tests/func/test_updater.py @@ -1,4 +1,5 @@ import os + import mock import pytest @@ -49,3 +50,13 @@ def test_check_version_outdated(updater): updater.current = "0.20.8" assert updater._is_outdated() + + +@mock.patch("dvc.updater.Updater._is_conda") +def test_check_dvc_from_conda(mocked_is_conda, updater): + mocked_is_conda.return_value = True + updater.latest = "0.21.0" + updater.current = "0.20.8" + + msg = "Run {yellow}conda{reset} {update}update{reset} dvc" + assert updater._get_update_instructions() == msg