1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3""" 4Copyright (C) 2022 Huawei Device Co., Ltd. 5SPDX-License-Identifier: GPL-2.0 6 7Unless required by applicable law or agreed to in writing, software 8distributed under the License is distributed on an "AS IS" BASIS, 9WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10See the License for the specific language governing permissions and 11limitations under the License. 12""" 13 14import logging 15import os 16import re 17import select 18import sys 19import subprocess 20import shlex 21import time 22 23 24ignores = [ 25 "include/trace/events/eas_sched.h: warning: format '%d' expects argument of type 'int', but argument 9 has type 'long unsigned int' [-Wformat=]", 26 "drivers/block/zram/zram_drv.c: warning: left shift count >= width of type", 27 "include/trace/events/eas_sched.h: note: in expansion of macro", 28 "include/trace/events/eas_sched.h: note: format string is defined here", 29 "include/trace/trace_events.h: note: in expansion of macro", 30 "mm/vmscan.c: warning: suggest parentheses around assignment used as truth value [-Wparentheses]", 31 "lib/bitfield_kunit.c: warning: the frame size of \d+ bytes is larger than \d+ bytes", 32 "drivers/mmc/host/sdhci-esdhc-imx.c: warning: 'sdhci_esdhc_imx_probe_nondt' defined but not used", 33] 34 35 36class Reporter: 37 def __init__(self, arch, path): 38 self.arch = arch 39 self.path = path 40 41 42 def normpath(self, filename): 43 if re.search("^[\.]+[^/]", filename): 44 filename = re.sub("^[\.]+", "", filename) 45 return os.path.normpath(filename) 46 47 48 def format_title(self, title): 49 title = re.sub("\u2018", "'", title) 50 title = re.sub("\u2019", "'", title) 51 52 return title.strip() 53 54 55 def report_build_warning(self, filename, regex, details): 56 report = {} 57 58 if len(details) == 0 or filename is None: 59 return report 60 61 if not os.path.exists(os.path.join(self.path, filename)): 62 return report 63 64 line = details[0] 65 try: 66 warning = re.search(regex, line).group(0) 67 except GetBuildWaringErr as e: 68 print('Except>>>', details) 69 return report 70 71 report = { 72 'title': self.format_title("%s: %s" % (filename, warning)), 73 'filename': filename, 74 'report': '\n'.join(details), 75 'nr': line.split(':')[1], 76 } 77 78 return report 79 80 81 def parse_build_warning(self, blocks, regex, title_regex): 82 issues = {} 83 reports = [] 84 details = [] 85 filename = None 86 unused = False 87 88 for line in blocks: 89 attrs = line.split(':') 90 if len(attrs) < 5 and filename is None: 91 continue 92 if line.startswith(' ') or len(attrs) < 2: 93 if unused is True: 94 details.append(line) 95 continue 96 if not regex in line: 97 unused = False 98 continue 99 unused = True 100 newfile = self.normpath(attrs[0]) 101 if newfile != filename: 102 if len(details) and filename: 103 if filename in issues: 104 issues[filename].extend(details) 105 else: 106 issues[filename] = details 107 filename = newfile 108 details = [] 109 110 details.append(line) 111 112 if len(details) and filename: 113 if filename in issues: 114 issues[filename].extend(details) 115 else: 116 issues[filename] = details 117 118 for filename, details in issues.items(): 119 report = self.report_build_warning(filename, title_regex, details) 120 if not report is None: 121 reports.append(report) 122 123 return reports 124 125 126 def parse(self, content): 127 blocks = content.split('\n') 128 reports = [] 129 130 patterns = ( 131 ('[-Wunused-but-set-variable]', 'warning: .* set but not used'), 132 ('[-Wunused-but-set-parameter]', 'warning: .* set but not used'), 133 ('[-Wunused-const-variable=]', 'warning: .* defined but not used'), 134 ('[-Wold-style-definition]', 'warning: .* definition'), 135 ('[-Wold-style-declaration]', 'warning: .* declaration'), 136 ('[-Wmaybe-uninitialized]', 'warning: .* uninitialized'), 137 ('[-Wtype-limits]', 'warning: .* always (false|true)'), 138 ('[-Wunused-function]', 'warning: .* defined but not used'), 139 ('[-Wsequence-point]', 'warning: .* may be undefined'), 140 ('[-Wformat=]', 'warning: format.*'), 141 ('[-Wunused-variable]', 'warning: [^\[]*'), 142 ('[-Wframe-larger-than=]', 'warning: the frame size [^\[]*'), 143 ('[-Wshift-count-overflow]', 'warning: left shift count >= width of type'), 144 ('definition or declaration', 'warning: .* declared inside parameter list will not be visible outside of this definition or declaration'), 145 ('character', 'warning: missing terminating .* character'), 146 ('in expansion of macro', 'note: in expansion of macro'), 147 ('note: format string is defined here', 'note: format string is defined here'), 148 ('[-Wparentheses]', 'suggest parentheses around assignment used as truth value'), 149 ) 150 151 for regex, title_regex in patterns: 152 items = self.parse_build_warning(blocks, regex, title_regex) 153 if items is None: 154 continue 155 156 reports.extend(items) 157 158 return reports 159 160 161def exec_cmd(command_list, shell=False, show_output=False, cwd=None): 162 if isinstance(command_list, str): 163 command_list = shlex.split(command_list) 164 elif not isinstance(command_list, list): 165 raise f"command_list to exec_cmd need to be a list or string" 166 command_list = ['nice'] + [str(s) for s in command_list] 167 168 print(f"cwd: '{cwd}'") 169 print(f"cmd: '{command_list}'") 170 start = time.time() 171 proc = subprocess.Popen( 172 command_list if not shell else ' '.join(command_list), 173 cwd=cwd, 174 shell=shell, 175 stdout=subprocess.PIPE, 176 stderr=subprocess.PIPE, 177 bufsize=1, 178 universal_newlines=True, 179 ) 180 181 outmsg = "" 182 errmsg = "" 183 poller = select.epoll() 184 poller.register(proc.stdout, select.EPOLLIN) 185 poller.register(proc.stderr, select.EPOLLIN) 186 while proc.poll() is None: 187 for fd, event in poller.poll(): 188 if event is not select.EPOLLIN: 189 continue 190 if fd == proc.stdout.fileno(): 191 line = proc.stdout.readline() 192 if show_output is True: 193 print(">> [stdout] %s", line.strip('\n')) 194 outmsg += line 195 elif fd == proc.stderr.fileno(): 196 line = proc.stderr.readline() 197 if show_output is True: 198 print(">> [stderr] %s", line.strip('\n')) 199 errmsg += line 200 201 for line in proc.stdout.readlines(): 202 if show_output is True: 203 print(">> [stdout] %s", line.strip('\n')) 204 outmsg += line 205 for line in proc.stderr.readlines(): 206 if show_output is True: 207 print(">> [stderr] %s", line.strip('\n')) 208 errmsg += line 209 210 ret = proc.wait() 211 print(f"Returned {ret} in {int(time.time() - start)} seconds") 212 213 return outmsg, errmsg, ret 214 215 216def make_cmd(cmd, arch, cross_compile, knl_path): 217 make = f"{cmd} ARCH={arch} CROSS_COMPILE={cross_compile}" 218 outmsg, errmsg, ret = exec_cmd(make, cwd=knl_path) 219 if ret: 220 print(f'"{make}" errors --> \n {errmsg}') 221 return ret, f'"{make}" errors --> \n {errmsg}' 222 223 return ret, f'"{make}" success!' 224 225 226def make_config(arch, config, corss_compile, knl_path): 227 make = f"make {config} ARCH={arch} CROSS_COMPILE={corss_compile}" 228 outmsg, errmsg, ret = exec_cmd(make, cwd=knl_path) 229 if ret: 230 print(f'"{make}" errors --> \n {errmsg}') 231 return ret, f'"{make}" errors --> \n {errmsg}' 232 233 return ret, f'"{make}" success!' 234 235 236def make_j(arch, cross_compile, knl_path): 237 make = f'make -j{os.cpu_count()} ARCH={arch} CROSS_COMPILE={cross_compile}' 238 outmsg, errmsg, ret = exec_cmd(make, cwd=knl_path) 239 if ret: 240 print(f'"{make}" errors --> \n {errmsg}') 241 return ret, f'"{make}" errors --> \n {errmsg}' 242 elif len(errmsg) > 0: 243 print(f'"{make}" warnings --> \n {errmsg}') 244 result = "success" 245 reporter = Reporter(arch, knl_path) 246 known_issue = "\nKnown issue:\n" 247 for report in reporter.parse(errmsg): 248 if ignores and [i for i in ignores if re.match(i, report['title'])]: 249 known_issue = known_issue + report['title'] + "\n" 250 known_issue = known_issue + report['report'] + "\n" 251 continue 252 result = 'failed' 253 254 print(known_issue) 255 new_issue = "\nNew Issue:\n" 256 if result == "failed": 257 for report in reporter.parse(errmsg): 258 if ignores and [i for i in ignores if re.match(i, report['title'])]: 259 continue 260 new_issue = new_issue + report['title'] + "\n" 261 new_issue = new_issue + report['report'] + "\n" 262 print(new_issue) 263 return 2, f'"{make}" warning --> \n {new_issue}' 264 265 return ret, f'"{make}" warnings in ignores --> \n {known_issue}' 266 267 return ret, f'"{make}" success!' 268 269 270def cp_config(arch, config, config_path, knl_path): 271 if os.path.exists(config_path.format(arch, config)): 272 cp = f'cp ' + config_path.format(arch, config) + ' ' + os.path.join(knl_path, 'arch', arch, 'configs', config) 273 outmsg, errmsg, ret = exec_cmd(cp) 274 if ret: 275 print(f'"{cp}" errors --> \n {errmsg}') 276 return ret, f'"{cp}" errors --> \n {errmsg}' 277 else: 278 print(f'"{config_path.format(arch, config)}" not exists!') 279 return ret, f'"{config_path.format(arch, config)}" not exists!' 280 281 return ret, f'"{cp}" success!' 282 283 284def get_logger(filename): 285 log_format = '%(asctime)s %(name)s %(levelname)s %(message)s' 286 log_date_format = '%Y-%m-%d %H:%M:%S' 287 logging.basicConfig( 288 level=logging.INFO, 289 filename=filename, 290 format=log_format, 291 datefmt=log_date_format 292 ) 293 logger = logging.getLogger(__name__) 294 return logger 295 296 297def build(arch, config, config_path, cross_compile, knl_path, logger): 298 ret, msg = make_cmd('make defconfig', arch, cross_compile, knl_path) 299 if ret: 300 logger.error(msg) 301 return ret, msg 302 303 ret, msg = make_cmd('make oldconfig', arch, cross_compile, knl_path) 304 if ret: 305 logger.error(msg) 306 return ret, msg 307 308 ret, msg = make_cmd('make clean', arch, cross_compile, knl_path) 309 if ret: 310 logger.error(msg) 311 return ret, msg 312 313 ret, msg = make_j(arch, cross_compile, knl_path) 314 if ret: 315 logger.error(msg) 316 return ret, msg 317 318 ret, msg = cp_config(arch, config, config_path, knl_path) 319 if ret: 320 logger.error(msg) 321 return ret, msg 322 else: 323 ret, msg = make_config(arch, config, cross_compile, knl_path) 324 if ret: 325 logger.error(msg) 326 return ret, msg 327 328 ret, msg = make_cmd('make clean', arch, cross_compile, knl_path) 329 if ret: 330 logger.error(msg) 331 return ret, msg 332 333 ret, msg = make_j(arch, cross_compile, knl_path) 334 if ret: 335 logger.error(msg) 336 return ret, msg 337 338 ret, msg = make_cmd('make allmodconfig', arch, cross_compile, knl_path) 339 if ret: 340 logger.error(msg) 341 return ret, msg 342 343 sed = f'sed -i s/^.*CONFIG_FRAME_WARN.*$/CONFIG_FRAME_WARN=2048/ .config' 344 outmsg, errmsg, ret = exec_cmd(sed, cwd=knl_path) 345 346 ret, msg = make_cmd('make clean', arch, cross_compile, knl_path) 347 if ret: 348 logger.error(msg) 349 return ret, msg 350 351 ret, msg = make_j(arch, cross_compile, knl_path) 352 if ret: 353 logger.error(msg) 354 return ret, msg 355 356 return 0, f'build success!' 357 358 359def main(): 360 config_path = './kernel/linux/config/linux-5.10/arch/{0}/configs/{1}' 361 knl_path = './kernel/linux/linux-5.10' 362 log_path = os.getcwd() 363 now_date = time.strftime("%Y%m%d%H%M%S", time.localtime()) 364 log_file = os.path.join(log_path, 'kernel_build_test.log') 365 logger = get_logger(log_file) 366 367 arch = 'arm' 368 config = 'hispark_taurus_standard_defconfig' 369 cross_compile = '../../../prebuilts/gcc/linux-x86/arm/gcc-linaro-7.5.0-arm-linux-gnueabi/bin/arm-linux-gnueabi-' 370 arm_ret, arm_msg = build(arch, config, config_path, cross_compile, knl_path, logger) 371 372 arch = 'arm64' 373 config = 'rk3568_standard_defconfig' 374 cross_compile = '../../../prebuilts/gcc/linux-x86/aarch64/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-' 375 arm64_ret, arm64_msg = build(arch, config, config_path, cross_compile, knl_path, logger) 376 377 print(f'arm_ret: {arm_ret}, arm64_ret: {arm64_ret}') 378 if any([arm_ret, arm64_ret]): 379 print('kernel build test failed!') 380 exit(arm_ret or arm64_ret) 381 382 print('kernel build test success.') 383 exit(0) 384 385 386if __name__ == "__main__": 387 main() 388