tools, scripts: refactor error-out statements of Python scripts
[platform/kernel/u-boot.git] / scripts / multiconfig.py
1 #!/usr/bin/env python
2 #
3 # Copyright (C) 2014, Masahiro Yamada <yamada.m@jp.panasonic.com>
4 #
5 # SPDX-License-Identifier:      GPL-2.0+
6 #
7
8 """
9 A wrapper script to adjust Kconfig for U-Boot
10
11 The biggest difference between Linux Kernel and U-Boot in terms of the
12 board configuration is that U-Boot has to configure multiple boot images
13 per board: Normal, SPL, TPL.
14 We need to expand the functions of Kconfig to handle multiple boot
15 images.
16
17 Instead of touching various parts under the scripts/kconfig/ directory,
18 pushing necessary adjustments into this single script would be better
19 for code maintainance. All the make targets related to the configuration
20 (make %config) should be invoked via this script.
21
22 Let's see what is different from the original Kconfig.
23
24 - config, menuconfig, etc.
25
26 The commands 'make config', 'make menuconfig', etc. are used to create
27 or modify the .config file, which stores configs for Normal boot image.
28
29 The location of the one for SPL, TPL image is spl/.config, tpl/.config,
30 respectively. Use 'make spl/config', 'make spl/menuconfig', etc.
31 to create or modify the spl/.config file, which contains configs
32 for SPL image.
33 Do likewise for the tpl/.config file.
34 The generic syntax for SPL, TPL configuration is
35 'make <target_image>/<config_command>'.
36
37 - silentoldconfig
38
39 The command 'make silentoldconfig' updates .config, if necessary, and
40 additionally updates include/generated/autoconf.h and files under
41 include/configs/ directory. In U-Boot, it should do the same things for
42 SPL, TPL images for boards supporting them.
43 Depending on whether CONFIG_SPL, CONFIG_TPL is defined or not,
44 'make silentoldconfig' iterates three times at most changing the target
45 directory.
46
47 To sum up, 'make silentoldconfig' possibly updates
48   - .config, include/generated/autoconf.h, include/config/*
49   - spl/.config, spl/include/generated/autoconf.h, spl/include/config/*
50     (in case CONFIG_SPL=y)
51   - tpl/.config, tpl/include/generated/autoconf.h, tpl/include/config/*
52     (in case CONFIG_TPL=y)
53
54 - defconfig, <board>_defconfig
55
56 The command 'make <board>_defconfig' creates a new .config based on the
57 file configs/<board>_defconfig. The command 'make defconfig' is the same
58 but the difference is it uses the file specified with KBUILD_DEFCONFIG
59 environment.
60
61 We need to create .config, spl/.config, tpl/.config for boards where SPL
62 and TPL images are supported. One possible solution for that is to have
63 multiple defconfig files per board, but it would produce duplication
64 among the defconfigs.
65 The approach chosen here is to expand the feature and support
66 conditional definition in defconfig, that is, each line in defconfig
67 files has the form of:
68 <condition>:<macro definition>
69
70 The '<condition>:' prefix specifies which image the line is valid for.
71 The '<condition>:' is one of:
72   None  - the line is valid only for Normal image
73   S:    - the line is valid only for SPL image
74   T:    - the line is valid only for TPL image
75   ST:   - the line is valid for SPL and TPL images
76   +S:   - the line is valid for Normal and SPL images
77   +T:   - the line is valid for Normal and TPL images
78   +ST:  - the line is valid for Normal, SPL and SPL images
79
80 So, if neither CONFIG_SPL nor CONFIG_TPL is defined, the defconfig file
81 has no '<condition>:' part and therefore has the same form of that of
82 Linux Kernel.
83
84 In U-Boot, for example, a defconfig file can be written like this:
85
86   CONFIG_FOO=100
87   S:CONFIG_FOO=200
88   T:CONFIG_FOO=300
89   ST:CONFIG_BAR=y
90   +S:CONFIG_BAZ=y
91   +T:CONFIG_QUX=y
92   +ST:CONFIG_QUUX=y
93
94 The defconfig above is parsed by this script and internally divided into
95 three temporary defconfig files.
96
97   - Temporary defconfig for Normal image
98      CONFIG_FOO=100
99      CONFIG_BAZ=y
100      CONFIG_QUX=y
101      CONFIG_QUUX=y
102
103   - Temporary defconfig for SPL image
104      CONFIG_FOO=200
105      CONFIG_BAR=y
106      CONFIG_BAZ=y
107      CONFIG_QUUX=y
108
109   - Temporary defconfig for TPL image
110      CONFIG_FOO=300
111      CONFIG_BAR=y
112      CONFIG_QUX=y
113      CONFIG_QUUX=y
114
115 They are passed to scripts/kconfig/conf, each is used for generating
116 .config, spl/.config, tpl/.config, respectively.
117
118 - savedefconfig
119
120 This is the reverse operation of 'make defconfig'.
121 If neither CONFIG_SPL nor CONFIG_TPL is defined in the .config file,
122 it works as 'make savedefconfig' in Linux Kernel: create the minimal set
123 of config based on the .config and save it into 'defconfig' file.
124
125 If CONFIG_SPL or CONFIG_TPL is defined, the common lines among .config,
126 spl/.config, tpl/.config are coalesced together and output to the file
127 'defconfig' in the form like:
128
129   CONFIG_FOO=100
130   S:CONFIG_FOO=200
131   T:CONFIG_FOO=300
132   ST:CONFIG_BAR=y
133   +S:CONFIG_BAZ=y
134   +T:CONFIG_QUX=y
135   +ST:CONFIG_QUUX=y
136
137 This can be used as an input of 'make <board>_defconfig' command.
138 """
139
140 import errno
141 import os
142 import re
143 import subprocess
144 import sys
145
146 # Constant variables
147 SUB_IMAGES = ('spl', 'tpl')
148 IMAGES = ('',) + SUB_IMAGES
149 SYMBOL_MAP = {'': '+', 'spl': 'S', 'tpl': 'T'}
150 PATTERN_SYMBOL = re.compile(r'(\+?)(S?)(T?):(.*)')
151
152 # Environment variables (should be defined in the top Makefile)
153 # .get('key', 'default_value') method is useful for standalone testing.
154 MAKE = os.environ.get('MAKE', 'make')
155 srctree = os.environ.get('srctree', '.')
156 KCONFIG_CONFIG = os.environ.get('KCONFIG_CONFIG', '.config')
157
158 # Useful shorthand
159 build = '%s -f %s/scripts/Makefile.build obj=scripts/kconfig %%s' % (MAKE, srctree)
160 autoconf = '%s -f %s/scripts/Makefile.autoconf obj=%%s %%s' % (MAKE, srctree)
161
162 ### helper functions ###
163 def mkdirs(*dirs):
164     """Make directories ignoring 'File exists' error."""
165     for d in dirs:
166         try:
167             os.makedirs(d)
168         except OSError as exception:
169             # Ignore 'File exists' error
170             if exception.errno != errno.EEXIST:
171                 raise
172
173 def rmfiles(*files):
174     """Remove files ignoring 'No such file or directory' error."""
175     for f in files:
176         try:
177             os.remove(f)
178         except OSError as exception:
179             # Ignore 'No such file or directory' error
180             if exception.errno != errno.ENOENT:
181                 raise
182
183 def rmdirs(*dirs):
184     """Remove directories ignoring 'No such file or directory'
185     and 'Directory not empty' error.
186     """
187     for d in dirs:
188         try:
189             os.rmdir(d)
190         except OSError as exception:
191             # Ignore 'No such file or directory'
192             # and 'Directory not empty' error
193             if exception.errno != errno.ENOENT and \
194                exception.errno != errno.ENOTEMPTY:
195                 raise
196
197 def run_command(command, callback_on_error=None):
198     """Run the given command in a sub-shell (and exit if it fails).
199
200     Arguments:
201       command: A string of the command
202       callback_on_error: Callback handler invoked just before exit
203                          when the command fails (Default=None)
204     """
205     retcode = subprocess.call(command, shell=True)
206     if retcode:
207         if callback_on_error:
208             callback_on_error()
209         sys.exit("'%s' Failed" % command)
210
211 def run_make_config(cmd, objdir, callback_on_error=None):
212     """Run the make command in a sub-shell (and exit if it fails).
213
214     Arguments:
215       cmd: Make target such as 'config', 'menuconfig', 'defconfig', etc.
216       objdir: Target directory where the make command is run.
217               Typically '', 'spl', 'tpl' for Normal, SPL, TPL image,
218               respectively.
219       callback_on_error: Callback handler invoked just before exit
220                          when the command fails (Default=None)
221     """
222     # Linux expects defconfig files in arch/$(SRCARCH)/configs/ directory,
223     # but U-Boot puts them in configs/ directory.
224     # Give SRCARCH=.. to fake scripts/kconfig/Makefile.
225     options = 'SRCARCH=.. KCONFIG_OBJDIR=%s' % objdir
226     if objdir:
227         options += ' KCONFIG_CONFIG=%s/%s' % (objdir, KCONFIG_CONFIG)
228         mkdirs(objdir)
229     run_command(build % cmd + ' ' + options, callback_on_error)
230
231 def get_enabled_subimages(ignore_error=False):
232     """Parse .config file to detect if CONFIG_SPL, CONFIG_TPL is enabled
233     and return a tuple of enabled subimages.
234
235     Arguments:
236       ignore_error: Specify the behavior when '.config' is not found;
237                     Raise an exception if this flag is False.
238                     Return a null tuple if this flag is True.
239
240     Returns:
241       A tuple of enabled subimages as follows:
242         ()             if neither CONFIG_SPL nor CONFIG_TPL is defined
243         ('spl',)       if CONFIG_SPL is defined but CONFIG_TPL is not
244         ('spl', 'tpl') if both CONFIG_SPL and CONFIG_TPL are defined
245     """
246     enabled = ()
247     match_patterns = [ (img, 'CONFIG_' + img.upper() + '=y\n')
248                                                         for img in SUB_IMAGES ]
249     try:
250         f = open(KCONFIG_CONFIG)
251     except IOError as exception:
252         if not ignore_error or exception.errno != errno.ENOENT:
253             raise
254         return enabled
255     with f:
256         for line in f:
257             for img, pattern in match_patterns:
258                 if line == pattern:
259                     enabled += (img,)
260     return enabled
261
262 def do_silentoldconfig(cmd):
263     """Run 'make silentoldconfig' for all the enabled images.
264
265     Arguments:
266       cmd: should always be a string 'silentoldconfig'
267     """
268     run_make_config(cmd, '')
269     subimages = get_enabled_subimages()
270     for obj in subimages:
271         mkdirs(os.path.join(obj, 'include', 'config'),
272                os.path.join(obj, 'include', 'generated'))
273         run_make_config(cmd, obj)
274     remove_auto_conf = lambda : rmfiles('include/config/auto.conf')
275     # If the following part failed, include/config/auto.conf should be deleted
276     # so 'make silentoldconfig' will be re-run on the next build.
277     run_command(autoconf %
278                 ('include', 'include/autoconf.mk include/autoconf.mk.dep'),
279                 remove_auto_conf)
280     # include/config.h has been updated after 'make silentoldconfig'.
281     # We need to touch include/config/auto.conf so it gets newer
282     # than include/config.h.
283     # Otherwise, 'make silentoldconfig' would be invoked twice.
284     os.utime('include/config/auto.conf', None)
285     for obj in subimages:
286         run_command(autoconf % (obj + '/include',
287                                 obj + '/include/autoconf.mk'),
288                     remove_auto_conf)
289
290 def do_tmp_defconfig(output_lines, img):
291     """Helper function for do_board_defconfig().
292
293     Write the defconfig contents into a file '.tmp_defconfig' and
294     invoke 'make .tmp_defconfig'.
295
296     Arguments:
297       output_lines: A sequence of defconfig lines of each image
298       img: Target image. Typically '', 'spl', 'tpl' for
299            Normal, SPL, TPL images, respectively.
300     """
301     TMP_DEFCONFIG = '.tmp_defconfig'
302     TMP_DIRS = ('arch', 'configs')
303     defconfig_path = os.path.join('configs', TMP_DEFCONFIG)
304     mkdirs(*TMP_DIRS)
305     with open(defconfig_path, 'w') as f:
306         f.write(''.join(output_lines[img]))
307     cleanup = lambda: (rmfiles(defconfig_path), rmdirs(*TMP_DIRS))
308     run_make_config(TMP_DEFCONFIG, img, cleanup)
309     cleanup()
310
311 def do_board_defconfig(cmd):
312     """Run 'make <board>_defconfig'.
313
314     Arguments:
315       cmd: should be a string '<board>_defconfig'
316     """
317     defconfig_path = os.path.join(srctree, 'configs', cmd)
318     output_lines = dict([ (img, []) for img in IMAGES ])
319     with open(defconfig_path) as f:
320         for line in f:
321             m = PATTERN_SYMBOL.match(line)
322             if m:
323                 for idx, img in enumerate(IMAGES):
324                     if m.group(idx + 1):
325                         output_lines[img].append(m.group(4) + '\n')
326                 continue
327             output_lines[''].append(line)
328     do_tmp_defconfig(output_lines, '')
329     for img in get_enabled_subimages():
330         do_tmp_defconfig(output_lines, img)
331
332 def do_defconfig(cmd):
333     """Run 'make defconfig'.
334
335     Arguments:
336       cmd: should always be a string 'defconfig'
337     """
338     KBUILD_DEFCONFIG = os.environ['KBUILD_DEFCONFIG']
339     print "*** Default configuration is based on '%s'" % KBUILD_DEFCONFIG
340     do_board_defconfig(KBUILD_DEFCONFIG)
341
342 def do_savedefconfig(cmd):
343     """Run 'make savedefconfig'.
344
345     Arguments:
346       cmd: should always be a string 'savedefconfig'
347     """
348     DEFCONFIG = 'defconfig'
349     # Continue even if '.config' does not exist
350     subimages = get_enabled_subimages(True)
351     run_make_config(cmd, '')
352     output_lines = []
353     prefix = {}
354     with open(DEFCONFIG) as f:
355         for line in f:
356             output_lines.append(line)
357             prefix[line] = '+'
358     for img in subimages:
359         run_make_config(cmd, img)
360         unmatched_lines = []
361         with open(DEFCONFIG) as f:
362             for line in f:
363                 if line in output_lines:
364                     index = output_lines.index(line)
365                     output_lines[index:index] = unmatched_lines
366                     unmatched_lines = []
367                     prefix[line] += SYMBOL_MAP[img]
368                 else:
369                     ummatched_lines.append(line)
370                     prefix[line] = SYMBOL_MAP[img]
371     with open(DEFCONFIG, 'w') as f:
372         for line in output_lines:
373             if prefix[line] == '+':
374                 f.write(line)
375             else:
376                 f.write(prefix[line] + ':' + line)
377
378 def do_others(cmd):
379     """Run the make command other than 'silentoldconfig', 'defconfig',
380     '<board>_defconfig' and 'savedefconfig'.
381
382     Arguments:
383       cmd: Make target in the form of '<target_image>/<config_command>'
384            The field '<target_image>/' is typically empty, 'spl/', 'tpl/'
385            for Normal, SPL, TPL images, respectively.
386            The field '<config_command>' is make target such as 'config',
387            'menuconfig', etc.
388     """
389     objdir, _, cmd = cmd.rpartition('/')
390     run_make_config(cmd, objdir)
391
392 cmd_list = {'silentoldconfig': do_silentoldconfig,
393             'defconfig': do_defconfig,
394             'savedefconfig': do_savedefconfig}
395
396 def main():
397     cmd = sys.argv[1]
398     if cmd.endswith('_defconfig'):
399         do_board_defconfig(cmd)
400     else:
401         func = cmd_list.get(cmd, do_others)
402         func(cmd)
403
404 if __name__ == '__main__':
405     main()