1# Copyright (C) 2022 Intel Corporation.
2#
3# SPDX-License-Identifier: BSD-3-Clause
4#
5
6import os
7import logging
8import subprocess # nosec
9
10available_tools = {}
11
12class ExecutableNotFound(ValueError):
13    pass
14
15def run(command, **kwargs):
16    """Run the given command which can be either a string or a list."""
17    try:
18        if isinstance(command, list):
19            full_path = available_tools[command[0]]
20            kwargs.setdefault("capture_output", True)
21            return subprocess.run([full_path] + command[1:], **kwargs)
22        elif isinstance(command, str):
23            parts = command.split(" ", maxsplit=1)
24            full_path = available_tools[parts[0]]
25            kwargs["shell"] = True
26            kwargs.setdefault("stdout", subprocess.PIPE)
27            kwargs.setdefault("stderr", subprocess.PIPE)
28            kwargs.setdefault("close_fds", True)
29            cmd = f"{full_path} {parts[1]}" if len(parts) == 2 else full_path
30            return subprocess.Popen(cmd, **kwargs)
31    except KeyError:
32        raise ExecutableNotFound
33
34    assert False, f"A command is expected either a list or a string: {command}"
35
36def detect_tool(name):
37    """Detect the full path of a system tool."""
38    system_paths = [
39        "/usr/bin/",
40        "/usr/sbin/",
41        "/usr/local/bin/",
42        "/usr/local/sbin/",
43    ]
44
45    # Look for `which` first.
46    for path in system_paths:
47        candidate = os.path.join(path, "which")
48        if os.path.exists(candidate):
49            available_tools["which"] = candidate
50            break
51
52    try:
53        result = run(["which", "-a", name])
54        for path in result.stdout.decode().strip().split("\n"):
55            if any(map(lambda x: path.startswith(x), system_paths)):
56                logging.debug(f"Use {name} found at {path}.")
57                return path
58    except ExecutableNotFound:
59        pass
60
61    logging.critical(f"'{name}' cannot be found. Please install it and run again.")
62    return None
63
64def locate_tools(tool_list):
65    """Find a list of tools under common system executable paths. Return True if and only if all tools are found."""
66    had_error = False
67
68    for tool in tool_list:
69        full_path = detect_tool(tool)
70        if full_path != None:
71            available_tools[tool] = full_path
72        else:
73            had_error = True
74
75    return not had_error
76