mirror of
				https://github.com/smaeul/u-boot.git
				synced 2025-10-22 08:38:15 +01:00 
			
		
		
		
	We are still keeping invalid email addressed in MAINTAINERS because they carry information. The problem is that scripts/get_maintainer.pl adds emails in the "M:" field including invalid ones. We want to comment out invalid email addresses in MAINTAINERS to prevent scripts/get_maintainer.pl from picking them up. On the other hand, we want to collect them for boards.cfg to know the last known maintainer of each board. This commit adjusts tools/genboardscfg.py to parse also the commented "M:" fields, which is useful for the next commit. Signed-off-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
		
			
				
	
	
		
			445 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			445 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env python2
 | |
| #
 | |
| # Author: Masahiro Yamada <yamada.m@jp.panasonic.com>
 | |
| #
 | |
| # SPDX-License-Identifier:	GPL-2.0+
 | |
| #
 | |
| 
 | |
| """
 | |
| 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.
 | |
| 
 | |
| Python 2.6 or later, but not Python 3.x is necessary to run this script.
 | |
| """
 | |
| 
 | |
| import errno
 | |
| import fnmatch
 | |
| import glob
 | |
| import multiprocessing
 | |
| import optparse
 | |
| import os
 | |
| import subprocess
 | |
| import sys
 | |
| import tempfile
 | |
| import time
 | |
| 
 | |
| sys.path.append(os.path.join(os.path.dirname(__file__), '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) 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 Config object."""
 | |
|         # Define environment variables referenced from Kconfig
 | |
|         os.environ['srctree'] = os.getcwd()
 | |
|         os.environ['UBOOTVERSION'] = 'dummy'
 | |
|         os.environ['KCONFIG_OBJDIR'] = ''
 | |
|         self._conf = kconfiglib.Config()
 | |
| 
 | |
|     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 self._SYMBOL_TABLE.items():
 | |
|             value = self._conf.get_symbol(symbol).get_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 >> sys.stderr, "WARNING: no status info for '%s'" % target
 | |
|             return '-'
 | |
| 
 | |
|         tmp = self.database[target][0]
 | |
|         if tmp.startswith('Maintained'):
 | |
|             return 'Active'
 | |
|         elif tmp.startswith('Orphan'):
 | |
|             return 'Orphan'
 | |
|         else:
 | |
|             print >> sys.stderr, ("WARNING: %s: unknown status for '%s'" %
 | |
|                                   (tmp, target))
 | |
|             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 >> sys.stderr, "WARNING: no maintainers for '%s'" % target
 | |
|             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):
 | |
|             # 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') as f:
 | |
|         f.write(COMMENT_BLOCK + '\n'.join(output_lines) + '\n')
 | |
| 
 | |
| def gen_boards_cfg(output, jobs=1, force=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
 | |
|     """
 | |
|     check_top_directory()
 | |
| 
 | |
|     if not force and output_is_new(output):
 | |
|         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=cpu_count,
 | |
|                       help='the number of jobs to run simultaneously')
 | |
|     parser.add_option('-o', '--output', default=OUTPUT_FILE,
 | |
|                       help='output file [default=%s]' % OUTPUT_FILE)
 | |
|     (options, args) = parser.parse_args()
 | |
| 
 | |
|     gen_boards_cfg(options.output, jobs=options.jobs, force=options.force)
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     main()
 |