tools: moveconfig: a tool to move CONFIGs from headers to defconfigs
[platform/kernel/u-boot.git] / tools / moveconfig.py
1 #!/usr/bin/env python2
2 #
3 # Author: Masahiro Yamada <yamada.masahiro@socionext.com>
4 #
5 # SPDX-License-Identifier:      GPL-2.0+
6 #
7
8 """
9 Move config options from headers to defconfig files.
10
11 Since Kconfig was introduced to U-Boot, we have worked on moving
12 config options from headers to Kconfig (defconfig).
13
14 This tool intends to help this tremendous work.
15
16
17 Usage
18 -----
19
20 This tool takes one input file.  (let's say 'recipe' file here.)
21 The recipe describes the list of config options you want to move.
22 Each line takes the form:
23 <config_name> <type> <default>
24 (the fields must be separated with whitespaces.)
25
26 <config_name> is the name of config option.
27
28 <type> is the type of the option.  It must be one of bool, tristate,
29 string, int, and hex.
30
31 <default> is the default value of the option.  It must be appropriate
32 value corresponding to the option type.  It must be either y or n for
33 the bool type.  Tristate options can also take m (although U-Boot has
34 not supported the module feature).
35
36 You can add two or more lines in the recipe file, so you can move
37 multiple options at once.
38
39 Let's say, for example, you want to move CONFIG_CMD_USB and
40 CONFIG_SYS_TEXT_BASE.
41
42 The type should be bool, hex, respectively.  So, the recipe file
43 should look like this:
44
45   $ cat recipe
46   CONFIG_CMD_USB bool n
47   CONFIG_SYS_TEXT_BASE hex 0x00000000
48
49 And then run this tool giving the file name of the recipe
50
51   $ tools/moveconfig.py recipe
52
53 The tool walks through all the defconfig files to move the config
54 options specified by the recipe file.
55
56 The log is also displayed on the terminal.
57
58 Each line is printed in the format
59 <defconfig_name>   :  <action>
60
61 <defconfig_name> is the name of the defconfig
62 (without the suffix _defconfig).
63
64 <action> shows what the tool did for that defconfig.
65 It looks like one of the followings:
66
67  - Move 'CONFIG_... '
68    This config option was moved to the defconfig
69
70  - Default value 'CONFIG_...'.  Do nothing.
71    The value of this option is the same as default.
72    We do not have to add it to the defconfig.
73
74  - 'CONFIG_...' already exists in Kconfig.  Do nothing.
75    This config option is already defined in Kconfig.
76    We do not need/want to touch it.
77
78  - Undefined.  Do nothing.
79    This config option was not found in the config header.
80    Nothing to do.
81
82  - Failed to process.  Skip.
83    An error occurred during processing this defconfig.  Skipped.
84    (If -e option is passed, the tool exits immediately on error.)
85
86 Finally, you will be asked, Clean up headers? [y/n]:
87
88 If you say 'y' here, the unnecessary config defines are removed
89 from the config headers (include/configs/*.h).
90 It just uses the regex method, so you should not rely on it.
91 Just in case, please do 'git diff' to see what happened.
92
93
94 How does it works?
95 ------------------
96
97 This tool runs configuration and builds include/autoconf.mk for every
98 defconfig.  The config options defined in Kconfig appear in the .config
99 file (unless they are hidden because of unmet dependency.)
100 On the other hand, the config options defined by board headers are seen
101 in include/autoconf.mk.  The tool looks for the specified options in both
102 of them to decide the appropriate action for the options.  If the option
103 is found in the .config or the value is the same as the specified default,
104 the option does not need to be touched.  If the option is found in
105 include/autoconf.mk, but not in the .config, and the value is different
106 from the default, the tools adds the option to the defconfig.
107
108 For faster processing, this tool handles multi-threading.  It creates
109 separate build directories where the out-of-tree build is run.  The
110 temporary build directories are automatically created and deleted as
111 needed.  The number of threads are chosen based on the number of the CPU
112 cores of your system although you can change it via -j (--jobs) option.
113
114
115 Toolchains
116 ----------
117
118 Appropriate toolchain are necessary to generate include/autoconf.mk
119 for all the architectures supported by U-Boot.  Most of them are available
120 at the kernel.org site, some are not provided by kernel.org.
121
122 The default per-arch CROSS_COMPILE used by this tool is specified by
123 the list below, CROSS_COMPILE.  You may wish to update the list to
124 use your own.  Instead of modifying the list directly, you can give
125 them via environments.
126
127
128 Available options
129 -----------------
130
131  -c, --color
132    Surround each portion of the log with escape sequences to display it
133    in color on the terminal.
134
135  -n, --dry-run
136    Peform a trial run that does not make any changes.  It is useful to
137    see what is going to happen before one actually runs it.
138
139  -e, --exit-on-error
140    Exit immediately if Make exits with a non-zero status while processing
141    a defconfig file.
142
143  -j, --jobs
144    Specify the number of threads to run simultaneously.  If not specified,
145    the number of threads is the same as the number of CPU cores.
146
147 To see the complete list of supported options, run
148
149   $ tools/moveconfig.py -h
150
151 """
152
153 import fnmatch
154 import multiprocessing
155 import optparse
156 import os
157 import re
158 import shutil
159 import subprocess
160 import sys
161 import tempfile
162 import time
163
164 SHOW_GNU_MAKE = 'scripts/show-gnu-make'
165 SLEEP_TIME=0.03
166
167 # Here is the list of cross-tools I use.
168 # Most of them are available at kernel.org
169 # (https://www.kernel.org/pub/tools/crosstool/files/bin/), except the followings:
170 # arc: https://github.com/foss-for-synopsys-dwc-arc-processors/toolchain/releases
171 # blackfin: http://sourceforge.net/projects/adi-toolchain/files/
172 # nds32: http://osdk.andestech.com/packages/
173 # nios2: https://sourcery.mentor.com/GNUToolchain/subscription42545
174 # sh: http://sourcery.mentor.com/public/gnu_toolchain/sh-linux-gnu
175 CROSS_COMPILE = {
176     'arc': 'arc-linux-',
177     'aarch64': 'aarch64-linux-',
178     'arm': 'arm-unknown-linux-gnueabi-',
179     'avr32': 'avr32-linux-',
180     'blackfin': 'bfin-elf-',
181     'm68k': 'm68k-linux-',
182     'microblaze': 'microblaze-linux-',
183     'mips': 'mips-linux-',
184     'nds32': 'nds32le-linux-',
185     'nios2': 'nios2-linux-gnu-',
186     'openrisc': 'or32-linux-',
187     'powerpc': 'powerpc-linux-',
188     'sh': 'sh-linux-gnu-',
189     'sparc': 'sparc-linux-',
190     'x86': 'i386-linux-'
191 }
192
193 STATE_IDLE = 0
194 STATE_DEFCONFIG = 1
195 STATE_AUTOCONF = 2
196
197 ACTION_MOVE = 0
198 ACTION_DEFAULT_VALUE = 1
199 ACTION_ALREADY_EXIST = 2
200 ACTION_UNDEFINED = 3
201
202 COLOR_BLACK        = '0;30'
203 COLOR_RED          = '0;31'
204 COLOR_GREEN        = '0;32'
205 COLOR_BROWN        = '0;33'
206 COLOR_BLUE         = '0;34'
207 COLOR_PURPLE       = '0;35'
208 COLOR_CYAN         = '0;36'
209 COLOR_LIGHT_GRAY   = '0;37'
210 COLOR_DARK_GRAY    = '1;30'
211 COLOR_LIGHT_RED    = '1;31'
212 COLOR_LIGHT_GREEN  = '1;32'
213 COLOR_YELLOW       = '1;33'
214 COLOR_LIGHT_BLUE   = '1;34'
215 COLOR_LIGHT_PURPLE = '1;35'
216 COLOR_LIGHT_CYAN   = '1;36'
217 COLOR_WHITE        = '1;37'
218
219 ### helper functions ###
220 def get_devnull():
221     """Get the file object of '/dev/null' device."""
222     try:
223         devnull = subprocess.DEVNULL # py3k
224     except AttributeError:
225         devnull = open(os.devnull, 'wb')
226     return devnull
227
228 def check_top_directory():
229     """Exit if we are not at the top of source directory."""
230     for f in ('README', 'Licenses'):
231         if not os.path.exists(f):
232             sys.exit('Please run at the top of source directory.')
233
234 def get_make_cmd():
235     """Get the command name of GNU Make.
236
237     U-Boot needs GNU Make for building, but the command name is not
238     necessarily "make". (for example, "gmake" on FreeBSD).
239     Returns the most appropriate command name on your system.
240     """
241     process = subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE)
242     ret = process.communicate()
243     if process.returncode:
244         sys.exit('GNU Make not found')
245     return ret[0].rstrip()
246
247 def color_text(color_enabled, color, string):
248     """Return colored string."""
249     if color_enabled:
250         return '\033[' + color + 'm' + string + '\033[0m'
251     else:
252         return string
253
254 def log_msg(color_enabled, color, defconfig, msg):
255     """Return the formated line for the log."""
256     return defconfig[:-len('_defconfig')].ljust(37) + ': ' + \
257         color_text(color_enabled, color, msg) + '\n'
258
259 def update_cross_compile():
260     """Update per-arch CROSS_COMPILE via enviroment variables
261
262     The default CROSS_COMPILE values are available
263     in the CROSS_COMPILE list above.
264
265     You can override them via enviroment variables
266     CROSS_COMPILE_{ARCH}.
267
268     For example, if you want to override toolchain prefixes
269     for ARM and PowerPC, you can do as follows in your shell:
270
271     export CROSS_COMPILE_ARM=...
272     export CROSS_COMPILE_POWERPC=...
273     """
274     archs = []
275
276     for arch in os.listdir('arch'):
277         if os.path.exists(os.path.join('arch', arch, 'Makefile')):
278             archs.append(arch)
279
280     # arm64 is a special case
281     archs.append('aarch64')
282
283     for arch in archs:
284         env = 'CROSS_COMPILE_' + arch.upper()
285         cross_compile = os.environ.get(env)
286         if cross_compile:
287             CROSS_COMPILE[arch] = cross_compile
288
289 def cleanup_one_header(header_path, patterns, dry_run):
290     """Clean regex-matched lines away from a file.
291
292     Arguments:
293       header_path: path to the cleaned file.
294       patterns: list of regex patterns.  Any lines matching to these
295                 patterns are deleted.
296       dry_run: make no changes, but still display log.
297     """
298     with open(header_path) as f:
299         lines = f.readlines()
300
301     matched = []
302     for i, line in enumerate(lines):
303         for pattern in patterns:
304             m = pattern.search(line)
305             if m:
306                 print '%s: %s: %s' % (header_path, i + 1, line),
307                 matched.append(i)
308                 break
309
310     if dry_run or not matched:
311         return
312
313     with open(header_path, 'w') as f:
314         for i, line in enumerate(lines):
315             if not i in matched:
316                 f.write(line)
317
318 def cleanup_headers(config_attrs, dry_run):
319     """Delete config defines from board headers.
320
321     Arguments:
322       config_attrs: A list of dictionaris, each of them includes the name,
323                     the type, and the default value of the target config.
324       dry_run: make no changes, but still display log.
325     """
326     while True:
327         choice = raw_input('Clean up headers? [y/n]: ').lower()
328         print choice
329         if choice == 'y' or choice == 'n':
330             break
331
332     if choice == 'n':
333         return
334
335     patterns = []
336     for config_attr in config_attrs:
337         config = config_attr['config']
338         patterns.append(re.compile(r'#\s*define\s+%s\W' % config))
339         patterns.append(re.compile(r'#\s*undef\s+%s\W' % config))
340
341     for (dirpath, dirnames, filenames) in os.walk('include'):
342         for filename in filenames:
343             if not fnmatch.fnmatch(filename, '*~'):
344                 cleanup_one_header(os.path.join(dirpath, filename), patterns,
345                                    dry_run)
346
347 ### classes ###
348 class KconfigParser:
349
350     """A parser of .config and include/autoconf.mk."""
351
352     re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
353     re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
354
355     def __init__(self, config_attrs, options, build_dir):
356         """Create a new parser.
357
358         Arguments:
359           config_attrs: A list of dictionaris, each of them includes the name,
360                         the type, and the default value of the target config.
361           options: option flags.
362           build_dir: Build directory.
363         """
364         self.config_attrs = config_attrs
365         self.options = options
366         self.build_dir = build_dir
367
368     def get_cross_compile(self):
369         """Parse .config file and return CROSS_COMPILE.
370
371         Returns:
372           A string storing the compiler prefix for the architecture.
373         """
374         arch = ''
375         cpu = ''
376         dotconfig = os.path.join(self.build_dir, '.config')
377         for line in open(dotconfig):
378             m = self.re_arch.match(line)
379             if m:
380                 arch = m.group(1)
381                 continue
382             m = self.re_cpu.match(line)
383             if m:
384                 cpu = m.group(1)
385
386         assert arch, 'Error: arch is not defined in %s' % defconfig
387
388         # fix-up for aarch64
389         if arch == 'arm' and cpu == 'armv8':
390             arch = 'aarch64'
391
392         return CROSS_COMPILE.get(arch, '')
393
394     def parse_one_config(self, config_attr, defconfig_lines,
395                          dotconfig_lines, autoconf_lines):
396         """Parse .config, defconfig, include/autoconf.mk for one config.
397
398         This function looks for the config options in the lines from
399         defconfig, .config, and include/autoconf.mk in order to decide
400         which action should be taken for this defconfig.
401
402         Arguments:
403           config_attr: A dictionary including the name, the type,
404                        and the default value of the target config.
405           defconfig_lines: lines from the original defconfig file.
406           dotconfig_lines: lines from the .config file.
407           autoconf_lines: lines from the include/autoconf.mk file.
408
409         Returns:
410           A tupple of the action for this defconfig and the line
411           matched for the config.
412         """
413         config = config_attr['config']
414         not_set = '# %s is not set' % config
415
416         if config_attr['type'] in ('bool', 'tristate') and \
417            config_attr['default'] == 'n':
418             default = not_set
419         else:
420             default = config + '=' + config_attr['default']
421
422         for line in defconfig_lines + dotconfig_lines:
423             line = line.rstrip()
424             if line.startswith(config + '=') or line == not_set:
425                 return (ACTION_ALREADY_EXIST, line)
426
427         if config_attr['type'] in ('bool', 'tristate'):
428             value = not_set
429         else:
430             value = '(undefined)'
431
432         for line in autoconf_lines:
433             line = line.rstrip()
434             if line.startswith(config + '='):
435                 value = line
436                 break
437
438         if value == default:
439             action = ACTION_DEFAULT_VALUE
440         elif value == '(undefined)':
441             action = ACTION_UNDEFINED
442         else:
443             action = ACTION_MOVE
444
445         return (action, value)
446
447     def update_defconfig(self, defconfig):
448         """Parse files for the config options and update the defconfig.
449
450         This function parses the given defconfig, the generated .config
451         and include/autoconf.mk searching the target options.
452         Move the config option(s) to the defconfig or do nothing if unneeded.
453         Also, display the log to show what happened to this defconfig.
454
455         Arguments:
456           defconfig: defconfig name.
457         """
458
459         defconfig_path = os.path.join('configs', defconfig)
460         dotconfig_path = os.path.join(self.build_dir, '.config')
461         autoconf_path = os.path.join(self.build_dir, 'include', 'autoconf.mk')
462         results = []
463
464         with open(defconfig_path) as f:
465             defconfig_lines = f.readlines()
466
467         with open(dotconfig_path) as f:
468             dotconfig_lines = f.readlines()
469
470         with open(autoconf_path) as f:
471             autoconf_lines = f.readlines()
472
473         for config_attr in self.config_attrs:
474             result = self.parse_one_config(config_attr, defconfig_lines,
475                                            dotconfig_lines, autoconf_lines)
476             results.append(result)
477
478         log = ''
479
480         for (action, value) in results:
481             if action == ACTION_MOVE:
482                 actlog = "Move '%s'" % value
483                 log_color = COLOR_LIGHT_GREEN
484             elif action == ACTION_DEFAULT_VALUE:
485                 actlog = "Default value '%s'.  Do nothing." % value
486                 log_color = COLOR_LIGHT_BLUE
487             elif action == ACTION_ALREADY_EXIST:
488                 actlog = "'%s' already defined in Kconfig.  Do nothing." % value
489                 log_color = COLOR_LIGHT_PURPLE
490             elif action == ACTION_UNDEFINED:
491                 actlog = "Undefined.  Do nothing."
492                 log_color = COLOR_DARK_GRAY
493             else:
494                 sys.exit("Internal Error. This should not happen.")
495
496             log += log_msg(self.options.color, log_color, defconfig, actlog)
497
498         # Some threads are running in parallel.
499         # Print log in one shot to not mix up logs from different threads.
500         print log,
501
502         if not self.options.dry_run:
503             with open(defconfig_path, 'a') as f:
504                 for (action, value) in results:
505                     if action == ACTION_MOVE:
506                         f.write(value + '\n')
507
508         os.remove(os.path.join(self.build_dir, 'include', 'config', 'auto.conf'))
509         os.remove(autoconf_path)
510
511 class Slot:
512
513     """A slot to store a subprocess.
514
515     Each instance of this class handles one subprocess.
516     This class is useful to control multiple threads
517     for faster processing.
518     """
519
520     def __init__(self, config_attrs, options, devnull, make_cmd):
521         """Create a new process slot.
522
523         Arguments:
524           config_attrs: A list of dictionaris, each of them includes the name,
525                         the type, and the default value of the target config.
526           options: option flags.
527           devnull: A file object of '/dev/null'.
528           make_cmd: command name of GNU Make.
529         """
530         self.options = options
531         self.build_dir = tempfile.mkdtemp()
532         self.devnull = devnull
533         self.make_cmd = (make_cmd, 'O=' + self.build_dir)
534         self.parser = KconfigParser(config_attrs, options, self.build_dir)
535         self.state = STATE_IDLE
536         self.failed_boards = []
537
538     def __del__(self):
539         """Delete the working directory
540
541         This function makes sure the temporary directory is cleaned away
542         even if Python suddenly dies due to error.  It should be done in here
543         because it is guranteed the destructor is always invoked when the
544         instance of the class gets unreferenced.
545
546         If the subprocess is still running, wait until it finishes.
547         """
548         if self.state != STATE_IDLE:
549             while self.ps.poll() == None:
550                 pass
551         shutil.rmtree(self.build_dir)
552
553     def add(self, defconfig):
554         """Assign a new subprocess for defconfig and add it to the slot.
555
556         If the slot is vacant, create a new subprocess for processing the
557         given defconfig and add it to the slot.  Just returns False if
558         the slot is occupied (i.e. the current subprocess is still running).
559
560         Arguments:
561           defconfig: defconfig name.
562
563         Returns:
564           Return True on success or False on failure
565         """
566         if self.state != STATE_IDLE:
567             return False
568         cmd = list(self.make_cmd)
569         cmd.append(defconfig)
570         self.ps = subprocess.Popen(cmd, stdout=self.devnull)
571         self.defconfig = defconfig
572         self.state = STATE_DEFCONFIG
573         return True
574
575     def poll(self):
576         """Check the status of the subprocess and handle it as needed.
577
578         Returns True if the slot is vacant (i.e. in idle state).
579         If the configuration is successfully finished, assign a new
580         subprocess to build include/autoconf.mk.
581         If include/autoconf.mk is generated, invoke the parser to
582         parse the .config and the include/autoconf.mk, and then set the
583         slot back to the idle state.
584
585         Returns:
586           Return True if the subprocess is terminated, False otherwise
587         """
588         if self.state == STATE_IDLE:
589             return True
590
591         if self.ps.poll() == None:
592             return False
593
594         if self.ps.poll() != 0:
595
596             print >> sys.stderr, log_msg(self.options.color,
597                                          COLOR_LIGHT_RED,
598                                          self.defconfig,
599                                          "failed to process.")
600             if self.options.exit_on_error:
601                 sys.exit("Exit on error.")
602             else:
603                 # If --exit-on-error flag is not set,
604                 # skip this board and continue.
605                 # Record the failed board.
606                 self.failed_boards.append(self.defconfig)
607                 self.state = STATE_IDLE
608                 return True
609
610         if self.state == STATE_AUTOCONF:
611             self.parser.update_defconfig(self.defconfig)
612             self.state = STATE_IDLE
613             return True
614
615         cross_compile = self.parser.get_cross_compile()
616         cmd = list(self.make_cmd)
617         if cross_compile:
618             cmd.append('CROSS_COMPILE=%s' % cross_compile)
619         cmd.append('include/config/auto.conf')
620         self.ps = subprocess.Popen(cmd, stdout=self.devnull)
621         self.state = STATE_AUTOCONF
622         return False
623
624     def get_failed_boards(self):
625         """Returns a list of failed boards (defconfigs) in this slot.
626         """
627         return self.failed_boards
628
629 class Slots:
630
631     """Controller of the array of subprocess slots."""
632
633     def __init__(self, config_attrs, options):
634         """Create a new slots controller.
635
636         Arguments:
637           config_attrs: A list of dictionaris containing the name, the type,
638                         and the default value of the target CONFIG.
639           options: option flags.
640         """
641         self.options = options
642         self.slots = []
643         devnull = get_devnull()
644         make_cmd = get_make_cmd()
645         for i in range(options.jobs):
646             self.slots.append(Slot(config_attrs, options, devnull, make_cmd))
647
648     def add(self, defconfig):
649         """Add a new subprocess if a vacant slot is found.
650
651         Arguments:
652           defconfig: defconfig name to be put into.
653
654         Returns:
655           Return True on success or False on failure
656         """
657         for slot in self.slots:
658             if slot.add(defconfig):
659                 return True
660         return False
661
662     def available(self):
663         """Check if there is a vacant slot.
664
665         Returns:
666           Return True if at lease one vacant slot is found, False otherwise.
667         """
668         for slot in self.slots:
669             if slot.poll():
670                 return True
671         return False
672
673     def empty(self):
674         """Check if all slots are vacant.
675
676         Returns:
677           Return True if all the slots are vacant, False otherwise.
678         """
679         ret = True
680         for slot in self.slots:
681             if not slot.poll():
682                 ret = False
683         return ret
684
685     def show_failed_boards(self):
686         """Display all of the failed boards (defconfigs)."""
687         failed_boards = []
688
689         for slot in self.slots:
690             failed_boards += slot.get_failed_boards()
691
692         if len(failed_boards) > 0:
693             msg = [ "The following boards were not processed due to error:" ]
694             msg += failed_boards
695             for line in msg:
696                 print >> sys.stderr, color_text(self.options.color,
697                                                 COLOR_LIGHT_RED, line)
698
699 def move_config(config_attrs, options):
700     """Move config options to defconfig files.
701
702     Arguments:
703       config_attrs: A list of dictionaris, each of them includes the name,
704                     the type, and the default value of the target config.
705       options: option flags
706     """
707     check_top_directory()
708
709     if len(config_attrs) == 0:
710         print 'Nothing to do. exit.'
711         sys.exit(0)
712
713     print 'Move the following CONFIG options (jobs: %d)' % options.jobs
714     for config_attr in config_attrs:
715         print '  %s (type: %s, default: %s)' % (config_attr['config'],
716                                                 config_attr['type'],
717                                                 config_attr['default'])
718
719     # All the defconfig files to be processed
720     defconfigs = []
721     for (dirpath, dirnames, filenames) in os.walk('configs'):
722         dirpath = dirpath[len('configs') + 1:]
723         for filename in fnmatch.filter(filenames, '*_defconfig'):
724             defconfigs.append(os.path.join(dirpath, filename))
725
726     slots = Slots(config_attrs, options)
727
728     # Main loop to process defconfig files:
729     #  Add a new subprocess into a vacant slot.
730     #  Sleep if there is no available slot.
731     for defconfig in defconfigs:
732         while not slots.add(defconfig):
733             while not slots.available():
734                 # No available slot: sleep for a while
735                 time.sleep(SLEEP_TIME)
736
737     # wait until all the subprocesses finish
738     while not slots.empty():
739         time.sleep(SLEEP_TIME)
740
741     slots.show_failed_boards()
742
743     cleanup_headers(config_attrs, options.dry_run)
744
745 def bad_recipe(filename, linenum, msg):
746     """Print error message with the file name and the line number and exit."""
747     sys.exit("%s: line %d: error : " % (filename, linenum) + msg)
748
749 def parse_recipe(filename):
750     """Parse the recipe file and retrieve the config attributes.
751
752     This function parses the given recipe file and gets the name,
753     the type, and the default value of the target config options.
754
755     Arguments:
756       filename: path to file to be parsed.
757     Returns:
758       A list of dictionaris, each of them includes the name,
759       the type, and the default value of the target config.
760     """
761     config_attrs = []
762     linenum = 1
763
764     for line in open(filename):
765         tokens = line.split()
766         if len(tokens) != 3:
767             bad_recipe(filename, linenum,
768                        "%d fields in this line.  Each line must contain 3 fields"
769                        % len(tokens))
770
771         (config, type, default) = tokens
772
773         # prefix the option name with CONFIG_ if missing
774         if not config.startswith('CONFIG_'):
775             config = 'CONFIG_' + config
776
777         # sanity check of default values
778         if type == 'bool':
779             if not default in ('y', 'n'):
780                 bad_recipe(filename, linenum,
781                            "default for bool type must be either y or n")
782         elif type == 'tristate':
783             if not default in ('y', 'm', 'n'):
784                 bad_recipe(filename, linenum,
785                            "default for tristate type must be y, m, or n")
786         elif type == 'string':
787             if default[0] != '"' or default[-1] != '"':
788                 bad_recipe(filename, linenum,
789                            "default for string type must be surrounded by double-quotations")
790         elif type == 'int':
791             try:
792                 int(default)
793             except:
794                 bad_recipe(filename, linenum,
795                            "type is int, but default value is not decimal")
796         elif type == 'hex':
797             if len(default) < 2 or default[:2] != '0x':
798                 bad_recipe(filename, linenum,
799                            "default for hex type must be prefixed with 0x")
800             try:
801                 int(default, 16)
802             except:
803                 bad_recipe(filename, linenum,
804                            "type is hex, but default value is not hexadecimal")
805         else:
806             bad_recipe(filename, linenum,
807                        "unsupported type '%s'. type must be one of bool, tristate, string, int, hex"
808                        % type)
809
810         config_attrs.append({'config': config, 'type': type, 'default': default})
811         linenum += 1
812
813     return config_attrs
814
815 def main():
816     try:
817         cpu_count = multiprocessing.cpu_count()
818     except NotImplementedError:
819         cpu_count = 1
820
821     parser = optparse.OptionParser()
822     # Add options here
823     parser.add_option('-c', '--color', action='store_true', default=False,
824                       help='display the log in color')
825     parser.add_option('-n', '--dry-run', action='store_true', default=False,
826                       help='perform a trial run (show log with no changes)')
827     parser.add_option('-e', '--exit-on-error', action='store_true',
828                       default=False,
829                       help='exit immediately on any error')
830     parser.add_option('-j', '--jobs', type='int', default=cpu_count,
831                       help='the number of jobs to run simultaneously')
832     parser.usage += ' recipe_file\n\n' + \
833                     'The recipe_file should describe config options you want to move.\n' + \
834                     'Each line should contain config_name, type, default_value\n\n' + \
835                     'Example:\n' + \
836                     'CONFIG_FOO bool n\n' + \
837                     'CONFIG_BAR int 100\n' + \
838                     'CONFIG_BAZ string "hello"\n'
839
840     (options, args) = parser.parse_args()
841
842     if len(args) != 1:
843         parser.print_usage()
844         sys.exit(1)
845
846     config_attrs = parse_recipe(args[0])
847
848     update_cross_compile()
849
850     move_config(config_attrs, options)
851
852 if __name__ == '__main__':
853     main()