]> begriffs open source - cmsis/blob - CMSIS/CoreValidation/Project/build.py
CoreValidation: Initial support to run tests in Qemu
[cmsis] / CMSIS / CoreValidation / Project / build.py
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3
4 import logging
5
6 from datetime import datetime
7 from enum import Enum
8 from glob import glob, iglob
9 from pathlib import Path
10
11 from lxml.etree import XMLSyntaxError
12 from zipfile import ZipFile
13
14 from matrix_runner import main, matrix_axis, matrix_action, matrix_command, matrix_filter, \
15     ConsoleReport, CropReport, TransformReport, JUnitReport
16
17
18 @matrix_axis("device", "d", "Device(s) to be considered.")
19 class DeviceAxis(Enum):
20     CM0 = ('Cortex-M0', 'CM0')
21     CM0plus = ('Cortex-M0plus', 'CM0plus')
22     CM3 = ('Cortex-M3', 'CM3')
23     CM4 = ('Cortex-M4', 'CM4')
24     CM7 = ('Cortex-M7', 'CM7')
25     CM23 = ('Cortex-M23', 'CM23')
26     CM23S = ('Cortex-M23S', 'CM23S')
27     CM23NS = ('Cortex-M23NS', 'CM23NS')
28     CM33 = ('Cortex-M33', 'CM33')
29     CM33S = ('Cortex-M33S', 'CM33S')
30     CM33NS = ('Cortex-M33NS', 'CM33NS')
31     CM35P = ('Cortex-M35P', 'CM35P')
32     CM35PS = ('Cortex-M35PS', 'CM35PS')
33     CM35PNS = ('Cortex-M35PNS', 'CM35PNS')
34     CM55 = ('Cortex-M55', 'CM55')
35     CM55S = ('Cortex-M55S', 'CM55S')
36     CM55NS = ('Cortex-M55NS', 'CM55NS')
37     CM85 = ('Cortex-M85', 'CM85')
38     CM85S = ('Cortex-M85S', 'CM85S')
39     CM85NS = ('Cortex-M85NS', 'CM85NS')
40     CA5 = ('Cortex-A5', 'CA5')
41     CA7 = ('Cortex-A7', 'CA7')
42     CA9 = ('Cortex-A9', 'CA9')
43 #    CA5NEON = ('Cortex-A5neon', 'CA5neon')
44 #    CA7NEON = ('Cortex-A7neon', 'CA7neon')
45 #    CA9NEON = ('Cortex-A9neon', 'CA9neon')
46
47     def has_bl(self):
48         return self in [
49             DeviceAxis.CM23NS,
50             DeviceAxis.CM33NS,
51             DeviceAxis.CM35PNS,
52             DeviceAxis.CM55NS,
53             DeviceAxis.CM85NS
54         ]
55
56     @property
57     def bl_device(self):
58         bld = {
59             DeviceAxis.CM23NS: 'CM23S',
60             DeviceAxis.CM33NS: 'CM33S',
61             DeviceAxis.CM35PNS: 'CM35PS',
62             DeviceAxis.CM55NS: 'CM55S',
63             DeviceAxis.CM85NS: 'CM85S'
64         }
65         return bld[self]
66
67
68 @matrix_axis("compiler", "c", "Compiler(s) to be considered.")
69 class CompilerAxis(Enum):
70     AC6 = ('AC6')
71     GCC = ('GCC')
72     IAR = ('IAR')
73     CLANG = ('Clang')
74
75     @property
76     def image_ext(self):
77         ext = {
78             CompilerAxis.AC6: 'axf',
79             CompilerAxis.GCC: 'elf',
80             CompilerAxis.IAR: 'elf',
81             CompilerAxis.CLANG: 'elf',
82         }
83         return ext[self]
84
85     @property
86     def toolchain(self):
87         ext = {
88             CompilerAxis.AC6: 'AC6',
89             CompilerAxis.GCC: 'GCC',
90             CompilerAxis.IAR: 'IAR',
91             CompilerAxis.CLANG: 'CLANG'
92         }
93         return ext[self]
94
95
96 @matrix_axis("optimize", "o", "Optimization level(s) to be considered.")
97 class OptimizationAxis(Enum):
98     NONE = ('none')
99     BALANCED = ('balanced')
100     SPEED = ('speed')
101     SIZE = ('size')
102
103
104 MODEL_EXECUTABLE = {
105     DeviceAxis.CM0: ("FVP_MPS2_Cortex-M0", []),
106     DeviceAxis.CM0plus: ("FVP_MPS2_Cortex-M0plus", []),
107     DeviceAxis.CM3: ("FVP_MPS2_Cortex-M3", []),
108     DeviceAxis.CM4: ("FVP_MPS2_Cortex-M4", []),
109     DeviceAxis.CM7: ("FVP_MPS2_Cortex-M7", []),
110     DeviceAxis.CM23: ("FVP_MPS2_Cortex-M23", []),
111     DeviceAxis.CM23S: ("FVP_MPS2_Cortex-M23", []),
112     DeviceAxis.CM23NS: ("FVP_MPS2_Cortex-M23", []),
113     DeviceAxis.CM33: ("FVP_MPS2_Cortex-M33", []),
114     DeviceAxis.CM33S: ("FVP_MPS2_Cortex-M33", []),
115     DeviceAxis.CM33NS: ("FVP_MPS2_Cortex-M33", []),
116     DeviceAxis.CM35P: ("FVP_MPS2_Cortex-M35P", []),
117     DeviceAxis.CM35PS: ("FVP_MPS2_Cortex-M35P", []),
118     DeviceAxis.CM35PNS: ("FVP_MPS2_Cortex-M35P", []),
119     DeviceAxis.CM55: ("FVP_MPS2_Cortex-M55", []),
120     DeviceAxis.CM55S: ("FVP_MPS2_Cortex-M55", []),
121     DeviceAxis.CM55NS: ("FVP_MPS2_Cortex-M55", []),
122     DeviceAxis.CM85: ("FVP_MPS2_Cortex-M85", []),
123     DeviceAxis.CM85S: ("FVP_MPS2_Cortex-M85", []),
124     DeviceAxis.CM85NS: ("FVP_MPS2_Cortex-M85", []),
125     DeviceAxis.CA5: ("FVP_VE_Cortex-A5x1", []),
126     DeviceAxis.CA7: ("FVP_VE_Cortex-A7x1", []),
127     DeviceAxis.CA9: ("FVP_VE_Cortex-A9x1", []),
128 #    DeviceAxis.CA5NEON: ("_VE_Cortex-A5x1", []),
129 #    DeviceAxis.CA7NEON: ("_VE_Cortex-A7x1", []),
130 #    DeviceAxis.CA9NEON: ("_VE_Cortex-A9x1", [])
131 }
132
133 QEMU_MACHINE = {
134     DeviceAxis.CM3: ("mps2-an385"),
135     DeviceAxis.CM4: ("mps2-an386"),
136     DeviceAxis.CM7: ("mps2-an500"),
137     DeviceAxis.CM33: ("mps2-an505"),
138     DeviceAxis.CM55: ("mps3-an547")
139 }
140
141 def config_suffix(config, timestamp=True):
142     suffix = f"{config.compiler[0]}-{config.optimize[0]}-{config.device[1]}"
143     if timestamp:
144         suffix += f"-{datetime.now().strftime('%Y%m%d%H%M%S')}"
145     return suffix
146
147
148 def project_name(config):
149     return f"Validation.{config.compiler}_{config.optimize}+{config.device[1]}"
150
151
152 def bl_project_name(config):
153     return f"Bootloader.{config.compiler}_{config.optimize}+{config.device.bl_device}"
154
155
156 def output_dir(config):
157     return f"Validation/outdir"
158
159
160 def bl_output_dir(config):
161     return f"Bootloader/outdir"
162
163
164 def model_config(config):
165     return f"../Layer/Target/{config.device[1]}/model_config.txt"
166
167
168 def build_dir(config):
169     return f"build/{config.device[1]}/{config.compiler.toolchain}/{config.optimize}"
170
171
172 @matrix_action
173 def clean(config):
174     """Build the selected configurations using CMSIS-Build."""
175     yield cbuild_clean(f"{project_name(config)}/{project_name(config)}.cprj")
176
177
178 @matrix_action
179 def build(config, results):
180     """Build the selected configurations using CMSIS-Build."""
181
182     logging.info("Compiling Tests...")
183
184     yield cbuild(config)
185
186     if not all(r.success for r in results):
187         return
188
189     file = f"build/CoreValidation-{config_suffix(config)}.zip"
190     logging.info("Archiving build output to %s...", file)
191     with ZipFile(file, "w") as archive:
192         for content in iglob(f"{build_dir(config)}/**/*", recursive=True):
193             if Path(content).is_file():
194                 archive.write(content)
195
196
197 @matrix_action
198 def extract(config):
199     """Extract the latest build archive."""
200     archives = sorted(glob(f"build/CoreValidation-{config_suffix(config, timestamp=False)}-*.zip"), reverse=True)
201     yield unzip(archives[0])
202
203
204 @matrix_action
205 def run(config, results):
206     """Run the selected configurations."""
207     logging.info("Running Core Validation on Arm model ...")
208     yield model_exec(config)
209
210     try:
211         results[0].test_report.write(f"build/CoreValidation-{config_suffix(config)}.junit")
212     except RuntimeError as ex:
213         if isinstance(ex.__cause__, XMLSyntaxError):
214             logging.error("No valid test report found in model output!")
215         else:
216             logging.exception(ex)
217
218
219 @matrix_action
220 def qemu(config, results):
221     """Run the selected configurations."""
222     if not config.device in QEMU_MACHINE:
223         logging.error("Qemu doesn't support target device '%s'!", config.device)
224         return
225     logging.info("Running Core Validation on Qemu ...")
226     yield qemu_exec(config)
227
228
229 @matrix_command()
230 def cbuild_clean(project):
231     return ["cbuild", "-c", project]
232
233
234 @matrix_command()
235 def unzip(archive):
236     return ["bash", "-c", f"unzip {archive}"]
237
238
239 @matrix_command()
240 def preprocess(infile, outfile):
241     return ["arm-none-eabi-gcc", "-xc", "-E", infile, "-P", "-o", outfile]
242
243
244 @matrix_command()
245 def cbuild(config):
246     return ["cbuild", "--toolchain", config.compiler.toolchain, "--update-rte", \
247              "--context", f".{config.optimize}+{config.device[1]}", \
248              "Validation.csolution.yml" ]
249
250
251 @matrix_command(test_report=ConsoleReport() |
252                             CropReport('<\?xml version="1.0"\?>', '</report>') |
253                             TransformReport('validation.xsl') |
254                             JUnitReport(title=lambda title, result: f"{result.command.config.compiler}."
255                                                                     f"{result.command.config.optimize}."
256                                                                     f"{result.command.config.device}."
257                                                                     f"{title}"))
258 def model_exec(config):
259     cmdline = [MODEL_EXECUTABLE[config.device][0], "-q", "--simlimit", 100, "-f", model_config(config)]
260     cmdline += MODEL_EXECUTABLE[config.device][1]
261     cmdline += ["-a", f"{build_dir(config)}/{output_dir(config)}/Validation.{config.compiler.image_ext}"]
262     if config.device.has_bl():
263         cmdline += ["-a", f"{build_dir(config)}/{bl_output_dir(config)}/Bootloader.{config.compiler.image_ext}"]
264     return cmdline
265
266
267 @matrix_command(test_report=ConsoleReport() |
268                             CropReport('<\?xml version="1.0"\?>', '</report>') |
269                             TransformReport('validation.xsl') |
270                             JUnitReport(title=lambda title, result: f"{result.command.config.compiler}."
271                                                                     f"{result.command.config.optimize}."
272                                                                     f"{result.command.config.device}."
273                                                                     f"{title}"))
274 def qemu_exec(config):
275     cmdline = ["qemu-system-arm", "-semihosting-config", "enable=on", "-monitor", "none", "-serial", "none", "-nographic"]
276     cmdline += ["-machine", QEMU_MACHINE[config.device]]
277     cmdline += ["-kernel", f"{build_dir(config)}/{output_dir(config)}/Validation.{config.compiler.image_ext}"]
278     return cmdline
279
280 @matrix_filter
281 def filter_iar(config):
282     return config.compiler == CompilerAxis.IAR
283
284
285 if __name__ == "__main__":
286     main()