Skip to content
Commits on Source (3)
......@@ -30,7 +30,8 @@ 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
......@@ -166,7 +167,8 @@ def create_parser():
'spilling it into child pages (--html-dir) or skipping the '
'rest of the diff block. Child pages are limited instead by '
'--max-page-size-child. (default: %(default)s, remains in '
'effect even with --no-default-limits)', default=Config().max_page_diff_block_lines).completer = RangeCompleter(
'effect even with --no-default-limits)',
default=Config().max_page_diff_block_lines).completer = RangeCompleter(
Config().max_page_diff_block_lines)
# TODO: old flag kept for backwards-compat, drop 6 months after v84
group2.add_argument("--max-diff-block-lines-parent", metavar='LINES', type=int,
......@@ -182,8 +184,8 @@ def create_parser():
group3.add_argument('--exclude-command', dest='exclude_commands',
metavar='REGEX_PATTERN', action='append', default=[],
help='Exclude commands that match %(metavar)s. For '
"example '^readelf.*\s--debug-dump=info' can take a long "
'time and differences here are likely secondary '
"example '^readelf.*\\s--debug-dump=info' can take a "
'long time and differences here are likely secondary '
'differences caused by something represented '
'elsewhere. Use this option to disable commands that '
'use a lot of resources.')
......@@ -246,14 +248,27 @@ 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'
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'
)
if argcomplete:
argcomplete.autocomplete(parser)
elif '_ARGCOMPLETE' in os.environ:
logger.error(
'Argument completion requested but the "argcomplete" module is not installed. It can be obtained at https://pypi.python.org/pypi/argcomplete')
'Argument completion requested but the "argcomplete" module is '
'not installed. It can be obtained at '
'https://pypi.python.org/pypi/argcomplete'
)
sys.exit(1)
def post_parse(parsed_args):
......@@ -265,7 +280,9 @@ def create_parser():
for f in x.option_strings]
if ineffective_flags:
logger.warning(
"Loading diff instead of calculating it, but diff-calculation flags were given; they will be ignored:")
'Loading diff instead of calculating it, but '
'diff-calculation flags were given; they will be ignored:'
)
logger.warning(ineffective_flags)
return parser, post_parse
......@@ -302,13 +319,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 +346,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 +356,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=-')
......