blob: ec143f9e0f5d310347c4f1887bafd4a78af1e6f5 [file] [log] [blame]
Simon Glassc52bd222022-07-11 19:04:03 -06001# SPDX-License-Identifier: GPL-2.0+
2# Copyright (c) 2012 The Chromium OS Authors.
3
4"""Maintains a list of boards and allows them to be selected"""
5
6from collections import OrderedDict
7import re
8
9from buildman import board
10
11
12class Expr:
13 """A single regular expression for matching boards to build"""
14
15 def __init__(self, expr):
16 """Set up a new Expr object.
17
18 Args:
19 expr: String cotaining regular expression to store
20 """
21 self._expr = expr
22 self._re = re.compile(expr)
23
24 def matches(self, props):
25 """Check if any of the properties match the regular expression.
26
27 Args:
28 props: List of properties to check
29 Returns:
30 True if any of the properties match the regular expression
31 """
32 for prop in props:
33 if self._re.match(prop):
34 return True
35 return False
36
37 def __str__(self):
38 return self._expr
39
40class Term:
41 """A list of expressions each of which must match with properties.
42
43 This provides a list of 'AND' expressions, meaning that each must
44 match the board properties for that board to be built.
45 """
46 def __init__(self):
47 self._expr_list = []
48 self._board_count = 0
49
50 def add_expr(self, expr):
51 """Add an Expr object to the list to check.
52
53 Args:
54 expr: New Expr object to add to the list of those that must
55 match for a board to be built.
56 """
57 self._expr_list.append(Expr(expr))
58
59 def __str__(self):
60 """Return some sort of useful string describing the term"""
61 return '&'.join([str(expr) for expr in self._expr_list])
62
63 def matches(self, props):
64 """Check if any of the properties match this term
65
66 Each of the expressions in the term is checked. All must match.
67
68 Args:
69 props: List of properties to check
70 Returns:
71 True if all of the expressions in the Term match, else False
72 """
73 for expr in self._expr_list:
74 if not expr.matches(props):
75 return False
76 return True
77
78
79class Boards:
80 """Manage a list of boards."""
81 def __init__(self):
82 # Use a simple list here, sinc OrderedDict requires Python 2.7
83 self._boards = []
84
85 def add_board(self, brd):
86 """Add a new board to the list.
87
88 The board's target member must not already exist in the board list.
89
90 Args:
91 brd: board to add
92 """
93 self._boards.append(brd)
94
95 def read_boards(self, fname):
96 """Read a list of boards from a board file.
97
98 Create a Board object for each and add it to our _boards list.
99
100 Args:
101 fname: Filename of boards.cfg file
102 """
103 with open(fname, 'r', encoding='utf-8') as inf:
104 for line in inf:
105 if line[0] == '#':
106 continue
107 fields = line.split()
108 if not fields:
109 continue
110 for upto, field in enumerate(fields):
111 if field == '-':
112 fields[upto] = ''
113 while len(fields) < 8:
114 fields.append('')
115 if len(fields) > 8:
116 fields = fields[:8]
117
118 brd = board.Board(*fields)
119 self.add_board(brd)
120
121
122 def get_list(self):
123 """Return a list of available boards.
124
125 Returns:
126 List of Board objects
127 """
128 return self._boards
129
130 def get_dict(self):
131 """Build a dictionary containing all the boards.
132
133 Returns:
134 Dictionary:
135 key is board.target
136 value is board
137 """
138 board_dict = OrderedDict()
139 for brd in self._boards:
140 board_dict[brd.target] = brd
141 return board_dict
142
143 def get_selected_dict(self):
144 """Return a dictionary containing the selected boards
145
146 Returns:
147 List of Board objects that are marked selected
148 """
149 board_dict = OrderedDict()
150 for brd in self._boards:
151 if brd.build_it:
152 board_dict[brd.target] = brd
153 return board_dict
154
155 def get_selected(self):
156 """Return a list of selected boards
157
158 Returns:
159 List of Board objects that are marked selected
160 """
161 return [brd for brd in self._boards if brd.build_it]
162
163 def get_selected_names(self):
164 """Return a list of selected boards
165
166 Returns:
167 List of board names that are marked selected
168 """
169 return [brd.target for brd in self._boards if brd.build_it]
170
171 @classmethod
172 def _build_terms(cls, args):
173 """Convert command line arguments to a list of terms.
174
175 This deals with parsing of the arguments. It handles the '&'
176 operator, which joins several expressions into a single Term.
177
178 For example:
179 ['arm & freescale sandbox', 'tegra']
180
181 will produce 3 Terms containing expressions as follows:
182 arm, freescale
183 sandbox
184 tegra
185
186 The first Term has two expressions, both of which must match for
187 a board to be selected.
188
189 Args:
190 args: List of command line arguments
191 Returns:
192 A list of Term objects
193 """
194 syms = []
195 for arg in args:
196 for word in arg.split():
197 sym_build = []
198 for term in word.split('&'):
199 if term:
200 sym_build.append(term)
201 sym_build.append('&')
202 syms += sym_build[:-1]
203 terms = []
204 term = None
205 oper = None
206 for sym in syms:
207 if sym == '&':
208 oper = sym
209 elif oper:
210 term.add_expr(sym)
211 oper = None
212 else:
213 if term:
214 terms.append(term)
215 term = Term()
216 term.add_expr(sym)
217 if term:
218 terms.append(term)
219 return terms
220
221 def select_boards(self, args, exclude=None, brds=None):
222 """Mark boards selected based on args
223
224 Normally either boards (an explicit list of boards) or args (a list of
225 terms to match against) is used. It is possible to specify both, in
226 which case they are additive.
227
228 If brds and args are both empty, all boards are selected.
229
230 Args:
231 args: List of strings specifying boards to include, either named,
232 or by their target, architecture, cpu, vendor or soc. If
233 empty, all boards are selected.
234 exclude: List of boards to exclude, regardless of 'args'
235 brds: List of boards to build
236
237 Returns:
238 Tuple
239 Dictionary which holds the list of boards which were selected
240 due to each argument, arranged by argument.
241 List of errors found
242 """
243 result = OrderedDict()
244 warnings = []
245 terms = self._build_terms(args)
246
247 result['all'] = []
248 for term in terms:
249 result[str(term)] = []
250
251 exclude_list = []
252 if exclude:
253 for expr in exclude:
254 exclude_list.append(Expr(expr))
255
256 found = []
257 for brd in self._boards:
258 matching_term = None
259 build_it = False
260 if terms:
261 for term in terms:
262 if term.matches(brd.props):
263 matching_term = str(term)
264 build_it = True
265 break
266 elif brds:
267 if brd.target in brds:
268 build_it = True
269 found.append(brd.target)
270 else:
271 build_it = True
272
273 # Check that it is not specifically excluded
274 for expr in exclude_list:
275 if expr.matches(brd.props):
276 build_it = False
277 break
278
279 if build_it:
280 brd.build_it = True
281 if matching_term:
282 result[matching_term].append(brd.target)
283 result['all'].append(brd.target)
284
285 if brds:
286 remaining = set(brds) - set(found)
287 if remaining:
288 warnings.append(f"Boards not found: {', '.join(remaining)}\n")
289
290 return result, warnings