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