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