| # SPDX-License-Identifier: GPL-2.0+ | |
| # Copyright (c) 2012 The Chromium OS Authors. | |
| # Author: Simon Glass <sjg@chromium.org> | |
| # Author: Masahiro Yamada <yamada.m@jp.panasonic.com> | |
| """Maintains a list of boards and allows them to be selected""" | |
| from collections import OrderedDict | |
| import errno | |
| import fnmatch | |
| import glob | |
| import multiprocessing | |
| import os | |
| import re | |
| import sys | |
| import tempfile | |
| import time | |
| from buildman import board | |
| from buildman import kconfiglib | |
| from u_boot_pylib.terminal import print_clear, tprint | |
| ### constant variables ### | |
| OUTPUT_FILE = 'boards.cfg' | |
| CONFIG_DIR = 'configs' | |
| SLEEP_TIME = 0.03 | |
| COMMENT_BLOCK = f'''# | |
| # List of boards | |
| # Automatically generated by {__file__}: don't edit | |
| # | |
| # Status, Arch, CPU, SoC, Vendor, Board, Target, Config, Maintainers | |
| ''' | |
| def try_remove(fname): | |
| """Remove a file ignoring 'No such file or directory' error. | |
| Args: | |
| fname (str): Filename to remove | |
| Raises: | |
| OSError: output file exists but could not be removed | |
| """ | |
| try: | |
| os.remove(fname) | |
| except OSError as exception: | |
| # Ignore 'No such file or directory' error | |
| if exception.errno != errno.ENOENT: | |
| raise | |
| def output_is_new(output, config_dir, srcdir): | |
| """Check if the output file is up to date. | |
| Looks at defconfig and Kconfig files to make sure none is newer than the | |
| output file. Also ensures that the boards.cfg does not mention any removed | |
| boards. | |
| Args: | |
| output (str): Filename to check | |
| config_dir (str): Directory containing defconfig files | |
| srcdir (str): Directory containing Kconfig and MAINTAINERS files | |
| Returns: | |
| True if the given output file exists and is newer than any of | |
| *_defconfig, MAINTAINERS and Kconfig*. False otherwise. | |
| Raises: | |
| OSError: output file exists but could not be opened | |
| """ | |
| # pylint: disable=too-many-branches | |
| try: | |
| ctime = os.path.getctime(output) | |
| except OSError as exception: | |
| if exception.errno == errno.ENOENT: | |
| # return False on 'No such file or directory' error | |
| return False | |
| raise | |
| for (dirpath, _, filenames) in os.walk(config_dir): | |
| for filename in fnmatch.filter(filenames, '*_defconfig'): | |
| if fnmatch.fnmatch(filename, '.*'): | |
| continue | |
| filepath = os.path.join(dirpath, filename) | |
| if ctime < os.path.getctime(filepath): | |
| return False | |
| for (dirpath, _, filenames) in os.walk(srcdir): | |
| for filename in filenames: | |
| if (fnmatch.fnmatch(filename, '*~') or | |
| not fnmatch.fnmatch(filename, 'Kconfig*') and | |
| not filename == 'MAINTAINERS'): | |
| continue | |
| filepath = os.path.join(dirpath, filename) | |
| if ctime < os.path.getctime(filepath): | |
| return False | |
| # Detect a board that has been removed since the current board database | |
| # was generated | |
| with open(output, encoding="utf-8") as inf: | |
| for line in inf: | |
| if 'Options,' in line: | |
| return False | |
| if line[0] == '#' or line == '\n': | |
| continue | |
| defconfig = line.split()[6] + '_defconfig' | |
| if not os.path.exists(os.path.join(config_dir, defconfig)): | |
| return False | |
| return True | |
| class Expr: | |
| """A single regular expression for matching boards to build""" | |
| def __init__(self, expr): | |
| """Set up a new Expr object. | |
| Args: | |
| expr (str): String containing regular expression to store | |
| """ | |
| self._expr = expr | |
| self._re = re.compile(expr) | |
| def matches(self, props): | |
| """Check if any of the properties match the regular expression. | |
| Args: | |
| props (list of str): List of properties to check | |
| Returns: | |
| True if any of the properties match the regular expression | |
| """ | |
| for prop in props: | |
| if self._re.match(prop): | |
| return True | |
| return False | |
| def __str__(self): | |
| return self._expr | |
| class Term: | |
| """A list of expressions each of which must match with properties. | |
| This provides a list of 'AND' expressions, meaning that each must | |
| match the board properties for that board to be built. | |
| """ | |
| def __init__(self): | |
| self._expr_list = [] | |
| self._board_count = 0 | |
| def add_expr(self, expr): | |
| """Add an Expr object to the list to check. | |
| Args: | |
| expr (Expr): New Expr object to add to the list of those that must | |
| match for a board to be built. | |
| """ | |
| self._expr_list.append(Expr(expr)) | |
| def __str__(self): | |
| """Return some sort of useful string describing the term""" | |
| return '&'.join([str(expr) for expr in self._expr_list]) | |
| def matches(self, props): | |
| """Check if any of the properties match this term | |
| Each of the expressions in the term is checked. All must match. | |
| Args: | |
| props (list of str): List of properties to check | |
| Returns: | |
| True if all of the expressions in the Term match, else False | |
| """ | |
| for expr in self._expr_list: | |
| if not expr.matches(props): | |
| return False | |
| return True | |
| class KconfigScanner: | |
| """Kconfig scanner.""" | |
| ### constant variable only used in this class ### | |
| _SYMBOL_TABLE = { | |
| 'arch' : 'SYS_ARCH', | |
| 'cpu' : 'SYS_CPU', | |
| 'soc' : 'SYS_SOC', | |
| 'vendor' : 'SYS_VENDOR', | |
| 'board' : 'SYS_BOARD', | |
| 'config' : 'SYS_CONFIG_NAME', | |
| # 'target' is added later | |
| } | |
| def __init__(self, srctree): | |
| """Scan all the Kconfig files and create a Kconfig object.""" | |
| # Define environment variables referenced from Kconfig | |
| os.environ['srctree'] = srctree | |
| os.environ['UBOOTVERSION'] = 'dummy' | |
| os.environ['KCONFIG_OBJDIR'] = '' | |
| self._tmpfile = None | |
| self._conf = kconfiglib.Kconfig(warn=False) | |
| def __del__(self): | |
| """Delete a leftover temporary file before exit. | |
| The scan() method of this class creates a temporay file and deletes | |
| it on success. If scan() method throws an exception on the way, | |
| the temporary file might be left over. In that case, it should be | |
| deleted in this destructor. | |
| """ | |
| if self._tmpfile: | |
| try_remove(self._tmpfile) | |
| def scan(self, defconfig, warn_targets): | |
| """Load a defconfig file to obtain board parameters. | |
| Args: | |
| defconfig (str): path to the defconfig file to be processed | |
| warn_targets (bool): True to warn about missing or duplicate | |
| CONFIG_TARGET options | |
| Returns: | |
| tuple: dictionary of board parameters. It has a form of: | |
| { | |
| 'arch': <arch_name>, | |
| 'cpu': <cpu_name>, | |
| 'soc': <soc_name>, | |
| 'vendor': <vendor_name>, | |
| 'board': <board_name>, | |
| 'target': <target_name>, | |
| 'config': <config_header_name>, | |
| } | |
| warnings (list of str): list of warnings found | |
| """ | |
| leaf = os.path.basename(defconfig) | |
| expect_target, match, rear = leaf.partition('_defconfig') | |
| assert match and not rear, f'{leaf} : invalid defconfig' | |
| self._conf.load_config(defconfig) | |
| self._tmpfile = None | |
| params = {} | |
| warnings = [] | |
| # Get the value of CONFIG_SYS_ARCH, CONFIG_SYS_CPU, ... etc. | |
| # Set '-' if the value is empty. | |
| for key, symbol in list(self._SYMBOL_TABLE.items()): | |
| value = self._conf.syms.get(symbol).str_value | |
| if value: | |
| params[key] = value | |
| else: | |
| params[key] = '-' | |
| # Check there is exactly one TARGET_xxx set | |
| if warn_targets: | |
| target = None | |
| for name, sym in self._conf.syms.items(): | |
| if name.startswith('TARGET_') and sym.str_value == 'y': | |
| tname = name[7:].lower() | |
| if target: | |
| warnings.append( | |
| f'WARNING: {leaf}: Duplicate TARGET_xxx: {target} and {tname}') | |
| else: | |
| target = tname | |
| if not target: | |
| cfg_name = expect_target.replace('-', '_').upper() | |
| warnings.append(f'WARNING: {leaf}: No TARGET_{cfg_name} enabled') | |
| params['target'] = expect_target | |
| # fix-up for aarch64 | |
| if params['arch'] == 'arm' and params['cpu'] == 'armv8': | |
| params['arch'] = 'aarch64' | |
| # fix-up for riscv | |
| if params['arch'] == 'riscv': | |
| try: | |
| value = self._conf.syms.get('ARCH_RV32I').str_value | |
| except: | |
| value = '' | |
| if value == 'y': | |
| params['arch'] = 'riscv32' | |
| else: | |
| params['arch'] = 'riscv64' | |
| return params, warnings | |
| class MaintainersDatabase: | |
| """The database of board status and maintainers. | |
| Properties: | |
| database: dict: | |
| key: Board-target name (e.g. 'snow') | |
| value: tuple: | |
| str: Board status (e.g. 'Active') | |
| str: List of maintainers, separated by : | |
| warnings (list of str): List of warnings due to missing status, etc. | |
| """ | |
| def __init__(self): | |
| """Create an empty database.""" | |
| self.database = {} | |
| self.warnings = [] | |
| def get_status(self, target): | |
| """Return the status of the given board. | |
| The board status is generally either 'Active' or 'Orphan'. | |
| Display a warning message and return '-' if status information | |
| is not found. | |
| Args: | |
| target (str): Build-target name | |
| Returns: | |
| str: 'Active', 'Orphan' or '-'. | |
| """ | |
| if not target in self.database: | |
| self.warnings.append(f"WARNING: no status info for '{target}'") | |
| return '-' | |
| tmp = self.database[target][0] | |
| if tmp.startswith('Maintained'): | |
| return 'Active' | |
| if tmp.startswith('Supported'): | |
| return 'Active' | |
| if tmp.startswith('Orphan'): | |
| return 'Orphan' | |
| self.warnings.append(f"WARNING: {tmp}: unknown status for '{target}'") | |
| return '-' | |
| def get_maintainers(self, target): | |
| """Return the maintainers of the given board. | |
| Args: | |
| target (str): Build-target name | |
| Returns: | |
| str: Maintainers of the board. If the board has two or more | |
| maintainers, they are separated with colons. | |
| """ | |
| entry = self.database.get(target) | |
| if entry: | |
| status, maint_list = entry | |
| if not status.startswith('Orphan'): | |
| if len(maint_list) > 1 or (maint_list and maint_list[0] != '-'): | |
| return ':'.join(maint_list) | |
| self.warnings.append(f"WARNING: no maintainers for '{target}'") | |
| return '' | |
| def parse_file(self, srcdir, fname): | |
| """Parse a MAINTAINERS file. | |
| Parse a MAINTAINERS file and accumulate board status and maintainers | |
| information in the self.database dict. | |
| defconfig files are used to specify the target, e.g. xxx_defconfig is | |
| used for target 'xxx'. If there is no defconfig file mentioned in the | |
| MAINTAINERS file F: entries, then this function does nothing. | |
| The N: name entries can be used to specify a defconfig file using | |
| wildcards. | |
| Args: | |
| srcdir (str): Directory containing source code (Kconfig files) | |
| fname (str): MAINTAINERS file to be parsed | |
| """ | |
| def add_targets(linenum): | |
| """Add any new targets | |
| Args: | |
| linenum (int): Current line number | |
| """ | |
| if targets: | |
| for target in targets: | |
| self.database[target] = (status, maintainers) | |
| targets = [] | |
| maintainers = [] | |
| status = '-' | |
| with open(fname, encoding="utf-8") as inf: | |
| for linenum, line in enumerate(inf): | |
| # Check also commented maintainers | |
| if line[:3] == '#M:': | |
| line = line[1:] | |
| tag, rest = line[:2], line[2:].strip() | |
| if tag == 'M:': | |
| maintainers.append(rest) | |
| elif tag == 'F:': | |
| # expand wildcard and filter by 'configs/*_defconfig' | |
| glob_path = os.path.join(srcdir, rest) | |
| for item in glob.glob(glob_path): | |
| front, match, rear = item.partition('configs/') | |
| if front.endswith('/'): | |
| front = front[:-1] | |
| if front == srcdir and match: | |
| front, match, rear = rear.rpartition('_defconfig') | |
| if match and not rear: | |
| targets.append(front) | |
| elif tag == 'S:': | |
| status = rest | |
| elif tag == 'N:': | |
| # Just scan the configs directory since that's all we care | |
| # about | |
| walk_path = os.walk(os.path.join(srcdir, 'configs')) | |
| for dirpath, _, fnames in walk_path: | |
| for cfg in fnames: | |
| path = os.path.join(dirpath, cfg)[len(srcdir) + 1:] | |
| front, match, rear = path.partition('configs/') | |
| if front or not match: | |
| continue | |
| front, match, rear = rear.rpartition('_defconfig') | |
| # Use this entry if it matches the defconfig file | |
| # without the _defconfig suffix. For example | |
| # 'am335x.*' matches am335x_guardian_defconfig | |
| if match and not rear and re.search(rest, front): | |
| targets.append(front) | |
| elif line == '\n': | |
| add_targets(linenum) | |
| targets = [] | |
| maintainers = [] | |
| status = '-' | |
| add_targets(linenum) | |
| class Boards: | |
| """Manage a list of boards.""" | |
| def __init__(self): | |
| self._boards = [] | |
| def add_board(self, brd): | |
| """Add a new board to the list. | |
| The board's target member must not already exist in the board list. | |
| Args: | |
| brd (Board): board to add | |
| """ | |
| self._boards.append(brd) | |
| def read_boards(self, fname): | |
| """Read a list of boards from a board file. | |
| Create a Board object for each and add it to our _boards list. | |
| Args: | |
| fname (str): Filename of boards.cfg file | |
| """ | |
| with open(fname, 'r', encoding='utf-8') as inf: | |
| for line in inf: | |
| if line[0] == '#': | |
| continue | |
| fields = line.split() | |
| if not fields: | |
| continue | |
| for upto, field in enumerate(fields): | |
| if field == '-': | |
| fields[upto] = '' | |
| while len(fields) < 8: | |
| fields.append('') | |
| if len(fields) > 8: | |
| fields = fields[:8] | |
| brd = board.Board(*fields) | |
| self.add_board(brd) | |
| def get_list(self): | |
| """Return a list of available boards. | |
| Returns: | |
| List of Board objects | |
| """ | |
| return self._boards | |
| def get_dict(self): | |
| """Build a dictionary containing all the boards. | |
| Returns: | |
| Dictionary: | |
| key is board.target | |
| value is board | |
| """ | |
| board_dict = OrderedDict() | |
| for brd in self._boards: | |
| board_dict[brd.target] = brd | |
| return board_dict | |
| def get_selected_dict(self): | |
| """Return a dictionary containing the selected boards | |
| Returns: | |
| List of Board objects that are marked selected | |
| """ | |
| board_dict = OrderedDict() | |
| for brd in self._boards: | |
| if brd.build_it: | |
| board_dict[brd.target] = brd | |
| return board_dict | |
| def get_selected(self): | |
| """Return a list of selected boards | |
| Returns: | |
| List of Board objects that are marked selected | |
| """ | |
| return [brd for brd in self._boards if brd.build_it] | |
| def get_selected_names(self): | |
| """Return a list of selected boards | |
| Returns: | |
| List of board names that are marked selected | |
| """ | |
| return [brd.target for brd in self._boards if brd.build_it] | |
| @classmethod | |
| def _build_terms(cls, args): | |
| """Convert command line arguments to a list of terms. | |
| This deals with parsing of the arguments. It handles the '&' | |
| operator, which joins several expressions into a single Term. | |
| For example: | |
| ['arm & freescale sandbox', 'tegra'] | |
| will produce 3 Terms containing expressions as follows: | |
| arm, freescale | |
| sandbox | |
| tegra | |
| The first Term has two expressions, both of which must match for | |
| a board to be selected. | |
| Args: | |
| args (list of str): List of command line arguments | |
| Returns: | |
| list of Term: A list of Term objects | |
| """ | |
| syms = [] | |
| for arg in args: | |
| for word in arg.split(): | |
| sym_build = [] | |
| for term in word.split('&'): | |
| if term: | |
| sym_build.append(term) | |
| sym_build.append('&') | |
| syms += sym_build[:-1] | |
| terms = [] | |
| term = None | |
| oper = None | |
| for sym in syms: | |
| if sym == '&': | |
| oper = sym | |
| elif oper: | |
| term.add_expr(sym) | |
| oper = None | |
| else: | |
| if term: | |
| terms.append(term) | |
| term = Term() | |
| term.add_expr(sym) | |
| if term: | |
| terms.append(term) | |
| return terms | |
| def select_boards(self, args, exclude=None, brds=None): | |
| """Mark boards selected based on args | |
| Normally either boards (an explicit list of boards) or args (a list of | |
| terms to match against) is used. It is possible to specify both, in | |
| which case they are additive. | |
| If brds and args are both empty, all boards are selected. | |
| Args: | |
| args (list of str): List of strings specifying boards to include, | |
| either named, or by their target, architecture, cpu, vendor or | |
| soc. If empty, all boards are selected. | |
| exclude (list of str): List of boards to exclude, regardless of | |
| 'args', or None for none | |
| brds (list of Board): List of boards to build, or None/[] for all | |
| Returns: | |
| Tuple | |
| Dictionary which holds the list of boards which were selected | |
| due to each argument, arranged by argument. | |
| List of errors found | |
| """ | |
| def _check_board(brd): | |
| """Check whether to include or exclude a board | |
| Checks the various terms and decide whether to build it or not (the | |
| 'build_it' variable). | |
| If it is built, add the board to the result[term] list so we know | |
| which term caused it to be built. Add it to result['all'] also. | |
| Keep a list of boards we found in 'found', so we can report boards | |
| which appear in self._boards but not in brds. | |
| Args: | |
| brd (Board): Board to check | |
| """ | |
| matching_term = None | |
| build_it = False | |
| if terms: | |
| for term in terms: | |
| if term.matches(brd.props): | |
| matching_term = str(term) | |
| build_it = True | |
| break | |
| elif brds: | |
| if brd.target in brds: | |
| build_it = True | |
| found.append(brd.target) | |
| else: | |
| build_it = True | |
| # Check that it is not specifically excluded | |
| for expr in exclude_list: | |
| if expr.matches(brd.props): | |
| build_it = False | |
| break | |
| if build_it: | |
| brd.build_it = True | |
| if matching_term: | |
| result[matching_term].append(brd.target) | |
| result['all'].append(brd.target) | |
| result = OrderedDict() | |
| warnings = [] | |
| terms = self._build_terms(args) | |
| result['all'] = [] | |
| for term in terms: | |
| result[str(term)] = [] | |
| exclude_list = [] | |
| if exclude: | |
| for expr in exclude: | |
| exclude_list.append(Expr(expr)) | |
| found = [] | |
| for brd in self._boards: | |
| _check_board(brd) | |
| if brds: | |
| remaining = set(brds) - set(found) | |
| if remaining: | |
| warnings.append(f"Boards not found: {', '.join(remaining)}\n") | |
| return result, warnings | |
| @classmethod | |
| def scan_defconfigs_for_multiprocess(cls, srcdir, queue, defconfigs, | |
| warn_targets): | |
| """Scan defconfig files and queue their board parameters | |
| This function is intended to be passed to multiprocessing.Process() | |
| constructor. | |
| Args: | |
| srcdir (str): Directory containing source code | |
| queue (multiprocessing.Queue): The resulting board parameters are | |
| written into this. | |
| defconfigs (sequence of str): A sequence of defconfig files to be | |
| scanned. | |
| warn_targets (bool): True to warn about missing or duplicate | |
| CONFIG_TARGET options | |
| """ | |
| kconf_scanner = KconfigScanner(srcdir) | |
| for defconfig in defconfigs: | |
| queue.put(kconf_scanner.scan(defconfig, warn_targets)) | |
| @classmethod | |
| def read_queues(cls, queues, params_list, warnings): | |
| """Read the queues and append the data to the paramers list | |
| Args: | |
| queues (list of multiprocessing.Queue): Queues to read | |
| params_list (list of dict): List to add params too | |
| warnings (set of str): Set to add warnings to | |
| """ | |
| for que in queues: | |
| while not que.empty(): | |
| params, warn = que.get() | |
| params_list.append(params) | |
| warnings.update(warn) | |
| def scan_defconfigs(self, config_dir, srcdir, jobs=1, warn_targets=False): | |
| """Collect board parameters for all defconfig files. | |
| This function invokes multiple processes for faster processing. | |
| Args: | |
| config_dir (str): Directory containing the defconfig files | |
| srcdir (str): Directory containing source code (Kconfig files) | |
| jobs (int): The number of jobs to run simultaneously | |
| warn_targets (bool): True to warn about missing or duplicate | |
| CONFIG_TARGET options | |
| Returns: | |
| tuple: | |
| list of dict: List of board parameters, each a dict: | |
| key: 'arch', 'cpu', 'soc', 'vendor', 'board', 'target', | |
| 'config' | |
| value: string value of the key | |
| list of str: List of warnings recorded | |
| """ | |
| all_defconfigs = [] | |
| for (dirpath, _, filenames) in os.walk(config_dir): | |
| for filename in fnmatch.filter(filenames, '*_defconfig'): | |
| if fnmatch.fnmatch(filename, '.*'): | |
| continue | |
| all_defconfigs.append(os.path.join(dirpath, filename)) | |
| total_boards = len(all_defconfigs) | |
| processes = [] | |
| queues = [] | |
| for i in range(jobs): | |
| defconfigs = all_defconfigs[total_boards * i // jobs : | |
| total_boards * (i + 1) // jobs] | |
| que = multiprocessing.Queue(maxsize=-1) | |
| proc = multiprocessing.Process( | |
| target=self.scan_defconfigs_for_multiprocess, | |
| args=(srcdir, que, defconfigs, warn_targets)) | |
| proc.start() | |
| processes.append(proc) | |
| queues.append(que) | |
| # The resulting data should be accumulated to these lists | |
| params_list = [] | |
| warnings = set() | |
| # Data in the queues should be retrieved preriodically. | |
| # Otherwise, the queues would become full and subprocesses would get stuck. | |
| while any(p.is_alive() for p in processes): | |
| self.read_queues(queues, params_list, warnings) | |
| # sleep for a while until the queues are filled | |
| time.sleep(SLEEP_TIME) | |
| # Joining subprocesses just in case | |
| # (All subprocesses should already have been finished) | |
| for proc in processes: | |
| proc.join() | |
| # retrieve leftover data | |
| self.read_queues(queues, params_list, warnings) | |
| return params_list, sorted(list(warnings)) | |
| @classmethod | |
| def insert_maintainers_info(cls, srcdir, params_list): | |
| """Add Status and Maintainers information to the board parameters list. | |
| Args: | |
| params_list (list of dict): A list of the board parameters | |
| Returns: | |
| list of str: List of warnings collected due to missing status, etc. | |
| """ | |
| database = MaintainersDatabase() | |
| for (dirpath, _, filenames) in os.walk(srcdir): | |
| if 'MAINTAINERS' in filenames and 'tools/buildman' not in dirpath: | |
| database.parse_file(srcdir, | |
| os.path.join(dirpath, 'MAINTAINERS')) | |
| for i, params in enumerate(params_list): | |
| target = params['target'] | |
| maintainers = database.get_maintainers(target) | |
| params['maintainers'] = maintainers | |
| if maintainers: | |
| params['status'] = database.get_status(target) | |
| else: | |
| params['status'] = '-' | |
| params_list[i] = params | |
| return sorted(database.warnings) | |
| @classmethod | |
| def format_and_output(cls, params_list, output): | |
| """Write board parameters into a file. | |
| Columnate the board parameters, sort lines alphabetically, | |
| and then write them to a file. | |
| Args: | |
| params_list (list of dict): The list of board parameters | |
| output (str): The path to the output file | |
| """ | |
| fields = ('status', 'arch', 'cpu', 'soc', 'vendor', 'board', 'target', | |
| 'config', 'maintainers') | |
| # First, decide the width of each column | |
| max_length = {f: 0 for f in fields} | |
| for params in params_list: | |
| for field in fields: | |
| max_length[field] = max(max_length[field], len(params[field])) | |
| output_lines = [] | |
| for params in params_list: | |
| line = '' | |
| for field in fields: | |
| # insert two spaces between fields like column -t would | |
| line += ' ' + params[field].ljust(max_length[field]) | |
| output_lines.append(line.strip()) | |
| # ignore case when sorting | |
| output_lines.sort(key=str.lower) | |
| with open(output, 'w', encoding="utf-8") as outf: | |
| outf.write(COMMENT_BLOCK + '\n'.join(output_lines) + '\n') | |
| def build_board_list(self, config_dir=CONFIG_DIR, srcdir='.', jobs=1, | |
| warn_targets=False): | |
| """Generate a board-database file | |
| This works by reading the Kconfig, then loading each board's defconfig | |
| in to get the setting for each option. In particular, CONFIG_TARGET_xxx | |
| is typically set by the defconfig, where xxx is the target to build. | |
| Args: | |
| config_dir (str): Directory containing the defconfig files | |
| srcdir (str): Directory containing source code (Kconfig files) | |
| jobs (int): The number of jobs to run simultaneously | |
| warn_targets (bool): True to warn about missing or duplicate | |
| CONFIG_TARGET options | |
| Returns: | |
| tuple: | |
| list of dict: List of board parameters, each a dict: | |
| key: 'arch', 'cpu', 'soc', 'vendor', 'board', 'config', | |
| 'target' | |
| value: string value of the key | |
| list of str: Warnings that came up | |
| """ | |
| params_list, warnings = self.scan_defconfigs(config_dir, srcdir, jobs, | |
| warn_targets) | |
| m_warnings = self.insert_maintainers_info(srcdir, params_list) | |
| return params_list, warnings + m_warnings | |
| def ensure_board_list(self, output, jobs=1, force=False, quiet=False): | |
| """Generate a board database file if needed. | |
| This is intended to check if Kconfig has changed since the boards.cfg | |
| files was generated. | |
| Args: | |
| output (str): The name of the output file | |
| jobs (int): The number of jobs to run simultaneously | |
| force (bool): Force to generate the output even if it is new | |
| quiet (bool): True to avoid printing a message if nothing needs doing | |
| Returns: | |
| bool: True if all is well, False if there were warnings | |
| """ | |
| if not force: | |
| if not quiet: | |
| tprint('\rChecking for Kconfig changes...', newline=False) | |
| is_new = output_is_new(output, CONFIG_DIR, '.') | |
| print_clear() | |
| if is_new: | |
| if not quiet: | |
| print(f'{output} is up to date. Nothing to do.') | |
| return True | |
| if not quiet: | |
| tprint('\rGenerating board list...', newline=False) | |
| params_list, warnings = self.build_board_list(CONFIG_DIR, '.', jobs) | |
| print_clear() | |
| for warn in warnings: | |
| print(warn, file=sys.stderr) | |
| self.format_and_output(params_list, output) | |
| return not warnings |