blob: 25ce850f07aa148aef488a3e78b194e25043e4e2 [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/*
Karl Zhang08681e62020-10-30 13:56:03 +080011 * Copyright (c) 2018-2020, Arm Limited. All rights reserved.
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010012 *
13 * SPDX-License-Identifier: BSD-3-Clause
14 *
15 */
16 """
Karl Zhang08681e62020-10-30 13:56:03 +080017
18__author__ = "tf-m@lists.trustedfirmware.org"
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010019__project__ = "Trusted Firmware-M Open CI"
Xinyu Zhang06286a92021-07-22 14:00:51 +080020__version__ = "1.4.0"
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010021
22import os
Minos Galanakisea421232019-06-20 17:11:28 +010023import re
Karl Zhangaff558a2020-05-15 14:28:23 +010024import time
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010025import shutil
Minos Galanakisea421232019-06-20 17:11:28 +010026from .utils import *
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010027from .structured_task import structuredTask
28
29
30class TFM_Builder(structuredTask):
31 """ Wrap around tfm cmake system and spawn a thread to build the project.
32 """
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010033 def __init__(self,
Nicola Mazzucato935f9cb2025-05-16 17:21:07 +010034 name, # Process name
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010035 work_dir, # Current working directory(ie logs)
36 cfg_dict, # Input config dictionary of the following form
37 # input_dict = {"PROJ_CONFIG": "ConfigRegression",
38 # "TARGET_PLATFORM": "MUSCA_A",
39 # "COMPILER": "ARMCLANG",
40 # "CMAKE_BUILD_TYPE": "Debug"}
Nicola Mazzucato935f9cb2025-05-16 17:21:07 +010041 build_threads=4, # Number of CPU threads used in build
42 silent=False, # Silence stdout output
Minos Galanakisea421232019-06-20 17:11:28 +010043 img_sizes=False, # Use arm-none-eabi-size for size info
44 relative_paths=False): # Store relative paths in report
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010045
46 self._tfb_cfg = cfg_dict
47 self._tfb_build_threads = build_threads
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010048 self._tfb_silent = silent
Minos Galanakisea421232019-06-20 17:11:28 +010049 self._tfb_img_sizes = img_sizes
50 self._tfb_relative_paths = relative_paths
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010051 self._tfb_binaries = []
52
53 # Required by other methods, always set working directory first
54 self._tfb_work_dir = os.path.abspath(os.path.expanduser(work_dir))
55
Minos Galanakisea421232019-06-20 17:11:28 +010056 # Override code_base_dir with abspath
57 _code_dir = self._tfb_cfg["codebase_root_dir"]
58 self._tfb_code_dir = os.path.abspath(os.path.expanduser(_code_dir))
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010059 # Entries will be filled after sanity test on cfg_dict dring pre_exec
60 self._tfb_build_dir = None
61 self._tfb_log_f = None
Minos Galanakisea421232019-06-20 17:11:28 +010062
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010063 super(TFM_Builder, self).__init__(name=name)
64
65 def mute(self):
66 self._tfb_silent = True
67
68 def log(self):
69 """ Print and return the contents of log file """
Minos Galanakisea421232019-06-20 17:11:28 +010070 try:
71 with open(self._tfb_log_f, "r") as F:
72 log = F.read()
73 print(log)
74 return log
75 except FileNotFoundError:
76 print("Log %s not found" % self._tfb_log_f)
77 return ""
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010078
79 def report(self):
80 """Return the report on the job """
81 return self.unstash("Build Report")
82
83 def pre_eval(self):
84 """ Tests that need to be run in set-up state """
85
Minos Galanakisea421232019-06-20 17:11:28 +010086 if not os.path.isdir(self._tfb_code_dir):
87 print("Missing code-base directory:", self._tfb_code_dir)
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010088 return False
Minos Galanakisea421232019-06-20 17:11:28 +010089
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010090 return True
91
92 def pre_exec(self, eval_ret):
93 """ Create all required directories, files if they do not exist """
94
95 self._tfb_build_dir = os.path.join(self._tfb_work_dir,
96 self.get_name())
97 # Ensure we have a clean build directory
98 shutil.rmtree(self._tfb_build_dir, ignore_errors=True)
99
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100100 # Log will be placed in work directory, named as the build dir
101 self._tfb_log_f = "%s.log" % self._tfb_build_dir
102
103 # Confirm that the work/build directory exists
104 for p in [self._tfb_work_dir, self._tfb_build_dir]:
105 if not os.path.exists(p):
106 os.makedirs(p)
107
Karl Zhangaff558a2020-05-15 14:28:23 +0100108 def pre_build(self):
109 print("builder start %s \r\nself._tfb_cfg %s\r\n" %
110 (self, self._tfb_cfg))
111
112 try:
113 self._lock.acquire()
114 if self._tfb_code_base_updated:
115 print("Code base has been updated")
116 return True
117
118 self._tfb_code_base_updated = True
119 self._lock.release()
120
121 if "build_psa_api" in self._tfb_cfg:
122 # FF IPC build needs repo manifest update for TFM and PSA arch test
123 if "build_ff_ipc" in self._tfb_cfg:
124 print("Checkout to FF IPC code base")
125 os.chdir(self._tfb_cfg["codebase_root_dir"] + "/../psa-arch-tests/api-tests")
126 _api_test_manifest = "git checkout . ; python3 tools/scripts/manifest_update.py"
127 if subprocess_log(_api_test_manifest,
128 self._tfb_log_f,
129 append=True,
130 prefix=_api_test_manifest,
131 silent=self._tfb_silent):
132
133 raise Exception("Python Failed please check log: %s" %
134 self._tfb_log_f)
135
136 _api_test_manifest_tfm = "python3 tools/tfm_parse_manifest_list.py -m tools/tfm_psa_ff_test_manifest_list.yaml append"
137 os.chdir(self._tfb_cfg["codebase_root_dir"])
138 if subprocess_log(_api_test_manifest_tfm,
139 self._tfb_log_f,
140 append=True,
141 prefix=_api_test_manifest_tfm,
142 silent=self._tfb_silent):
143
144 raise Exception("Python TFM Failed please check log: %s" %
145 self._tfb_log_f)
146 else:
147 print("Checkout to default code base")
148 os.chdir(self._tfb_cfg["codebase_root_dir"] + "/../psa-arch-tests/api-tests")
149 _api_test_manifest = "git checkout ."
150 if subprocess_log(_api_test_manifest,
151 self._tfb_log_f,
152 append=True,
153 prefix=_api_test_manifest,
154 silent=self._tfb_silent):
155
156 raise Exception("Python Failed please check log: %s" %
157 self._tfb_log_f)
158
159 _api_test_manifest_tfm = "python3 tools/tfm_parse_manifest_list.py"
160 os.chdir(self._tfb_cfg["codebase_root_dir"])
161 if subprocess_log(_api_test_manifest_tfm,
162 self._tfb_log_f,
163 append=True,
164 prefix=_api_test_manifest_tfm,
165 silent=self._tfb_silent):
166
167 raise Exception("Python TFM Failed please check log: %s" %
168 self._tfb_log_f)
169 finally:
170 print("python pass after builder prepare")
171
172 p = self._tfb_build_dir + "/BUILD"
173 if not os.path.exists(p):
174 os.makedirs(p)
175
176 os.chdir(p)
177 if subprocess_log(self._tfb_cfg["build_psa_api"],
178 self._tfb_log_f,
179 append=True,
180 prefix=self._tfb_cfg["build_psa_api"],
181 silent=self._tfb_silent):
182
183 raise Exception("Build Failed please check log: %s" %
184 self._tfb_log_f)
Karl Zhangaff558a2020-05-15 14:28:23 +0100185
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100186 def task_exec(self):
187 """ Main tasks """
188
Nicola Mazzucato935f9cb2025-05-16 17:21:07 +0100189 # Mark process running as status
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100190 self.set_status(-1)
Karl Zhangaff558a2020-05-15 14:28:23 +0100191 print("builder _tfb_cfg %s" % self._tfb_cfg)
192
193 if "build_psa_api" in self._tfb_cfg:
194 p = self._tfb_build_dir + "/BUILD"
195 if not os.path.exists(p):
196 os.makedirs(p)
197 os.chdir(p)
198 if subprocess_log(self._tfb_cfg["build_psa_api"],
199 self._tfb_log_f,
200 append=True,
201 prefix=self._tfb_cfg["build_psa_api"],
202 silent=self._tfb_silent):
203 raise Exception("Build Failed please check log: %s" %
204 self._tfb_log_f)
205
Karl Zhangaff558a2020-05-15 14:28:23 +0100206 print("Go to build directory")
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100207 # Go to build directory
208 os.chdir(self._tfb_build_dir)
Minos Galanakisea421232019-06-20 17:11:28 +0100209
210 build_cmds = self._tfb_cfg["build_cmds"]
211
212 threads_no_rex = re.compile(r'.*(-j\s?(\d+))')
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100213
214 # Pass the report to later stages
Minos Galanakisea421232019-06-20 17:11:28 +0100215 rep = {"build_cmd": "%s" % ",".join(build_cmds)}
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100216 self.stash("Build Report", rep)
217
Nicola Mazzucato935f9cb2025-05-16 17:21:07 +0100218 # Call cmake to configure the project
Minos Galanakisea421232019-06-20 17:11:28 +0100219 for build_cmd in build_cmds:
220 # if a -j parameter is passed as user argument
221 user_set_threads_match = threads_no_rex.findall(build_cmd)
222
223 if user_set_threads_match:
224 # Unpack the regex groups (fullmatch, decimal match)
225 user_jtxt, user_set_threads = user_set_threads_match[0]
226 if int(user_set_threads) > self._tfb_build_threads:
227 print("Ignoring user requested n=%s threads because it"
228 " exceeds the maximum thread set ( %d )" %
229 (user_set_threads, self._tfb_build_threads))
230 thread_no = self._tfb_build_threads
231 else:
232 print("Using %s build threads" % user_set_threads)
233 thread_no = user_set_threads
234 build_cmd = build_cmd.replace(user_jtxt,
235 "-j %s " % thread_no)
236
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100237 # Build it
Karl Zhangaff558a2020-05-15 14:28:23 +0100238 print("~builder build_cmd %s\r\n" % build_cmd)
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100239 if subprocess_log(build_cmd,
240 self._tfb_log_f,
241 append=True,
242 prefix=build_cmd,
243 silent=self._tfb_silent):
Minos Galanakisea421232019-06-20 17:11:28 +0100244
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100245 raise Exception("Build Failed please check log: %s" %
246 self._tfb_log_f)
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100247
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100248 self._t_stop()
249
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100250 def post_eval(self):
251 """ Verify that the artefacts exist """
252 print("%s Post eval" % self.get_name())
253
254 ret_eval = False
255 rep = self.unstash("Build Report")
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100256
Minos Galanakisea421232019-06-20 17:11:28 +0100257 artefacts = list_filtered_tree(self._tfb_work_dir, r'%s' %
258 self._tfb_cfg["artifact_capture_rex"])
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100259
260 # Add artefact related information to report
261 rep["log"] = self._tfb_log_f
Minos Galanakisea421232019-06-20 17:11:28 +0100262
263 if not len(artefacts):
264 print("ERROR: Could not capture any binaries:")
265
266 # TODO update self._tfb_binaries
267 ret_eval = False
268 else:
269 print("SUCCESS: Produced the following binaries:")
270 print("\n\t".join(artefacts))
271 ret_eval = True
272
273 rep["artefacts"] = artefacts
274
Nicola Mazzucato935f9cb2025-05-16 17:21:07 +0100275 # Process the artifacts into file structures
Minos Galanakisea421232019-06-20 17:11:28 +0100276 art_files = {}
277 for art_item in artefacts:
278 art_f = {"pl_source": 1,
279 "resource": art_item if not self._tfb_relative_paths
280 else resolve_rel_path(art_item),
281 "size": {"bytes": str(os.path.getsize(art_item))}
282 }
283 if self._tfb_img_sizes and ".axf" in art_item:
284 eabi_size, _ = arm_non_eabi_size(art_item)
285 art_f["size"]["text"] = eabi_size["text"]
286 art_f["size"]["data"] = eabi_size["data"]
287 art_f["size"]["bss"] = eabi_size["bss"]
288 # filename is used as key for artfacts
289 art_files[os.path.split(art_item)[-1]] = art_f
290 rep["artefacts"] = art_files
291
292 if "required_artefacts" in self._tfb_cfg.keys():
293 if len(self._tfb_cfg["required_artefacts"]):
294 print("Searching for required binaries")
295 missing_binaries = list(filter(lambda x: not os.path.isfile(x),
296 self._tfb_cfg["required_artefacts"]))
297 if len(missing_binaries):
298 rep["missing_artefacts"] = missing_binaries
299 print("ERROR: Missing required artefacts:")
300 print("\n".join(missing_binaries))
301 ret_eval = False
302 else:
303 ret_eval = True
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100304
305 rep["status"] = "Success" if ret_eval else "Failed"
306 self.stash("Build Report", rep)
307 return ret_eval
308
309 def post_exec(self, eval_ret):
310 """ """
311
312 if eval_ret:
313 print("TFM Builder %s was Successful" % self.get_name())
314 else:
315 print("TFM Builder %s was UnSuccessful" % self.get_name())
Minos Galanakisea421232019-06-20 17:11:28 +0100316
317
318if __name__ == "__main__":
319 pass