xref: /OK3568_Linux_fs/u-boot/tools/genboardscfg.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun#!/usr/bin/env python2
2*4882a593Smuzhiyun#
3*4882a593Smuzhiyun# Author: Masahiro Yamada <yamada.m@jp.panasonic.com>
4*4882a593Smuzhiyun#
5*4882a593Smuzhiyun# SPDX-License-Identifier:	GPL-2.0+
6*4882a593Smuzhiyun#
7*4882a593Smuzhiyun
8*4882a593Smuzhiyun"""
9*4882a593SmuzhiyunConverter from Kconfig and MAINTAINERS to a board database.
10*4882a593Smuzhiyun
11*4882a593SmuzhiyunRun 'tools/genboardscfg.py' to create a board database.
12*4882a593Smuzhiyun
13*4882a593SmuzhiyunRun 'tools/genboardscfg.py -h' for available options.
14*4882a593Smuzhiyun
15*4882a593SmuzhiyunPython 2.6 or later, but not Python 3.x is necessary to run this script.
16*4882a593Smuzhiyun"""
17*4882a593Smuzhiyun
18*4882a593Smuzhiyunimport errno
19*4882a593Smuzhiyunimport fnmatch
20*4882a593Smuzhiyunimport glob
21*4882a593Smuzhiyunimport multiprocessing
22*4882a593Smuzhiyunimport optparse
23*4882a593Smuzhiyunimport os
24*4882a593Smuzhiyunimport sys
25*4882a593Smuzhiyunimport tempfile
26*4882a593Smuzhiyunimport time
27*4882a593Smuzhiyun
28*4882a593Smuzhiyunsys.path.append(os.path.join(os.path.dirname(__file__), 'buildman'))
29*4882a593Smuzhiyunimport kconfiglib
30*4882a593Smuzhiyun
31*4882a593Smuzhiyun### constant variables ###
32*4882a593SmuzhiyunOUTPUT_FILE = 'boards.cfg'
33*4882a593SmuzhiyunCONFIG_DIR = 'configs'
34*4882a593SmuzhiyunSLEEP_TIME = 0.03
35*4882a593SmuzhiyunCOMMENT_BLOCK = '''#
36*4882a593Smuzhiyun# List of boards
37*4882a593Smuzhiyun#   Automatically generated by %s: don't edit
38*4882a593Smuzhiyun#
39*4882a593Smuzhiyun# Status, Arch, CPU, SoC, Vendor, Board, Target, Options, Maintainers
40*4882a593Smuzhiyun
41*4882a593Smuzhiyun''' % __file__
42*4882a593Smuzhiyun
43*4882a593Smuzhiyun### helper functions ###
44*4882a593Smuzhiyundef try_remove(f):
45*4882a593Smuzhiyun    """Remove a file ignoring 'No such file or directory' error."""
46*4882a593Smuzhiyun    try:
47*4882a593Smuzhiyun        os.remove(f)
48*4882a593Smuzhiyun    except OSError as exception:
49*4882a593Smuzhiyun        # Ignore 'No such file or directory' error
50*4882a593Smuzhiyun        if exception.errno != errno.ENOENT:
51*4882a593Smuzhiyun            raise
52*4882a593Smuzhiyun
53*4882a593Smuzhiyundef check_top_directory():
54*4882a593Smuzhiyun    """Exit if we are not at the top of source directory."""
55*4882a593Smuzhiyun    for f in ('README', 'Licenses'):
56*4882a593Smuzhiyun        if not os.path.exists(f):
57*4882a593Smuzhiyun            sys.exit('Please run at the top of source directory.')
58*4882a593Smuzhiyun
59*4882a593Smuzhiyundef output_is_new(output):
60*4882a593Smuzhiyun    """Check if the output file is up to date.
61*4882a593Smuzhiyun
62*4882a593Smuzhiyun    Returns:
63*4882a593Smuzhiyun      True if the given output file exists and is newer than any of
64*4882a593Smuzhiyun      *_defconfig, MAINTAINERS and Kconfig*.  False otherwise.
65*4882a593Smuzhiyun    """
66*4882a593Smuzhiyun    try:
67*4882a593Smuzhiyun        ctime = os.path.getctime(output)
68*4882a593Smuzhiyun    except OSError as exception:
69*4882a593Smuzhiyun        if exception.errno == errno.ENOENT:
70*4882a593Smuzhiyun            # return False on 'No such file or directory' error
71*4882a593Smuzhiyun            return False
72*4882a593Smuzhiyun        else:
73*4882a593Smuzhiyun            raise
74*4882a593Smuzhiyun
75*4882a593Smuzhiyun    for (dirpath, dirnames, filenames) in os.walk(CONFIG_DIR):
76*4882a593Smuzhiyun        for filename in fnmatch.filter(filenames, '*_defconfig'):
77*4882a593Smuzhiyun            if fnmatch.fnmatch(filename, '.*'):
78*4882a593Smuzhiyun                continue
79*4882a593Smuzhiyun            filepath = os.path.join(dirpath, filename)
80*4882a593Smuzhiyun            if ctime < os.path.getctime(filepath):
81*4882a593Smuzhiyun                return False
82*4882a593Smuzhiyun
83*4882a593Smuzhiyun    for (dirpath, dirnames, filenames) in os.walk('.'):
84*4882a593Smuzhiyun        for filename in filenames:
85*4882a593Smuzhiyun            if (fnmatch.fnmatch(filename, '*~') or
86*4882a593Smuzhiyun                not fnmatch.fnmatch(filename, 'Kconfig*') and
87*4882a593Smuzhiyun                not filename == 'MAINTAINERS'):
88*4882a593Smuzhiyun                continue
89*4882a593Smuzhiyun            filepath = os.path.join(dirpath, filename)
90*4882a593Smuzhiyun            if ctime < os.path.getctime(filepath):
91*4882a593Smuzhiyun                return False
92*4882a593Smuzhiyun
93*4882a593Smuzhiyun    # Detect a board that has been removed since the current board database
94*4882a593Smuzhiyun    # was generated
95*4882a593Smuzhiyun    with open(output) as f:
96*4882a593Smuzhiyun        for line in f:
97*4882a593Smuzhiyun            if line[0] == '#' or line == '\n':
98*4882a593Smuzhiyun                continue
99*4882a593Smuzhiyun            defconfig = line.split()[6] + '_defconfig'
100*4882a593Smuzhiyun            if not os.path.exists(os.path.join(CONFIG_DIR, defconfig)):
101*4882a593Smuzhiyun                return False
102*4882a593Smuzhiyun
103*4882a593Smuzhiyun    return True
104*4882a593Smuzhiyun
105*4882a593Smuzhiyun### classes ###
106*4882a593Smuzhiyunclass KconfigScanner:
107*4882a593Smuzhiyun
108*4882a593Smuzhiyun    """Kconfig scanner."""
109*4882a593Smuzhiyun
110*4882a593Smuzhiyun    ### constant variable only used in this class ###
111*4882a593Smuzhiyun    _SYMBOL_TABLE = {
112*4882a593Smuzhiyun        'arch' : 'SYS_ARCH',
113*4882a593Smuzhiyun        'cpu' : 'SYS_CPU',
114*4882a593Smuzhiyun        'soc' : 'SYS_SOC',
115*4882a593Smuzhiyun        'vendor' : 'SYS_VENDOR',
116*4882a593Smuzhiyun        'board' : 'SYS_BOARD',
117*4882a593Smuzhiyun        'config' : 'SYS_CONFIG_NAME',
118*4882a593Smuzhiyun        'options' : 'SYS_EXTRA_OPTIONS'
119*4882a593Smuzhiyun    }
120*4882a593Smuzhiyun
121*4882a593Smuzhiyun    def __init__(self):
122*4882a593Smuzhiyun        """Scan all the Kconfig files and create a Config object."""
123*4882a593Smuzhiyun        # Define environment variables referenced from Kconfig
124*4882a593Smuzhiyun        os.environ['srctree'] = os.getcwd()
125*4882a593Smuzhiyun        os.environ['UBOOTVERSION'] = 'dummy'
126*4882a593Smuzhiyun        os.environ['KCONFIG_OBJDIR'] = ''
127*4882a593Smuzhiyun        self._conf = kconfiglib.Config(print_warnings=False)
128*4882a593Smuzhiyun
129*4882a593Smuzhiyun    def __del__(self):
130*4882a593Smuzhiyun        """Delete a leftover temporary file before exit.
131*4882a593Smuzhiyun
132*4882a593Smuzhiyun        The scan() method of this class creates a temporay file and deletes
133*4882a593Smuzhiyun        it on success.  If scan() method throws an exception on the way,
134*4882a593Smuzhiyun        the temporary file might be left over.  In that case, it should be
135*4882a593Smuzhiyun        deleted in this destructor.
136*4882a593Smuzhiyun        """
137*4882a593Smuzhiyun        if hasattr(self, '_tmpfile') and self._tmpfile:
138*4882a593Smuzhiyun            try_remove(self._tmpfile)
139*4882a593Smuzhiyun
140*4882a593Smuzhiyun    def scan(self, defconfig):
141*4882a593Smuzhiyun        """Load a defconfig file to obtain board parameters.
142*4882a593Smuzhiyun
143*4882a593Smuzhiyun        Arguments:
144*4882a593Smuzhiyun          defconfig: path to the defconfig file to be processed
145*4882a593Smuzhiyun
146*4882a593Smuzhiyun        Returns:
147*4882a593Smuzhiyun          A dictionary of board parameters.  It has a form of:
148*4882a593Smuzhiyun          {
149*4882a593Smuzhiyun              'arch': <arch_name>,
150*4882a593Smuzhiyun              'cpu': <cpu_name>,
151*4882a593Smuzhiyun              'soc': <soc_name>,
152*4882a593Smuzhiyun              'vendor': <vendor_name>,
153*4882a593Smuzhiyun              'board': <board_name>,
154*4882a593Smuzhiyun              'target': <target_name>,
155*4882a593Smuzhiyun              'config': <config_header_name>,
156*4882a593Smuzhiyun              'options': <extra_options>
157*4882a593Smuzhiyun          }
158*4882a593Smuzhiyun        """
159*4882a593Smuzhiyun        # strip special prefixes and save it in a temporary file
160*4882a593Smuzhiyun        fd, self._tmpfile = tempfile.mkstemp()
161*4882a593Smuzhiyun        with os.fdopen(fd, 'w') as f:
162*4882a593Smuzhiyun            for line in open(defconfig):
163*4882a593Smuzhiyun                colon = line.find(':CONFIG_')
164*4882a593Smuzhiyun                if colon == -1:
165*4882a593Smuzhiyun                    f.write(line)
166*4882a593Smuzhiyun                else:
167*4882a593Smuzhiyun                    f.write(line[colon + 1:])
168*4882a593Smuzhiyun
169*4882a593Smuzhiyun        warnings = self._conf.load_config(self._tmpfile)
170*4882a593Smuzhiyun        if warnings:
171*4882a593Smuzhiyun            for warning in warnings:
172*4882a593Smuzhiyun                print '%s: %s' % (defconfig, warning)
173*4882a593Smuzhiyun
174*4882a593Smuzhiyun        try_remove(self._tmpfile)
175*4882a593Smuzhiyun        self._tmpfile = None
176*4882a593Smuzhiyun
177*4882a593Smuzhiyun        params = {}
178*4882a593Smuzhiyun
179*4882a593Smuzhiyun        # Get the value of CONFIG_SYS_ARCH, CONFIG_SYS_CPU, ... etc.
180*4882a593Smuzhiyun        # Set '-' if the value is empty.
181*4882a593Smuzhiyun        for key, symbol in self._SYMBOL_TABLE.items():
182*4882a593Smuzhiyun            value = self._conf.get_symbol(symbol).get_value()
183*4882a593Smuzhiyun            if value:
184*4882a593Smuzhiyun                params[key] = value
185*4882a593Smuzhiyun            else:
186*4882a593Smuzhiyun                params[key] = '-'
187*4882a593Smuzhiyun
188*4882a593Smuzhiyun        defconfig = os.path.basename(defconfig)
189*4882a593Smuzhiyun        params['target'], match, rear = defconfig.partition('_defconfig')
190*4882a593Smuzhiyun        assert match and not rear, '%s : invalid defconfig' % defconfig
191*4882a593Smuzhiyun
192*4882a593Smuzhiyun        # fix-up for aarch64
193*4882a593Smuzhiyun        if params['arch'] == 'arm' and params['cpu'] == 'armv8':
194*4882a593Smuzhiyun            params['arch'] = 'aarch64'
195*4882a593Smuzhiyun
196*4882a593Smuzhiyun        # fix-up options field. It should have the form:
197*4882a593Smuzhiyun        # <config name>[:comma separated config options]
198*4882a593Smuzhiyun        if params['options'] != '-':
199*4882a593Smuzhiyun            params['options'] = params['config'] + ':' + \
200*4882a593Smuzhiyun                                params['options'].replace(r'\"', '"')
201*4882a593Smuzhiyun        elif params['config'] != params['target']:
202*4882a593Smuzhiyun            params['options'] = params['config']
203*4882a593Smuzhiyun
204*4882a593Smuzhiyun        return params
205*4882a593Smuzhiyun
206*4882a593Smuzhiyundef scan_defconfigs_for_multiprocess(queue, defconfigs):
207*4882a593Smuzhiyun    """Scan defconfig files and queue their board parameters
208*4882a593Smuzhiyun
209*4882a593Smuzhiyun    This function is intended to be passed to
210*4882a593Smuzhiyun    multiprocessing.Process() constructor.
211*4882a593Smuzhiyun
212*4882a593Smuzhiyun    Arguments:
213*4882a593Smuzhiyun      queue: An instance of multiprocessing.Queue().
214*4882a593Smuzhiyun             The resulting board parameters are written into it.
215*4882a593Smuzhiyun      defconfigs: A sequence of defconfig files to be scanned.
216*4882a593Smuzhiyun    """
217*4882a593Smuzhiyun    kconf_scanner = KconfigScanner()
218*4882a593Smuzhiyun    for defconfig in defconfigs:
219*4882a593Smuzhiyun        queue.put(kconf_scanner.scan(defconfig))
220*4882a593Smuzhiyun
221*4882a593Smuzhiyundef read_queues(queues, params_list):
222*4882a593Smuzhiyun    """Read the queues and append the data to the paramers list"""
223*4882a593Smuzhiyun    for q in queues:
224*4882a593Smuzhiyun        while not q.empty():
225*4882a593Smuzhiyun            params_list.append(q.get())
226*4882a593Smuzhiyun
227*4882a593Smuzhiyundef scan_defconfigs(jobs=1):
228*4882a593Smuzhiyun    """Collect board parameters for all defconfig files.
229*4882a593Smuzhiyun
230*4882a593Smuzhiyun    This function invokes multiple processes for faster processing.
231*4882a593Smuzhiyun
232*4882a593Smuzhiyun    Arguments:
233*4882a593Smuzhiyun      jobs: The number of jobs to run simultaneously
234*4882a593Smuzhiyun    """
235*4882a593Smuzhiyun    all_defconfigs = []
236*4882a593Smuzhiyun    for (dirpath, dirnames, filenames) in os.walk(CONFIG_DIR):
237*4882a593Smuzhiyun        for filename in fnmatch.filter(filenames, '*_defconfig'):
238*4882a593Smuzhiyun            if fnmatch.fnmatch(filename, '.*'):
239*4882a593Smuzhiyun                continue
240*4882a593Smuzhiyun            all_defconfigs.append(os.path.join(dirpath, filename))
241*4882a593Smuzhiyun
242*4882a593Smuzhiyun    total_boards = len(all_defconfigs)
243*4882a593Smuzhiyun    processes = []
244*4882a593Smuzhiyun    queues = []
245*4882a593Smuzhiyun    for i in range(jobs):
246*4882a593Smuzhiyun        defconfigs = all_defconfigs[total_boards * i / jobs :
247*4882a593Smuzhiyun                                    total_boards * (i + 1) / jobs]
248*4882a593Smuzhiyun        q = multiprocessing.Queue(maxsize=-1)
249*4882a593Smuzhiyun        p = multiprocessing.Process(target=scan_defconfigs_for_multiprocess,
250*4882a593Smuzhiyun                                    args=(q, defconfigs))
251*4882a593Smuzhiyun        p.start()
252*4882a593Smuzhiyun        processes.append(p)
253*4882a593Smuzhiyun        queues.append(q)
254*4882a593Smuzhiyun
255*4882a593Smuzhiyun    # The resulting data should be accumulated to this list
256*4882a593Smuzhiyun    params_list = []
257*4882a593Smuzhiyun
258*4882a593Smuzhiyun    # Data in the queues should be retrieved preriodically.
259*4882a593Smuzhiyun    # Otherwise, the queues would become full and subprocesses would get stuck.
260*4882a593Smuzhiyun    while any([p.is_alive() for p in processes]):
261*4882a593Smuzhiyun        read_queues(queues, params_list)
262*4882a593Smuzhiyun        # sleep for a while until the queues are filled
263*4882a593Smuzhiyun        time.sleep(SLEEP_TIME)
264*4882a593Smuzhiyun
265*4882a593Smuzhiyun    # Joining subprocesses just in case
266*4882a593Smuzhiyun    # (All subprocesses should already have been finished)
267*4882a593Smuzhiyun    for p in processes:
268*4882a593Smuzhiyun        p.join()
269*4882a593Smuzhiyun
270*4882a593Smuzhiyun    # retrieve leftover data
271*4882a593Smuzhiyun    read_queues(queues, params_list)
272*4882a593Smuzhiyun
273*4882a593Smuzhiyun    return params_list
274*4882a593Smuzhiyun
275*4882a593Smuzhiyunclass MaintainersDatabase:
276*4882a593Smuzhiyun
277*4882a593Smuzhiyun    """The database of board status and maintainers."""
278*4882a593Smuzhiyun
279*4882a593Smuzhiyun    def __init__(self):
280*4882a593Smuzhiyun        """Create an empty database."""
281*4882a593Smuzhiyun        self.database = {}
282*4882a593Smuzhiyun
283*4882a593Smuzhiyun    def get_status(self, target):
284*4882a593Smuzhiyun        """Return the status of the given board.
285*4882a593Smuzhiyun
286*4882a593Smuzhiyun        The board status is generally either 'Active' or 'Orphan'.
287*4882a593Smuzhiyun        Display a warning message and return '-' if status information
288*4882a593Smuzhiyun        is not found.
289*4882a593Smuzhiyun
290*4882a593Smuzhiyun        Returns:
291*4882a593Smuzhiyun          'Active', 'Orphan' or '-'.
292*4882a593Smuzhiyun        """
293*4882a593Smuzhiyun        if not target in self.database:
294*4882a593Smuzhiyun            print >> sys.stderr, "WARNING: no status info for '%s'" % target
295*4882a593Smuzhiyun            return '-'
296*4882a593Smuzhiyun
297*4882a593Smuzhiyun        tmp = self.database[target][0]
298*4882a593Smuzhiyun        if tmp.startswith('Maintained'):
299*4882a593Smuzhiyun            return 'Active'
300*4882a593Smuzhiyun        elif tmp.startswith('Supported'):
301*4882a593Smuzhiyun            return 'Active'
302*4882a593Smuzhiyun        elif tmp.startswith('Orphan'):
303*4882a593Smuzhiyun            return 'Orphan'
304*4882a593Smuzhiyun        else:
305*4882a593Smuzhiyun            print >> sys.stderr, ("WARNING: %s: unknown status for '%s'" %
306*4882a593Smuzhiyun                                  (tmp, target))
307*4882a593Smuzhiyun            return '-'
308*4882a593Smuzhiyun
309*4882a593Smuzhiyun    def get_maintainers(self, target):
310*4882a593Smuzhiyun        """Return the maintainers of the given board.
311*4882a593Smuzhiyun
312*4882a593Smuzhiyun        Returns:
313*4882a593Smuzhiyun          Maintainers of the board.  If the board has two or more maintainers,
314*4882a593Smuzhiyun          they are separated with colons.
315*4882a593Smuzhiyun        """
316*4882a593Smuzhiyun        if not target in self.database:
317*4882a593Smuzhiyun            print >> sys.stderr, "WARNING: no maintainers for '%s'" % target
318*4882a593Smuzhiyun            return ''
319*4882a593Smuzhiyun
320*4882a593Smuzhiyun        return ':'.join(self.database[target][1])
321*4882a593Smuzhiyun
322*4882a593Smuzhiyun    def parse_file(self, file):
323*4882a593Smuzhiyun        """Parse a MAINTAINERS file.
324*4882a593Smuzhiyun
325*4882a593Smuzhiyun        Parse a MAINTAINERS file and accumulates board status and
326*4882a593Smuzhiyun        maintainers information.
327*4882a593Smuzhiyun
328*4882a593Smuzhiyun        Arguments:
329*4882a593Smuzhiyun          file: MAINTAINERS file to be parsed
330*4882a593Smuzhiyun        """
331*4882a593Smuzhiyun        targets = []
332*4882a593Smuzhiyun        maintainers = []
333*4882a593Smuzhiyun        status = '-'
334*4882a593Smuzhiyun        for line in open(file):
335*4882a593Smuzhiyun            # Check also commented maintainers
336*4882a593Smuzhiyun            if line[:3] == '#M:':
337*4882a593Smuzhiyun                line = line[1:]
338*4882a593Smuzhiyun            tag, rest = line[:2], line[2:].strip()
339*4882a593Smuzhiyun            if tag == 'M:':
340*4882a593Smuzhiyun                maintainers.append(rest)
341*4882a593Smuzhiyun            elif tag == 'F:':
342*4882a593Smuzhiyun                # expand wildcard and filter by 'configs/*_defconfig'
343*4882a593Smuzhiyun                for f in glob.glob(rest):
344*4882a593Smuzhiyun                    front, match, rear = f.partition('configs/')
345*4882a593Smuzhiyun                    if not front and match:
346*4882a593Smuzhiyun                        front, match, rear = rear.rpartition('_defconfig')
347*4882a593Smuzhiyun                        if match and not rear:
348*4882a593Smuzhiyun                            targets.append(front)
349*4882a593Smuzhiyun            elif tag == 'S:':
350*4882a593Smuzhiyun                status = rest
351*4882a593Smuzhiyun            elif line == '\n':
352*4882a593Smuzhiyun                for target in targets:
353*4882a593Smuzhiyun                    self.database[target] = (status, maintainers)
354*4882a593Smuzhiyun                targets = []
355*4882a593Smuzhiyun                maintainers = []
356*4882a593Smuzhiyun                status = '-'
357*4882a593Smuzhiyun        if targets:
358*4882a593Smuzhiyun            for target in targets:
359*4882a593Smuzhiyun                self.database[target] = (status, maintainers)
360*4882a593Smuzhiyun
361*4882a593Smuzhiyundef insert_maintainers_info(params_list):
362*4882a593Smuzhiyun    """Add Status and Maintainers information to the board parameters list.
363*4882a593Smuzhiyun
364*4882a593Smuzhiyun    Arguments:
365*4882a593Smuzhiyun      params_list: A list of the board parameters
366*4882a593Smuzhiyun    """
367*4882a593Smuzhiyun    database = MaintainersDatabase()
368*4882a593Smuzhiyun    for (dirpath, dirnames, filenames) in os.walk('.'):
369*4882a593Smuzhiyun        if 'MAINTAINERS' in filenames:
370*4882a593Smuzhiyun            database.parse_file(os.path.join(dirpath, 'MAINTAINERS'))
371*4882a593Smuzhiyun
372*4882a593Smuzhiyun    for i, params in enumerate(params_list):
373*4882a593Smuzhiyun        target = params['target']
374*4882a593Smuzhiyun        params['status'] = database.get_status(target)
375*4882a593Smuzhiyun        params['maintainers'] = database.get_maintainers(target)
376*4882a593Smuzhiyun        params_list[i] = params
377*4882a593Smuzhiyun
378*4882a593Smuzhiyundef format_and_output(params_list, output):
379*4882a593Smuzhiyun    """Write board parameters into a file.
380*4882a593Smuzhiyun
381*4882a593Smuzhiyun    Columnate the board parameters, sort lines alphabetically,
382*4882a593Smuzhiyun    and then write them to a file.
383*4882a593Smuzhiyun
384*4882a593Smuzhiyun    Arguments:
385*4882a593Smuzhiyun      params_list: The list of board parameters
386*4882a593Smuzhiyun      output: The path to the output file
387*4882a593Smuzhiyun    """
388*4882a593Smuzhiyun    FIELDS = ('status', 'arch', 'cpu', 'soc', 'vendor', 'board', 'target',
389*4882a593Smuzhiyun              'options', 'maintainers')
390*4882a593Smuzhiyun
391*4882a593Smuzhiyun    # First, decide the width of each column
392*4882a593Smuzhiyun    max_length = dict([ (f, 0) for f in FIELDS])
393*4882a593Smuzhiyun    for params in params_list:
394*4882a593Smuzhiyun        for f in FIELDS:
395*4882a593Smuzhiyun            max_length[f] = max(max_length[f], len(params[f]))
396*4882a593Smuzhiyun
397*4882a593Smuzhiyun    output_lines = []
398*4882a593Smuzhiyun    for params in params_list:
399*4882a593Smuzhiyun        line = ''
400*4882a593Smuzhiyun        for f in FIELDS:
401*4882a593Smuzhiyun            # insert two spaces between fields like column -t would
402*4882a593Smuzhiyun            line += '  ' + params[f].ljust(max_length[f])
403*4882a593Smuzhiyun        output_lines.append(line.strip())
404*4882a593Smuzhiyun
405*4882a593Smuzhiyun    # ignore case when sorting
406*4882a593Smuzhiyun    output_lines.sort(key=str.lower)
407*4882a593Smuzhiyun
408*4882a593Smuzhiyun    with open(output, 'w') as f:
409*4882a593Smuzhiyun        f.write(COMMENT_BLOCK + '\n'.join(output_lines) + '\n')
410*4882a593Smuzhiyun
411*4882a593Smuzhiyundef gen_boards_cfg(output, jobs=1, force=False):
412*4882a593Smuzhiyun    """Generate a board database file.
413*4882a593Smuzhiyun
414*4882a593Smuzhiyun    Arguments:
415*4882a593Smuzhiyun      output: The name of the output file
416*4882a593Smuzhiyun      jobs: The number of jobs to run simultaneously
417*4882a593Smuzhiyun      force: Force to generate the output even if it is new
418*4882a593Smuzhiyun    """
419*4882a593Smuzhiyun    check_top_directory()
420*4882a593Smuzhiyun
421*4882a593Smuzhiyun    if not force and output_is_new(output):
422*4882a593Smuzhiyun        print "%s is up to date. Nothing to do." % output
423*4882a593Smuzhiyun        sys.exit(0)
424*4882a593Smuzhiyun
425*4882a593Smuzhiyun    params_list = scan_defconfigs(jobs)
426*4882a593Smuzhiyun    insert_maintainers_info(params_list)
427*4882a593Smuzhiyun    format_and_output(params_list, output)
428*4882a593Smuzhiyun
429*4882a593Smuzhiyundef main():
430*4882a593Smuzhiyun    try:
431*4882a593Smuzhiyun        cpu_count = multiprocessing.cpu_count()
432*4882a593Smuzhiyun    except NotImplementedError:
433*4882a593Smuzhiyun        cpu_count = 1
434*4882a593Smuzhiyun
435*4882a593Smuzhiyun    parser = optparse.OptionParser()
436*4882a593Smuzhiyun    # Add options here
437*4882a593Smuzhiyun    parser.add_option('-f', '--force', action="store_true", default=False,
438*4882a593Smuzhiyun                      help='regenerate the output even if it is new')
439*4882a593Smuzhiyun    parser.add_option('-j', '--jobs', type='int', default=cpu_count,
440*4882a593Smuzhiyun                      help='the number of jobs to run simultaneously')
441*4882a593Smuzhiyun    parser.add_option('-o', '--output', default=OUTPUT_FILE,
442*4882a593Smuzhiyun                      help='output file [default=%s]' % OUTPUT_FILE)
443*4882a593Smuzhiyun    (options, args) = parser.parse_args()
444*4882a593Smuzhiyun
445*4882a593Smuzhiyun    gen_boards_cfg(options.output, jobs=options.jobs, force=options.force)
446*4882a593Smuzhiyun
447*4882a593Smuzhiyunif __name__ == '__main__':
448*4882a593Smuzhiyun    main()
449