1#!/usr/bin/env python3 2# 3# Arm SCP/MCP Software 4# Copyright (c) 2021-2023, Arm Limited and Contributors. All rights reserved. 5# 6# SPDX-License-Identifier: BSD-3-Clause 7# 8 9import argparse 10import check_copyright 11import check_doc 12import check_spacing 13import check_tabs 14import check_framework 15import check_module_utest 16import check_pycodestyle 17import docker 18import os 19import signal 20import subprocess 21import sys 22from product import Product, Build, Parameter 23from typing import List, Tuple 24 25from docker.errors import DockerException 26 27 28code_validations = [ 29 check_copyright, 30 check_spacing, 31 check_tabs, 32 check_doc, 33 check_framework, 34 check_module_utest, 35 check_pycodestyle, 36] 37 38products = [ 39 Product('host', toolchains=[Parameter('GNU')]), 40 Product('juno'), 41 Product('morello'), 42 Product('n1sdp'), 43 Product('rdv1'), 44 Product('rdv1mc'), 45 Product('rdn1e1'), 46 Product('sgi575'), 47 Product('sgm775'), 48 Product('sgm776'), 49 Product('synquacer'), 50 Product('tc1'), 51 Product('rcar', toolchains=[Parameter('GNU')]), 52 Product('rdn2', variants=[ 53 Parameter('0'), Parameter('1'), Parameter('2'), Parameter('3')]), 54 Product('rdfremont'), 55 Product('tc2', variants=[Parameter('0'), Parameter('1')]), 56] 57 58 59def banner(text): 60 columns = 80 61 title = " {} ".format(text) 62 print("\n\n{}".format(title.center(columns, "*"))) 63 64 65def dockerize(client): 66 (image, _) = client.images.build(path="docker", target="dev", rm=True) 67 68 env = {"ARMLMD_LICENSE_FILE": os.environ["ARMLMD_LICENSE_FILE"]} 69 70 volumes = { 71 os.getcwd(): { 72 "bind": "/scp-firmware", 73 "mode": "rw", 74 } 75 } 76 77 container = client.containers.run( 78 image.id, __file__, environment=env, volumes=volumes, detach=True, 79 tty=True 80 ) 81 82 sigint_handler = signal.getsignal(signal.SIGINT) 83 signal.signal(signal.SIGINT, lambda signal, _: container.kill(signal)) 84 85 logs = container.attach(stdout=True, stderr=True, stream=True, logs=True) 86 for log in logs: 87 sys.stdout.buffer.write(log) 88 89 signal.signal(signal.SIGINT, sigint_handler) 90 91 92def code_validation(checks: list) -> List[Tuple[str, int]]: 93 banner('Code validation') 94 results: List[Tuple[str, int]] = [] 95 for check in checks: 96 result = check.main() 97 test_name = check.__name__.split('_')[-1] 98 results.append(('Check {}'.format(test_name), result)) 99 return results 100 101 102def do_build(build_info: List[Build], output_path: str) -> \ 103 List[Tuple[Build, subprocess.Popen]]: 104 build_status: List[Tuple[Build, subprocess.Popen]] = [] 105 results: List[Tuple[str, int]] = [] 106 107 if not os.path.exists(output_path): 108 os.makedirs(output_path) 109 110 files = [] 111 for build in build_info: 112 file_path = os.path.join(output_path, build.file_name()) 113 files.append(open(file_path, "w", encoding="utf-8")) 114 115 build_id = subprocess.Popen( 116 build.command(), 117 shell=True, 118 stdout=files[-1], 119 stderr=subprocess.STDOUT) 120 121 build_status.append((build, build_id)) 122 print('Test building [{}]'.format(build.tag())) 123 print('[CMD] {}'.format(build.command())) 124 125 for i, (build, build_id) in enumerate(build_status): 126 build_id.communicate() 127 results.append((build.tag(), build_id.returncode)) 128 files[i].close() 129 return results 130 131 132def print_results(results: List[Tuple[str, int]]) -> Tuple[int, int]: 133 banner('Tests summary') 134 total_success = 0 135 for result in results: 136 if result[1] == 0: 137 total_success += 1 138 verbose_result = "Success" 139 else: 140 verbose_result = "Failed" 141 print("{}: {}".format(result[0], verbose_result)) 142 143 assert total_success <= len(results) 144 return (total_success, len(results)) 145 146 147def analyze_results(success: int, total: int) -> int: 148 print("{} / {} passed ({}% pass rate)".format(success, total, 149 int(success * 100 / total))) 150 return 1 if success < total else 0 151 152 153def check_errors(ignore_errors: bool, results: List[Tuple[str, int]]) -> bool: 154 return not ignore_errors and len(list(filter(lambda x: x[1] != 0, 155 results))) 156 157 158def main(ignore_errors: bool, skip_container: bool, log_level: str, 159 output_path: str): 160 # This code is only applicable if there is valid docker instance 161 # On CI there is no docker instance at the moment 162 if not skip_container: 163 try: 164 client = docker.from_env() 165 banner("Spawning container") 166 167 try: 168 return dockerize(client) 169 except Exception as ex: 170 print(ex) 171 return 1 172 173 except DockerException: 174 pass 175 else: 176 banner("Skipping spawning container") 177 178 results = [] 179 180 results.extend(code_validation(code_validations)) 181 if check_errors(ignore_errors, results): 182 print('Errors detected! Excecution stopped') 183 return analyze_results(*print_results(results)) 184 185 banner('Test building products') 186 187 if output_path == "": 188 output_path = os.path.join("/tmp", "scp") 189 output_path = os.path.join(output_path, "build-output") 190 191 for product in products: 192 if log_level != "": 193 product.log_level = Parameter(log_level) 194 results.extend(do_build(product.builds, output_path)) 195 if check_errors(ignore_errors, results): 196 print('Errors detected! Excecution stopped') 197 return analyze_results(*print_results(results)) 198 199 return analyze_results(*print_results(results)) 200 201 202def parse_args(): 203 parser = argparse.ArgumentParser( 204 description='Perform basic checks to SCP-Firmware and build for all \ 205 supported platforms, modes and compilers.') 206 207 parser.add_argument('-i', '--ignore-errors', dest='ignore_errors', 208 required=False, default=False, action='store_true', 209 help='Ignore errors and continue testing.') 210 211 parser.add_argument('-ll', '--log-level', dest='log_level', 212 required=False, default="", type=str, 213 action='store', help='Build every product with the \ 214 specified log level.') 215 216 parser.add_argument('-bod', '--build-output-dir', dest='output_path', 217 required=False, default="", type=str, action='store', 218 help='Parent directory of the "build-output" directory\ 219 , the one were the build logs will be stored in.\n \ 220 If bod is not given, the default location is /tmp/scp/\ 221 build-output') 222 223 parser.add_argument('-sc', '--skip-container', dest='skip_container', 224 required=False, default=False, action='store_true', 225 help='Skip container execution.') 226 227 return parser.parse_args() 228 229 230if __name__ == "__main__": 231 args = parse_args() 232 sys.exit(main(args.ignore_errors, args.skip_container, args.log_level, 233 args.output_path)) 234