Hi Masahiro, On 24 July 2014 05:00, Masahiro Yamada <yamad...@jp.panasonic.com> wrote: > Now the primary data for each board is in Kconfig, defconfig and > MAINTAINERS. > > It is true boards.cfg is needed for MAKEALL and buildman and > might be useful to brouse boards in a single database. > But it would be painful to maintain the boards.cfg in sync. > > So, this is the solution. > Add a tool to generate the equivalent boards.cfg file based on > the latest Kconfig, defconfig and MAINTAINERS. > > We can keep all the functions of MAKEALL and buildman with it. > > The best thing would be to change MAKEALL and buildman for not > depending on boards.cfg in future, but it would take some time. > > Signed-off-by: Masahiro Yamada <yamad...@jp.panasonic.com>
Looks good, some nits beflow. Acked-by: Simon Glass <s...@chromium.org> > --- > > Changes in v5: > - Support wildcard pattern like 'F: configs/foo_*_defconfig' > - Rename genboardscfg to genboardscfg.py > - Use assert statement for sanity check > - Do not run if imported from another script > if __name__ == '__main__': > main() > - Check if we are at the top of source directory > > Changes in v4: > - Newly added > > Changes in v3: None > Changes in v2: None > > tools/genboardscfg.py | 449 > ++++++++++++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 449 insertions(+) > create mode 100755 tools/genboardscfg.py > > diff --git a/tools/genboardscfg.py b/tools/genboardscfg.py > new file mode 100755 > index 0000000..3c68027 > --- /dev/null > +++ b/tools/genboardscfg.py > @@ -0,0 +1,449 @@ > +#!/usr/bin/env python > +# > +# Author: Masahiro Yamada <yamad...@jp.panasonic.com> > +# > +# SPDX-License-Identifier: GPL-2.0+ > +# > + > +''' > +Converter from Kconfig and MAINTAINERS to boards.cfg > + > +Run 'tools/genboardscfg.py' to create boards.cfg file. > + > +Run 'tools/genboardscfg.py -h' for available options. > +''' > + > +import sys > +import os > +import errno > +import shutil > +import time > +import subprocess > +import fnmatch > +import glob > +import re > +import optparse You can sort the imports. > + > +BUILD_DIR = '.' + os.path.basename(__file__) > +BOARD_FILE = 'boards.cfg' > +CONFIG_DIR = 'configs' > +REFORMAT_CMD = [os.path.join('tools', 'reformat.py'), > + '-i', '-d', '-', '-s', '8'] > +SLEEP_TIME=0.03 > + > +COMMENT_BLOCK = '''# > +# List of boards > +# Automatically generated by %s: don't edit > +# > +# Status, Arch, CPU(:SPLCPU), SoC, Vendor, Board, Target, Options, > Maintainers > + > +''' % __file__ > + > +### helper functions ### > +def get_terminal_columns(): > + ''' > + Get the width of the terminal > + Return 0 if the stdout is not associated with tty. > + ''' > + try: > + return shutil.get_terminal_size().columns # Python 3.3~ > + except: > + import fcntl > + import termios > + import struct > + arg = struct.pack('hhhh', 0, 0, 0, 0) > + try: > + ret = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, arg) Perhaps could use sys.version_info here and below? Also I hesitate to suggest this, but there is a terminal.py library in patman. Should we consider moving that into a common directory and putting all terminal-related code there? > + except IOError as exception: > + if exception.errno != errno.ENOTTY: > + raise > + # If 'Inappropriate ioctl for device' error occurs, > + # stdout is probably redirected. Return 0. > + return 0 > + return struct.unpack('hhhh', ret)[1] > + > +def get_devnull(): > + '''Get the file object of '/dev/null' device''' > + try: > + DEVNULL = subprocess.DEVNULL # py3k > + except: > + DEVNULL = open(os.devnull, 'wb') > + return DEVNULL > + > +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): > + print >> sys.stderr, 'Please run at the top of source directory.' > + sys.exit(1) > + > +### classes ### > +class MaintainersDatabase: > + '''The database of board status and maintainers''' > + def __init__(self): > + '''Create an empty database''' > + self.database = {} Suggest a blank line after this and other functions. > + def get_status(self, target): > + ''' > + Return the status of the given board You can put the description on the same line immediately after the ''' > + > + The status is either 'Active' or 'Orphan'. > + ''' > + tmp = self.database[target][0] > + if tmp.startswith('Maintained'): > + return 'Active' > + elif tmp.startswith('Orphan'): > + return 'Orphan' > + else: > + print >> sys.stderr, 'Error: %s: unknown status' % tmp > + def get_maintainers(self, target): > + ''' > + Return the maintainers of the given board > + > + If the board has two or more maintainers, they are separated with > + colons. > + ''' > + return ':'.join(self.database[target][1]) > + def parse_file(self, file): > + ''' > + Parse the given MAINTAINERS file and add board status and > + maintainers information to the database > + > + Arguments: > + file - MAINTAINERS file to be parsed > + ''' > + targets = [] > + maintainers = [] > + status = '-' > + for line in open(file): > + if line[:2] == 'M:': Perhaps put line[:2] in a variable like 'tag'? > + maintainers.append(line[2:].strip()) > + elif line[:2] == 'F:': > + # expand wildcard and filter by 'configs/*_defconfig' > + for f in glob.glob(line[2:].strip()): > + 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 line[:2] == 'S:': > + status = line[2:].strip() > + elif line == '\n' and targets: > + for target in targets: > + self.database[target] = (status, maintainers) > + targets = [] > + maintainers = [] > + status = '-' > + if targets: > + for target in targets: > + self.database[target] = (status, maintainers) > + > +class DotConfigParser: > + ''' > + A parser of .config file > + > + Each line of the boards.cfg has the form of: > + Status, Arch, CPU, SoC, Vendor, Board, Target, Options, Maintainers > + Most of the fields are extracted from .config file. > + MAINTAINERS files are also consulted for Status and Maintainers fields. > + ''' > + re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"') > + re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"') > + re_soc = re.compile(r'CONFIG_SYS_SOC="(.*)"') > + re_vendor = re.compile(r'CONFIG_SYS_VENDOR="(.*)"') > + re_board = re.compile(r'CONFIG_SYS_BOARD="(.*)"') > + re_config = re.compile(r'CONFIG_SYS_CONFIG_NAME="(.*)"') > + re_options = re.compile(r'CONFIG_SYS_EXTRA_OPTIONS="(.*)"') > + re_list = (('arch', re_arch), ('cpu', re_cpu), ('soc', re_soc), > + ('vendor', re_vendor), ('board', re_board), > + ('config', re_config), ('options', re_options)) > + must_fields = ('arch', 'config') > + def __init__(self, build_dir, output, maintainers_database): > + ''' > + Create a new .config perser > + > + Arguments: > + build_dir - Build directory where .config is located > + output - File object which the result is written to > + maintainers_database - A instance of class MaintainersDatabase > + ''' > + self.dotconfig = os.path.join(build_dir, '.config') > + self.output = output > + self.database = maintainers_database > + def parse(self, defconfig): > + ''' > + Parse .config file and output one-line database for the given board > + > + Arguments: > + defconfig - Board (defconfig) name > + ''' > + fields = {} > + for line in open(self.dotconfig): > + if not line.startswith('CONFIG_SYS_'): > + continue > + for (key, pattern) in self.re_list: > + m = pattern.match(line) > + if m and m.group(1): > + fields[key] = m.group(1) > + break > + for field in self.must_fields: > + # sanity check of '.config' file > + if not field in fields: > + print >> sys.stderr, 'Error: %s is not defined in %s' % \ > + (field, > defconfig) > + sys.exit(1) > + # fix-up for aarch64 and tegra > + if fields['arch'] == 'arm' and 'cpu' in fields: > + if fields['cpu'] == 'armv8': > + fields['arch'] = 'aarch64' Is this always true? Do we not support aarch32 yet? > + if 'soc' in fields and re.match('tegra[0-9]*$', fields['soc']): > + fields['cpu'] += ':arm720t' This is unfortunate, but necessary I think. > + target, match, rear = defconfig.partition('_defconfig') > + assert match and not rear, \ > + '%s : invalid defconfig file name' % > defconfig > + fields['status'] = self.database.get_status(target) > + fields['maintainers'] = self.database.get_maintainers(target) > + if 'options' in fields: > + options = fields['config'] + ':' \ > + + fields['options'].replace(r'\"', > '"') > + elif fields['config'] != target: > + options = fields['config'] > + else: > + options = '-' > + self.output.write((' '.join(['%s'] * 9) + '\n') % > + (fields['status'], > + fields['arch'], > + fields.get('cpu', '-'), > + fields.get('soc', '-'), > + fields.get('vendor', '-'), > + fields.get('board', '-'), > + target, > + options, > + fields['maintainers'])) > + > +class Slot: > + ''' > + Subprocess slot Slot? > + > + Each instance of this class handles one subprocess. > + This class is useful to control multiple processes of > + "make <board>_defconfig" for faster processing. > + > + Private members: > + occupied - Show if this slot is ocuppied or not. To keep consistent with patman, use a : instead of - after occupied. Same for other functions. > + A new subprocess can be set if this flag is False. > + build_dir - Working directory of this slot > + parser - A instance of class DotConfigParser > + ''' > + DEVNULL = get_devnull() > + def __init__(self, build_dir, output, maintainers_database): > + ''' > + Create a new slot > + > + Arguments: > + build_dir - Working directory of this slot > + output - File object which the result is written to > + maintainers_database - A instance of class MaintainersDatabase > + ''' > + self.occupied = False > + self.build_dir = build_dir > + self.parser = DotConfigParser(build_dir, output, > maintainers_database) > + def add(self, defconfig): > + ''' > + Add a new subprocess to the slot > + > + Fails if the slot is occupied (= current subprocess is still running) > + > + Arguments: > + defconfig - Board (defconfig) name > + > + Returns: > + Return True on success or False on fail > + ''' > + if self.occupied: > + return False > + o = 'O=' + self.build_dir > + self.ps = subprocess.Popen(['make', o, defconfig], > stdout=self.DEVNULL) > + self.defconfig = defconfig > + self.occupied = True > + return True > + def poll(self): > + ''' > + Check if the subprocess is running or not and invoke the .config > parser > + if the subprocess is terminated > + > + Returns: > + Return True if the subprocess is terminated, False otherwise > + ''' > + if not self.occupied: > + return True > + if self.ps.poll() == None: > + return False > + self.parser.parse(self.defconfig) > + self.occupied = False > + return True > + > +class Slots: > + ''' > + Controller of the array of subprocess slots > + > + Private members: > + slots - A list of instances of class Slot > + ''' > + def __init__(self, jobs, output, maintainers_database): > + ''' > + Create a new slots controller > + > + Arguments: > + jobs - A number of slots to instantiate > + output - Working directory. Each slot is allocated with > + the working directory "output/<slot_number>". > + maintainers_database - A instance of class MaintainersDatabase > + ''' > + self.slots = [] > + for i in range(jobs): > + build_dir = os.path.join(BUILD_DIR, str(i)) > + self.slots.append(Slot(build_dir, output, maintainers_database)) > + def add(self, defconfig): > + ''' > + Add a new subprocess if a vacant slot is available > + > + Arguments: > + defconfig - Board (defconfig) name > + > + Returns: > + Return True on success or False on fail > + ''' > + for slot in self.slots: > + if slot.add(defconfig): > + return True > + return False > + def available(self): > + ''' > + Check if there is a vacant slot > + > + Returns: > + Return True if a vacant slot is found, False if all slots are full > + ''' > + for slot in self.slots: > + if slot.poll(): > + return True > + return False > + def empty(self): > + ''' > + Check if all slots are vacant > + > + Returns: > + Return True if all slots are vacant, False if at least one slot > + is running > + ''' > + ret = True > + for slot in self.slots: > + if not slot.poll(): > + ret = False > + return ret > + > +class Indicator: > + ''' > + A class to control the progress indicator > + > + Private members: > + total - A number of boards to be processed > + cur - The current counter > + width - The width of the prograss bar > + enabled - Show the progress bar only when this flag is True > + ''' > + def __init__(self, total): > + ''' > + Create a instance getting the width of the terminal Out of date? Regards, Simon _______________________________________________ U-Boot mailing list U-Boot@lists.denx.de http://lists.denx.de/mailman/listinfo/u-boot