blob: 8832e40cd5d0b941f5e709cb4e0260b5e5156f8b [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#
Simon Glass256126c2022-07-11 19:04:06 -060031# Status, Arch, CPU, SoC, Vendor, Board, Target, Config, Maintainers
Simon Glassa8a01412022-07-11 19:04:04 -060032
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 Glass256126c2022-07-11 19:04:06 -0600101 if 'Options,' in line:
102 return False
Simon Glassa8a01412022-07-11 19:04:04 -0600103 if line[0] == '#' or line == '\n':
104 continue
105 defconfig = line.split()[6] + '_defconfig'
106 if not os.path.exists(os.path.join(CONFIG_DIR, defconfig)):
107 return False
108
109 return True
Simon Glassc52bd222022-07-11 19:04:03 -0600110
111
112class Expr:
113 """A single regular expression for matching boards to build"""
114
115 def __init__(self, expr):
116 """Set up a new Expr object.
117
118 Args:
Simon Glass969fd332022-07-11 19:04:05 -0600119 expr (str): String cotaining regular expression to store
Simon Glassc52bd222022-07-11 19:04:03 -0600120 """
121 self._expr = expr
122 self._re = re.compile(expr)
123
124 def matches(self, props):
125 """Check if any of the properties match the regular expression.
126
127 Args:
Simon Glass969fd332022-07-11 19:04:05 -0600128 props (list of str): List of properties to check
Simon Glassc52bd222022-07-11 19:04:03 -0600129 Returns:
130 True if any of the properties match the regular expression
131 """
132 for prop in props:
133 if self._re.match(prop):
134 return True
135 return False
136
137 def __str__(self):
138 return self._expr
139
140class Term:
141 """A list of expressions each of which must match with properties.
142
143 This provides a list of 'AND' expressions, meaning that each must
144 match the board properties for that board to be built.
145 """
146 def __init__(self):
147 self._expr_list = []
148 self._board_count = 0
149
150 def add_expr(self, expr):
151 """Add an Expr object to the list to check.
152
153 Args:
Simon Glass969fd332022-07-11 19:04:05 -0600154 expr (Expr): New Expr object to add to the list of those that must
Simon Glassc52bd222022-07-11 19:04:03 -0600155 match for a board to be built.
156 """
157 self._expr_list.append(Expr(expr))
158
159 def __str__(self):
160 """Return some sort of useful string describing the term"""
161 return '&'.join([str(expr) for expr in self._expr_list])
162
163 def matches(self, props):
164 """Check if any of the properties match this term
165
166 Each of the expressions in the term is checked. All must match.
167
168 Args:
Simon Glass969fd332022-07-11 19:04:05 -0600169 props (list of str): List of properties to check
Simon Glassc52bd222022-07-11 19:04:03 -0600170 Returns:
171 True if all of the expressions in the Term match, else False
172 """
173 for expr in self._expr_list:
174 if not expr.matches(props):
175 return False
176 return True
177
178
Simon Glassa8a01412022-07-11 19:04:04 -0600179class KconfigScanner:
180
181 """Kconfig scanner."""
182
183 ### constant variable only used in this class ###
184 _SYMBOL_TABLE = {
185 'arch' : 'SYS_ARCH',
186 'cpu' : 'SYS_CPU',
187 'soc' : 'SYS_SOC',
188 'vendor' : 'SYS_VENDOR',
189 'board' : 'SYS_BOARD',
190 'config' : 'SYS_CONFIG_NAME',
Simon Glass256126c2022-07-11 19:04:06 -0600191 # 'target' is added later
Simon Glassa8a01412022-07-11 19:04:04 -0600192 }
193
194 def __init__(self):
195 """Scan all the Kconfig files and create a Kconfig object."""
196 # Define environment variables referenced from Kconfig
197 os.environ['srctree'] = os.getcwd()
198 os.environ['UBOOTVERSION'] = 'dummy'
199 os.environ['KCONFIG_OBJDIR'] = ''
Simon Glass969fd332022-07-11 19:04:05 -0600200 self._tmpfile = None
Simon Glassa8a01412022-07-11 19:04:04 -0600201 self._conf = kconfiglib.Kconfig(warn=False)
202
203 def __del__(self):
204 """Delete a leftover temporary file before exit.
205
206 The scan() method of this class creates a temporay file and deletes
207 it on success. If scan() method throws an exception on the way,
208 the temporary file might be left over. In that case, it should be
209 deleted in this destructor.
210 """
Simon Glass969fd332022-07-11 19:04:05 -0600211 if self._tmpfile:
Simon Glassa8a01412022-07-11 19:04:04 -0600212 try_remove(self._tmpfile)
213
214 def scan(self, defconfig):
215 """Load a defconfig file to obtain board parameters.
216
Simon Glass969fd332022-07-11 19:04:05 -0600217 Args:
218 defconfig (str): path to the defconfig file to be processed
Simon Glassa8a01412022-07-11 19:04:04 -0600219
220 Returns:
Simon Glass256126c2022-07-11 19:04:06 -0600221 A dictionary of board parameters. It has a form of:
Simon Glass969fd332022-07-11 19:04:05 -0600222 {
223 'arch': <arch_name>,
224 'cpu': <cpu_name>,
225 'soc': <soc_name>,
226 'vendor': <vendor_name>,
227 'board': <board_name>,
228 'target': <target_name>,
229 'config': <config_header_name>,
Simon Glass969fd332022-07-11 19:04:05 -0600230 }
Simon Glassa8a01412022-07-11 19:04:04 -0600231 """
232 # strip special prefixes and save it in a temporary file
Simon Glass969fd332022-07-11 19:04:05 -0600233 outfd, self._tmpfile = tempfile.mkstemp()
234 with os.fdopen(outfd, 'w') as outf:
235 with open(defconfig, encoding='utf-8') as inf:
236 for line in inf:
237 colon = line.find(':CONFIG_')
238 if colon == -1:
239 outf.write(line)
240 else:
241 outf.write(line[colon + 1:])
Simon Glassa8a01412022-07-11 19:04:04 -0600242
243 self._conf.load_config(self._tmpfile)
244 try_remove(self._tmpfile)
245 self._tmpfile = None
246
247 params = {}
248
249 # Get the value of CONFIG_SYS_ARCH, CONFIG_SYS_CPU, ... etc.
250 # Set '-' if the value is empty.
251 for key, symbol in list(self._SYMBOL_TABLE.items()):
252 value = self._conf.syms.get(symbol).str_value
253 if value:
254 params[key] = value
255 else:
256 params[key] = '-'
257
258 defconfig = os.path.basename(defconfig)
259 params['target'], match, rear = defconfig.partition('_defconfig')
Simon Glass969fd332022-07-11 19:04:05 -0600260 assert match and not rear, f'{defconfig} : invalid defconfig'
Simon Glassa8a01412022-07-11 19:04:04 -0600261
262 # fix-up for aarch64
263 if params['arch'] == 'arm' and params['cpu'] == 'armv8':
264 params['arch'] = 'aarch64'
265
Simon Glassa8a01412022-07-11 19:04:04 -0600266 return params
267
268
269class MaintainersDatabase:
270
Simon Glass969fd332022-07-11 19:04:05 -0600271 """The database of board status and maintainers.
272
273 Properties:
274 database: dict:
275 key: Board-target name (e.g. 'snow')
276 value: tuple:
277 str: Board status (e.g. 'Active')
278 str: List of maintainers, separated by :
279 """
Simon Glassa8a01412022-07-11 19:04:04 -0600280
281 def __init__(self):
282 """Create an empty database."""
283 self.database = {}
284
285 def get_status(self, target):
286 """Return the status of the given board.
287
288 The board status is generally either 'Active' or 'Orphan'.
289 Display a warning message and return '-' if status information
290 is not found.
291
Simon Glass969fd332022-07-11 19:04:05 -0600292 Args:
293 target (str): Build-target name
294
Simon Glassa8a01412022-07-11 19:04:04 -0600295 Returns:
Simon Glass969fd332022-07-11 19:04:05 -0600296 str: 'Active', 'Orphan' or '-'.
Simon Glassa8a01412022-07-11 19:04:04 -0600297 """
298 if not target in self.database:
Simon Glass969fd332022-07-11 19:04:05 -0600299 print(f"WARNING: no status info for '{target}'", file=sys.stderr)
Simon Glassa8a01412022-07-11 19:04:04 -0600300 return '-'
301
302 tmp = self.database[target][0]
303 if tmp.startswith('Maintained'):
304 return 'Active'
Simon Glass969fd332022-07-11 19:04:05 -0600305 if tmp.startswith('Supported'):
Simon Glassa8a01412022-07-11 19:04:04 -0600306 return 'Active'
Simon Glass969fd332022-07-11 19:04:05 -0600307 if tmp.startswith('Orphan'):
Simon Glassa8a01412022-07-11 19:04:04 -0600308 return 'Orphan'
Simon Glass969fd332022-07-11 19:04:05 -0600309 print(f"WARNING: {tmp}: unknown status for '{target}'", file=sys.stderr)
310 return '-'
Simon Glassa8a01412022-07-11 19:04:04 -0600311
312 def get_maintainers(self, target):
313 """Return the maintainers of the given board.
314
Simon Glass969fd332022-07-11 19:04:05 -0600315 Args:
316 target (str): Build-target name
317
Simon Glassa8a01412022-07-11 19:04:04 -0600318 Returns:
Simon Glass969fd332022-07-11 19:04:05 -0600319 str: Maintainers of the board. If the board has two or more
320 maintainers, they are separated with colons.
Simon Glassa8a01412022-07-11 19:04:04 -0600321 """
322 if not target in self.database:
Simon Glass969fd332022-07-11 19:04:05 -0600323 print(f"WARNING: no maintainers for '{target}'", file=sys.stderr)
Simon Glassa8a01412022-07-11 19:04:04 -0600324 return ''
325
326 return ':'.join(self.database[target][1])
327
Simon Glass969fd332022-07-11 19:04:05 -0600328 def parse_file(self, fname):
Simon Glassa8a01412022-07-11 19:04:04 -0600329 """Parse a MAINTAINERS file.
330
Simon Glass969fd332022-07-11 19:04:05 -0600331 Parse a MAINTAINERS file and accumulate board status and maintainers
332 information in the self.database dict.
Simon Glassa8a01412022-07-11 19:04:04 -0600333
Simon Glass969fd332022-07-11 19:04:05 -0600334 Args:
335 fname (str): MAINTAINERS file to be parsed
Simon Glassa8a01412022-07-11 19:04:04 -0600336 """
337 targets = []
338 maintainers = []
339 status = '-'
Simon Glass969fd332022-07-11 19:04:05 -0600340 with open(fname, encoding="utf-8") as inf:
341 for line in inf:
342 # Check also commented maintainers
343 if line[:3] == '#M:':
344 line = line[1:]
345 tag, rest = line[:2], line[2:].strip()
346 if tag == 'M:':
347 maintainers.append(rest)
348 elif tag == 'F:':
349 # expand wildcard and filter by 'configs/*_defconfig'
350 for item in glob.glob(rest):
351 front, match, rear = item.partition('configs/')
352 if not front and match:
353 front, match, rear = rear.rpartition('_defconfig')
354 if match and not rear:
355 targets.append(front)
356 elif tag == 'S:':
357 status = rest
358 elif line == '\n':
359 for target in targets:
360 self.database[target] = (status, maintainers)
361 targets = []
362 maintainers = []
363 status = '-'
Simon Glassa8a01412022-07-11 19:04:04 -0600364 if targets:
365 for target in targets:
366 self.database[target] = (status, maintainers)
367
368
Simon Glassc52bd222022-07-11 19:04:03 -0600369class Boards:
370 """Manage a list of boards."""
371 def __init__(self):
372 # Use a simple list here, sinc OrderedDict requires Python 2.7
373 self._boards = []
374
375 def add_board(self, brd):
376 """Add a new board to the list.
377
378 The board's target member must not already exist in the board list.
379
380 Args:
Simon Glass969fd332022-07-11 19:04:05 -0600381 brd (Board): board to add
Simon Glassc52bd222022-07-11 19:04:03 -0600382 """
383 self._boards.append(brd)
384
385 def read_boards(self, fname):
386 """Read a list of boards from a board file.
387
388 Create a Board object for each and add it to our _boards list.
389
390 Args:
Simon Glass969fd332022-07-11 19:04:05 -0600391 fname (str): Filename of boards.cfg file
Simon Glassc52bd222022-07-11 19:04:03 -0600392 """
393 with open(fname, 'r', encoding='utf-8') as inf:
394 for line in inf:
395 if line[0] == '#':
396 continue
397 fields = line.split()
398 if not fields:
399 continue
400 for upto, field in enumerate(fields):
401 if field == '-':
402 fields[upto] = ''
403 while len(fields) < 8:
404 fields.append('')
405 if len(fields) > 8:
406 fields = fields[:8]
407
408 brd = board.Board(*fields)
409 self.add_board(brd)
410
411
412 def get_list(self):
413 """Return a list of available boards.
414
415 Returns:
416 List of Board objects
417 """
418 return self._boards
419
420 def get_dict(self):
421 """Build a dictionary containing all the boards.
422
423 Returns:
424 Dictionary:
425 key is board.target
426 value is board
427 """
428 board_dict = OrderedDict()
429 for brd in self._boards:
430 board_dict[brd.target] = brd
431 return board_dict
432
433 def get_selected_dict(self):
434 """Return a dictionary containing the selected boards
435
436 Returns:
437 List of Board objects that are marked selected
438 """
439 board_dict = OrderedDict()
440 for brd in self._boards:
441 if brd.build_it:
442 board_dict[brd.target] = brd
443 return board_dict
444
445 def get_selected(self):
446 """Return a list of selected boards
447
448 Returns:
449 List of Board objects that are marked selected
450 """
451 return [brd for brd in self._boards if brd.build_it]
452
453 def get_selected_names(self):
454 """Return a list of selected boards
455
456 Returns:
457 List of board names that are marked selected
458 """
459 return [brd.target for brd in self._boards if brd.build_it]
460
461 @classmethod
462 def _build_terms(cls, args):
463 """Convert command line arguments to a list of terms.
464
465 This deals with parsing of the arguments. It handles the '&'
466 operator, which joins several expressions into a single Term.
467
468 For example:
469 ['arm & freescale sandbox', 'tegra']
470
471 will produce 3 Terms containing expressions as follows:
472 arm, freescale
473 sandbox
474 tegra
475
476 The first Term has two expressions, both of which must match for
477 a board to be selected.
478
479 Args:
Simon Glass969fd332022-07-11 19:04:05 -0600480 args (list of str): List of command line arguments
481
Simon Glassc52bd222022-07-11 19:04:03 -0600482 Returns:
Simon Glass969fd332022-07-11 19:04:05 -0600483 list of Term: A list of Term objects
Simon Glassc52bd222022-07-11 19:04:03 -0600484 """
485 syms = []
486 for arg in args:
487 for word in arg.split():
488 sym_build = []
489 for term in word.split('&'):
490 if term:
491 sym_build.append(term)
492 sym_build.append('&')
493 syms += sym_build[:-1]
494 terms = []
495 term = None
496 oper = None
497 for sym in syms:
498 if sym == '&':
499 oper = sym
500 elif oper:
501 term.add_expr(sym)
502 oper = None
503 else:
504 if term:
505 terms.append(term)
506 term = Term()
507 term.add_expr(sym)
508 if term:
509 terms.append(term)
510 return terms
511
512 def select_boards(self, args, exclude=None, brds=None):
513 """Mark boards selected based on args
514
515 Normally either boards (an explicit list of boards) or args (a list of
516 terms to match against) is used. It is possible to specify both, in
517 which case they are additive.
518
519 If brds and args are both empty, all boards are selected.
520
521 Args:
Simon Glass969fd332022-07-11 19:04:05 -0600522 args (list of str): List of strings specifying boards to include,
523 either named, or by their target, architecture, cpu, vendor or
524 soc. If empty, all boards are selected.
525 exclude (list of str): List of boards to exclude, regardless of
526 'args', or None for none
527 brds (list of Board): List of boards to build, or None/[] for all
Simon Glassc52bd222022-07-11 19:04:03 -0600528
529 Returns:
530 Tuple
531 Dictionary which holds the list of boards which were selected
532 due to each argument, arranged by argument.
533 List of errors found
534 """
Simon Glass969fd332022-07-11 19:04:05 -0600535 def _check_board(brd):
536 """Check whether to include or exclude a board
Simon Glassc52bd222022-07-11 19:04:03 -0600537
Simon Glass969fd332022-07-11 19:04:05 -0600538 Checks the various terms and decide whether to build it or not (the
539 'build_it' variable).
Simon Glassc52bd222022-07-11 19:04:03 -0600540
Simon Glass969fd332022-07-11 19:04:05 -0600541 If it is built, add the board to the result[term] list so we know
542 which term caused it to be built. Add it to result['all'] also.
Simon Glassc52bd222022-07-11 19:04:03 -0600543
Simon Glass969fd332022-07-11 19:04:05 -0600544 Keep a list of boards we found in 'found', so we can report boards
545 which appear in self._boards but not in brds.
546
547 Args:
548 brd (Board): Board to check
549 """
Simon Glassc52bd222022-07-11 19:04:03 -0600550 matching_term = None
551 build_it = False
552 if terms:
553 for term in terms:
554 if term.matches(brd.props):
555 matching_term = str(term)
556 build_it = True
557 break
558 elif brds:
559 if brd.target in brds:
560 build_it = True
561 found.append(brd.target)
562 else:
563 build_it = True
564
565 # Check that it is not specifically excluded
566 for expr in exclude_list:
567 if expr.matches(brd.props):
568 build_it = False
569 break
570
571 if build_it:
572 brd.build_it = True
573 if matching_term:
574 result[matching_term].append(brd.target)
575 result['all'].append(brd.target)
576
Simon Glass969fd332022-07-11 19:04:05 -0600577 result = OrderedDict()
578 warnings = []
579 terms = self._build_terms(args)
580
581 result['all'] = []
582 for term in terms:
583 result[str(term)] = []
584
585 exclude_list = []
586 if exclude:
587 for expr in exclude:
588 exclude_list.append(Expr(expr))
589
590 found = []
591 for brd in self._boards:
592 _check_board(brd)
593
Simon Glassc52bd222022-07-11 19:04:03 -0600594 if brds:
595 remaining = set(brds) - set(found)
596 if remaining:
597 warnings.append(f"Boards not found: {', '.join(remaining)}\n")
598
599 return result, warnings
Simon Glassa8a01412022-07-11 19:04:04 -0600600
Simon Glass969fd332022-07-11 19:04:05 -0600601 @classmethod
602 def scan_defconfigs_for_multiprocess(cls, queue, defconfigs):
Simon Glassa8a01412022-07-11 19:04:04 -0600603 """Scan defconfig files and queue their board parameters
604
Simon Glass969fd332022-07-11 19:04:05 -0600605 This function is intended to be passed to multiprocessing.Process()
606 constructor.
Simon Glassa8a01412022-07-11 19:04:04 -0600607
Simon Glass969fd332022-07-11 19:04:05 -0600608 Args:
609 queue (multiprocessing.Queue): The resulting board parameters are
610 written into this.
611 defconfigs (sequence of str): A sequence of defconfig files to be
612 scanned.
Simon Glassa8a01412022-07-11 19:04:04 -0600613 """
614 kconf_scanner = KconfigScanner()
615 for defconfig in defconfigs:
616 queue.put(kconf_scanner.scan(defconfig))
617
Simon Glass969fd332022-07-11 19:04:05 -0600618 @classmethod
619 def read_queues(cls, queues, params_list):
Simon Glassa8a01412022-07-11 19:04:04 -0600620 """Read the queues and append the data to the paramers list"""
Simon Glass969fd332022-07-11 19:04:05 -0600621 for que in queues:
622 while not que.empty():
623 params_list.append(que.get())
Simon Glassa8a01412022-07-11 19:04:04 -0600624
625 def scan_defconfigs(self, jobs=1):
626 """Collect board parameters for all defconfig files.
627
628 This function invokes multiple processes for faster processing.
629
Simon Glass969fd332022-07-11 19:04:05 -0600630 Args:
631 jobs (int): The number of jobs to run simultaneously
Simon Glassa8a01412022-07-11 19:04:04 -0600632 """
633 all_defconfigs = []
Simon Glass969fd332022-07-11 19:04:05 -0600634 for (dirpath, _, filenames) in os.walk(CONFIG_DIR):
Simon Glassa8a01412022-07-11 19:04:04 -0600635 for filename in fnmatch.filter(filenames, '*_defconfig'):
636 if fnmatch.fnmatch(filename, '.*'):
637 continue
638 all_defconfigs.append(os.path.join(dirpath, filename))
639
640 total_boards = len(all_defconfigs)
641 processes = []
642 queues = []
643 for i in range(jobs):
644 defconfigs = all_defconfigs[total_boards * i // jobs :
645 total_boards * (i + 1) // jobs]
Simon Glass969fd332022-07-11 19:04:05 -0600646 que = multiprocessing.Queue(maxsize=-1)
647 proc = multiprocessing.Process(
Simon Glassa8a01412022-07-11 19:04:04 -0600648 target=self.scan_defconfigs_for_multiprocess,
Simon Glass969fd332022-07-11 19:04:05 -0600649 args=(que, defconfigs))
650 proc.start()
651 processes.append(proc)
652 queues.append(que)
Simon Glassa8a01412022-07-11 19:04:04 -0600653
654 # The resulting data should be accumulated to this list
655 params_list = []
656
657 # Data in the queues should be retrieved preriodically.
658 # Otherwise, the queues would become full and subprocesses would get stuck.
Simon Glass969fd332022-07-11 19:04:05 -0600659 while any(p.is_alive() for p in processes):
Simon Glassa8a01412022-07-11 19:04:04 -0600660 self.read_queues(queues, params_list)
661 # sleep for a while until the queues are filled
662 time.sleep(SLEEP_TIME)
663
664 # Joining subprocesses just in case
665 # (All subprocesses should already have been finished)
Simon Glass969fd332022-07-11 19:04:05 -0600666 for proc in processes:
667 proc.join()
Simon Glassa8a01412022-07-11 19:04:04 -0600668
669 # retrieve leftover data
670 self.read_queues(queues, params_list)
671
672 return params_list
673
Simon Glass969fd332022-07-11 19:04:05 -0600674 @classmethod
675 def insert_maintainers_info(cls, params_list):
Simon Glassa8a01412022-07-11 19:04:04 -0600676 """Add Status and Maintainers information to the board parameters list.
677
Simon Glass969fd332022-07-11 19:04:05 -0600678 Args:
679 params_list (list of dict): A list of the board parameters
Simon Glassa8a01412022-07-11 19:04:04 -0600680 """
681 database = MaintainersDatabase()
Simon Glass969fd332022-07-11 19:04:05 -0600682 for (dirpath, _, filenames) in os.walk('.'):
Simon Glassa8a01412022-07-11 19:04:04 -0600683 if 'MAINTAINERS' in filenames:
684 database.parse_file(os.path.join(dirpath, 'MAINTAINERS'))
685
686 for i, params in enumerate(params_list):
687 target = params['target']
688 params['status'] = database.get_status(target)
689 params['maintainers'] = database.get_maintainers(target)
690 params_list[i] = params
691
Simon Glass969fd332022-07-11 19:04:05 -0600692 @classmethod
693 def format_and_output(cls, params_list, output):
Simon Glassa8a01412022-07-11 19:04:04 -0600694 """Write board parameters into a file.
695
696 Columnate the board parameters, sort lines alphabetically,
697 and then write them to a file.
698
Simon Glass969fd332022-07-11 19:04:05 -0600699 Args:
700 params_list (list of dict): The list of board parameters
701 output (str): The path to the output file
Simon Glassa8a01412022-07-11 19:04:04 -0600702 """
Simon Glass969fd332022-07-11 19:04:05 -0600703 fields = ('status', 'arch', 'cpu', 'soc', 'vendor', 'board', 'target',
Simon Glass256126c2022-07-11 19:04:06 -0600704 'config', 'maintainers')
Simon Glassa8a01412022-07-11 19:04:04 -0600705
706 # First, decide the width of each column
Simon Glass969fd332022-07-11 19:04:05 -0600707 max_length = {f: 0 for f in fields}
Simon Glassa8a01412022-07-11 19:04:04 -0600708 for params in params_list:
Simon Glass969fd332022-07-11 19:04:05 -0600709 for field in fields:
710 max_length[field] = max(max_length[field], len(params[field]))
Simon Glassa8a01412022-07-11 19:04:04 -0600711
712 output_lines = []
713 for params in params_list:
714 line = ''
Simon Glass969fd332022-07-11 19:04:05 -0600715 for field in fields:
Simon Glassa8a01412022-07-11 19:04:04 -0600716 # insert two spaces between fields like column -t would
Simon Glass969fd332022-07-11 19:04:05 -0600717 line += ' ' + params[field].ljust(max_length[field])
Simon Glassa8a01412022-07-11 19:04:04 -0600718 output_lines.append(line.strip())
719
720 # ignore case when sorting
721 output_lines.sort(key=str.lower)
722
Simon Glass969fd332022-07-11 19:04:05 -0600723 with open(output, 'w', encoding="utf-8") as outf:
724 outf.write(COMMENT_BLOCK + '\n'.join(output_lines) + '\n')
Simon Glassa8a01412022-07-11 19:04:04 -0600725
726 def ensure_board_list(self, output, jobs=1, force=False, quiet=False):
727 """Generate a board database file if needed.
728
Simon Glass969fd332022-07-11 19:04:05 -0600729 Args:
730 output (str): The name of the output file
731 jobs (int): The number of jobs to run simultaneously
732 force (bool): Force to generate the output even if it is new
733 quiet (bool): True to avoid printing a message if nothing needs doing
Simon Glassa8a01412022-07-11 19:04:04 -0600734 """
735 if not force and output_is_new(output):
736 if not quiet:
Simon Glass969fd332022-07-11 19:04:05 -0600737 print(f'{output} is up to date. Nothing to do.')
Simon Glassa8a01412022-07-11 19:04:04 -0600738 return
739 params_list = self.scan_defconfigs(jobs)
740 self.insert_maintainers_info(params_list)
741 self.format_and_output(params_list, output)