+++ /dev/null
-#!/usr/bin/env python3
-# SPDX-License-Identifier: GPL-2.0+
-#
-# Author: Masahiro Yamada <yamada.m@jp.panasonic.com>
-#
-
-"""
-Converter from Kconfig and MAINTAINERS to a board database.
-
-Run 'tools/genboardscfg.py' to create a board database.
-
-Run 'tools/genboardscfg.py -h' for available options.
-"""
-
-import errno
-import fnmatch
-import glob
-import multiprocessing
-import optparse
-import os
-import sys
-import tempfile
-import time
-
-from buildman import kconfiglib
-
-### constant variables ###
-OUTPUT_FILE = 'boards.cfg'
-CONFIG_DIR = 'configs'
-SLEEP_TIME = 0.03
-COMMENT_BLOCK = '''#
-# List of boards
-# Automatically generated by %s: don't edit
-#
-# Status, Arch, CPU, SoC, Vendor, Board, Target, Options, Maintainers
-
-''' % __file__
-
-### helper functions ###
-def try_remove(f):
- """Remove a file ignoring 'No such file or directory' error."""
- try:
- os.remove(f)
- except OSError as exception:
- # Ignore 'No such file or directory' error
- if exception.errno != errno.ENOENT:
- raise
-
-def check_top_directory():
- """Exit if we are not at the top of source directory."""
- for f in ('README', 'Licenses'):
- if not os.path.exists(f):
- sys.exit('Please run at the top of source directory.')
-
-def output_is_new(output):
- """Check if the output file is up to date.
-
- Returns:
- True if the given output file exists and is newer than any of
- *_defconfig, MAINTAINERS and Kconfig*. False otherwise.
- """
- 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
- else:
- raise
-
- for (dirpath, dirnames, 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, dirnames, filenames) in os.walk('.'):
- 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 f:
- for line in f:
- 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
-
-### classes ###
-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',
- 'options' : 'SYS_EXTRA_OPTIONS'
- }
-
- def __init__(self):
- """Scan all the Kconfig files and create a Kconfig object."""
- # Define environment variables referenced from Kconfig
- os.environ['srctree'] = os.getcwd()
- os.environ['UBOOTVERSION'] = 'dummy'
- os.environ['KCONFIG_OBJDIR'] = ''
- 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 hasattr(self, '_tmpfile') and self._tmpfile:
- try_remove(self._tmpfile)
-
- def scan(self, defconfig):
- """Load a defconfig file to obtain board parameters.
-
- Arguments:
- defconfig: path to the defconfig file to be processed
-
- Returns:
- A 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>,
- 'options': <extra_options>
- }
- """
- # strip special prefixes and save it in a temporary file
- fd, self._tmpfile = tempfile.mkstemp()
- with os.fdopen(fd, 'w') as f:
- for line in open(defconfig):
- colon = line.find(':CONFIG_')
- if colon == -1:
- f.write(line)
- else:
- f.write(line[colon + 1:])
-
- self._conf.load_config(self._tmpfile)
- try_remove(self._tmpfile)
- self._tmpfile = None
-
- params = {}
-
- # 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] = '-'
-
- defconfig = os.path.basename(defconfig)
- params['target'], match, rear = defconfig.partition('_defconfig')
- assert match and not rear, '%s : invalid defconfig' % defconfig
-
- # fix-up for aarch64
- if params['arch'] == 'arm' and params['cpu'] == 'armv8':
- params['arch'] = 'aarch64'
-
- # fix-up options field. It should have the form:
- # <config name>[:comma separated config options]
- if params['options'] != '-':
- params['options'] = params['config'] + ':' + \
- params['options'].replace(r'\"', '"')
- elif params['config'] != params['target']:
- params['options'] = params['config']
-
- return params
-
-def scan_defconfigs_for_multiprocess(queue, defconfigs):
- """Scan defconfig files and queue their board parameters
-
- This function is intended to be passed to
- multiprocessing.Process() constructor.
-
- Arguments:
- queue: An instance of multiprocessing.Queue().
- The resulting board parameters are written into it.
- defconfigs: A sequence of defconfig files to be scanned.
- """
- kconf_scanner = KconfigScanner()
- for defconfig in defconfigs:
- queue.put(kconf_scanner.scan(defconfig))
-
-def read_queues(queues, params_list):
- """Read the queues and append the data to the paramers list"""
- for q in queues:
- while not q.empty():
- params_list.append(q.get())
-
-def scan_defconfigs(jobs=1):
- """Collect board parameters for all defconfig files.
-
- This function invokes multiple processes for faster processing.
-
- Arguments:
- jobs: The number of jobs to run simultaneously
- """
- all_defconfigs = []
- for (dirpath, dirnames, 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]
- q = multiprocessing.Queue(maxsize=-1)
- p = multiprocessing.Process(target=scan_defconfigs_for_multiprocess,
- args=(q, defconfigs))
- p.start()
- processes.append(p)
- queues.append(q)
-
- # The resulting data should be accumulated to this list
- params_list = []
-
- # 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]):
- read_queues(queues, params_list)
- # 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 p in processes:
- p.join()
-
- # retrieve leftover data
- read_queues(queues, params_list)
-
- return params_list
-
-class MaintainersDatabase:
-
- """The database of board status and maintainers."""
-
- def __init__(self):
- """Create an empty database."""
- self.database = {}
-
- 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.
-
- Returns:
- 'Active', 'Orphan' or '-'.
- """
- if not target in self.database:
- print("WARNING: no status info for '%s'" % target, file=sys.stderr)
- return '-'
-
- tmp = self.database[target][0]
- if tmp.startswith('Maintained'):
- return 'Active'
- elif tmp.startswith('Supported'):
- return 'Active'
- elif tmp.startswith('Orphan'):
- return 'Orphan'
- else:
- print(("WARNING: %s: unknown status for '%s'" %
- (tmp, target)), file=sys.stderr)
- return '-'
-
- def get_maintainers(self, target):
- """Return the maintainers of the given board.
-
- Returns:
- Maintainers of the board. If the board has two or more maintainers,
- they are separated with colons.
- """
- if not target in self.database:
- print("WARNING: no maintainers for '%s'" % target, file=sys.stderr)
- return ''
-
- return ':'.join(self.database[target][1])
-
- def parse_file(self, file):
- """Parse a MAINTAINERS file.
-
- Parse a MAINTAINERS file and accumulates board status and
- maintainers information.
-
- Arguments:
- file: MAINTAINERS file to be parsed
- """
- targets = []
- maintainers = []
- status = '-'
- for line in open(file, encoding="utf-8"):
- # 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'
- for f in glob.glob(rest):
- front, match, rear = f.partition('configs/')
- if not front and match:
- front, match, rear = rear.rpartition('_defconfig')
- if match and not rear:
- targets.append(front)
- elif tag == 'S:':
- status = rest
- elif line == '\n':
- for target in targets:
- self.database[target] = (status, maintainers)
- targets = []
- maintainers = []
- status = '-'
- if targets:
- for target in targets:
- self.database[target] = (status, maintainers)
-
-def insert_maintainers_info(params_list):
- """Add Status and Maintainers information to the board parameters list.
-
- Arguments:
- params_list: A list of the board parameters
- """
- database = MaintainersDatabase()
- for (dirpath, dirnames, filenames) in os.walk('.'):
- if 'MAINTAINERS' in filenames:
- database.parse_file(os.path.join(dirpath, 'MAINTAINERS'))
-
- for i, params in enumerate(params_list):
- target = params['target']
- params['status'] = database.get_status(target)
- params['maintainers'] = database.get_maintainers(target)
- params_list[i] = params
-
-def format_and_output(params_list, output):
- """Write board parameters into a file.
-
- Columnate the board parameters, sort lines alphabetically,
- and then write them to a file.
-
- Arguments:
- params_list: The list of board parameters
- output: The path to the output file
- """
- FIELDS = ('status', 'arch', 'cpu', 'soc', 'vendor', 'board', 'target',
- 'options', 'maintainers')
-
- # First, decide the width of each column
- max_length = dict([ (f, 0) for f in FIELDS])
- for params in params_list:
- for f in FIELDS:
- max_length[f] = max(max_length[f], len(params[f]))
-
- output_lines = []
- for params in params_list:
- line = ''
- for f in FIELDS:
- # insert two spaces between fields like column -t would
- line += ' ' + params[f].ljust(max_length[f])
- output_lines.append(line.strip())
-
- # ignore case when sorting
- output_lines.sort(key=str.lower)
-
- with open(output, 'w', encoding="utf-8") as f:
- f.write(COMMENT_BLOCK + '\n'.join(output_lines) + '\n')
-
-def gen_boards_cfg(output, jobs=1, force=False, quiet=False):
- """Generate a board database file.
-
- Arguments:
- output: The name of the output file
- jobs: The number of jobs to run simultaneously
- force: Force to generate the output even if it is new
- quiet: True to avoid printing a message if nothing needs doing
- """
- check_top_directory()
-
- if not force and output_is_new(output):
- if not quiet:
- print("%s is up to date. Nothing to do." % output)
- sys.exit(0)
-
- params_list = scan_defconfigs(jobs)
- insert_maintainers_info(params_list)
- format_and_output(params_list, output)
-
-def main():
- try:
- cpu_count = multiprocessing.cpu_count()
- except NotImplementedError:
- cpu_count = 1
-
- parser = optparse.OptionParser()
- # Add options here
- parser.add_option('-f', '--force', action="store_true", default=False,
- help='regenerate the output even if it is new')
- parser.add_option('-j', '--jobs', type='int', default=min(cpu_count, 240),
- help='the number of jobs to run simultaneously')
- parser.add_option('-o', '--output', default=OUTPUT_FILE,
- help='output file [default=%s]' % OUTPUT_FILE)
- parser.add_option('-q', '--quiet', action="store_true", help='run silently')
- (options, args) = parser.parse_args()
-
- gen_boards_cfg(options.output, jobs=options.jobs, force=options.force,
- quiet=options.quiet)
-
-if __name__ == '__main__':
- main()