blob: 1908a8e57e023cfc471dd9bee9ed4b1b9fa0c009 [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
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,
34 name, # Proccess 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"}
Minos Galanakisea421232019-06-20 17:11:28 +010041 build_threads=4, # Number of CPU thrads used in build
42 silent=False, # Silence stdout ouptut
43 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
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100108 def task_exec(self):
109 """ Main tasks """
110
111 # Mark proccess running as status
112 self.set_status(-1)
113 # Go to build directory
114 os.chdir(self._tfb_build_dir)
Minos Galanakisea421232019-06-20 17:11:28 +0100115
116 build_cmds = self._tfb_cfg["build_cmds"]
117
118 threads_no_rex = re.compile(r'.*(-j\s?(\d+))')
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100119
120 # Pass the report to later stages
Minos Galanakisea421232019-06-20 17:11:28 +0100121 rep = {"build_cmd": "%s" % ",".join(build_cmds)}
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100122 self.stash("Build Report", rep)
123
Minos Galanakisea421232019-06-20 17:11:28 +0100124 # Calll cmake to configure the project
125 for build_cmd in build_cmds:
126 # if a -j parameter is passed as user argument
127 user_set_threads_match = threads_no_rex.findall(build_cmd)
128
129 if user_set_threads_match:
130 # Unpack the regex groups (fullmatch, decimal match)
131 user_jtxt, user_set_threads = user_set_threads_match[0]
132 if int(user_set_threads) > self._tfb_build_threads:
133 print("Ignoring user requested n=%s threads because it"
134 " exceeds the maximum thread set ( %d )" %
135 (user_set_threads, self._tfb_build_threads))
136 thread_no = self._tfb_build_threads
137 else:
138 print("Using %s build threads" % user_set_threads)
139 thread_no = user_set_threads
140 build_cmd = build_cmd.replace(user_jtxt,
141 "-j %s " % thread_no)
142
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100143 # Build it
144 if subprocess_log(build_cmd,
145 self._tfb_log_f,
146 append=True,
147 prefix=build_cmd,
148 silent=self._tfb_silent):
Minos Galanakisea421232019-06-20 17:11:28 +0100149
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100150 raise Exception("Build Failed please check log: %s" %
151 self._tfb_log_f)
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100152
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100153 self._t_stop()
154
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100155 def post_eval(self):
156 """ Verify that the artefacts exist """
157 print("%s Post eval" % self.get_name())
158
159 ret_eval = False
160 rep = self.unstash("Build Report")
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100161
Minos Galanakisea421232019-06-20 17:11:28 +0100162 artefacts = list_filtered_tree(self._tfb_work_dir, r'%s' %
163 self._tfb_cfg["artifact_capture_rex"])
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100164
165 # Add artefact related information to report
166 rep["log"] = self._tfb_log_f
Minos Galanakisea421232019-06-20 17:11:28 +0100167
168 if not len(artefacts):
169 print("ERROR: Could not capture any binaries:")
170
171 # TODO update self._tfb_binaries
172 ret_eval = False
173 else:
174 print("SUCCESS: Produced the following binaries:")
175 print("\n\t".join(artefacts))
176 ret_eval = True
177
178 rep["artefacts"] = artefacts
179
180 # Proccess the artifacts into file structures
181 art_files = {}
182 for art_item in artefacts:
183 art_f = {"pl_source": 1,
184 "resource": art_item if not self._tfb_relative_paths
185 else resolve_rel_path(art_item),
186 "size": {"bytes": str(os.path.getsize(art_item))}
187 }
188 if self._tfb_img_sizes and ".axf" in art_item:
189 eabi_size, _ = arm_non_eabi_size(art_item)
190 art_f["size"]["text"] = eabi_size["text"]
191 art_f["size"]["data"] = eabi_size["data"]
192 art_f["size"]["bss"] = eabi_size["bss"]
193 # filename is used as key for artfacts
194 art_files[os.path.split(art_item)[-1]] = art_f
195 rep["artefacts"] = art_files
196
197 if "required_artefacts" in self._tfb_cfg.keys():
198 if len(self._tfb_cfg["required_artefacts"]):
199 print("Searching for required binaries")
200 missing_binaries = list(filter(lambda x: not os.path.isfile(x),
201 self._tfb_cfg["required_artefacts"]))
202 if len(missing_binaries):
203 rep["missing_artefacts"] = missing_binaries
204 print("ERROR: Missing required artefacts:")
205 print("\n".join(missing_binaries))
206 ret_eval = False
207 else:
208 ret_eval = True
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100209
210 rep["status"] = "Success" if ret_eval else "Failed"
211 self.stash("Build Report", rep)
212 return ret_eval
213
214 def post_exec(self, eval_ret):
215 """ """
216
217 if eval_ret:
218 print("TFM Builder %s was Successful" % self.get_name())
219 else:
220 print("TFM Builder %s was UnSuccessful" % self.get_name())
Minos Galanakisea421232019-06-20 17:11:28 +0100221
222
223if __name__ == "__main__":
224 pass