1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3 4# Copyright (c) 2024 Huawei Device Co., Ltd. 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17 18import os 19import logging 20import shutil 21import json 22from pathlib import Path 23from typing import Optional, Iterable, Tuple 24from abc import ABC, abstractmethod 25from enum import Flag, auto 26from subprocess import TimeoutExpired 27from vmb.unit import BenchUnit 28from vmb.helpers import StringEnum 29from vmb.shell import ShellDevice, ShellUnix, ShellResult 30from vmb.target import Target 31from vmb.x_shell import CrossShell 32 33log = logging.getLogger('vmb') 34 35 36class VmbToolExecError(Exception): 37 """VMB Error. Tool execution failed.""" 38 39 def __init__(self, message: str, 40 res: Optional[ShellResult] = None) -> None: 41 super().__init__(message) 42 self.out = f'{message}\n\nout:\n{res.out}\n\nerr:\n{res.err}\n' \ 43 if res is not None else message 44 45 46class ToolMode(StringEnum): 47 AOT = 'aot' 48 INT = 'int' 49 JIT = 'jit' 50 DEFAULT = 'default' 51 52 53class OptFlags(Flag): 54 NONE = auto() 55 GC_STATS = auto() 56 JIT_STATS = auto() 57 AOT_STATS = auto() 58 AOT_SKIP_LIBS = auto() 59 DRY_RUN = auto() 60 DISABLE_INLINING = auto() 61 # these 3 flags are mutually exclusive (this is guarantied by ToolMode) 62 AOT = auto() 63 INT = auto() 64 JIT = auto() 65 66 67class ToolBase(CrossShell, ABC): 68 69 sh_: ShellUnix 70 andb_: ShellDevice 71 hdc_: ShellDevice 72 dev_dir: Path 73 libs: Path 74 75 def __init__(self, 76 target: Target = Target.HOST, 77 flags: OptFlags = OptFlags.NONE, 78 custom: str = ''): 79 self._target = target 80 self.flags = flags 81 self.custom = custom 82 83 def __call__(self, bu: BenchUnit) -> None: 84 self.exec(bu) 85 86 @property 87 @abstractmethod 88 def name(self) -> str: 89 return '' 90 91 @property 92 def version(self) -> str: 93 return 'version n/a' 94 95 @property 96 def target(self) -> Target: 97 return self._target 98 99 @property 100 def sh(self) -> ShellUnix: 101 return ToolBase.sh_ 102 103 @property 104 def andb(self) -> ShellDevice: 105 return ToolBase.andb_ 106 107 @property 108 def hdc(self) -> ShellDevice: 109 return ToolBase.hdc_ 110 111 @staticmethod 112 def rename_suffix(old: Path, new_suffix: str) -> Path: 113 new = old.with_suffix(new_suffix) 114 old.rename(new) 115 return new 116 117 @staticmethod 118 def get_cmd_path(cmd: str, env_var: str = '') -> Optional[str]: 119 # use specifically requested 120 p: Optional[str] = os.environ.get(env_var, '') 121 # . or use default 122 if not p: 123 p = shutil.which(cmd) 124 if not p or (not os.path.isfile(p)): 125 extra_msg = f' or set via {env_var} env var' if env_var else '' 126 raise RuntimeError( 127 f'{cmd} not found. Add it to PATH{extra_msg}') 128 log.info('Using %s as %s', p, cmd) 129 return p 130 131 @staticmethod 132 def ensure_file(*args, err: str = '') -> str: 133 f = os.path.join(*args) 134 if not os.path.isfile(f): 135 raise RuntimeError(f'File "{f}" not found! {err}') 136 return str(f) 137 138 @staticmethod 139 def ensure_dir(*args, err: str = '') -> str: 140 d = os.path.join(*args) 141 if not os.path.isdir(d): 142 raise RuntimeError(f'Dir "{d}" not found! {err}') 143 return str(d) 144 145 @staticmethod 146 def ensure_dir_env(var_name: str) -> str: 147 return ToolBase.ensure_dir(os.environ.get(var_name, ''), 148 err=f'Please set {var_name} env var.') 149 150 @abstractmethod 151 def exec(self, bu: BenchUnit) -> None: 152 pass 153 154 def kill(self) -> None: 155 """Kill tool process(es). 156 157 For host target there is os.killpg() in Shell, 158 but on device tool process needs remote pkill <tool> 159 """ 160 161 def x_run(self, cmd: str, measure_time: bool = True, 162 timeout: Optional[float] = None, cwd: str = '') -> ShellResult: 163 try: 164 res = self.x_sh.run( 165 cmd, measure_time=measure_time, timeout=timeout, cwd=cwd) 166 except TimeoutExpired as e: 167 self.kill() 168 raise e 169 if not res or res.ret != 0: 170 raise VmbToolExecError(f'{self.name} failed', res) 171 return res 172 173 def x_src(self, bu: BenchUnit, *ext) -> Path: 174 if self.target == Target.HOST: 175 return bu.src(*ext) 176 return bu.src_device(*ext) 177 178 def x_libs(self, bu: BenchUnit, *ext) -> Iterable[Path]: 179 if self.target == Target.HOST: 180 return bu.libs(*ext) 181 return bu.libs_device(*ext) 182 183 def get_bu_opts(self, bu: BenchUnit) -> Tuple[OptFlags, str]: 184 conf = bu.path.joinpath('config.json') 185 flags: OptFlags = self.flags 186 aot_opts: str = '' 187 if conf.exists(): 188 with open(conf, 'r', encoding='utf-8') as f: 189 conf_data = json.load(f) 190 if conf_data.get('disable_inlining', False): 191 flags |= OptFlags.DISABLE_INLINING 192 aot_opts = conf_data.get('aot_opts', '') 193 return flags, aot_opts 194