| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874 |
- # 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
- ### 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 cotaining 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 and output_is_new(output, CONFIG_DIR, '.'):
- if not quiet:
- print(f'{output} is up to date. Nothing to do.')
- return True
- params_list, warnings = self.build_board_list(CONFIG_DIR, '.', jobs)
- for warn in warnings:
- print(warn, file=sys.stderr)
- self.format_and_output(params_list, output)
- return not warnings
|