]> git.sur5r.net Git - u-boot/blob - tools/moveconfig.py
8bca7e43623c7a4f53b1d07af00ac4a19b2022b5
[u-boot] / 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 First, you must edit the Kconfig to add the menu entries for the configs
21 you are moving.
22
23 And then run this tool giving CONFIG names you want to move.
24 For example, if you want to move CONFIG_CMD_USB and CONFIG_SYS_TEXT_BASE,
25 simply type as follows:
26
27   $ tools/moveconfig.py CONFIG_CMD_USB CONFIG_SYS_TEXT_BASE
28
29 The tool walks through all the defconfig files and move the given CONFIGs.
30
31 The log is also displayed on the terminal.
32
33 The log is printed for each defconfig as follows:
34
35 <defconfig_name>
36     <action1>
37     <action2>
38     <action3>
39     ...
40
41 <defconfig_name> is the name of the defconfig.
42
43 <action*> shows what the tool did for that defconfig.
44 It looks like one of the followings:
45
46  - Move 'CONFIG_... '
47    This config option was moved to the defconfig
48
49  - CONFIG_... is not defined in Kconfig.  Do nothing.
50    The entry for this CONFIG was not found in Kconfig.
51    There are two common cases:
52      - You forgot to create an entry for the CONFIG before running
53        this tool, or made a typo in a CONFIG passed to this tool.
54      - The entry was hidden due to unmet 'depends on'.
55        This is correct behavior.
56
57  - 'CONFIG_...' is the same as the define in Kconfig.  Do nothing.
58    The define in the config header matched the one in Kconfig.
59    We do not need to touch it.
60
61  - Undefined.  Do nothing.
62    This config option was not found in the config header.
63    Nothing to do.
64
65  - Compiler is missing.  Do nothing.
66    The compiler specified for this architecture was not found
67    in your PATH environment.
68    (If -e option is passed, the tool exits immediately.)
69
70  - Failed to process.
71    An error occurred during processing this defconfig.  Skipped.
72    (If -e option is passed, the tool exits immediately on error.)
73
74 Finally, you will be asked, Clean up headers? [y/n]:
75
76 If you say 'y' here, the unnecessary config defines are removed
77 from the config headers (include/configs/*.h).
78 It just uses the regex method, so you should not rely on it.
79 Just in case, please do 'git diff' to see what happened.
80
81
82 How does it work?
83 -----------------
84
85 This tool runs configuration and builds include/autoconf.mk for every
86 defconfig.  The config options defined in Kconfig appear in the .config
87 file (unless they are hidden because of unmet dependency.)
88 On the other hand, the config options defined by board headers are seen
89 in include/autoconf.mk.  The tool looks for the specified options in both
90 of them to decide the appropriate action for the options.  If the given
91 config option is found in the .config, but its value does not match the
92 one from the board header, the config option in the .config is replaced
93 with the define in the board header.  Then, the .config is synced by
94 "make savedefconfig" and the defconfig is updated with it.
95
96 For faster processing, this tool handles multi-threading.  It creates
97 separate build directories where the out-of-tree build is run.  The
98 temporary build directories are automatically created and deleted as
99 needed.  The number of threads are chosen based on the number of the CPU
100 cores of your system although you can change it via -j (--jobs) option.
101
102
103 Toolchains
104 ----------
105
106 Appropriate toolchain are necessary to generate include/autoconf.mk
107 for all the architectures supported by U-Boot.  Most of them are available
108 at the kernel.org site, some are not provided by kernel.org.
109
110 The default per-arch CROSS_COMPILE used by this tool is specified by
111 the list below, CROSS_COMPILE.  You may wish to update the list to
112 use your own.  Instead of modifying the list directly, you can give
113 them via environments.
114
115
116 Available options
117 -----------------
118
119  -c, --color
120    Surround each portion of the log with escape sequences to display it
121    in color on the terminal.
122
123  -d, --defconfigs
124   Specify a file containing a list of defconfigs to move
125
126  -n, --dry-run
127    Perform a trial run that does not make any changes.  It is useful to
128    see what is going to happen before one actually runs it.
129
130  -e, --exit-on-error
131    Exit immediately if Make exits with a non-zero status while processing
132    a defconfig file.
133
134  -s, --force-sync
135    Do "make savedefconfig" forcibly for all the defconfig files.
136    If not specified, "make savedefconfig" only occurs for cases
137    where at least one CONFIG was moved.
138
139  -H, --headers-only
140    Only cleanup the headers; skip the defconfig processing
141
142  -j, --jobs
143    Specify the number of threads to run simultaneously.  If not specified,
144    the number of threads is the same as the number of CPU cores.
145
146  -r, --git-ref
147    Specify the git ref to clone for building the autoconf.mk. If unspecified
148    use the CWD. This is useful for when changes to the Kconfig affect the
149    default values and you want to capture the state of the defconfig from
150    before that change was in effect. If in doubt, specify a ref pre-Kconfig
151    changes (use HEAD if Kconfig changes are not committed). Worst case it will
152    take a bit longer to run, but will always do the right thing.
153
154  -v, --verbose
155    Show any build errors as boards are built
156
157 To see the complete list of supported options, run
158
159   $ tools/moveconfig.py -h
160
161 """
162
163 import copy
164 import difflib
165 import filecmp
166 import fnmatch
167 import multiprocessing
168 import optparse
169 import os
170 import re
171 import shutil
172 import subprocess
173 import sys
174 import tempfile
175 import time
176
177 SHOW_GNU_MAKE = 'scripts/show-gnu-make'
178 SLEEP_TIME=0.03
179
180 # Here is the list of cross-tools I use.
181 # Most of them are available at kernel.org
182 # (https://www.kernel.org/pub/tools/crosstool/files/bin/), except the followings:
183 # arc: https://github.com/foss-for-synopsys-dwc-arc-processors/toolchain/releases
184 # blackfin: http://sourceforge.net/projects/adi-toolchain/files/
185 # nds32: http://osdk.andestech.com/packages/nds32le-linux-glibc-v1.tgz
186 # nios2: https://sourcery.mentor.com/GNUToolchain/subscription42545
187 # sh: http://sourcery.mentor.com/public/gnu_toolchain/sh-linux-gnu
188 #
189 # openrisc kernel.org toolchain is out of date, download latest one from
190 # http://opencores.org/or1k/OpenRISC_GNU_tool_chain#Prebuilt_versions
191 CROSS_COMPILE = {
192     'arc': 'arc-linux-',
193     'aarch64': 'aarch64-linux-',
194     'arm': 'arm-unknown-linux-gnueabi-',
195     'avr32': 'avr32-linux-',
196     'blackfin': 'bfin-elf-',
197     'm68k': 'm68k-linux-',
198     'microblaze': 'microblaze-linux-',
199     'mips': 'mips-linux-',
200     'nds32': 'nds32le-linux-',
201     'nios2': 'nios2-linux-gnu-',
202     'openrisc': 'or1k-elf-',
203     'powerpc': 'powerpc-linux-',
204     'sh': 'sh-linux-gnu-',
205     'sparc': 'sparc-linux-',
206     'x86': 'i386-linux-'
207 }
208
209 STATE_IDLE = 0
210 STATE_DEFCONFIG = 1
211 STATE_AUTOCONF = 2
212 STATE_SAVEDEFCONFIG = 3
213
214 ACTION_MOVE = 0
215 ACTION_NO_ENTRY = 1
216 ACTION_NO_CHANGE = 2
217
218 COLOR_BLACK        = '0;30'
219 COLOR_RED          = '0;31'
220 COLOR_GREEN        = '0;32'
221 COLOR_BROWN        = '0;33'
222 COLOR_BLUE         = '0;34'
223 COLOR_PURPLE       = '0;35'
224 COLOR_CYAN         = '0;36'
225 COLOR_LIGHT_GRAY   = '0;37'
226 COLOR_DARK_GRAY    = '1;30'
227 COLOR_LIGHT_RED    = '1;31'
228 COLOR_LIGHT_GREEN  = '1;32'
229 COLOR_YELLOW       = '1;33'
230 COLOR_LIGHT_BLUE   = '1;34'
231 COLOR_LIGHT_PURPLE = '1;35'
232 COLOR_LIGHT_CYAN   = '1;36'
233 COLOR_WHITE        = '1;37'
234
235 ### helper functions ###
236 def get_devnull():
237     """Get the file object of '/dev/null' device."""
238     try:
239         devnull = subprocess.DEVNULL # py3k
240     except AttributeError:
241         devnull = open(os.devnull, 'wb')
242     return devnull
243
244 def check_top_directory():
245     """Exit if we are not at the top of source directory."""
246     for f in ('README', 'Licenses'):
247         if not os.path.exists(f):
248             sys.exit('Please run at the top of source directory.')
249
250 def check_clean_directory():
251     """Exit if the source tree is not clean."""
252     for f in ('.config', 'include/config'):
253         if os.path.exists(f):
254             sys.exit("source tree is not clean, please run 'make mrproper'")
255
256 def get_make_cmd():
257     """Get the command name of GNU Make.
258
259     U-Boot needs GNU Make for building, but the command name is not
260     necessarily "make". (for example, "gmake" on FreeBSD).
261     Returns the most appropriate command name on your system.
262     """
263     process = subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE)
264     ret = process.communicate()
265     if process.returncode:
266         sys.exit('GNU Make not found')
267     return ret[0].rstrip()
268
269 def color_text(color_enabled, color, string):
270     """Return colored string."""
271     if color_enabled:
272         # LF should not be surrounded by the escape sequence.
273         # Otherwise, additional whitespace or line-feed might be printed.
274         return '\n'.join([ '\033[' + color + 'm' + s + '\033[0m' if s else ''
275                            for s in string.split('\n') ])
276     else:
277         return string
278
279 def show_diff(a, b, file_path):
280     """Show unidified diff.
281
282     Arguments:
283       a: A list of lines (before)
284       b: A list of lines (after)
285       file_path: Path to the file
286     """
287
288     diff = difflib.unified_diff(a, b,
289                                 fromfile=os.path.join('a', file_path),
290                                 tofile=os.path.join('b', file_path))
291
292     for line in diff:
293         print line,
294
295 def update_cross_compile(color_enabled):
296     """Update per-arch CROSS_COMPILE via environment variables
297
298     The default CROSS_COMPILE values are available
299     in the CROSS_COMPILE list above.
300
301     You can override them via environment variables
302     CROSS_COMPILE_{ARCH}.
303
304     For example, if you want to override toolchain prefixes
305     for ARM and PowerPC, you can do as follows in your shell:
306
307     export CROSS_COMPILE_ARM=...
308     export CROSS_COMPILE_POWERPC=...
309
310     Then, this function checks if specified compilers really exist in your
311     PATH environment.
312     """
313     archs = []
314
315     for arch in os.listdir('arch'):
316         if os.path.exists(os.path.join('arch', arch, 'Makefile')):
317             archs.append(arch)
318
319     # arm64 is a special case
320     archs.append('aarch64')
321
322     for arch in archs:
323         env = 'CROSS_COMPILE_' + arch.upper()
324         cross_compile = os.environ.get(env)
325         if not cross_compile:
326             cross_compile = CROSS_COMPILE.get(arch, '')
327
328         for path in os.environ["PATH"].split(os.pathsep):
329             gcc_path = os.path.join(path, cross_compile + 'gcc')
330             if os.path.isfile(gcc_path) and os.access(gcc_path, os.X_OK):
331                 break
332         else:
333             print >> sys.stderr, color_text(color_enabled, COLOR_YELLOW,
334                  'warning: %sgcc: not found in PATH.  %s architecture boards will be skipped'
335                                             % (cross_compile, arch))
336             cross_compile = None
337
338         CROSS_COMPILE[arch] = cross_compile
339
340 def extend_matched_lines(lines, matched, pre_patterns, post_patterns, extend_pre,
341                          extend_post):
342     """Extend matched lines if desired patterns are found before/after already
343     matched lines.
344
345     Arguments:
346       lines: A list of lines handled.
347       matched: A list of line numbers that have been already matched.
348                (will be updated by this function)
349       pre_patterns: A list of regular expression that should be matched as
350                     preamble.
351       post_patterns: A list of regular expression that should be matched as
352                      postamble.
353       extend_pre: Add the line number of matched preamble to the matched list.
354       extend_post: Add the line number of matched postamble to the matched list.
355     """
356     extended_matched = []
357
358     j = matched[0]
359
360     for i in matched:
361         if i == 0 or i < j:
362             continue
363         j = i
364         while j in matched:
365             j += 1
366         if j >= len(lines):
367             break
368
369         for p in pre_patterns:
370             if p.search(lines[i - 1]):
371                 break
372         else:
373             # not matched
374             continue
375
376         for p in post_patterns:
377             if p.search(lines[j]):
378                 break
379         else:
380             # not matched
381             continue
382
383         if extend_pre:
384             extended_matched.append(i - 1)
385         if extend_post:
386             extended_matched.append(j)
387
388     matched += extended_matched
389     matched.sort()
390
391 def cleanup_one_header(header_path, patterns, dry_run):
392     """Clean regex-matched lines away from a file.
393
394     Arguments:
395       header_path: path to the cleaned file.
396       patterns: list of regex patterns.  Any lines matching to these
397                 patterns are deleted.
398       dry_run: make no changes, but still display log.
399     """
400     with open(header_path) as f:
401         lines = f.readlines()
402
403     matched = []
404     for i, line in enumerate(lines):
405         for pattern in patterns:
406             if pattern.search(line):
407                 matched.append(i)
408                 break
409
410     if not matched:
411         return
412
413     # remove empty #ifdef ... #endif, successive blank lines
414     pattern_if = re.compile(r'#\s*if(def|ndef)?\W') #  #if, #ifdef, #ifndef
415     pattern_elif = re.compile(r'#\s*el(if|se)\W')   #  #elif, #else
416     pattern_endif = re.compile(r'#\s*endif\W')      #  #endif
417     pattern_blank = re.compile(r'^\s*$')            #  empty line
418
419     while True:
420         old_matched = copy.copy(matched)
421         extend_matched_lines(lines, matched, [pattern_if],
422                              [pattern_endif], True, True)
423         extend_matched_lines(lines, matched, [pattern_elif],
424                              [pattern_elif, pattern_endif], True, False)
425         extend_matched_lines(lines, matched, [pattern_if, pattern_elif],
426                              [pattern_blank], False, True)
427         extend_matched_lines(lines, matched, [pattern_blank],
428                              [pattern_elif, pattern_endif], True, False)
429         extend_matched_lines(lines, matched, [pattern_blank],
430                              [pattern_blank], True, False)
431         if matched == old_matched:
432             break
433
434     tolines = copy.copy(lines)
435
436     for i in reversed(matched):
437         tolines.pop(i)
438
439     show_diff(lines, tolines, header_path)
440
441     if dry_run:
442         return
443
444     with open(header_path, 'w') as f:
445         for line in tolines:
446             f.write(line)
447
448 def cleanup_headers(configs, dry_run):
449     """Delete config defines from board headers.
450
451     Arguments:
452       configs: A list of CONFIGs to remove.
453       dry_run: make no changes, but still display log.
454     """
455     while True:
456         choice = raw_input('Clean up headers? [y/n]: ').lower()
457         print choice
458         if choice == 'y' or choice == 'n':
459             break
460
461     if choice == 'n':
462         return
463
464     patterns = []
465     for config in configs:
466         patterns.append(re.compile(r'#\s*define\s+%s\W' % config))
467         patterns.append(re.compile(r'#\s*undef\s+%s\W' % config))
468
469     for dir in 'include', 'arch', 'board':
470         for (dirpath, dirnames, filenames) in os.walk(dir):
471             if dirpath == os.path.join('include', 'generated'):
472                 continue
473             for filename in filenames:
474                 if not fnmatch.fnmatch(filename, '*~'):
475                     cleanup_one_header(os.path.join(dirpath, filename),
476                                        patterns, dry_run)
477
478 ### classes ###
479 class Progress:
480
481     """Progress Indicator"""
482
483     def __init__(self, total):
484         """Create a new progress indicator.
485
486         Arguments:
487           total: A number of defconfig files to process.
488         """
489         self.current = 0
490         self.total = total
491
492     def inc(self):
493         """Increment the number of processed defconfig files."""
494
495         self.current += 1
496
497     def show(self):
498         """Display the progress."""
499         print ' %d defconfigs out of %d\r' % (self.current, self.total),
500         sys.stdout.flush()
501
502 class KconfigParser:
503
504     """A parser of .config and include/autoconf.mk."""
505
506     re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
507     re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
508
509     def __init__(self, configs, options, build_dir):
510         """Create a new parser.
511
512         Arguments:
513           configs: A list of CONFIGs to move.
514           options: option flags.
515           build_dir: Build directory.
516         """
517         self.configs = configs
518         self.options = options
519         self.dotconfig = os.path.join(build_dir, '.config')
520         self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
521         self.config_autoconf = os.path.join(build_dir, 'include', 'config',
522                                             'auto.conf')
523         self.defconfig = os.path.join(build_dir, 'defconfig')
524
525     def get_cross_compile(self):
526         """Parse .config file and return CROSS_COMPILE.
527
528         Returns:
529           A string storing the compiler prefix for the architecture.
530           Return a NULL string for architectures that do not require
531           compiler prefix (Sandbox and native build is the case).
532           Return None if the specified compiler is missing in your PATH.
533           Caller should distinguish '' and None.
534         """
535         arch = ''
536         cpu = ''
537         for line in open(self.dotconfig):
538             m = self.re_arch.match(line)
539             if m:
540                 arch = m.group(1)
541                 continue
542             m = self.re_cpu.match(line)
543             if m:
544                 cpu = m.group(1)
545
546         if not arch:
547             return None
548
549         # fix-up for aarch64
550         if arch == 'arm' and cpu == 'armv8':
551             arch = 'aarch64'
552
553         return CROSS_COMPILE.get(arch, None)
554
555     def parse_one_config(self, config, dotconfig_lines, autoconf_lines):
556         """Parse .config, defconfig, include/autoconf.mk for one config.
557
558         This function looks for the config options in the lines from
559         defconfig, .config, and include/autoconf.mk in order to decide
560         which action should be taken for this defconfig.
561
562         Arguments:
563           config: CONFIG name to parse.
564           dotconfig_lines: lines from the .config file.
565           autoconf_lines: lines from the include/autoconf.mk file.
566
567         Returns:
568           A tupple of the action for this defconfig and the line
569           matched for the config.
570         """
571         not_set = '# %s is not set' % config
572
573         for line in dotconfig_lines:
574             line = line.rstrip()
575             if line.startswith(config + '=') or line == not_set:
576                 old_val = line
577                 break
578         else:
579             return (ACTION_NO_ENTRY, config)
580
581         for line in autoconf_lines:
582             line = line.rstrip()
583             if line.startswith(config + '='):
584                 new_val = line
585                 break
586         else:
587             new_val = not_set
588
589         # If this CONFIG is neither bool nor trisate
590         if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set:
591             # tools/scripts/define2mk.sed changes '1' to 'y'.
592             # This is a problem if the CONFIG is int type.
593             # Check the type in Kconfig and handle it correctly.
594             if new_val[-2:] == '=y':
595                 new_val = new_val[:-1] + '1'
596
597         return (ACTION_NO_CHANGE if old_val == new_val else ACTION_MOVE,
598                 new_val)
599
600     def update_dotconfig(self):
601         """Parse files for the config options and update the .config.
602
603         This function parses the generated .config and include/autoconf.mk
604         searching the target options.
605         Move the config option(s) to the .config as needed.
606
607         Arguments:
608           defconfig: defconfig name.
609
610         Returns:
611           Return a tuple of (updated flag, log string).
612           The "updated flag" is True if the .config was updated, False
613           otherwise.  The "log string" shows what happend to the .config.
614         """
615
616         results = []
617         updated = False
618
619         with open(self.dotconfig) as f:
620             dotconfig_lines = f.readlines()
621
622         with open(self.autoconf) as f:
623             autoconf_lines = f.readlines()
624
625         for config in self.configs:
626             result = self.parse_one_config(config, dotconfig_lines,
627                                            autoconf_lines)
628             results.append(result)
629
630         log = ''
631
632         for (action, value) in results:
633             if action == ACTION_MOVE:
634                 actlog = "Move '%s'" % value
635                 log_color = COLOR_LIGHT_GREEN
636             elif action == ACTION_NO_ENTRY:
637                 actlog = "%s is not defined in Kconfig.  Do nothing." % value
638                 log_color = COLOR_LIGHT_BLUE
639             elif action == ACTION_NO_CHANGE:
640                 actlog = "'%s' is the same as the define in Kconfig.  Do nothing." \
641                          % value
642                 log_color = COLOR_LIGHT_PURPLE
643             else:
644                 sys.exit("Internal Error. This should not happen.")
645
646             log += color_text(self.options.color, log_color, actlog) + '\n'
647
648         with open(self.dotconfig, 'a') as f:
649             for (action, value) in results:
650                 if action == ACTION_MOVE:
651                     f.write(value + '\n')
652                     updated = True
653
654         self.results = results
655         os.remove(self.config_autoconf)
656         os.remove(self.autoconf)
657
658         return (updated, log)
659
660     def check_defconfig(self):
661         """Check the defconfig after savedefconfig
662
663         Returns:
664           Return additional log if moved CONFIGs were removed again by
665           'make savedefconfig'.
666         """
667
668         log = ''
669
670         with open(self.defconfig) as f:
671             defconfig_lines = f.readlines()
672
673         for (action, value) in self.results:
674             if action != ACTION_MOVE:
675                 continue
676             if not value + '\n' in defconfig_lines:
677                 log += color_text(self.options.color, COLOR_YELLOW,
678                                   "'%s' was removed by savedefconfig.\n" %
679                                   value)
680
681         return log
682
683 class Slot:
684
685     """A slot to store a subprocess.
686
687     Each instance of this class handles one subprocess.
688     This class is useful to control multiple threads
689     for faster processing.
690     """
691
692     def __init__(self, configs, options, progress, devnull, make_cmd, reference_src_dir):
693         """Create a new process slot.
694
695         Arguments:
696           configs: A list of CONFIGs to move.
697           options: option flags.
698           progress: A progress indicator.
699           devnull: A file object of '/dev/null'.
700           make_cmd: command name of GNU Make.
701           reference_src_dir: Determine the true starting config state from this
702                              source tree.
703         """
704         self.options = options
705         self.progress = progress
706         self.build_dir = tempfile.mkdtemp()
707         self.devnull = devnull
708         self.make_cmd = (make_cmd, 'O=' + self.build_dir)
709         self.reference_src_dir = reference_src_dir
710         self.parser = KconfigParser(configs, options, self.build_dir)
711         self.state = STATE_IDLE
712         self.failed_boards = []
713         self.suspicious_boards = []
714
715     def __del__(self):
716         """Delete the working directory
717
718         This function makes sure the temporary directory is cleaned away
719         even if Python suddenly dies due to error.  It should be done in here
720         because it is guaranteed the destructor is always invoked when the
721         instance of the class gets unreferenced.
722
723         If the subprocess is still running, wait until it finishes.
724         """
725         if self.state != STATE_IDLE:
726             while self.ps.poll() == None:
727                 pass
728         shutil.rmtree(self.build_dir)
729
730     def add(self, defconfig):
731         """Assign a new subprocess for defconfig and add it to the slot.
732
733         If the slot is vacant, create a new subprocess for processing the
734         given defconfig and add it to the slot.  Just returns False if
735         the slot is occupied (i.e. the current subprocess is still running).
736
737         Arguments:
738           defconfig: defconfig name.
739
740         Returns:
741           Return True on success or False on failure
742         """
743         if self.state != STATE_IDLE:
744             return False
745
746         self.defconfig = defconfig
747         self.log = ''
748         self.current_src_dir = self.reference_src_dir
749         self.do_defconfig()
750         return True
751
752     def poll(self):
753         """Check the status of the subprocess and handle it as needed.
754
755         Returns True if the slot is vacant (i.e. in idle state).
756         If the configuration is successfully finished, assign a new
757         subprocess to build include/autoconf.mk.
758         If include/autoconf.mk is generated, invoke the parser to
759         parse the .config and the include/autoconf.mk, moving
760         config options to the .config as needed.
761         If the .config was updated, run "make savedefconfig" to sync
762         it, update the original defconfig, and then set the slot back
763         to the idle state.
764
765         Returns:
766           Return True if the subprocess is terminated, False otherwise
767         """
768         if self.state == STATE_IDLE:
769             return True
770
771         if self.ps.poll() == None:
772             return False
773
774         if self.ps.poll() != 0:
775             self.handle_error()
776         elif self.state == STATE_DEFCONFIG:
777             if self.reference_src_dir and not self.current_src_dir:
778                 self.do_savedefconfig()
779             else:
780                 self.do_autoconf()
781         elif self.state == STATE_AUTOCONF:
782             if self.current_src_dir:
783                 self.current_src_dir = None
784                 self.do_defconfig()
785             else:
786                 self.do_savedefconfig()
787         elif self.state == STATE_SAVEDEFCONFIG:
788             self.update_defconfig()
789         else:
790             sys.exit("Internal Error. This should not happen.")
791
792         return True if self.state == STATE_IDLE else False
793
794     def handle_error(self):
795         """Handle error cases."""
796
797         self.log += color_text(self.options.color, COLOR_LIGHT_RED,
798                                "Failed to process.\n")
799         if self.options.verbose:
800             self.log += color_text(self.options.color, COLOR_LIGHT_CYAN,
801                                    self.ps.stderr.read())
802         self.finish(False)
803
804     def do_defconfig(self):
805         """Run 'make <board>_defconfig' to create the .config file."""
806
807         cmd = list(self.make_cmd)
808         cmd.append(self.defconfig)
809         self.ps = subprocess.Popen(cmd, stdout=self.devnull,
810                                    stderr=subprocess.PIPE,
811                                    cwd=self.current_src_dir)
812         self.state = STATE_DEFCONFIG
813
814     def do_autoconf(self):
815         """Run 'make include/config/auto.conf'."""
816
817         self.cross_compile = self.parser.get_cross_compile()
818         if self.cross_compile is None:
819             self.log += color_text(self.options.color, COLOR_YELLOW,
820                                    "Compiler is missing.  Do nothing.\n")
821             self.finish(False)
822             return
823
824         cmd = list(self.make_cmd)
825         if self.cross_compile:
826             cmd.append('CROSS_COMPILE=%s' % self.cross_compile)
827         cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
828         cmd.append('include/config/auto.conf')
829         self.ps = subprocess.Popen(cmd, stdout=self.devnull,
830                                    stderr=subprocess.PIPE,
831                                    cwd=self.current_src_dir)
832         self.state = STATE_AUTOCONF
833
834     def do_savedefconfig(self):
835         """Update the .config and run 'make savedefconfig'."""
836
837         (updated, log) = self.parser.update_dotconfig()
838         self.log += log
839
840         if not self.options.force_sync and not updated:
841             self.finish(True)
842             return
843         if updated:
844             self.log += color_text(self.options.color, COLOR_LIGHT_GREEN,
845                                    "Syncing by savedefconfig...\n")
846         else:
847             self.log += "Syncing by savedefconfig (forced by option)...\n"
848
849         cmd = list(self.make_cmd)
850         cmd.append('savedefconfig')
851         self.ps = subprocess.Popen(cmd, stdout=self.devnull,
852                                    stderr=subprocess.PIPE)
853         self.state = STATE_SAVEDEFCONFIG
854
855     def update_defconfig(self):
856         """Update the input defconfig and go back to the idle state."""
857
858         log = self.parser.check_defconfig()
859         if log:
860             self.suspicious_boards.append(self.defconfig)
861             self.log += log
862         orig_defconfig = os.path.join('configs', self.defconfig)
863         new_defconfig = os.path.join(self.build_dir, 'defconfig')
864         updated = not filecmp.cmp(orig_defconfig, new_defconfig)
865
866         if updated:
867             self.log += color_text(self.options.color, COLOR_LIGHT_BLUE,
868                                    "defconfig was updated.\n")
869
870         if not self.options.dry_run and updated:
871             shutil.move(new_defconfig, orig_defconfig)
872         self.finish(True)
873
874     def finish(self, success):
875         """Display log along with progress and go to the idle state.
876
877         Arguments:
878           success: Should be True when the defconfig was processed
879                    successfully, or False when it fails.
880         """
881         # output at least 30 characters to hide the "* defconfigs out of *".
882         log = self.defconfig.ljust(30) + '\n'
883
884         log += '\n'.join([ '    ' + s for s in self.log.split('\n') ])
885         # Some threads are running in parallel.
886         # Print log atomically to not mix up logs from different threads.
887         print >> (sys.stdout if success else sys.stderr), log
888
889         if not success:
890             if self.options.exit_on_error:
891                 sys.exit("Exit on error.")
892             # If --exit-on-error flag is not set, skip this board and continue.
893             # Record the failed board.
894             self.failed_boards.append(self.defconfig)
895
896         self.progress.inc()
897         self.progress.show()
898         self.state = STATE_IDLE
899
900     def get_failed_boards(self):
901         """Returns a list of failed boards (defconfigs) in this slot.
902         """
903         return self.failed_boards
904
905     def get_suspicious_boards(self):
906         """Returns a list of boards (defconfigs) with possible misconversion.
907         """
908         return self.suspicious_boards
909
910 class Slots:
911
912     """Controller of the array of subprocess slots."""
913
914     def __init__(self, configs, options, progress, reference_src_dir):
915         """Create a new slots controller.
916
917         Arguments:
918           configs: A list of CONFIGs to move.
919           options: option flags.
920           progress: A progress indicator.
921           reference_src_dir: Determine the true starting config state from this
922                              source tree.
923         """
924         self.options = options
925         self.slots = []
926         devnull = get_devnull()
927         make_cmd = get_make_cmd()
928         for i in range(options.jobs):
929             self.slots.append(Slot(configs, options, progress, devnull,
930                                    make_cmd, reference_src_dir))
931
932     def add(self, defconfig):
933         """Add a new subprocess if a vacant slot is found.
934
935         Arguments:
936           defconfig: defconfig name to be put into.
937
938         Returns:
939           Return True on success or False on failure
940         """
941         for slot in self.slots:
942             if slot.add(defconfig):
943                 return True
944         return False
945
946     def available(self):
947         """Check if there is a vacant slot.
948
949         Returns:
950           Return True if at lease one vacant slot is found, False otherwise.
951         """
952         for slot in self.slots:
953             if slot.poll():
954                 return True
955         return False
956
957     def empty(self):
958         """Check if all slots are vacant.
959
960         Returns:
961           Return True if all the slots are vacant, False otherwise.
962         """
963         ret = True
964         for slot in self.slots:
965             if not slot.poll():
966                 ret = False
967         return ret
968
969     def show_failed_boards(self):
970         """Display all of the failed boards (defconfigs)."""
971         boards = []
972         output_file = 'moveconfig.failed'
973
974         for slot in self.slots:
975             boards += slot.get_failed_boards()
976
977         if boards:
978             boards = '\n'.join(boards) + '\n'
979             msg = "The following boards were not processed due to error:\n"
980             msg += boards
981             msg += "(the list has been saved in %s)\n" % output_file
982             print >> sys.stderr, color_text(self.options.color, COLOR_LIGHT_RED,
983                                             msg)
984
985             with open(output_file, 'w') as f:
986                 f.write(boards)
987
988     def show_suspicious_boards(self):
989         """Display all boards (defconfigs) with possible misconversion."""
990         boards = []
991         output_file = 'moveconfig.suspicious'
992
993         for slot in self.slots:
994             boards += slot.get_suspicious_boards()
995
996         if boards:
997             boards = '\n'.join(boards) + '\n'
998             msg = "The following boards might have been converted incorrectly.\n"
999             msg += "It is highly recommended to check them manually:\n"
1000             msg += boards
1001             msg += "(the list has been saved in %s)\n" % output_file
1002             print >> sys.stderr, color_text(self.options.color, COLOR_YELLOW,
1003                                             msg)
1004
1005             with open(output_file, 'w') as f:
1006                 f.write(boards)
1007
1008 class ReferenceSource:
1009
1010     """Reference source against which original configs should be parsed."""
1011
1012     def __init__(self, commit):
1013         """Create a reference source directory based on a specified commit.
1014
1015         Arguments:
1016           commit: commit to git-clone
1017         """
1018         self.src_dir = tempfile.mkdtemp()
1019         print "Cloning git repo to a separate work directory..."
1020         subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
1021                                 cwd=self.src_dir)
1022         print "Checkout '%s' to build the original autoconf.mk." % \
1023             subprocess.check_output(['git', 'rev-parse', '--short', commit]).strip()
1024         subprocess.check_output(['git', 'checkout', commit],
1025                                 stderr=subprocess.STDOUT, cwd=self.src_dir)
1026
1027     def __del__(self):
1028         """Delete the reference source directory
1029
1030         This function makes sure the temporary directory is cleaned away
1031         even if Python suddenly dies due to error.  It should be done in here
1032         because it is guaranteed the destructor is always invoked when the
1033         instance of the class gets unreferenced.
1034         """
1035         shutil.rmtree(self.src_dir)
1036
1037     def get_dir(self):
1038         """Return the absolute path to the reference source directory."""
1039
1040         return self.src_dir
1041
1042 def move_config(configs, options):
1043     """Move config options to defconfig files.
1044
1045     Arguments:
1046       configs: A list of CONFIGs to move.
1047       options: option flags
1048     """
1049     if len(configs) == 0:
1050         if options.force_sync:
1051             print 'No CONFIG is specified. You are probably syncing defconfigs.',
1052         else:
1053             print 'Neither CONFIG nor --force-sync is specified. Nothing will happen.',
1054     else:
1055         print 'Move ' + ', '.join(configs),
1056     print '(jobs: %d)\n' % options.jobs
1057
1058     if options.git_ref:
1059         reference_src = ReferenceSource(options.git_ref)
1060         reference_src_dir = reference_src.get_dir()
1061     else:
1062         reference_src_dir = None
1063
1064     if options.defconfigs:
1065         defconfigs = [line.strip() for line in open(options.defconfigs)]
1066         for i, defconfig in enumerate(defconfigs):
1067             if not defconfig.endswith('_defconfig'):
1068                 defconfigs[i] = defconfig + '_defconfig'
1069             if not os.path.exists(os.path.join('configs', defconfigs[i])):
1070                 sys.exit('%s - defconfig does not exist. Stopping.' %
1071                          defconfigs[i])
1072     else:
1073         # All the defconfig files to be processed
1074         defconfigs = []
1075         for (dirpath, dirnames, filenames) in os.walk('configs'):
1076             dirpath = dirpath[len('configs') + 1:]
1077             for filename in fnmatch.filter(filenames, '*_defconfig'):
1078                 defconfigs.append(os.path.join(dirpath, filename))
1079
1080     progress = Progress(len(defconfigs))
1081     slots = Slots(configs, options, progress, reference_src_dir)
1082
1083     # Main loop to process defconfig files:
1084     #  Add a new subprocess into a vacant slot.
1085     #  Sleep if there is no available slot.
1086     for defconfig in defconfigs:
1087         while not slots.add(defconfig):
1088             while not slots.available():
1089                 # No available slot: sleep for a while
1090                 time.sleep(SLEEP_TIME)
1091
1092     # wait until all the subprocesses finish
1093     while not slots.empty():
1094         time.sleep(SLEEP_TIME)
1095
1096     print ''
1097     slots.show_failed_boards()
1098     slots.show_suspicious_boards()
1099
1100 def main():
1101     try:
1102         cpu_count = multiprocessing.cpu_count()
1103     except NotImplementedError:
1104         cpu_count = 1
1105
1106     parser = optparse.OptionParser()
1107     # Add options here
1108     parser.add_option('-c', '--color', action='store_true', default=False,
1109                       help='display the log in color')
1110     parser.add_option('-d', '--defconfigs', type='string',
1111                       help='a file containing a list of defconfigs to move')
1112     parser.add_option('-n', '--dry-run', action='store_true', default=False,
1113                       help='perform a trial run (show log with no changes)')
1114     parser.add_option('-e', '--exit-on-error', action='store_true',
1115                       default=False,
1116                       help='exit immediately on any error')
1117     parser.add_option('-s', '--force-sync', action='store_true', default=False,
1118                       help='force sync by savedefconfig')
1119     parser.add_option('-H', '--headers-only', dest='cleanup_headers_only',
1120                       action='store_true', default=False,
1121                       help='only cleanup the headers')
1122     parser.add_option('-j', '--jobs', type='int', default=cpu_count,
1123                       help='the number of jobs to run simultaneously')
1124     parser.add_option('-r', '--git-ref', type='string',
1125                       help='the git ref to clone for building the autoconf.mk')
1126     parser.add_option('-v', '--verbose', action='store_true', default=False,
1127                       help='show any build errors as boards are built')
1128     parser.usage += ' CONFIG ...'
1129
1130     (options, configs) = parser.parse_args()
1131
1132     if len(configs) == 0 and not options.force_sync:
1133         parser.print_usage()
1134         sys.exit(1)
1135
1136     # prefix the option name with CONFIG_ if missing
1137     configs = [ config if config.startswith('CONFIG_') else 'CONFIG_' + config
1138                 for config in configs ]
1139
1140     check_top_directory()
1141
1142     if not options.cleanup_headers_only:
1143         check_clean_directory()
1144         update_cross_compile(options.color)
1145         move_config(configs, options)
1146
1147     if configs:
1148         cleanup_headers(configs, options.dry_run)
1149
1150 if __name__ == '__main__':
1151     main()