Add --list-missing-tools

While creating a Flatpak package for Diffoscope, I found myself wanting
to compare the list reported by --list-tools to what was available in
the sandbox (so that I could add interesting external tools). Since
Diffoscope already has all the machinery to find external tools –
including logic to prefix "g" for GNU tools on non-GNU systems – it is
best placed to report the list of missing tools.
parent 0dfb8182
......@@ -30,7 +30,7 @@ import traceback
from . import VERSION
from .path import set_path
from .tools import tool_prepend_prefix, tool_required, OS_NAMES, get_current_os
from .tools import tool_check_installed, tool_prepend_prefix, tool_required, OS_NAMES, get_current_os
from .config import Config
from .locale import set_locale
from .logging import line_eraser, setup_logging
......@@ -246,6 +246,12 @@ def create_parser():
'distribution that satisfy these dependencies.')
group4.add_argument('--list-debian-substvars', action=ListDebianSubstvarsAction,
help="List packages needed for Debian in 'substvar' format.")
group4.add_argument('--list-missing-tools', nargs='?', type=str, action=ListMissingToolsAction,
metavar='DISTRO', choices=OS_NAMES,
help='Show missing external tools and exit. '
'DISTRO can be one of {%(choices)s}. '
'If specified, the output will list packages in that '
'distribution that satisfy these dependencies.')
if not tlsh:
parser.epilog = 'File renaming detection based on fuzzy-matching is currently disabled. It can be enabled by installing the "tlsh" module available at https://github.com/trendmicro/tlsh'
......@@ -302,13 +308,24 @@ class RangeCompleter(object):
class ListToolsAction(argparse.Action):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.only_missing = False
def __call__(self, parser, namespace, os_override, option_string=None):
# Ensure all comparators are imported so tool_required.all is
# populated.
ComparatorManager().reload()
external_tools = sorted(tool_required.all)
if self.only_missing:
external_tools = [
tool for tool in external_tools
if not tool_check_installed(tool)
]
print("External-Tools-Required: ", end='')
print(', '.join(sorted(tool_required.all)))
print(', '.join(external_tools))
current_os = get_current_os()
os_list = [current_os] if (current_os in OS_NAMES) else iter(OS_NAMES)
......@@ -318,7 +335,7 @@ class ListToolsAction(argparse.Action):
for os_ in os_list:
tools = set()
print("Available-in-{}-packages: ".format(OS_NAMES[os_]), end='')
for x in tool_required.all:
for x in external_tools:
try:
tools.add(EXTERNAL_TOOLS[x][os_])
except KeyError:
......@@ -328,6 +345,12 @@ class ListToolsAction(argparse.Action):
sys.exit(0)
class ListMissingToolsAction(ListToolsAction):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.only_missing = True
class ListDebianSubstvarsAction(argparse._StoreTrueAction):
def __call__(self, *args, **kwargs):
# Attempt to import all comparators so tool_required.all is as
......
......@@ -55,6 +55,15 @@ def tool_prepend_prefix(prefix, *tools):
REMAPPED_TOOL_NAMES[tool] = prefix + tool
def tool_check_installed(command):
if command == get_tool_name(command) and not os_is_gnu() and tool_is_gnu(command):
# try "g" + command for each tool, if we're on a non-GNU system
if find_executable("g" + command):
tool_prepend_prefix("g", command)
return find_executable(get_tool_name(command))
def tool_required(command):
"""
Decorator that checks if the specified tool is installed
......@@ -77,12 +86,7 @@ def tool_required(command):
This ensures that any os.environ['PATH'] modifications are
performed prior to the `find_executable` tests.
"""
if command == get_tool_name(command) and not os_is_gnu() and tool_is_gnu(command):
# try "g" + command for each tool, if we're on a non-GNU system
if find_executable("g" + command):
tool_prepend_prefix("g", command)
if not find_executable(get_tool_name(command)):
if not tool_check_installed(command):
raise RequiredToolNotFound(command)
with profile('command', command):
......
......@@ -138,6 +138,16 @@ def test_list_tools(capsys):
assert 'xxd,' in out
def test_list_missing_tools(capsys):
ret, out, err = run(capsys, '--list-missing-tools')
assert ret == 0
assert err == ''
assert 'External-Tools-Required: ' in out
# No assertions about the contents of the output since we don't control
# what's installed in the test environment
def test_profiling(capsys):
ret, out, err = run(capsys, TEST_TAR1_PATH, TEST_TAR1_PATH, '--profile=-')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment