blob: a04551484e46d8a2ab93f30e8e38a9980fd4114b [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)
Karl Zhangaff558a2020-05-15 14:28:23 +0100186
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100187 def task_exec(self):
188 """ Main tasks """
189
190 # Mark proccess running as status
191 self.set_status(-1)
Karl Zhangaff558a2020-05-15 14:28:23 +0100192 print("builder _tfb_cfg %s" % self._tfb_cfg)
193
194 if "build_psa_api" in self._tfb_cfg:
195 p = self._tfb_build_dir + "/BUILD"
196 if not os.path.exists(p):
197 os.makedirs(p)
198 os.chdir(p)
199 if subprocess_log(self._tfb_cfg["build_psa_api"],
200 self._tfb_log_f,
201 append=True,
202 prefix=self._tfb_cfg["build_psa_api"],
203 silent=self._tfb_silent):
204 raise Exception("Build Failed please check log: %s" %
205 self._tfb_log_f)
206
Karl Zhangaff558a2020-05-15 14:28:23 +0100207 print("Go to build directory")
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100208 # Go to build directory
209 os.chdir(self._tfb_build_dir)
Minos Galanakisea421232019-06-20 17:11:28 +0100210
211 build_cmds = self._tfb_cfg["build_cmds"]
212
213 threads_no_rex = re.compile(r'.*(-j\s?(\d+))')
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100214
215 # Pass the report to later stages
Minos Galanakisea421232019-06-20 17:11:28 +0100216 rep = {"build_cmd": "%s" % ",".join(build_cmds)}
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100217 self.stash("Build Report", rep)
218
Minos Galanakisea421232019-06-20 17:11:28 +0100219 # Calll cmake to configure the project
220 for build_cmd in build_cmds:
221 # if a -j parameter is passed as user argument
222 user_set_threads_match = threads_no_rex.findall(build_cmd)
223
224 if user_set_threads_match:
225 # Unpack the regex groups (fullmatch, decimal match)
226 user_jtxt, user_set_threads = user_set_threads_match[0]
227 if int(user_set_threads) > self._tfb_build_threads:
228 print("Ignoring user requested n=%s threads because it"
229 " exceeds the maximum thread set ( %d )" %
230 (user_set_threads, self._tfb_build_threads))
231 thread_no = self._tfb_build_threads
232 else:
233 print("Using %s build threads" % user_set_threads)
234 thread_no = user_set_threads
235 build_cmd = build_cmd.replace(user_jtxt,
236 "-j %s " % thread_no)
237
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100238 # Build it
Karl Zhangaff558a2020-05-15 14:28:23 +0100239 print("~builder build_cmd %s\r\n" % build_cmd)
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100240 if subprocess_log(build_cmd,
241 self._tfb_log_f,
242 append=True,
243 prefix=build_cmd,
244 silent=self._tfb_silent):
Minos Galanakisea421232019-06-20 17:11:28 +0100245
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100246 raise Exception("Build Failed please check log: %s" %
247 self._tfb_log_f)
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100248
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100249 self._t_stop()
250
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100251 def post_eval(self):
252 """ Verify that the artefacts exist """
253 print("%s Post eval" % self.get_name())
254
255 ret_eval = False
256 rep = self.unstash("Build Report")
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100257
Minos Galanakisea421232019-06-20 17:11:28 +0100258 artefacts = list_filtered_tree(self._tfb_work_dir, r'%s' %
259 self._tfb_cfg["artifact_capture_rex"])
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100260
261 # Add artefact related information to report
262 rep["log"] = self._tfb_log_f
Minos Galanakisea421232019-06-20 17:11:28 +0100263
264 if not len(artefacts):
265 print("ERROR: Could not capture any binaries:")
266
267 # TODO update self._tfb_binaries
268 ret_eval = False
269 else:
270 print("SUCCESS: Produced the following binaries:")
271 print("\n\t".join(artefacts))
272 ret_eval = True
273
274 rep["artefacts"] = artefacts
275
276 # Proccess the artifacts into file structures
277 art_files = {}
278 for art_item in artefacts:
279 art_f = {"pl_source": 1,
280 "resource": art_item if not self._tfb_relative_paths
281 else resolve_rel_path(art_item),
282 "size": {"bytes": str(os.path.getsize(art_item))}
283 }
284 if self._tfb_img_sizes and ".axf" in art_item:
285 eabi_size, _ = arm_non_eabi_size(art_item)
286 art_f["size"]["text"] = eabi_size["text"]
287 art_f["size"]["data"] = eabi_size["data"]
288 art_f["size"]["bss"] = eabi_size["bss"]
289 # filename is used as key for artfacts
290 art_files[os.path.split(art_item)[-1]] = art_f
291 rep["artefacts"] = art_files
292
293 if "required_artefacts" in self._tfb_cfg.keys():
294 if len(self._tfb_cfg["required_artefacts"]):
295 print("Searching for required binaries")
296 missing_binaries = list(filter(lambda x: not os.path.isfile(x),
297 self._tfb_cfg["required_artefacts"]))
298 if len(missing_binaries):
299 rep["missing_artefacts"] = missing_binaries
300 print("ERROR: Missing required artefacts:")
301 print("\n".join(missing_binaries))
302 ret_eval = False
303 else:
304 ret_eval = True
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100305
306 rep["status"] = "Success" if ret_eval else "Failed"
307 self.stash("Build Report", rep)
308 return ret_eval
309
310 def post_exec(self, eval_ret):
311 """ """
312
313 if eval_ret:
314 print("TFM Builder %s was Successful" % self.get_name())
315 else:
316 print("TFM Builder %s was UnSuccessful" % self.get_name())
Minos Galanakisea421232019-06-20 17:11:28 +0100317
318
319if __name__ == "__main__":
320 pass