blob: 21257270f17260f679fea37887705d15932e9011 [file] [log] [blame]
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +01001#!/usr/bin/env python3
2
3""" tfm_builder.py:
4
5 Build wrapping class that builds a specific tfm configuration """
6
7from __future__ import print_function
8
9__copyright__ = """
10/*
11 * Copyright (c) 2018-2019, Arm Limited. All rights reserved.
12 *
13 * SPDX-License-Identifier: BSD-3-Clause
14 *
15 */
16 """
17__author__ = "Minos Galanakis"
18__email__ = "minos.galanakis@linaro.org"
19__project__ = "Trusted Firmware-M Open CI"
20__status__ = "stable"
Minos Galanakisea421232019-06-20 17:11:28 +010021__version__ = "1.1"
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010022
23import os
Minos Galanakisea421232019-06-20 17:11:28 +010024import re
Karl Zhangaff558a2020-05-15 14:28:23 +010025import time
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010026import shutil
Minos Galanakisea421232019-06-20 17:11:28 +010027from .utils import *
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010028from .structured_task import structuredTask
29
30
31class TFM_Builder(structuredTask):
32 """ Wrap around tfm cmake system and spawn a thread to build the project.
33 """
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010034 def __init__(self,
35 name, # Proccess name
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010036 work_dir, # Current working directory(ie logs)
37 cfg_dict, # Input config dictionary of the following form
38 # input_dict = {"PROJ_CONFIG": "ConfigRegression",
39 # "TARGET_PLATFORM": "MUSCA_A",
40 # "COMPILER": "ARMCLANG",
41 # "CMAKE_BUILD_TYPE": "Debug"}
Minos Galanakisea421232019-06-20 17:11:28 +010042 build_threads=4, # Number of CPU thrads used in build
43 silent=False, # Silence stdout ouptut
44 img_sizes=False, # Use arm-none-eabi-size for size info
45 relative_paths=False): # Store relative paths in report
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010046
47 self._tfb_cfg = cfg_dict
48 self._tfb_build_threads = build_threads
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010049 self._tfb_silent = silent
Minos Galanakisea421232019-06-20 17:11:28 +010050 self._tfb_img_sizes = img_sizes
51 self._tfb_relative_paths = relative_paths
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010052 self._tfb_binaries = []
53
54 # Required by other methods, always set working directory first
55 self._tfb_work_dir = os.path.abspath(os.path.expanduser(work_dir))
56
Minos Galanakisea421232019-06-20 17:11:28 +010057 # Override code_base_dir with abspath
58 _code_dir = self._tfb_cfg["codebase_root_dir"]
59 self._tfb_code_dir = os.path.abspath(os.path.expanduser(_code_dir))
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010060 # Entries will be filled after sanity test on cfg_dict dring pre_exec
61 self._tfb_build_dir = None
62 self._tfb_log_f = None
Minos Galanakisea421232019-06-20 17:11:28 +010063
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010064 super(TFM_Builder, self).__init__(name=name)
65
66 def mute(self):
67 self._tfb_silent = True
68
69 def log(self):
70 """ Print and return the contents of log file """
Minos Galanakisea421232019-06-20 17:11:28 +010071 try:
72 with open(self._tfb_log_f, "r") as F:
73 log = F.read()
74 print(log)
75 return log
76 except FileNotFoundError:
77 print("Log %s not found" % self._tfb_log_f)
78 return ""
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010079
80 def report(self):
81 """Return the report on the job """
82 return self.unstash("Build Report")
83
84 def pre_eval(self):
85 """ Tests that need to be run in set-up state """
86
Minos Galanakisea421232019-06-20 17:11:28 +010087 if not os.path.isdir(self._tfb_code_dir):
88 print("Missing code-base directory:", self._tfb_code_dir)
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010089 return False
Minos Galanakisea421232019-06-20 17:11:28 +010090
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010091 return True
92
93 def pre_exec(self, eval_ret):
94 """ Create all required directories, files if they do not exist """
95
96 self._tfb_build_dir = os.path.join(self._tfb_work_dir,
97 self.get_name())
98 # Ensure we have a clean build directory
99 shutil.rmtree(self._tfb_build_dir, ignore_errors=True)
100
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100101 # Log will be placed in work directory, named as the build dir
102 self._tfb_log_f = "%s.log" % self._tfb_build_dir
103
104 # Confirm that the work/build directory exists
105 for p in [self._tfb_work_dir, self._tfb_build_dir]:
106 if not os.path.exists(p):
107 os.makedirs(p)
108
Karl Zhangaff558a2020-05-15 14:28:23 +0100109 def pre_build(self):
110 print("builder start %s \r\nself._tfb_cfg %s\r\n" %
111 (self, self._tfb_cfg))
112
113 try:
114 self._lock.acquire()
115 if self._tfb_code_base_updated:
116 print("Code base has been updated")
117 return True
118
119 self._tfb_code_base_updated = True
120 self._lock.release()
121
122 if "build_psa_api" in self._tfb_cfg:
123 # FF IPC build needs repo manifest update for TFM and PSA arch test
124 if "build_ff_ipc" in self._tfb_cfg:
125 print("Checkout to FF IPC code base")
126 os.chdir(self._tfb_cfg["codebase_root_dir"] + "/../psa-arch-tests/api-tests")
127 _api_test_manifest = "git checkout . ; python3 tools/scripts/manifest_update.py"
128 if subprocess_log(_api_test_manifest,
129 self._tfb_log_f,
130 append=True,
131 prefix=_api_test_manifest,
132 silent=self._tfb_silent):
133
134 raise Exception("Python Failed please check log: %s" %
135 self._tfb_log_f)
136
137 _api_test_manifest_tfm = "python3 tools/tfm_parse_manifest_list.py -m tools/tfm_psa_ff_test_manifest_list.yaml append"
138 os.chdir(self._tfb_cfg["codebase_root_dir"])
139 if subprocess_log(_api_test_manifest_tfm,
140 self._tfb_log_f,
141 append=True,
142 prefix=_api_test_manifest_tfm,
143 silent=self._tfb_silent):
144
145 raise Exception("Python TFM Failed please check log: %s" %
146 self._tfb_log_f)
147 else:
148 print("Checkout to default code base")
149 os.chdir(self._tfb_cfg["codebase_root_dir"] + "/../psa-arch-tests/api-tests")
150 _api_test_manifest = "git checkout ."
151 if subprocess_log(_api_test_manifest,
152 self._tfb_log_f,
153 append=True,
154 prefix=_api_test_manifest,
155 silent=self._tfb_silent):
156
157 raise Exception("Python Failed please check log: %s" %
158 self._tfb_log_f)
159
160 _api_test_manifest_tfm = "python3 tools/tfm_parse_manifest_list.py"
161 os.chdir(self._tfb_cfg["codebase_root_dir"])
162 if subprocess_log(_api_test_manifest_tfm,
163 self._tfb_log_f,
164 append=True,
165 prefix=_api_test_manifest_tfm,
166 silent=self._tfb_silent):
167
168 raise Exception("Python TFM Failed please check log: %s" %
169 self._tfb_log_f)
170 finally:
171 print("python pass after builder prepare")
172
173 p = self._tfb_build_dir + "/BUILD"
174 if not os.path.exists(p):
175 os.makedirs(p)
176
177 os.chdir(p)
178 if subprocess_log(self._tfb_cfg["build_psa_api"],
179 self._tfb_log_f,
180 append=True,
181 prefix=self._tfb_cfg["build_psa_api"],
182 silent=self._tfb_silent):
183
184 raise Exception("Build Failed please check log: %s" %
185 self._tfb_log_f)
186 def copy_tfm(self):
187 """ Copy a new TFM for crypto compile """
188
189 cp_cmd = "cp -r " + self._tfb_cfg["codebase_root_dir"] + " " + \
190 self._tfb_build_dir
191 if subprocess_log(cp_cmd,
192 self._tfb_log_f,
193 append=True,
194 prefix=cp_cmd,
195 silent=self._tfb_silent):
196
197 raise Exception("Build Failed please check log: %s" %
198 self._tfb_log_f)
199
200 cp_cmd = "cp -r " + self._tfb_cfg["codebase_root_dir"] + "/../mbed-crypto " + \
201 self._tfb_build_dir
202 if subprocess_log(cp_cmd,
203 self._tfb_log_f,
204 append=True,
205 prefix=cp_cmd,
206 silent=self._tfb_silent):
207
208 raise Exception("Build Failed please check log: %s" %
209 self._tfb_log_f)
210
211 cp_cmd = "cp -r " + self._tfb_cfg["codebase_root_dir"] + "/../psa-arch-tests " + \
212 self._tfb_build_dir
213 if subprocess_log(cp_cmd,
214 self._tfb_log_f,
215 append=True,
216 prefix=cp_cmd,
217 silent=self._tfb_silent):
218
219 raise Exception("Build Failed please check log: %s" %
220 self._tfb_log_f)
221
222
223 cp_cmd = "mkdir -p " + self._tfb_build_dir + "/CMSIS_5/CMSIS ; "
224 cp_cmd += "cp -r " + self._tfb_cfg["codebase_root_dir"] + "/../CMSIS_5/CMSIS/RTOS2 " + \
225 self._tfb_build_dir + "/CMSIS_5/CMSIS"
226
227 if subprocess_log(cp_cmd,
228 self._tfb_log_f,
229 append=True,
230 prefix=cp_cmd,
231 silent=self._tfb_silent):
232
233 raise Exception("Build Failed please check log: %s" %
234 self._tfb_log_f)
235
236 self._tfb_cfg["build_cmds"][0] = \
237 self._tfb_cfg["build_cmds"][0].replace(self._tfb_cfg["codebase_root_dir"],
238 self._tfb_build_dir + "/tf-m")
239
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100240 def task_exec(self):
241 """ Main tasks """
242
243 # Mark proccess running as status
244 self.set_status(-1)
Karl Zhangaff558a2020-05-15 14:28:23 +0100245 print("builder _tfb_cfg %s" % self._tfb_cfg)
246
247 if "build_psa_api" in self._tfb_cfg:
248 p = self._tfb_build_dir + "/BUILD"
249 if not os.path.exists(p):
250 os.makedirs(p)
251 os.chdir(p)
252 if subprocess_log(self._tfb_cfg["build_psa_api"],
253 self._tfb_log_f,
254 append=True,
255 prefix=self._tfb_cfg["build_psa_api"],
256 silent=self._tfb_silent):
257 raise Exception("Build Failed please check log: %s" %
258 self._tfb_log_f)
259
260 if self._tfb_cfg["build_cmds"].__str__().find( \
261 "CRYPTO_HW_ACCELERATOR_OTP_STATE=ENABLED") > 0:
262 self.copy_tfm()
263 time.sleep(15)
264 os.sync()
265 print("new build cmd ", self._tfb_cfg["build_cmds"][0])
266
267 print("Go to build directory")
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100268 # Go to build directory
269 os.chdir(self._tfb_build_dir)
Minos Galanakisea421232019-06-20 17:11:28 +0100270
271 build_cmds = self._tfb_cfg["build_cmds"]
272
273 threads_no_rex = re.compile(r'.*(-j\s?(\d+))')
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100274
275 # Pass the report to later stages
Minos Galanakisea421232019-06-20 17:11:28 +0100276 rep = {"build_cmd": "%s" % ",".join(build_cmds)}
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100277 self.stash("Build Report", rep)
278
Minos Galanakisea421232019-06-20 17:11:28 +0100279 # Calll cmake to configure the project
280 for build_cmd in build_cmds:
281 # if a -j parameter is passed as user argument
282 user_set_threads_match = threads_no_rex.findall(build_cmd)
283
284 if user_set_threads_match:
285 # Unpack the regex groups (fullmatch, decimal match)
286 user_jtxt, user_set_threads = user_set_threads_match[0]
287 if int(user_set_threads) > self._tfb_build_threads:
288 print("Ignoring user requested n=%s threads because it"
289 " exceeds the maximum thread set ( %d )" %
290 (user_set_threads, self._tfb_build_threads))
291 thread_no = self._tfb_build_threads
292 else:
293 print("Using %s build threads" % user_set_threads)
294 thread_no = user_set_threads
295 build_cmd = build_cmd.replace(user_jtxt,
296 "-j %s " % thread_no)
297
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100298 # Build it
Karl Zhangaff558a2020-05-15 14:28:23 +0100299 print("~builder build_cmd %s\r\n" % build_cmd)
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100300 if subprocess_log(build_cmd,
301 self._tfb_log_f,
302 append=True,
303 prefix=build_cmd,
304 silent=self._tfb_silent):
Minos Galanakisea421232019-06-20 17:11:28 +0100305
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100306 raise Exception("Build Failed please check log: %s" %
307 self._tfb_log_f)
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100308
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100309 self._t_stop()
310
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100311 def post_eval(self):
312 """ Verify that the artefacts exist """
313 print("%s Post eval" % self.get_name())
314
315 ret_eval = False
316 rep = self.unstash("Build Report")
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100317
Minos Galanakisea421232019-06-20 17:11:28 +0100318 artefacts = list_filtered_tree(self._tfb_work_dir, r'%s' %
319 self._tfb_cfg["artifact_capture_rex"])
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100320
321 # Add artefact related information to report
322 rep["log"] = self._tfb_log_f
Minos Galanakisea421232019-06-20 17:11:28 +0100323
324 if not len(artefacts):
325 print("ERROR: Could not capture any binaries:")
326
327 # TODO update self._tfb_binaries
328 ret_eval = False
329 else:
330 print("SUCCESS: Produced the following binaries:")
331 print("\n\t".join(artefacts))
332 ret_eval = True
333
334 rep["artefacts"] = artefacts
335
336 # Proccess the artifacts into file structures
337 art_files = {}
338 for art_item in artefacts:
339 art_f = {"pl_source": 1,
340 "resource": art_item if not self._tfb_relative_paths
341 else resolve_rel_path(art_item),
342 "size": {"bytes": str(os.path.getsize(art_item))}
343 }
344 if self._tfb_img_sizes and ".axf" in art_item:
345 eabi_size, _ = arm_non_eabi_size(art_item)
346 art_f["size"]["text"] = eabi_size["text"]
347 art_f["size"]["data"] = eabi_size["data"]
348 art_f["size"]["bss"] = eabi_size["bss"]
349 # filename is used as key for artfacts
350 art_files[os.path.split(art_item)[-1]] = art_f
351 rep["artefacts"] = art_files
352
353 if "required_artefacts" in self._tfb_cfg.keys():
354 if len(self._tfb_cfg["required_artefacts"]):
355 print("Searching for required binaries")
356 missing_binaries = list(filter(lambda x: not os.path.isfile(x),
357 self._tfb_cfg["required_artefacts"]))
358 if len(missing_binaries):
359 rep["missing_artefacts"] = missing_binaries
360 print("ERROR: Missing required artefacts:")
361 print("\n".join(missing_binaries))
362 ret_eval = False
363 else:
364 ret_eval = True
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100365
366 rep["status"] = "Success" if ret_eval else "Failed"
367 self.stash("Build Report", rep)
368 return ret_eval
369
370 def post_exec(self, eval_ret):
371 """ """
372
373 if eval_ret:
374 print("TFM Builder %s was Successful" % self.get_name())
375 else:
376 print("TFM Builder %s was UnSuccessful" % self.get_name())
Minos Galanakisea421232019-06-20 17:11:28 +0100377
378
379if __name__ == "__main__":
380 pass