summaryrefslogtreecommitdiff
path: root/scripts/multiconfig.py
blob: 749abcb7a51e7dab35114fa784aefe5b81bfe4ed (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
#!/usr/bin/env python
#
# Copyright (C) 2014, Masahiro Yamada <yamada.m@jp.panasonic.com>
#
# SPDX-License-Identifier:	GPL-2.0+
#

"""
A wrapper script to adjust Kconfig for U-Boot

The biggest difference between Linux Kernel and U-Boot in terms of the
board configuration is that U-Boot has to configure multiple boot images
per board: Normal, SPL, TPL.
We need to expand the functions of Kconfig to handle multiple boot
images.

Instead of touching various parts under the scripts/kconfig/ directory,
pushing necessary adjustments into this single script would be better
for code maintainance. All the make targets related to the configuration
(make %config) should be invoked via this script.

Let's see what is different from the original Kconfig.

- config, menuconfig, etc.

The commands 'make config', 'make menuconfig', etc. are used to create
or modify the .config file, which stores configs for Normal boot image.

The location of the one for SPL, TPL image is spl/.config, tpl/.config,
respectively. Use 'make spl/config', 'make spl/menuconfig', etc.
to create or modify the spl/.config file, which contains configs
for SPL image.
Do likewise for the tpl/.config file.
The generic syntax for SPL, TPL configuration is
'make <target_image>/<config_command>'.

- silentoldconfig

The command 'make silentoldconfig' updates .config, if necessary, and
additionally updates include/generated/autoconf.h and files under
include/configs/ directory. In U-Boot, it should do the same things for
SPL, TPL images for boards supporting them.
Depending on whether CONFIG_SPL, CONFIG_TPL is defined or not,
'make silentoldconfig' iterates three times at most changing the target
directory.

To sum up, 'make silentoldconfig' possibly updates
  - .config, include/generated/autoconf.h, include/config/*
  - spl/.config, spl/include/generated/autoconf.h, spl/include/config/*
    (in case CONFIG_SPL=y)
  - tpl/.config, tpl/include/generated/autoconf.h, tpl/include/config/*
    (in case CONFIG_TPL=y)

- defconfig, <board>_defconfig

The command 'make <board>_defconfig' creates a new .config based on the
file configs/<board>_defconfig. The command 'make defconfig' is the same
but the difference is it uses the file specified with KBUILD_DEFCONFIG
environment.

We need to create .config, spl/.config, tpl/.config for boards where SPL
and TPL images are supported. One possible solution for that is to have
multiple defconfig files per board, but it would produce duplication
among the defconfigs.
The approach chosen here is to expand the feature and support
conditional definition in defconfig, that is, each line in defconfig
files has the form of:
<condition>:<macro definition>

The '<condition>:' prefix specifies which image the line is valid for.
The '<condition>:' is one of:
  None  - the line is valid only for Normal image
  S:    - the line is valid only for SPL image
  T:    - the line is valid only for TPL image
  ST:   - the line is valid for SPL and TPL images
  +S:   - the line is valid for Normal and SPL images
  +T:   - the line is valid for Normal and TPL images
  +ST:  - the line is valid for Normal, SPL and SPL images

So, if neither CONFIG_SPL nor CONFIG_TPL is defined, the defconfig file
has no '<condition>:' part and therefore has the same form of that of
Linux Kernel.

In U-Boot, for example, a defconfig file can be written like this:

  CONFIG_FOO=100
  S:CONFIG_FOO=200
  T:CONFIG_FOO=300
  ST:CONFIG_BAR=y
  +S:CONFIG_BAZ=y
  +T:CONFIG_QUX=y
  +ST:CONFIG_QUUX=y

The defconfig above is parsed by this script and internally divided into
three temporary defconfig files.

  - Temporary defconfig for Normal image
     CONFIG_FOO=100
     CONFIG_BAZ=y
     CONFIG_QUX=y
     CONFIG_QUUX=y

  - Temporary defconfig for SPL image
     CONFIG_FOO=200
     CONFIG_BAR=y
     CONFIG_BAZ=y
     CONFIG_QUUX=y

  - Temporary defconfig for TPL image
     CONFIG_FOO=300
     CONFIG_BAR=y
     CONFIG_QUX=y
     CONFIG_QUUX=y

They are passed to scripts/kconfig/conf, each is used for generating
.config, spl/.config, tpl/.config, respectively.

- savedefconfig

This is the reverse operation of 'make defconfig'.
If neither CONFIG_SPL nor CONFIG_TPL is defined in the .config file,
it works as 'make savedefconfig' in Linux Kernel: create the minimal set
of config based on the .config and save it into 'defconfig' file.

If CONFIG_SPL or CONFIG_TPL is defined, the common lines among .config,
spl/.config, tpl/.config are coalesced together and output to the file
'defconfig' in the form like:

  CONFIG_FOO=100
  S:CONFIG_FOO=200
  T:CONFIG_FOO=300
  ST:CONFIG_BAR=y
  +S:CONFIG_BAZ=y
  +T:CONFIG_QUX=y
  +ST:CONFIG_QUUX=y

This can be used as an input of 'make <board>_defconfig' command.
"""

import errno
import os
import re
import subprocess
import sys

# Constant variables
SUB_IMAGES = ('spl', 'tpl')
IMAGES = ('',) + SUB_IMAGES
SYMBOL_MAP = {'': '+', 'spl': 'S', 'tpl': 'T'}
PATTERN_SYMBOL = re.compile(r'(\+?)(S?)(T?):(.*)')

# Environment variables (should be defined in the top Makefile)
# .get('key', 'default_value') method is useful for standalone testing.
MAKE = os.environ.get('MAKE', 'make')
srctree = os.environ.get('srctree', '.')
KCONFIG_CONFIG = os.environ.get('KCONFIG_CONFIG', '.config')

# Useful shorthand
build = '%s -f %s/scripts/Makefile.build obj=scripts/kconfig %%s' % (MAKE, srctree)
autoconf = '%s -f %s/scripts/Makefile.autoconf obj=%%s %%s' % (MAKE, srctree)

### helper functions ###
def mkdirs(*dirs):
    """Make directories ignoring 'File exists' error."""
    for d in dirs:
        try:
            os.makedirs(d)
        except OSError as exception:
            # Ignore 'File exists' error
            if exception.errno != errno.EEXIST:
                raise

def rmfiles(*files):
    """Remove files ignoring 'No such file or directory' error."""
    for f in files:
        try:
            os.remove(f)
        except OSError as exception:
            # Ignore 'No such file or directory' error
            if exception.errno != errno.ENOENT:
                raise

def rmdirs(*dirs):
    """Remove directories ignoring 'No such file or directory'
    and 'Directory not empty' error.
    """
    for d in dirs:
        try:
            os.rmdir(d)
        except OSError as exception:
            # Ignore 'No such file or directory'
            # and 'Directory not empty' error
            if exception.errno != errno.ENOENT and \
               exception.errno != errno.ENOTEMPTY:
                raise

def error(msg):
    """Output the given argument to stderr and exit with return code 1."""
    print >> sys.stderr, msg
    sys.exit(1)

def run_command(command, callback_on_error=None):
    """Run the given command in a sub-shell (and exit if it fails).

    Arguments:
      command: A string of the command
      callback_on_error: Callback handler invoked just before exit
                         when the command fails (Default=None)
    """
    retcode = subprocess.call(command, shell=True)
    if retcode:
        if callback_on_error:
            callback_on_error()
        error("'%s' Failed" % command)

def run_make_config(cmd, objdir, callback_on_error=None):
    """Run the make command in a sub-shell (and exit if it fails).

    Arguments:
      cmd: Make target such as 'config', 'menuconfig', 'defconfig', etc.
      objdir: Target directory where the make command is run.
              Typically '', 'spl', 'tpl' for Normal, SPL, TPL image,
              respectively.
      callback_on_error: Callback handler invoked just before exit
                         when the command fails (Default=None)
    """
    # Linux expects defconfig files in arch/$(SRCARCH)/configs/ directory,
    # but U-Boot puts them in configs/ directory.
    # Give SRCARCH=.. to fake scripts/kconfig/Makefile.
    options = 'SRCARCH=.. KCONFIG_OBJDIR=%s' % objdir
    if objdir:
        options += ' KCONFIG_CONFIG=%s/%s' % (objdir, KCONFIG_CONFIG)
        mkdirs(objdir)
    run_command(build % cmd + ' ' + options, callback_on_error)

def get_enabled_subimages(ignore_error=False):
    """Parse .config file to detect if CONFIG_SPL, CONFIG_TPL is enabled
    and return a tuple of enabled subimages.

    Arguments:
      ignore_error: Specify the behavior when '.config' is not found;
                    Raise an exception if this flag is False.
                    Return a null tuple if this flag is True.

    Returns:
      A tuple of enabled subimages as follows:
        ()             if neither CONFIG_SPL nor CONFIG_TPL is defined
        ('spl',)       if CONFIG_SPL is defined but CONFIG_TPL is not
        ('spl', 'tpl') if both CONFIG_SPL and CONFIG_TPL are defined
    """
    enabled = ()
    match_patterns = [ (img, 'CONFIG_' + img.upper() + '=y\n')
                                                        for img in SUB_IMAGES ]
    try:
        f = open(KCONFIG_CONFIG)
    except IOError as exception:
        if not ignore_error or exception.errno != errno.ENOENT:
            raise
        return enabled
    with f:
        for line in f:
            for img, pattern in match_patterns:
                if line == pattern:
                    enabled += (img,)
    return enabled

def do_silentoldconfig(cmd):
    """Run 'make silentoldconfig' for all the enabled images.

    Arguments:
      cmd: should always be a string 'silentoldconfig'
    """
    run_make_config(cmd, '')
    subimages = get_enabled_subimages()
    for obj in subimages:
        mkdirs(os.path.join(obj, 'include', 'config'),
               os.path.join(obj, 'include', 'generated'))
        run_make_config(cmd, obj)
    remove_auto_conf = lambda : rmfiles('include/config/auto.conf')
    # If the following part failed, include/config/auto.conf should be deleted
    # so 'make silentoldconfig' will be re-run on the next build.
    run_command(autoconf %
                ('include', 'include/autoconf.mk include/autoconf.mk.dep'),
                remove_auto_conf)
    # include/config.h has been updated after 'make silentoldconfig'.
    # We need to touch include/config/auto.conf so it gets newer
    # than include/config.h.
    # Otherwise, 'make silentoldconfig' would be invoked twice.
    os.utime('include/config/auto.conf', None)
    for obj in subimages:
        run_command(autoconf % (obj + '/include',
                                obj + '/include/autoconf.mk'),
                    remove_auto_conf)

def do_tmp_defconfig(output_lines, img):
    """Helper function for do_board_defconfig().

    Write the defconfig contents into a file '.tmp_defconfig' and
    invoke 'make .tmp_defconfig'.

    Arguments:
      output_lines: A sequence of defconfig lines of each image
      img: Target image. Typically '', 'spl', 'tpl' for
           Normal, SPL, TPL images, respectively.
    """
    TMP_DEFCONFIG = '.tmp_defconfig'
    TMP_DIRS = ('arch', 'configs')
    defconfig_path = os.path.join('configs', TMP_DEFCONFIG)
    mkdirs(*TMP_DIRS)
    with open(defconfig_path, 'w') as f:
        f.write(''.join(output_lines[img]))
    cleanup = lambda: (rmfiles(defconfig_path), rmdirs(*TMP_DIRS))
    run_make_config(TMP_DEFCONFIG, img, cleanup)
    cleanup()

def do_board_defconfig(cmd):
    """Run 'make <board>_defconfig'.

    Arguments:
      cmd: should be a string '<board>_defconfig'
    """
    defconfig_path = os.path.join(srctree, 'configs', cmd)
    output_lines = dict([ (img, []) for img in IMAGES ])
    with open(defconfig_path) as f:
        for line in f:
            m = PATTERN_SYMBOL.match(line)
            if m:
                for idx, img in enumerate(IMAGES):
                    if m.group(idx + 1):
                        output_lines[img].append(m.group(4) + '\n')
                continue
            output_lines[''].append(line)
    do_tmp_defconfig(output_lines, '')
    for img in get_enabled_subimages():
        do_tmp_defconfig(output_lines, img)

def do_defconfig(cmd):
    """Run 'make defconfig'.

    Arguments:
      cmd: should always be a string 'defconfig'
    """
    KBUILD_DEFCONFIG = os.environ['KBUILD_DEFCONFIG']
    print "*** Default configuration is based on '%s'" % KBUILD_DEFCONFIG
    do_board_defconfig(KBUILD_DEFCONFIG)

def do_savedefconfig(cmd):
    """Run 'make savedefconfig'.

    Arguments:
      cmd: should always be a string 'savedefconfig'
    """
    DEFCONFIG = 'defconfig'
    # Continue even if '.config' does not exist
    subimages = get_enabled_subimages(True)
    run_make_config(cmd, '')
    output_lines = []
    prefix = {}
    with open(DEFCONFIG) as f:
        for line in f:
            output_lines.append(line)
            prefix[line] = '+'
    for img in subimages:
        run_make_config(cmd, img)
        unmatched_lines = []
        with open(DEFCONFIG) as f:
            for line in f:
                if line in output_lines:
                    index = output_lines.index(line)
                    output_lines[index:index] = unmatched_lines
                    unmatched_lines = []
                    prefix[line] += SYMBOL_MAP[img]
                else:
                    ummatched_lines.append(line)
                    prefix[line] = SYMBOL_MAP[img]
    with open(DEFCONFIG, 'w') as f:
        for line in output_lines:
            if prefix[line] == '+':
                f.write(line)
            else:
                f.write(prefix[line] + ':' + line)

def do_others(cmd):
    """Run the make command other than 'silentoldconfig', 'defconfig',
    '<board>_defconfig' and 'savedefconfig'.

    Arguments:
      cmd: Make target in the form of '<target_image>/<config_command>'
           The field '<target_image>/' is typically empty, 'spl/', 'tpl/'
           for Normal, SPL, TPL images, respectively.
           The field '<config_command>' is make target such as 'config',
           'menuconfig', etc.
    """
    objdir, _, cmd = cmd.rpartition('/')
    run_make_config(cmd, objdir)

cmd_list = {'silentoldconfig': do_silentoldconfig,
            'defconfig': do_defconfig,
            'savedefconfig': do_savedefconfig}

def main():
    cmd = sys.argv[1]
    if cmd.endswith('_defconfig'):
        do_board_defconfig(cmd)
    else:
        func = cmd_list.get(cmd, do_others)
        func(cmd)

if __name__ == '__main__':
    main()