blob: e16f3268ab1cf3479218efb3a6e3f2ff51e9a58c [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.
Simon Glassa8a01412022-07-11 19:04:04 -06003# Author: Simon Glass <sjg@chromium.org>
4# Author: Masahiro Yamada <yamada.m@jp.panasonic.com>
Simon Glassc52bd222022-07-11 19:04:03 -06005
6"""Maintains a list of boards and allows them to be selected"""
7
8from collections import OrderedDict
Simon Glassa8a01412022-07-11 19:04:04 -06009import errno
10import fnmatch
11import glob
12import multiprocessing
13import os
Simon Glassc52bd222022-07-11 19:04:03 -060014import re
Simon Glassa8a01412022-07-11 19:04:04 -060015import sys
16import tempfile
17import time
Simon Glassc52bd222022-07-11 19:04:03 -060018
19from buildman import board
Simon Glassa8a01412022-07-11 19:04:04 -060020from buildman import kconfiglib
21
22
23### constant variables ###
24OUTPUT_FILE = 'boards.cfg'
25CONFIG_DIR = 'configs'
26SLEEP_TIME = 0.03
Simon Glass969fd332022-07-11 19:04:05 -060027COMMENT_BLOCK = f'''#
Simon Glassa8a01412022-07-11 19:04:04 -060028# List of boards
Simon Glass969fd332022-07-11 19:04:05 -060029# Automatically generated by {__file__}: don't edit
Simon Glassa8a01412022-07-11 19:04:04 -060030#
31# Status, Arch, CPU, SoC, Vendor, Board, Target, Options, Maintainers
32
Simon Glass969fd332022-07-11 19:04:05 -060033'''
Simon Glassa8a01412022-07-11 19:04:04 -060034
35
Simon Glass969fd332022-07-11 19:04:05 -060036def try_remove(fname):
37 """Remove a file ignoring 'No such file or directory' error.
38
39 Args:
40 fname (str): Filename to remove
41
42 Raises:
43 OSError: output file exists but could not be removed
44 """
Simon Glassa8a01412022-07-11 19:04:04 -060045 try:
Simon Glass969fd332022-07-11 19:04:05 -060046 os.remove(fname)
Simon Glassa8a01412022-07-11 19:04:04 -060047 except OSError as exception:
48 # Ignore 'No such file or directory' error
49 if exception.errno != errno.ENOENT:
50 raise
51
52
53def output_is_new(output):
54 """Check if the output file is up to date.
55
Simon Glass969fd332022-07-11 19:04:05 -060056 Looks at defconfig and Kconfig files to make sure none is newer than the
57 output file. Also ensures that the boards.cfg does not mention any removed
58 boards.
59
60 Args:
61 output (str): Filename to check
62
Simon Glassa8a01412022-07-11 19:04:04 -060063 Returns:
Simon Glass969fd332022-07-11 19:04:05 -060064 True if the given output file exists and is newer than any of
65 *_defconfig, MAINTAINERS and Kconfig*. False otherwise.
66
67 Raises:
68 OSError: output file exists but could not be opened
Simon Glassa8a01412022-07-11 19:04:04 -060069 """
Simon Glass969fd332022-07-11 19:04:05 -060070 # pylint: disable=too-many-branches
Simon Glassa8a01412022-07-11 19:04:04 -060071 try:
72 ctime = os.path.getctime(output)
73 except OSError as exception:
74 if exception.errno == errno.ENOENT:
75 # return False on 'No such file or directory' error
76 return False
Simon Glass969fd332022-07-11 19:04:05 -060077 raise
Simon Glassa8a01412022-07-11 19:04:04 -060078
Simon Glass969fd332022-07-11 19:04:05 -060079 for (dirpath, _, filenames) in os.walk(CONFIG_DIR):
Simon Glassa8a01412022-07-11 19:04:04 -060080 for filename in fnmatch.filter(filenames, '*_defconfig'):
81 if fnmatch.fnmatch(filename, '.*'):
82 continue
83 filepath = os.path.join(dirpath, filename)
84 if ctime < os.path.getctime(filepath):
85 return False
86
Simon Glass969fd332022-07-11 19:04:05 -060087 for (dirpath, _, filenames) in os.walk('.'):
Simon Glassa8a01412022-07-11 19:04:04 -060088 for filename in filenames:
89 if (fnmatch.fnmatch(filename, '*~') or
90 not fnmatch.fnmatch(filename, 'Kconfig*') and
91 not filename == 'MAINTAINERS'):
92 continue
93 filepath = os.path.join(dirpath, filename)
94 if ctime < os.path.getctime(filepath):
95 return False
96
97 # Detect a board that has been removed since the current board database
98 # was generated
Simon Glass969fd332022-07-11 19:04:05 -060099 with open(output, encoding="utf-8") as inf:
100 for line in inf:
Simon Glassa8a01412022-07-11 19:04:04 -0600101 if line[0] == '#' or line == '\n':
102 continue
103 defconfig = line.split()[6] + '_defconfig'
104 if not os.path.exists(os.path.join(CONFIG_DIR, defconfig)):
105 return False
106
107 return True
Simon Glassc52bd222022-07-11 19:04:03 -0600108
109
110class Expr:
111 """A single regular expression for matching boards to build"""
112
113 def __init__(self, expr):
114 """Set up a new Expr object.
115
116 Args:
Simon Glass969fd332022-07-11 19:04:05 -0600117 expr (str): String cotaining regular expression to store
Simon Glassc52bd222022-07-11 19:04:03 -0600118 """
119 self._expr = expr
120 self._re = re.compile(expr)
121
122 def matches(self, props):
123 """Check if any of the properties match the regular expression.
124
125 Args:
Simon Glass969fd332022-07-11 19:04:05 -0600126 props (list of str): List of properties to check
Simon Glassc52bd222022-07-11 19:04:03 -0600127 Returns:
128 True if any of the properties match the regular expression
129 """
130 for prop in props:
131 if self._re.match(prop):
132 return True
133 return False
134
135 def __str__(self):
136 return self._expr
137
138class Term:
139 """A list of expressions each of which must match with properties.
140
141 This provides a list of 'AND' expressions, meaning that each must
142 match the board properties for that board to be built.
143 """
144 def __init__(self):
145 self._expr_list = []
146 self._board_count = 0
147
148 def add_expr(self, expr):
149 """Add an Expr object to the list to check.
150
151 Args:
Simon Glass969fd332022-07-11 19:04:05 -0600152 expr (Expr): New Expr object to add to the list of those that must
Simon Glassc52bd222022-07-11 19:04:03 -0600153 match for a board to be built.
154 """
155 self._expr_list.append(Expr(expr))
156
157 def __str__(self):
158 """Return some sort of useful string describing the term"""
159 return '&'.join([str(expr) for expr in self._expr_list])
160
161 def matches(self, props):
162 """Check if any of the properties match this term
163
164 Each of the expressions in the term is checked. All must match.
165
166 Args:
Simon Glass969fd332022-07-11 19:04:05 -0600167 props (list of str): List of properties to check
Simon Glassc52bd222022-07-11 19:04:03 -0600168 Returns:
169 True if all of the expressions in the Term match, else False
170 """
171 for expr in self._expr_list:
172 if not expr.matches(props):
173 return False
174 return True
175
176
Simon Glassa8a01412022-07-11 19:04:04 -0600177class KconfigScanner:
178
179 """Kconfig scanner."""
180
181 ### constant variable only used in this class ###
182 _SYMBOL_TABLE = {
183 'arch' : 'SYS_ARCH',
184 'cpu' : 'SYS_CPU',
185 'soc' : 'SYS_SOC',
186 'vendor' : 'SYS_VENDOR',
187 'board' : 'SYS_BOARD',
188 'config' : 'SYS_CONFIG_NAME',
189 'options' : 'SYS_EXTRA_OPTIONS'
190 }
191
192 def __init__(self):
193 """Scan all the Kconfig files and create a Kconfig object."""
194 # Define environment variables referenced from Kconfig
195 os.environ['srctree'] = os.getcwd()
196 os.environ['UBOOTVERSION'] = 'dummy'
197 os.environ['KCONFIG_OBJDIR'] = ''
Simon Glass969fd332022-07-11 19:04:05 -0600198 self._tmpfile = None
Simon Glassa8a01412022-07-11 19:04:04 -0600199 self._conf = kconfiglib.Kconfig(warn=False)
200
201 def __del__(self):
202 """Delete a leftover temporary file before exit.
203
204 The scan() method of this class creates a temporay file and deletes
205 it on success. If scan() method throws an exception on the way,
206 the temporary file might be left over. In that case, it should be
207 deleted in this destructor.
208 """
Simon Glass969fd332022-07-11 19:04:05 -0600209 if self._tmpfile:
Simon Glassa8a01412022-07-11 19:04:04 -0600210 try_remove(self._tmpfile)
211
212 def scan(self, defconfig):
213 """Load a defconfig file to obtain board parameters.
214
Simon Glass969fd332022-07-11 19:04:05 -0600215 Args:
216 defconfig (str): path to the defconfig file to be processed
Simon Glassa8a01412022-07-11 19:04:04 -0600217
218 Returns:
Simon Glass969fd332022-07-11 19:04:05 -0600219 Dictionary of board parameters. It has a form:
220 {
221 'arch': <arch_name>,
222 'cpu': <cpu_name>,
223 'soc': <soc_name>,
224 'vendor': <vendor_name>,
225 'board': <board_name>,
226 'target': <target_name>,
227 'config': <config_header_name>,
228 'options': <extra_options>
229 }
Simon Glassa8a01412022-07-11 19:04:04 -0600230 """
231 # strip special prefixes and save it in a temporary file
Simon Glass969fd332022-07-11 19:04:05 -0600232 outfd, self._tmpfile = tempfile.mkstemp()
233 with os.fdopen(outfd, 'w') as outf:
234 with open(defconfig, encoding='utf-8') as inf:
235 for line in inf:
236 colon = line.find(':CONFIG_')
237 if colon == -1:
238 outf.write(line)
239 else:
240 outf.write(line[colon + 1:])
Simon Glassa8a01412022-07-11 19:04:04 -0600241
242 self._conf.load_config(self._tmpfile)
243 try_remove(self._tmpfile)
244 self._tmpfile = None
245
246 params = {}
247
248 # Get the value of CONFIG_SYS_ARCH, CONFIG_SYS_CPU, ... etc.
249 # Set '-' if the value is empty.
250 for key, symbol in list(self._SYMBOL_TABLE.items()):
251 value = self._conf.syms.get(symbol).str_value
252 if value:
253 params[key] = value
254 else:
255 params[key] = '-'
256
257 defconfig = os.path.basename(defconfig)
258 params['target'], match, rear = defconfig.partition('_defconfig')
Simon Glass969fd332022-07-11 19:04:05 -0600259 assert match and not rear, f'{defconfig} : invalid defconfig'
Simon Glassa8a01412022-07-11 19:04:04 -0600260
261 # fix-up for aarch64
262 if params['arch'] == 'arm' and params['cpu'] == 'armv8':
263 params['arch'] = 'aarch64'
264
265 # fix-up options field. It should have the form:
266 # <config name>[:comma separated config options]
267 if params['options'] != '-':
268 params['options'] = params['config'] + ':' + \
269 params['options'].replace(r'\"', '"')
270 elif params['config'] != params['target']:
271 params['options'] = params['config']
272
273 return params
274
275
276class MaintainersDatabase:
277
Simon Glass969fd332022-07-11 19:04:05 -0600278 """The database of board status and maintainers.
279
280 Properties:
281 database: dict:
282 key: Board-target name (e.g. 'snow')
283 value: tuple:
284 str: Board status (e.g. 'Active')
285 str: List of maintainers, separated by :
286 """
Simon Glassa8a01412022-07-11 19:04:04 -0600287
288 def __init__(self):
289 """Create an empty database."""
290 self.database = {}
291
292 def get_status(self, target):
293 """Return the status of the given board.
294
295 The board status is generally either 'Active' or 'Orphan'.
296 Display a warning message and return '-' if status information
297 is not found.
298
Simon Glass969fd332022-07-11 19:04:05 -0600299 Args:
300 target (str): Build-target name
301
Simon Glassa8a01412022-07-11 19:04:04 -0600302 Returns:
Simon Glass969fd332022-07-11 19:04:05 -0600303 str: 'Active', 'Orphan' or '-'.
Simon Glassa8a01412022-07-11 19:04:04 -0600304 """
305 if not target in self.database:
Simon Glass969fd332022-07-11 19:04:05 -0600306 print(f"WARNING: no status info for '{target}'", file=sys.stderr)
Simon Glassa8a01412022-07-11 19:04:04 -0600307 return '-'
308
309 tmp = self.database[target][0]
310 if tmp.startswith('Maintained'):
311 return 'Active'
Simon Glass969fd332022-07-11 19:04:05 -0600312 if tmp.startswith('Supported'):
Simon Glassa8a01412022-07-11 19:04:04 -0600313 return 'Active'
Simon Glass969fd332022-07-11 19:04:05 -0600314 if tmp.startswith('Orphan'):
Simon Glassa8a01412022-07-11 19:04:04 -0600315 return 'Orphan'
Simon Glass969fd332022-07-11 19:04:05 -0600316 print(f"WARNING: {tmp}: unknown status for '{target}'", file=sys.stderr)
317 return '-'
Simon Glassa8a01412022-07-11 19:04:04 -0600318
319 def get_maintainers(self, target):
320 """Return the maintainers of the given board.
321
Simon Glass969fd332022-07-11 19:04:05 -0600322 Args:
323 target (str): Build-target name
324
Simon Glassa8a01412022-07-11 19:04:04 -0600325 Returns:
Simon Glass969fd332022-07-11 19:04:05 -0600326 str: Maintainers of the board. If the board has two or more
327 maintainers, they are separated with colons.
Simon Glassa8a01412022-07-11 19:04:04 -0600328 """
329 if not target in self.database:
Simon Glass969fd332022-07-11 19:04:05 -0600330 print(f"WARNING: no maintainers for '{target}'", file=sys.stderr)
Simon Glassa8a01412022-07-11 19:04:04 -0600331 return ''
332
333 return ':'.join(self.database[target][1])
334
Simon Glass969fd332022-07-11 19:04:05 -0600335 def parse_file(self, fname):
Simon Glassa8a01412022-07-11 19:04:04 -0600336 """Parse a MAINTAINERS file.
337
Simon Glass969fd332022-07-11 19:04:05 -0600338 Parse a MAINTAINERS file and accumulate board status and maintainers
339 information in the self.database dict.
Simon Glassa8a01412022-07-11 19:04:04 -0600340
Simon Glass969fd332022-07-11 19:04:05 -0600341 Args:
342 fname (str): MAINTAINERS file to be parsed
Simon Glassa8a01412022-07-11 19:04:04 -0600343 """
344 targets = []
345 maintainers = []
346 status = '-'
Simon Glass969fd332022-07-11 19:04:05 -0600347 with open(fname, encoding="utf-8") as inf:
348 for line in inf:
349 # Check also commented maintainers
350 if line[:3] == '#M:':
351 line = line[1:]
352 tag, rest = line[:2], line[2:].strip()
353 if tag == 'M:':
354 maintainers.append(rest)
355 elif tag == 'F:':
356 # expand wildcard and filter by 'configs/*_defconfig'
357 for item in glob.glob(rest):
358 front, match, rear = item.partition('configs/')
359 if not front and match:
360 front, match, rear = rear.rpartition('_defconfig')
361 if match and not rear:
362 targets.append(front)
363 elif tag == 'S:':
364 status = rest
365 elif line == '\n':
366 for target in targets:
367 self.database[target] = (status, maintainers)
368 targets = []
369 maintainers = []
370 status = '-'
Simon Glassa8a01412022-07-11 19:04:04 -0600371 if targets:
372 for target in targets:
373 self.database[target] = (status, maintainers)
374
375
Simon Glassc52bd222022-07-11 19:04:03 -0600376class Boards:
377 """Manage a list of boards."""
378 def __init__(self):
379 # Use a simple list here, sinc OrderedDict requires Python 2.7
380 self._boards = []
381
382 def add_board(self, brd):
383 """Add a new board to the list.
384
385 The board's target member must not already exist in the board list.
386
387 Args:
Simon Glass969fd332022-07-11 19:04:05 -0600388 brd (Board): board to add
Simon Glassc52bd222022-07-11 19:04:03 -0600389 """
390 self._boards.append(brd)
391
392 def read_boards(self, fname):
393 """Read a list of boards from a board file.
394
395 Create a Board object for each and add it to our _boards list.
396
397 Args:
Simon Glass969fd332022-07-11 19:04:05 -0600398 fname (str): Filename of boards.cfg file
Simon Glassc52bd222022-07-11 19:04:03 -0600399 """
400 with open(fname, 'r', encoding='utf-8') as inf:
401 for line in inf:
402 if line[0] == '#':
403 continue
404 fields = line.split()
405 if not fields:
406 continue
407 for upto, field in enumerate(fields):
408 if field == '-':
409 fields[upto] = ''
410 while len(fields) < 8:
411 fields.append('')
412 if len(fields) > 8:
413 fields = fields[:8]
414
415 brd = board.Board(*fields)
416 self.add_board(brd)
417
418
419 def get_list(self):
420 """Return a list of available boards.
421
422 Returns:
423 List of Board objects
424 """
425 return self._boards
426
427 def get_dict(self):
428 """Build a dictionary containing all the boards.
429
430 Returns:
431 Dictionary:
432 key is board.target
433 value is board
434 """
435 board_dict = OrderedDict()
436 for brd in self._boards:
437 board_dict[brd.target] = brd
438 return board_dict
439
440 def get_selected_dict(self):
441 """Return a dictionary containing the selected boards
442
443 Returns:
444 List of Board objects that are marked selected
445 """
446 board_dict = OrderedDict()
447 for brd in self._boards:
448 if brd.build_it:
449 board_dict[brd.target] = brd
450 return board_dict
451
452 def get_selected(self):
453 """Return a list of selected boards
454
455 Returns:
456 List of Board objects that are marked selected
457 """
458 return [brd for brd in self._boards if brd.build_it]
459
460 def get_selected_names(self):
461 """Return a list of selected boards
462
463 Returns:
464 List of board names that are marked selected
465 """
466 return [brd.target for brd in self._boards if brd.build_it]
467
468 @classmethod
469 def _build_terms(cls, args):
470 """Convert command line arguments to a list of terms.
471
472 This deals with parsing of the arguments. It handles the '&'
473 operator, which joins several expressions into a single Term.
474
475 For example:
476 ['arm & freescale sandbox', 'tegra']
477
478 will produce 3 Terms containing expressions as follows:
479 arm, freescale
480 sandbox
481 tegra
482
483 The first Term has two expressions, both of which must match for
484 a board to be selected.
485
486 Args:
Simon Glass969fd332022-07-11 19:04:05 -0600487 args (list of str): List of command line arguments
488
Simon Glassc52bd222022-07-11 19:04:03 -0600489 Returns:
Simon Glass969fd332022-07-11 19:04:05 -0600490 list of Term: A list of Term objects
Simon Glassc52bd222022-07-11 19:04:03 -0600491 """
492 syms = []
493 for arg in args:
494 for word in arg.split():
495 sym_build = []
496 for term in word.split('&'):
497 if term:
498 sym_build.append(term)
499 sym_build.append('&')
500 syms += sym_build[:-1]
501 terms = []
502 term = None
503 oper = None
504 for sym in syms:
505 if sym == '&':
506 oper = sym
507 elif oper:
508 term.add_expr(sym)
509 oper = None
510 else:
511 if term:
512 terms.append(term)
513 term = Term()
514 term.add_expr(sym)
515 if term:
516 terms.append(term)
517 return terms
518
519 def select_boards(self, args, exclude=None, brds=None):
520 """Mark boards selected based on args
521
522 Normally either boards (an explicit list of boards) or args (a list of
523 terms to match against) is used. It is possible to specify both, in
524 which case they are additive.
525
526 If brds and args are both empty, all boards are selected.
527
528 Args:
Simon Glass969fd332022-07-11 19:04:05 -0600529 args (list of str): List of strings specifying boards to include,
530 either named, or by their target, architecture, cpu, vendor or
531 soc. If empty, all boards are selected.
532 exclude (list of str): List of boards to exclude, regardless of
533 'args', or None for none
534 brds (list of Board): List of boards to build, or None/[] for all
Simon Glassc52bd222022-07-11 19:04:03 -0600535
536 Returns:
537 Tuple
538 Dictionary which holds the list of boards which were selected
539 due to each argument, arranged by argument.
540 List of errors found
541 """
Simon Glass969fd332022-07-11 19:04:05 -0600542 def _check_board(brd):
543 """Check whether to include or exclude a board
Simon Glassc52bd222022-07-11 19:04:03 -0600544
Simon Glass969fd332022-07-11 19:04:05 -0600545 Checks the various terms and decide whether to build it or not (the
546 'build_it' variable).
Simon Glassc52bd222022-07-11 19:04:03 -0600547
Simon Glass969fd332022-07-11 19:04:05 -0600548 If it is built, add the board to the result[term] list so we know
549 which term caused it to be built. Add it to result['all'] also.
Simon Glassc52bd222022-07-11 19:04:03 -0600550
Simon Glass969fd332022-07-11 19:04:05 -0600551 Keep a list of boards we found in 'found', so we can report boards
552 which appear in self._boards but not in brds.
553
554 Args:
555 brd (Board): Board to check
556 """
Simon Glassc52bd222022-07-11 19:04:03 -0600557 matching_term = None
558 build_it = False
559 if terms:
560 for term in terms:
561 if term.matches(brd.props):
562 matching_term = str(term)
563 build_it = True
564 break
565 elif brds:
566 if brd.target in brds:
567 build_it = True
568 found.append(brd.target)
569 else:
570 build_it = True
571
572 # Check that it is not specifically excluded
573 for expr in exclude_list:
574 if expr.matches(brd.props):
575 build_it = False
576 break
577
578 if build_it:
579 brd.build_it = True
580 if matching_term:
581 result[matching_term].append(brd.target)
582 result['all'].append(brd.target)
583
Simon Glass969fd332022-07-11 19:04:05 -0600584 result = OrderedDict()
585 warnings = []
586 terms = self._build_terms(args)
587
588 result['all'] = []
589 for term in terms:
590 result[str(term)] = []
591
592 exclude_list = []
593 if exclude:
594 for expr in exclude:
595 exclude_list.append(Expr(expr))
596
597 found = []
598 for brd in self._boards:
599 _check_board(brd)
600
Simon Glassc52bd222022-07-11 19:04:03 -0600601 if brds:
602 remaining = set(brds) - set(found)
603 if remaining:
604 warnings.append(f"Boards not found: {', '.join(remaining)}\n")
605
606 return result, warnings
Simon Glassa8a01412022-07-11 19:04:04 -0600607
Simon Glass969fd332022-07-11 19:04:05 -0600608 @classmethod
609 def scan_defconfigs_for_multiprocess(cls, queue, defconfigs):
Simon Glassa8a01412022-07-11 19:04:04 -0600610 """Scan defconfig files and queue their board parameters
611
Simon Glass969fd332022-07-11 19:04:05 -0600612 This function is intended to be passed to multiprocessing.Process()
613 constructor.
Simon Glassa8a01412022-07-11 19:04:04 -0600614
Simon Glass969fd332022-07-11 19:04:05 -0600615 Args:
616 queue (multiprocessing.Queue): The resulting board parameters are
617 written into this.
618 defconfigs (sequence of str): A sequence of defconfig files to be
619 scanned.
Simon Glassa8a01412022-07-11 19:04:04 -0600620 """
621 kconf_scanner = KconfigScanner()
622 for defconfig in defconfigs:
623 queue.put(kconf_scanner.scan(defconfig))
624
Simon Glass969fd332022-07-11 19:04:05 -0600625 @classmethod
626 def read_queues(cls, queues, params_list):
Simon Glassa8a01412022-07-11 19:04:04 -0600627 """Read the queues and append the data to the paramers list"""
Simon Glass969fd332022-07-11 19:04:05 -0600628 for que in queues:
629 while not que.empty():
630 params_list.append(que.get())
Simon Glassa8a01412022-07-11 19:04:04 -0600631
632 def scan_defconfigs(self, jobs=1):
633 """Collect board parameters for all defconfig files.
634
635 This function invokes multiple processes for faster processing.
636
Simon Glass969fd332022-07-11 19:04:05 -0600637 Args:
638 jobs (int): The number of jobs to run simultaneously
Simon Glassa8a01412022-07-11 19:04:04 -0600639 """
640 all_defconfigs = []
Simon Glass969fd332022-07-11 19:04:05 -0600641 for (dirpath, _, filenames) in os.walk(CONFIG_DIR):
Simon Glassa8a01412022-07-11 19:04:04 -0600642 for filename in fnmatch.filter(filenames, '*_defconfig'):
643 if fnmatch.fnmatch(filename, '.*'):
644 continue
645 all_defconfigs.append(os.path.join(dirpath, filename))
646
647 total_boards = len(all_defconfigs)
648 processes = []
649 queues = []
650 for i in range(jobs):
651 defconfigs = all_defconfigs[total_boards * i // jobs :
652 total_boards * (i + 1) // jobs]
Simon Glass969fd332022-07-11 19:04:05 -0600653 que = multiprocessing.Queue(maxsize=-1)
654 proc = multiprocessing.Process(
Simon Glassa8a01412022-07-11 19:04:04 -0600655 target=self.scan_defconfigs_for_multiprocess,
Simon Glass969fd332022-07-11 19:04:05 -0600656 args=(que, defconfigs))
657 proc.start()
658 processes.append(proc)
659 queues.append(que)
Simon Glassa8a01412022-07-11 19:04:04 -0600660
661 # The resulting data should be accumulated to this list
662 params_list = []
663
664 # Data in the queues should be retrieved preriodically.
665 # Otherwise, the queues would become full and subprocesses would get stuck.
Simon Glass969fd332022-07-11 19:04:05 -0600666 while any(p.is_alive() for p in processes):
Simon Glassa8a01412022-07-11 19:04:04 -0600667 self.read_queues(queues, params_list)
668 # sleep for a while until the queues are filled
669 time.sleep(SLEEP_TIME)
670
671 # Joining subprocesses just in case
672 # (All subprocesses should already have been finished)
Simon Glass969fd332022-07-11 19:04:05 -0600673 for proc in processes:
674 proc.join()
Simon Glassa8a01412022-07-11 19:04:04 -0600675
676 # retrieve leftover data
677 self.read_queues(queues, params_list)
678
679 return params_list
680
Simon Glass969fd332022-07-11 19:04:05 -0600681 @classmethod
682 def insert_maintainers_info(cls, params_list):
Simon Glassa8a01412022-07-11 19:04:04 -0600683 """Add Status and Maintainers information to the board parameters list.
684
Simon Glass969fd332022-07-11 19:04:05 -0600685 Args:
686 params_list (list of dict): A list of the board parameters
Simon Glassa8a01412022-07-11 19:04:04 -0600687 """
688 database = MaintainersDatabase()
Simon Glass969fd332022-07-11 19:04:05 -0600689 for (dirpath, _, filenames) in os.walk('.'):
Simon Glassa8a01412022-07-11 19:04:04 -0600690 if 'MAINTAINERS' in filenames:
691 database.parse_file(os.path.join(dirpath, 'MAINTAINERS'))
692
693 for i, params in enumerate(params_list):
694 target = params['target']
695 params['status'] = database.get_status(target)
696 params['maintainers'] = database.get_maintainers(target)
697 params_list[i] = params
698
Simon Glass969fd332022-07-11 19:04:05 -0600699 @classmethod
700 def format_and_output(cls, params_list, output):
Simon Glassa8a01412022-07-11 19:04:04 -0600701 """Write board parameters into a file.
702
703 Columnate the board parameters, sort lines alphabetically,
704 and then write them to a file.
705
Simon Glass969fd332022-07-11 19:04:05 -0600706 Args:
707 params_list (list of dict): The list of board parameters
708 output (str): The path to the output file
Simon Glassa8a01412022-07-11 19:04:04 -0600709 """
Simon Glass969fd332022-07-11 19:04:05 -0600710 fields = ('status', 'arch', 'cpu', 'soc', 'vendor', 'board', 'target',
711 'options', 'maintainers')
Simon Glassa8a01412022-07-11 19:04:04 -0600712
713 # First, decide the width of each column
Simon Glass969fd332022-07-11 19:04:05 -0600714 max_length = {f: 0 for f in fields}
Simon Glassa8a01412022-07-11 19:04:04 -0600715 for params in params_list:
Simon Glass969fd332022-07-11 19:04:05 -0600716 for field in fields:
717 max_length[field] = max(max_length[field], len(params[field]))
Simon Glassa8a01412022-07-11 19:04:04 -0600718
719 output_lines = []
720 for params in params_list:
721 line = ''
Simon Glass969fd332022-07-11 19:04:05 -0600722 for field in fields:
Simon Glassa8a01412022-07-11 19:04:04 -0600723 # insert two spaces between fields like column -t would
Simon Glass969fd332022-07-11 19:04:05 -0600724 line += ' ' + params[field].ljust(max_length[field])
Simon Glassa8a01412022-07-11 19:04:04 -0600725 output_lines.append(line.strip())
726
727 # ignore case when sorting
728 output_lines.sort(key=str.lower)
729
Simon Glass969fd332022-07-11 19:04:05 -0600730 with open(output, 'w', encoding="utf-8") as outf:
731 outf.write(COMMENT_BLOCK + '\n'.join(output_lines) + '\n')
Simon Glassa8a01412022-07-11 19:04:04 -0600732
733 def ensure_board_list(self, output, jobs=1, force=False, quiet=False):
734 """Generate a board database file if needed.
735
Simon Glass969fd332022-07-11 19:04:05 -0600736 Args:
737 output (str): The name of the output file
738 jobs (int): The number of jobs to run simultaneously
739 force (bool): Force to generate the output even if it is new
740 quiet (bool): True to avoid printing a message if nothing needs doing
Simon Glassa8a01412022-07-11 19:04:04 -0600741 """
742 if not force and output_is_new(output):
743 if not quiet:
Simon Glass969fd332022-07-11 19:04:05 -0600744 print(f'{output} is up to date. Nothing to do.')
Simon Glassa8a01412022-07-11 19:04:04 -0600745 return
746 params_list = self.scan_defconfigs(jobs)
747 self.insert_maintainers_info(params_list)
748 self.format_and_output(params_list, output)