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