tools.py 3.77 KB
Newer Older
1 2 3 4
# -*- coding: utf-8 -*-
#
# diffoscope: in-depth comparison of files, archives, and directories
#
5
# Copyright © 2016, 2017 Chris Lamb <lamby@debian.org>
6 7 8 9 10 11 12 13 14 15 16 17 18 19
#
# diffoscope 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 3 of the License, or
# (at your option) any later version.
#
# diffoscope 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 diffoscope.  If not, see <https://www.gnu.org/licenses/>.

20
import collections
21
import functools
22 23 24 25 26 27
import platform

try:
    import distro
except ImportError:
    distro = None
28 29 30 31

from distutils.spawn import find_executable

from .profiling import profile
32
from .external_tools import EXTERNAL_TOOLS, REMAPPED_TOOL_NAMES, GNU_TOOL_NAMES
33 34 35 36 37

# Memoize calls to ``distutils.spawn.find_executable`` to avoid excessive stat
# calls
find_executable = functools.lru_cache()(find_executable)

38 39 40 41 42 43 44
# The output of --help and --list-tools will use the order of this dict.
# Please keep it alphabetized.
OS_NAMES = collections.OrderedDict([
    ('arch', 'Arch Linux'),
    ('debian', 'Debian'),
    ('FreeBSD', 'FreeBSD'),
])
45 46


47 48 49
def get_tool_name(tool):
    return REMAPPED_TOOL_NAMES.get(tool, tool)

Mattia Rizzolo's avatar
Mattia Rizzolo committed
50

51 52 53 54 55 56
def tool_prepend_prefix(prefix, *tools):
    if not prefix:
        return
    for tool in tools:
        REMAPPED_TOOL_NAMES[tool] = prefix + tool

Mattia Rizzolo's avatar
Mattia Rizzolo committed
57

58 59 60 61
def tool_required(command):
    """
    Decorator that checks if the specified tool is installed
    """
62 63
    from .exc import RequiredToolNotFound

64 65 66
    if not hasattr(tool_required, 'all'):
        tool_required.all = set()
    tool_required.all.add(command)
67 68 69 70

    def wrapper(fn):
        @functools.wraps(fn)
        def tool_check(*args, **kwargs):
71 72 73 74 75 76 77 78 79
            """
            Due to the way decorators are executed at import-time we defer the
            execution of `find_executable` until we actually run the decorated
            function (instead of prematurely returning a different version of
            `tool_check`).

            This ensures that any os.environ['PATH'] modifications are
            performed prior to the `find_executable` tests.
            """
80
            if command == get_tool_name(command) and not os_is_gnu() and tool_is_gnu(command):
81 82 83
                # try "g" + command for each tool, if we're on a non-GNU system
                if find_executable("g" + command):
                    tool_prepend_prefix("g", command)
84

85
            if not find_executable(get_tool_name(command)):
86
                raise RequiredToolNotFound(command)
87 88 89

            with profile('command', command):
                return fn(*args, **kwargs)
90 91 92
        return tool_check
    return wrapper

Mattia Rizzolo's avatar
Mattia Rizzolo committed
93

94 95 96
def tool_is_gnu(command):
    return command in GNU_TOOL_NAMES

Mattia Rizzolo's avatar
Mattia Rizzolo committed
97

98 99 100
def os_is_gnu():
    system = platform.system()
    return system in ("Linux", "GNU") or system.startswith("GNU/")
Chris Lamb's avatar
Chris Lamb committed
101

Mattia Rizzolo's avatar
Mattia Rizzolo committed
102

103 104 105
def get_current_os():
    system = platform.system()
    if system == "Linux":
106 107
        if distro:
            return distro.id()
108
    return system  # noqa
109

Mattia Rizzolo's avatar
Mattia Rizzolo committed
110

111 112 113 114 115 116
def get_current_distro_like():
    if distro:
        return distro.like().split()
    else:
        return []

Mattia Rizzolo's avatar
Mattia Rizzolo committed
117

118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
def get_package_provider(tool, os=None):
    try:
        providers = EXTERNAL_TOOLS[tool]
    except KeyError:  # noqa
        return None

    try:
        return providers[get_current_os()]
    except KeyError:
        # lookup failed, try to look for a package for a similar distro
        for d in get_current_distro_like():
            try:
                return providers[d]
            except KeyError:
                pass

    return None