blob: a6d473df12acd2b365e99b1aba75abf5c20955c3 [file] [log] [blame]
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001#!/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
Masahiro Yamadab6ef3932016-05-19 15:51:58 +090020First, you must edit the Kconfig to add the menu entries for the configs
Joe Hershberger96464ba2015-05-19 13:21:17 -050021you are moving.
22
Masahiro Yamadab6ef3932016-05-19 15:51:58 +090023And 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:
Masahiro Yamada5a27c732015-05-20 11:36:07 +090026
Masahiro Yamadab6ef3932016-05-19 15:51:58 +090027 $ tools/moveconfig.py CONFIG_CMD_USB CONFIG_SYS_TEXT_BASE
Masahiro Yamada5a27c732015-05-20 11:36:07 +090028
Masahiro Yamadab6ef3932016-05-19 15:51:58 +090029The tool walks through all the defconfig files and move the given CONFIGs.
Masahiro Yamada5a27c732015-05-20 11:36:07 +090030
31The log is also displayed on the terminal.
32
Masahiro Yamada1d085562016-05-19 15:52:02 +090033The log is printed for each defconfig as follows:
Masahiro Yamada5a27c732015-05-20 11:36:07 +090034
Masahiro Yamada1d085562016-05-19 15:52:02 +090035<defconfig_name>
36 <action1>
37 <action2>
38 <action3>
39 ...
Masahiro Yamada5a27c732015-05-20 11:36:07 +090040
Masahiro Yamada1d085562016-05-19 15:52:02 +090041<defconfig_name> is the name of the defconfig.
42
43<action*> shows what the tool did for that defconfig.
Masahiro Yamadac21fc7e2016-08-21 16:12:36 +090044It looks like one of the following:
Masahiro Yamada5a27c732015-05-20 11:36:07 +090045
46 - Move 'CONFIG_... '
47 This config option was moved to the defconfig
48
Masahiro Yamadacc008292016-05-19 15:51:56 +090049 - CONFIG_... is not defined in Kconfig. Do nothing.
Masahiro Yamada916224c2016-08-22 22:18:21 +090050 The entry for this CONFIG was not found in Kconfig. The option is not
51 defined in the config header, either. So, this case can be just skipped.
52
53 - CONFIG_... is not defined in Kconfig (suspicious). Do nothing.
54 This option is defined in the config header, but its entry was not found
55 in Kconfig.
Masahiro Yamadacc008292016-05-19 15:51:56 +090056 There are two common cases:
57 - You forgot to create an entry for the CONFIG before running
58 this tool, or made a typo in a CONFIG passed to this tool.
59 - The entry was hidden due to unmet 'depends on'.
Masahiro Yamada916224c2016-08-22 22:18:21 +090060 The tool does not know if the result is reasonable, so please check it
61 manually.
Masahiro Yamada5a27c732015-05-20 11:36:07 +090062
Masahiro Yamadacc008292016-05-19 15:51:56 +090063 - 'CONFIG_...' is the same as the define in Kconfig. Do nothing.
64 The define in the config header matched the one in Kconfig.
65 We do not need to touch it.
Masahiro Yamada5a27c732015-05-20 11:36:07 +090066
Masahiro Yamada90ed6cb2016-05-19 15:51:53 +090067 - Compiler is missing. Do nothing.
68 The compiler specified for this architecture was not found
69 in your PATH environment.
70 (If -e option is passed, the tool exits immediately.)
71
72 - Failed to process.
Masahiro Yamada5a27c732015-05-20 11:36:07 +090073 An error occurred during processing this defconfig. Skipped.
74 (If -e option is passed, the tool exits immediately on error.)
75
76Finally, you will be asked, Clean up headers? [y/n]:
77
78If you say 'y' here, the unnecessary config defines are removed
79from the config headers (include/configs/*.h).
80It just uses the regex method, so you should not rely on it.
81Just in case, please do 'git diff' to see what happened.
82
83
Masahiro Yamadab6ef3932016-05-19 15:51:58 +090084How does it work?
85-----------------
Masahiro Yamada5a27c732015-05-20 11:36:07 +090086
87This tool runs configuration and builds include/autoconf.mk for every
88defconfig. The config options defined in Kconfig appear in the .config
89file (unless they are hidden because of unmet dependency.)
90On the other hand, the config options defined by board headers are seen
91in include/autoconf.mk. The tool looks for the specified options in both
Masahiro Yamadab6ef3932016-05-19 15:51:58 +090092of them to decide the appropriate action for the options. If the given
93config option is found in the .config, but its value does not match the
94one from the board header, the config option in the .config is replaced
95with the define in the board header. Then, the .config is synced by
96"make savedefconfig" and the defconfig is updated with it.
Masahiro Yamada5a27c732015-05-20 11:36:07 +090097
98For faster processing, this tool handles multi-threading. It creates
99separate build directories where the out-of-tree build is run. The
100temporary build directories are automatically created and deleted as
101needed. The number of threads are chosen based on the number of the CPU
102cores of your system although you can change it via -j (--jobs) option.
103
104
105Toolchains
106----------
107
108Appropriate toolchain are necessary to generate include/autoconf.mk
109for all the architectures supported by U-Boot. Most of them are available
110at the kernel.org site, some are not provided by kernel.org.
111
112The default per-arch CROSS_COMPILE used by this tool is specified by
113the list below, CROSS_COMPILE. You may wish to update the list to
114use your own. Instead of modifying the list directly, you can give
115them via environments.
116
117
Simon Glassddf91c62017-06-01 19:39:00 -0600118Tips and trips
119--------------
120
121To sync only X86 defconfigs:
122
123 ./tools/moveconfig.py -s -d <(grep -l X86 configs/*)
124
125or:
126
127 grep -l X86 configs/* | ./tools/moveconfig.py -s -d -
128
129To process CONFIG_CMD_FPGAD only for a subset of configs based on path match:
130
131 ls configs/{hrcon*,iocon*,strider*} | \
132 ./tools/moveconfig.py -Cy CONFIG_CMD_FPGAD -d -
133
134
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900135Available options
136-----------------
137
138 -c, --color
139 Surround each portion of the log with escape sequences to display it
140 in color on the terminal.
141
Simon Glass9ede2122016-09-12 23:18:21 -0600142 -C, --commit
143 Create a git commit with the changes when the operation is complete. A
144 standard commit message is used which may need to be edited.
145
Joe Hershberger91040e82015-05-19 13:21:19 -0500146 -d, --defconfigs
Masahiro Yamada0dbc9b52016-10-19 14:39:54 +0900147 Specify a file containing a list of defconfigs to move. The defconfig
Simon Glassddf91c62017-06-01 19:39:00 -0600148 files can be given with shell-style wildcards. Use '-' to read from stdin.
Joe Hershberger91040e82015-05-19 13:21:19 -0500149
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900150 -n, --dry-run
Masahiro Yamadab6ef3932016-05-19 15:51:58 +0900151 Perform a trial run that does not make any changes. It is useful to
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900152 see what is going to happen before one actually runs it.
153
154 -e, --exit-on-error
155 Exit immediately if Make exits with a non-zero status while processing
156 a defconfig file.
157
Masahiro Yamada8513dc02016-05-19 15:52:08 +0900158 -s, --force-sync
159 Do "make savedefconfig" forcibly for all the defconfig files.
160 If not specified, "make savedefconfig" only occurs for cases
161 where at least one CONFIG was moved.
162
Masahiro Yamada07913d12016-08-22 22:18:22 +0900163 -S, --spl
164 Look for moved config options in spl/include/autoconf.mk instead of
165 include/autoconf.mk. This is useful for moving options for SPL build
166 because SPL related options (mostly prefixed with CONFIG_SPL_) are
167 sometimes blocked by CONFIG_SPL_BUILD ifdef conditionals.
168
Joe Hershberger2144f882015-05-19 13:21:20 -0500169 -H, --headers-only
170 Only cleanup the headers; skip the defconfig processing
171
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900172 -j, --jobs
173 Specify the number of threads to run simultaneously. If not specified,
174 the number of threads is the same as the number of CPU cores.
175
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500176 -r, --git-ref
177 Specify the git ref to clone for building the autoconf.mk. If unspecified
178 use the CWD. This is useful for when changes to the Kconfig affect the
179 default values and you want to capture the state of the defconfig from
180 before that change was in effect. If in doubt, specify a ref pre-Kconfig
181 changes (use HEAD if Kconfig changes are not committed). Worst case it will
182 take a bit longer to run, but will always do the right thing.
183
Joe Hershberger95bf9c72015-05-19 13:21:24 -0500184 -v, --verbose
185 Show any build errors as boards are built
186
Simon Glass6b403df2016-09-12 23:18:20 -0600187 -y, --yes
188 Instead of prompting, automatically go ahead with all operations. This
Simon Glassddf91c62017-06-01 19:39:00 -0600189 includes cleaning up headers, CONFIG_SYS_EXTRA_OPTIONS, the config whitelist
190 and the README.
Simon Glass6b403df2016-09-12 23:18:20 -0600191
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900192To see the complete list of supported options, run
193
194 $ tools/moveconfig.py -h
195
196"""
197
Masahiro Yamada8ba1f5d2016-07-25 19:15:24 +0900198import copy
Masahiro Yamadaf2f69812016-07-25 19:15:25 +0900199import difflib
Masahiro Yamadac8e1b102016-05-19 15:52:07 +0900200import filecmp
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900201import fnmatch
Masahiro Yamada0dbc9b52016-10-19 14:39:54 +0900202import glob
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900203import multiprocessing
204import optparse
205import os
206import re
207import shutil
208import subprocess
209import sys
210import tempfile
211import time
212
213SHOW_GNU_MAKE = 'scripts/show-gnu-make'
214SLEEP_TIME=0.03
215
216# Here is the list of cross-tools I use.
217# Most of them are available at kernel.org
Masahiro Yamadac21fc7e2016-08-21 16:12:36 +0900218# (https://www.kernel.org/pub/tools/crosstool/files/bin/), except the following:
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900219# arc: https://github.com/foss-for-synopsys-dwc-arc-processors/toolchain/releases
Bin Meng4440ece2015-09-25 01:22:39 -0700220# nds32: http://osdk.andestech.com/packages/nds32le-linux-glibc-v1.tgz
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900221# nios2: https://sourcery.mentor.com/GNUToolchain/subscription42545
222# sh: http://sourcery.mentor.com/public/gnu_toolchain/sh-linux-gnu
223CROSS_COMPILE = {
224 'arc': 'arc-linux-',
225 'aarch64': 'aarch64-linux-',
226 'arm': 'arm-unknown-linux-gnueabi-',
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900227 'm68k': 'm68k-linux-',
228 'microblaze': 'microblaze-linux-',
229 'mips': 'mips-linux-',
230 'nds32': 'nds32le-linux-',
231 'nios2': 'nios2-linux-gnu-',
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900232 'powerpc': 'powerpc-linux-',
233 'sh': 'sh-linux-gnu-',
Masahiro Yamada88e13462016-08-21 16:03:08 +0900234 'x86': 'i386-linux-',
235 'xtensa': 'xtensa-linux-'
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900236}
237
238STATE_IDLE = 0
239STATE_DEFCONFIG = 1
240STATE_AUTOCONF = 2
Joe Hershberger96464ba2015-05-19 13:21:17 -0500241STATE_SAVEDEFCONFIG = 3
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900242
243ACTION_MOVE = 0
Masahiro Yamadacc008292016-05-19 15:51:56 +0900244ACTION_NO_ENTRY = 1
Masahiro Yamada916224c2016-08-22 22:18:21 +0900245ACTION_NO_ENTRY_WARN = 2
246ACTION_NO_CHANGE = 3
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900247
248COLOR_BLACK = '0;30'
249COLOR_RED = '0;31'
250COLOR_GREEN = '0;32'
251COLOR_BROWN = '0;33'
252COLOR_BLUE = '0;34'
253COLOR_PURPLE = '0;35'
254COLOR_CYAN = '0;36'
255COLOR_LIGHT_GRAY = '0;37'
256COLOR_DARK_GRAY = '1;30'
257COLOR_LIGHT_RED = '1;31'
258COLOR_LIGHT_GREEN = '1;32'
259COLOR_YELLOW = '1;33'
260COLOR_LIGHT_BLUE = '1;34'
261COLOR_LIGHT_PURPLE = '1;35'
262COLOR_LIGHT_CYAN = '1;36'
263COLOR_WHITE = '1;37'
264
265### helper functions ###
266def get_devnull():
267 """Get the file object of '/dev/null' device."""
268 try:
269 devnull = subprocess.DEVNULL # py3k
270 except AttributeError:
271 devnull = open(os.devnull, 'wb')
272 return devnull
273
274def check_top_directory():
275 """Exit if we are not at the top of source directory."""
276 for f in ('README', 'Licenses'):
277 if not os.path.exists(f):
278 sys.exit('Please run at the top of source directory.')
279
Masahiro Yamadabd63e5b2016-05-19 15:51:54 +0900280def check_clean_directory():
281 """Exit if the source tree is not clean."""
282 for f in ('.config', 'include/config'):
283 if os.path.exists(f):
284 sys.exit("source tree is not clean, please run 'make mrproper'")
285
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900286def get_make_cmd():
287 """Get the command name of GNU Make.
288
289 U-Boot needs GNU Make for building, but the command name is not
290 necessarily "make". (for example, "gmake" on FreeBSD).
291 Returns the most appropriate command name on your system.
292 """
293 process = subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE)
294 ret = process.communicate()
295 if process.returncode:
296 sys.exit('GNU Make not found')
297 return ret[0].rstrip()
298
Simon Glass25f978c2017-06-01 19:38:58 -0600299def get_matched_defconfig(line):
300 """Get the defconfig files that match a pattern
301
302 Args:
303 line: Path or filename to match, e.g. 'configs/snow_defconfig' or
304 'k2*_defconfig'. If no directory is provided, 'configs/' is
305 prepended
306
307 Returns:
308 a list of matching defconfig files
309 """
310 dirname = os.path.dirname(line)
311 if dirname:
312 pattern = line
313 else:
314 pattern = os.path.join('configs', line)
315 return glob.glob(pattern) + glob.glob(pattern + '_defconfig')
316
Masahiro Yamada0dbc9b52016-10-19 14:39:54 +0900317def get_matched_defconfigs(defconfigs_file):
Simon Glassee4e61b2017-06-01 19:38:59 -0600318 """Get all the defconfig files that match the patterns in a file.
319
320 Args:
321 defconfigs_file: File containing a list of defconfigs to process, or
322 '-' to read the list from stdin
323
324 Returns:
325 A list of paths to defconfig files, with no duplicates
326 """
Masahiro Yamada0dbc9b52016-10-19 14:39:54 +0900327 defconfigs = []
Simon Glassee4e61b2017-06-01 19:38:59 -0600328 if defconfigs_file == '-':
329 fd = sys.stdin
330 defconfigs_file = 'stdin'
331 else:
332 fd = open(defconfigs_file)
333 for i, line in enumerate(fd):
Masahiro Yamada0dbc9b52016-10-19 14:39:54 +0900334 line = line.strip()
335 if not line:
336 continue # skip blank lines silently
Simon Glass25f978c2017-06-01 19:38:58 -0600337 matched = get_matched_defconfig(line)
Masahiro Yamada0dbc9b52016-10-19 14:39:54 +0900338 if not matched:
339 print >> sys.stderr, "warning: %s:%d: no defconfig matched '%s'" % \
340 (defconfigs_file, i + 1, line)
341
342 defconfigs += matched
343
344 # use set() to drop multiple matching
345 return [ defconfig[len('configs') + 1:] for defconfig in set(defconfigs) ]
346
Masahiro Yamada684c3062016-07-25 19:15:28 +0900347def get_all_defconfigs():
348 """Get all the defconfig files under the configs/ directory."""
349 defconfigs = []
350 for (dirpath, dirnames, filenames) in os.walk('configs'):
351 dirpath = dirpath[len('configs') + 1:]
352 for filename in fnmatch.filter(filenames, '*_defconfig'):
353 defconfigs.append(os.path.join(dirpath, filename))
354
355 return defconfigs
356
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900357def color_text(color_enabled, color, string):
358 """Return colored string."""
359 if color_enabled:
Masahiro Yamada1d085562016-05-19 15:52:02 +0900360 # LF should not be surrounded by the escape sequence.
361 # Otherwise, additional whitespace or line-feed might be printed.
362 return '\n'.join([ '\033[' + color + 'm' + s + '\033[0m' if s else ''
363 for s in string.split('\n') ])
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900364 else:
365 return string
366
Masahiro Yamadae9ea1222016-07-25 19:15:26 +0900367def show_diff(a, b, file_path, color_enabled):
Masahiro Yamadaf2f69812016-07-25 19:15:25 +0900368 """Show unidified diff.
369
370 Arguments:
371 a: A list of lines (before)
372 b: A list of lines (after)
373 file_path: Path to the file
Masahiro Yamadae9ea1222016-07-25 19:15:26 +0900374 color_enabled: Display the diff in color
Masahiro Yamadaf2f69812016-07-25 19:15:25 +0900375 """
376
377 diff = difflib.unified_diff(a, b,
378 fromfile=os.path.join('a', file_path),
379 tofile=os.path.join('b', file_path))
380
381 for line in diff:
Masahiro Yamadae9ea1222016-07-25 19:15:26 +0900382 if line[0] == '-' and line[1] != '-':
383 print color_text(color_enabled, COLOR_RED, line),
384 elif line[0] == '+' and line[1] != '+':
385 print color_text(color_enabled, COLOR_GREEN, line),
386 else:
387 print line,
Masahiro Yamadaf2f69812016-07-25 19:15:25 +0900388
Masahiro Yamada90ed6cb2016-05-19 15:51:53 +0900389def update_cross_compile(color_enabled):
Robert P. J. Day1cc0a9f2016-05-04 04:47:31 -0400390 """Update per-arch CROSS_COMPILE via environment variables
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900391
392 The default CROSS_COMPILE values are available
393 in the CROSS_COMPILE list above.
394
Robert P. J. Day1cc0a9f2016-05-04 04:47:31 -0400395 You can override them via environment variables
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900396 CROSS_COMPILE_{ARCH}.
397
398 For example, if you want to override toolchain prefixes
399 for ARM and PowerPC, you can do as follows in your shell:
400
401 export CROSS_COMPILE_ARM=...
402 export CROSS_COMPILE_POWERPC=...
Masahiro Yamada90ed6cb2016-05-19 15:51:53 +0900403
404 Then, this function checks if specified compilers really exist in your
405 PATH environment.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900406 """
407 archs = []
408
409 for arch in os.listdir('arch'):
410 if os.path.exists(os.path.join('arch', arch, 'Makefile')):
411 archs.append(arch)
412
413 # arm64 is a special case
414 archs.append('aarch64')
415
416 for arch in archs:
417 env = 'CROSS_COMPILE_' + arch.upper()
418 cross_compile = os.environ.get(env)
Masahiro Yamada90ed6cb2016-05-19 15:51:53 +0900419 if not cross_compile:
420 cross_compile = CROSS_COMPILE.get(arch, '')
421
422 for path in os.environ["PATH"].split(os.pathsep):
423 gcc_path = os.path.join(path, cross_compile + 'gcc')
424 if os.path.isfile(gcc_path) and os.access(gcc_path, os.X_OK):
425 break
426 else:
427 print >> sys.stderr, color_text(color_enabled, COLOR_YELLOW,
428 'warning: %sgcc: not found in PATH. %s architecture boards will be skipped'
429 % (cross_compile, arch))
430 cross_compile = None
431
432 CROSS_COMPILE[arch] = cross_compile
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900433
Masahiro Yamada8ba1f5d2016-07-25 19:15:24 +0900434def extend_matched_lines(lines, matched, pre_patterns, post_patterns, extend_pre,
435 extend_post):
436 """Extend matched lines if desired patterns are found before/after already
437 matched lines.
438
439 Arguments:
440 lines: A list of lines handled.
441 matched: A list of line numbers that have been already matched.
442 (will be updated by this function)
443 pre_patterns: A list of regular expression that should be matched as
444 preamble.
445 post_patterns: A list of regular expression that should be matched as
446 postamble.
447 extend_pre: Add the line number of matched preamble to the matched list.
448 extend_post: Add the line number of matched postamble to the matched list.
449 """
450 extended_matched = []
451
452 j = matched[0]
453
454 for i in matched:
455 if i == 0 or i < j:
456 continue
457 j = i
458 while j in matched:
459 j += 1
460 if j >= len(lines):
461 break
462
463 for p in pre_patterns:
464 if p.search(lines[i - 1]):
465 break
466 else:
467 # not matched
468 continue
469
470 for p in post_patterns:
471 if p.search(lines[j]):
472 break
473 else:
474 # not matched
475 continue
476
477 if extend_pre:
478 extended_matched.append(i - 1)
479 if extend_post:
480 extended_matched.append(j)
481
482 matched += extended_matched
483 matched.sort()
484
Chris Packham85edfc12017-05-02 21:30:46 +1200485def confirm(options, prompt):
486 if not options.yes:
487 while True:
488 choice = raw_input('{} [y/n]: '.format(prompt))
489 choice = choice.lower()
490 print choice
491 if choice == 'y' or choice == 'n':
492 break
493
494 if choice == 'n':
495 return False
496
497 return True
498
Masahiro Yamadae9ea1222016-07-25 19:15:26 +0900499def cleanup_one_header(header_path, patterns, options):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900500 """Clean regex-matched lines away from a file.
501
502 Arguments:
503 header_path: path to the cleaned file.
504 patterns: list of regex patterns. Any lines matching to these
505 patterns are deleted.
Masahiro Yamadae9ea1222016-07-25 19:15:26 +0900506 options: option flags.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900507 """
508 with open(header_path) as f:
509 lines = f.readlines()
510
511 matched = []
512 for i, line in enumerate(lines):
Masahiro Yamadaa3a779f2016-07-25 19:15:27 +0900513 if i - 1 in matched and lines[i - 1][-2:] == '\\\n':
514 matched.append(i)
515 continue
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900516 for pattern in patterns:
Masahiro Yamada8ba1f5d2016-07-25 19:15:24 +0900517 if pattern.search(line):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900518 matched.append(i)
519 break
520
Masahiro Yamada8ba1f5d2016-07-25 19:15:24 +0900521 if not matched:
522 return
523
524 # remove empty #ifdef ... #endif, successive blank lines
525 pattern_if = re.compile(r'#\s*if(def|ndef)?\W') # #if, #ifdef, #ifndef
526 pattern_elif = re.compile(r'#\s*el(if|se)\W') # #elif, #else
527 pattern_endif = re.compile(r'#\s*endif\W') # #endif
528 pattern_blank = re.compile(r'^\s*$') # empty line
529
530 while True:
531 old_matched = copy.copy(matched)
532 extend_matched_lines(lines, matched, [pattern_if],
533 [pattern_endif], True, True)
534 extend_matched_lines(lines, matched, [pattern_elif],
535 [pattern_elif, pattern_endif], True, False)
536 extend_matched_lines(lines, matched, [pattern_if, pattern_elif],
537 [pattern_blank], False, True)
538 extend_matched_lines(lines, matched, [pattern_blank],
539 [pattern_elif, pattern_endif], True, False)
540 extend_matched_lines(lines, matched, [pattern_blank],
541 [pattern_blank], True, False)
542 if matched == old_matched:
543 break
544
Masahiro Yamadaf2f69812016-07-25 19:15:25 +0900545 tolines = copy.copy(lines)
546
547 for i in reversed(matched):
548 tolines.pop(i)
549
Masahiro Yamadae9ea1222016-07-25 19:15:26 +0900550 show_diff(lines, tolines, header_path, options.color)
Masahiro Yamada8ba1f5d2016-07-25 19:15:24 +0900551
Masahiro Yamadae9ea1222016-07-25 19:15:26 +0900552 if options.dry_run:
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900553 return
554
555 with open(header_path, 'w') as f:
Masahiro Yamadaf2f69812016-07-25 19:15:25 +0900556 for line in tolines:
557 f.write(line)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900558
Masahiro Yamadae9ea1222016-07-25 19:15:26 +0900559def cleanup_headers(configs, options):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900560 """Delete config defines from board headers.
561
562 Arguments:
Masahiro Yamadab134bc12016-05-19 15:51:57 +0900563 configs: A list of CONFIGs to remove.
Masahiro Yamadae9ea1222016-07-25 19:15:26 +0900564 options: option flags.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900565 """
Chris Packham85edfc12017-05-02 21:30:46 +1200566 if not confirm(options, 'Clean up headers?'):
567 return
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900568
569 patterns = []
Masahiro Yamadab134bc12016-05-19 15:51:57 +0900570 for config in configs:
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900571 patterns.append(re.compile(r'#\s*define\s+%s\W' % config))
572 patterns.append(re.compile(r'#\s*undef\s+%s\W' % config))
573
Joe Hershberger60727f52015-05-19 13:21:21 -0500574 for dir in 'include', 'arch', 'board':
575 for (dirpath, dirnames, filenames) in os.walk(dir):
Masahiro Yamadadc6de502016-07-25 19:15:22 +0900576 if dirpath == os.path.join('include', 'generated'):
577 continue
Joe Hershberger60727f52015-05-19 13:21:21 -0500578 for filename in filenames:
579 if not fnmatch.fnmatch(filename, '*~'):
580 cleanup_one_header(os.path.join(dirpath, filename),
Masahiro Yamadae9ea1222016-07-25 19:15:26 +0900581 patterns, options)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900582
Masahiro Yamada9ab02962016-07-25 19:15:29 +0900583def cleanup_one_extra_option(defconfig_path, configs, options):
584 """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in one defconfig file.
585
586 Arguments:
587 defconfig_path: path to the cleaned defconfig file.
588 configs: A list of CONFIGs to remove.
589 options: option flags.
590 """
591
592 start = 'CONFIG_SYS_EXTRA_OPTIONS="'
593 end = '"\n'
594
595 with open(defconfig_path) as f:
596 lines = f.readlines()
597
598 for i, line in enumerate(lines):
599 if line.startswith(start) and line.endswith(end):
600 break
601 else:
602 # CONFIG_SYS_EXTRA_OPTIONS was not found in this defconfig
603 return
604
605 old_tokens = line[len(start):-len(end)].split(',')
606 new_tokens = []
607
608 for token in old_tokens:
609 pos = token.find('=')
610 if not (token[:pos] if pos >= 0 else token) in configs:
611 new_tokens.append(token)
612
613 if new_tokens == old_tokens:
614 return
615
616 tolines = copy.copy(lines)
617
618 if new_tokens:
619 tolines[i] = start + ','.join(new_tokens) + end
620 else:
621 tolines.pop(i)
622
623 show_diff(lines, tolines, defconfig_path, options.color)
624
625 if options.dry_run:
626 return
627
628 with open(defconfig_path, 'w') as f:
629 for line in tolines:
630 f.write(line)
631
632def cleanup_extra_options(configs, options):
633 """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in defconfig files.
634
635 Arguments:
636 configs: A list of CONFIGs to remove.
637 options: option flags.
638 """
Chris Packham85edfc12017-05-02 21:30:46 +1200639 if not confirm(options, 'Clean up CONFIG_SYS_EXTRA_OPTIONS?'):
640 return
Masahiro Yamada9ab02962016-07-25 19:15:29 +0900641
642 configs = [ config[len('CONFIG_'):] for config in configs ]
643
644 defconfigs = get_all_defconfigs()
645
646 for defconfig in defconfigs:
647 cleanup_one_extra_option(os.path.join('configs', defconfig), configs,
648 options)
649
Chris Packhamca438342017-05-02 21:30:47 +1200650def cleanup_whitelist(configs, options):
651 """Delete config whitelist entries
652
653 Arguments:
654 configs: A list of CONFIGs to remove.
655 options: option flags.
656 """
657 if not confirm(options, 'Clean up whitelist entries?'):
658 return
659
660 with open(os.path.join('scripts', 'config_whitelist.txt')) as f:
661 lines = f.readlines()
662
663 lines = [x for x in lines if x.strip() not in configs]
664
665 with open(os.path.join('scripts', 'config_whitelist.txt'), 'w') as f:
666 f.write(''.join(lines))
667
Chris Packhamf90df592017-05-02 21:30:48 +1200668def find_matching(patterns, line):
669 for pat in patterns:
670 if pat.search(line):
671 return True
672 return False
673
674def cleanup_readme(configs, options):
675 """Delete config description in README
676
677 Arguments:
678 configs: A list of CONFIGs to remove.
679 options: option flags.
680 """
681 if not confirm(options, 'Clean up README?'):
682 return
683
684 patterns = []
685 for config in configs:
686 patterns.append(re.compile(r'^\s+%s' % config))
687
688 with open('README') as f:
689 lines = f.readlines()
690
691 found = False
692 newlines = []
693 for line in lines:
694 if not found:
695 found = find_matching(patterns, line)
696 if found:
697 continue
698
699 if found and re.search(r'^\s+CONFIG', line):
700 found = False
701
702 if not found:
703 newlines.append(line)
704
705 with open('README', 'w') as f:
706 f.write(''.join(newlines))
707
Chris Packhamca438342017-05-02 21:30:47 +1200708
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900709### classes ###
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900710class Progress:
711
712 """Progress Indicator"""
713
714 def __init__(self, total):
715 """Create a new progress indicator.
716
717 Arguments:
718 total: A number of defconfig files to process.
719 """
720 self.current = 0
721 self.total = total
722
723 def inc(self):
724 """Increment the number of processed defconfig files."""
725
726 self.current += 1
727
728 def show(self):
729 """Display the progress."""
730 print ' %d defconfigs out of %d\r' % (self.current, self.total),
731 sys.stdout.flush()
732
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900733class KconfigParser:
734
735 """A parser of .config and include/autoconf.mk."""
736
737 re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
738 re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
739
Masahiro Yamada522e8dc2016-05-19 15:52:01 +0900740 def __init__(self, configs, options, build_dir):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900741 """Create a new parser.
742
743 Arguments:
Masahiro Yamadab134bc12016-05-19 15:51:57 +0900744 configs: A list of CONFIGs to move.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900745 options: option flags.
746 build_dir: Build directory.
747 """
Masahiro Yamadab134bc12016-05-19 15:51:57 +0900748 self.configs = configs
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900749 self.options = options
Masahiro Yamada1f169922016-05-19 15:52:00 +0900750 self.dotconfig = os.path.join(build_dir, '.config')
751 self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
Masahiro Yamada07913d12016-08-22 22:18:22 +0900752 self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
753 'autoconf.mk')
Masahiro Yamada1f169922016-05-19 15:52:00 +0900754 self.config_autoconf = os.path.join(build_dir, 'include', 'config',
755 'auto.conf')
Masahiro Yamada5da4f852016-05-19 15:52:06 +0900756 self.defconfig = os.path.join(build_dir, 'defconfig')
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900757
758 def get_cross_compile(self):
759 """Parse .config file and return CROSS_COMPILE.
760
761 Returns:
762 A string storing the compiler prefix for the architecture.
Masahiro Yamada90ed6cb2016-05-19 15:51:53 +0900763 Return a NULL string for architectures that do not require
764 compiler prefix (Sandbox and native build is the case).
765 Return None if the specified compiler is missing in your PATH.
766 Caller should distinguish '' and None.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900767 """
768 arch = ''
769 cpu = ''
Masahiro Yamada1f169922016-05-19 15:52:00 +0900770 for line in open(self.dotconfig):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900771 m = self.re_arch.match(line)
772 if m:
773 arch = m.group(1)
774 continue
775 m = self.re_cpu.match(line)
776 if m:
777 cpu = m.group(1)
778
Masahiro Yamada90ed6cb2016-05-19 15:51:53 +0900779 if not arch:
780 return None
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900781
782 # fix-up for aarch64
783 if arch == 'arm' and cpu == 'armv8':
784 arch = 'aarch64'
785
Masahiro Yamada90ed6cb2016-05-19 15:51:53 +0900786 return CROSS_COMPILE.get(arch, None)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900787
Masahiro Yamadab134bc12016-05-19 15:51:57 +0900788 def parse_one_config(self, config, dotconfig_lines, autoconf_lines):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900789 """Parse .config, defconfig, include/autoconf.mk for one config.
790
791 This function looks for the config options in the lines from
792 defconfig, .config, and include/autoconf.mk in order to decide
793 which action should be taken for this defconfig.
794
795 Arguments:
Masahiro Yamadab134bc12016-05-19 15:51:57 +0900796 config: CONFIG name to parse.
Masahiro Yamadacc008292016-05-19 15:51:56 +0900797 dotconfig_lines: lines from the .config file.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900798 autoconf_lines: lines from the include/autoconf.mk file.
799
800 Returns:
801 A tupple of the action for this defconfig and the line
802 matched for the config.
803 """
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900804 not_set = '# %s is not set' % config
805
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900806 for line in autoconf_lines:
807 line = line.rstrip()
808 if line.startswith(config + '='):
Masahiro Yamadacc008292016-05-19 15:51:56 +0900809 new_val = line
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900810 break
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900811 else:
Masahiro Yamadacc008292016-05-19 15:51:56 +0900812 new_val = not_set
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900813
Masahiro Yamada916224c2016-08-22 22:18:21 +0900814 for line in dotconfig_lines:
815 line = line.rstrip()
816 if line.startswith(config + '=') or line == not_set:
817 old_val = line
818 break
819 else:
820 if new_val == not_set:
821 return (ACTION_NO_ENTRY, config)
822 else:
823 return (ACTION_NO_ENTRY_WARN, config)
824
Masahiro Yamadacc008292016-05-19 15:51:56 +0900825 # If this CONFIG is neither bool nor trisate
826 if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set:
827 # tools/scripts/define2mk.sed changes '1' to 'y'.
828 # This is a problem if the CONFIG is int type.
829 # Check the type in Kconfig and handle it correctly.
830 if new_val[-2:] == '=y':
831 new_val = new_val[:-1] + '1'
832
Masahiro Yamada50301592016-06-15 14:33:50 +0900833 return (ACTION_NO_CHANGE if old_val == new_val else ACTION_MOVE,
834 new_val)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900835
Masahiro Yamada1d085562016-05-19 15:52:02 +0900836 def update_dotconfig(self):
Masahiro Yamada6ff36d22016-05-19 15:51:50 +0900837 """Parse files for the config options and update the .config.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900838
Masahiro Yamadacc008292016-05-19 15:51:56 +0900839 This function parses the generated .config and include/autoconf.mk
840 searching the target options.
Masahiro Yamada6ff36d22016-05-19 15:51:50 +0900841 Move the config option(s) to the .config as needed.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900842
843 Arguments:
844 defconfig: defconfig name.
Masahiro Yamada522e8dc2016-05-19 15:52:01 +0900845
846 Returns:
Masahiro Yamada7fb0bac2016-05-19 15:52:04 +0900847 Return a tuple of (updated flag, log string).
848 The "updated flag" is True if the .config was updated, False
849 otherwise. The "log string" shows what happend to the .config.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900850 """
851
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900852 results = []
Masahiro Yamada7fb0bac2016-05-19 15:52:04 +0900853 updated = False
Masahiro Yamada916224c2016-08-22 22:18:21 +0900854 suspicious = False
Masahiro Yamada07913d12016-08-22 22:18:22 +0900855 rm_files = [self.config_autoconf, self.autoconf]
856
857 if self.options.spl:
858 if os.path.exists(self.spl_autoconf):
859 autoconf_path = self.spl_autoconf
860 rm_files.append(self.spl_autoconf)
861 else:
862 for f in rm_files:
863 os.remove(f)
864 return (updated, suspicious,
865 color_text(self.options.color, COLOR_BROWN,
866 "SPL is not enabled. Skipped.") + '\n')
867 else:
868 autoconf_path = self.autoconf
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900869
Masahiro Yamada1f169922016-05-19 15:52:00 +0900870 with open(self.dotconfig) as f:
Masahiro Yamadacc008292016-05-19 15:51:56 +0900871 dotconfig_lines = f.readlines()
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900872
Masahiro Yamada07913d12016-08-22 22:18:22 +0900873 with open(autoconf_path) as f:
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900874 autoconf_lines = f.readlines()
875
Masahiro Yamadab134bc12016-05-19 15:51:57 +0900876 for config in self.configs:
877 result = self.parse_one_config(config, dotconfig_lines,
Joe Hershberger96464ba2015-05-19 13:21:17 -0500878 autoconf_lines)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900879 results.append(result)
880
881 log = ''
882
883 for (action, value) in results:
884 if action == ACTION_MOVE:
885 actlog = "Move '%s'" % value
886 log_color = COLOR_LIGHT_GREEN
Masahiro Yamadacc008292016-05-19 15:51:56 +0900887 elif action == ACTION_NO_ENTRY:
888 actlog = "%s is not defined in Kconfig. Do nothing." % value
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900889 log_color = COLOR_LIGHT_BLUE
Masahiro Yamada916224c2016-08-22 22:18:21 +0900890 elif action == ACTION_NO_ENTRY_WARN:
891 actlog = "%s is not defined in Kconfig (suspicious). Do nothing." % value
892 log_color = COLOR_YELLOW
893 suspicious = True
Masahiro Yamadacc008292016-05-19 15:51:56 +0900894 elif action == ACTION_NO_CHANGE:
895 actlog = "'%s' is the same as the define in Kconfig. Do nothing." \
896 % value
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900897 log_color = COLOR_LIGHT_PURPLE
Masahiro Yamada07913d12016-08-22 22:18:22 +0900898 elif action == ACTION_SPL_NOT_EXIST:
899 actlog = "SPL is not enabled for this defconfig. Skip."
900 log_color = COLOR_PURPLE
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900901 else:
902 sys.exit("Internal Error. This should not happen.")
903
Masahiro Yamada1d085562016-05-19 15:52:02 +0900904 log += color_text(self.options.color, log_color, actlog) + '\n'
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900905
Masahiro Yamada1f169922016-05-19 15:52:00 +0900906 with open(self.dotconfig, 'a') as f:
Masahiro Yamadae423d172016-05-19 15:51:49 +0900907 for (action, value) in results:
908 if action == ACTION_MOVE:
909 f.write(value + '\n')
Masahiro Yamada7fb0bac2016-05-19 15:52:04 +0900910 updated = True
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900911
Masahiro Yamada5da4f852016-05-19 15:52:06 +0900912 self.results = results
Masahiro Yamada07913d12016-08-22 22:18:22 +0900913 for f in rm_files:
914 os.remove(f)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900915
Masahiro Yamada916224c2016-08-22 22:18:21 +0900916 return (updated, suspicious, log)
Masahiro Yamada522e8dc2016-05-19 15:52:01 +0900917
Masahiro Yamada5da4f852016-05-19 15:52:06 +0900918 def check_defconfig(self):
919 """Check the defconfig after savedefconfig
920
921 Returns:
922 Return additional log if moved CONFIGs were removed again by
923 'make savedefconfig'.
924 """
925
926 log = ''
927
928 with open(self.defconfig) as f:
929 defconfig_lines = f.readlines()
930
931 for (action, value) in self.results:
932 if action != ACTION_MOVE:
933 continue
934 if not value + '\n' in defconfig_lines:
935 log += color_text(self.options.color, COLOR_YELLOW,
936 "'%s' was removed by savedefconfig.\n" %
937 value)
938
939 return log
940
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900941class Slot:
942
943 """A slot to store a subprocess.
944
945 Each instance of this class handles one subprocess.
946 This class is useful to control multiple threads
947 for faster processing.
948 """
949
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500950 def __init__(self, configs, options, progress, devnull, make_cmd, reference_src_dir):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900951 """Create a new process slot.
952
953 Arguments:
Masahiro Yamadab134bc12016-05-19 15:51:57 +0900954 configs: A list of CONFIGs to move.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900955 options: option flags.
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900956 progress: A progress indicator.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900957 devnull: A file object of '/dev/null'.
958 make_cmd: command name of GNU Make.
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500959 reference_src_dir: Determine the true starting config state from this
960 source tree.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900961 """
962 self.options = options
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900963 self.progress = progress
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900964 self.build_dir = tempfile.mkdtemp()
965 self.devnull = devnull
966 self.make_cmd = (make_cmd, 'O=' + self.build_dir)
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500967 self.reference_src_dir = reference_src_dir
Masahiro Yamada522e8dc2016-05-19 15:52:01 +0900968 self.parser = KconfigParser(configs, options, self.build_dir)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900969 self.state = STATE_IDLE
Masahiro Yamada09c6c062016-08-22 22:18:20 +0900970 self.failed_boards = set()
971 self.suspicious_boards = set()
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900972
973 def __del__(self):
974 """Delete the working directory
975
976 This function makes sure the temporary directory is cleaned away
977 even if Python suddenly dies due to error. It should be done in here
Joe Hershbergerf2dae752016-06-10 14:53:29 -0500978 because it is guaranteed the destructor is always invoked when the
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900979 instance of the class gets unreferenced.
980
981 If the subprocess is still running, wait until it finishes.
982 """
983 if self.state != STATE_IDLE:
984 while self.ps.poll() == None:
985 pass
986 shutil.rmtree(self.build_dir)
987
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900988 def add(self, defconfig):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900989 """Assign a new subprocess for defconfig and add it to the slot.
990
991 If the slot is vacant, create a new subprocess for processing the
992 given defconfig and add it to the slot. Just returns False if
993 the slot is occupied (i.e. the current subprocess is still running).
994
995 Arguments:
996 defconfig: defconfig name.
997
998 Returns:
999 Return True on success or False on failure
1000 """
1001 if self.state != STATE_IDLE:
1002 return False
Masahiro Yamadae307fa92016-06-08 11:47:37 +09001003
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001004 self.defconfig = defconfig
Masahiro Yamada1d085562016-05-19 15:52:02 +09001005 self.log = ''
Masahiro Yamadaf432c332016-06-15 14:33:52 +09001006 self.current_src_dir = self.reference_src_dir
Masahiro Yamadae307fa92016-06-08 11:47:37 +09001007 self.do_defconfig()
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001008 return True
1009
1010 def poll(self):
1011 """Check the status of the subprocess and handle it as needed.
1012
1013 Returns True if the slot is vacant (i.e. in idle state).
1014 If the configuration is successfully finished, assign a new
1015 subprocess to build include/autoconf.mk.
1016 If include/autoconf.mk is generated, invoke the parser to
Masahiro Yamada7fb0bac2016-05-19 15:52:04 +09001017 parse the .config and the include/autoconf.mk, moving
1018 config options to the .config as needed.
1019 If the .config was updated, run "make savedefconfig" to sync
1020 it, update the original defconfig, and then set the slot back
1021 to the idle state.
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001022
1023 Returns:
1024 Return True if the subprocess is terminated, False otherwise
1025 """
1026 if self.state == STATE_IDLE:
1027 return True
1028
1029 if self.ps.poll() == None:
1030 return False
1031
1032 if self.ps.poll() != 0:
Masahiro Yamadae307fa92016-06-08 11:47:37 +09001033 self.handle_error()
1034 elif self.state == STATE_DEFCONFIG:
Masahiro Yamadaf432c332016-06-15 14:33:52 +09001035 if self.reference_src_dir and not self.current_src_dir:
Joe Hershberger6b96c1a2016-06-10 14:53:32 -05001036 self.do_savedefconfig()
1037 else:
1038 self.do_autoconf()
Masahiro Yamadae307fa92016-06-08 11:47:37 +09001039 elif self.state == STATE_AUTOCONF:
Masahiro Yamadaf432c332016-06-15 14:33:52 +09001040 if self.current_src_dir:
1041 self.current_src_dir = None
Joe Hershberger6b96c1a2016-06-10 14:53:32 -05001042 self.do_defconfig()
1043 else:
1044 self.do_savedefconfig()
Masahiro Yamadae307fa92016-06-08 11:47:37 +09001045 elif self.state == STATE_SAVEDEFCONFIG:
1046 self.update_defconfig()
1047 else:
1048 sys.exit("Internal Error. This should not happen.")
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001049
Masahiro Yamadae307fa92016-06-08 11:47:37 +09001050 return True if self.state == STATE_IDLE else False
Joe Hershberger96464ba2015-05-19 13:21:17 -05001051
Masahiro Yamadae307fa92016-06-08 11:47:37 +09001052 def handle_error(self):
1053 """Handle error cases."""
Masahiro Yamada8513dc02016-05-19 15:52:08 +09001054
Masahiro Yamadae307fa92016-06-08 11:47:37 +09001055 self.log += color_text(self.options.color, COLOR_LIGHT_RED,
1056 "Failed to process.\n")
1057 if self.options.verbose:
1058 self.log += color_text(self.options.color, COLOR_LIGHT_CYAN,
1059 self.ps.stderr.read())
1060 self.finish(False)
Joe Hershberger96464ba2015-05-19 13:21:17 -05001061
Masahiro Yamadae307fa92016-06-08 11:47:37 +09001062 def do_defconfig(self):
1063 """Run 'make <board>_defconfig' to create the .config file."""
Masahiro Yamadac8e1b102016-05-19 15:52:07 +09001064
Masahiro Yamadae307fa92016-06-08 11:47:37 +09001065 cmd = list(self.make_cmd)
1066 cmd.append(self.defconfig)
1067 self.ps = subprocess.Popen(cmd, stdout=self.devnull,
Masahiro Yamadaf432c332016-06-15 14:33:52 +09001068 stderr=subprocess.PIPE,
1069 cwd=self.current_src_dir)
Masahiro Yamadae307fa92016-06-08 11:47:37 +09001070 self.state = STATE_DEFCONFIG
Masahiro Yamadac8e1b102016-05-19 15:52:07 +09001071
Masahiro Yamadae307fa92016-06-08 11:47:37 +09001072 def do_autoconf(self):
1073 """Run 'make include/config/auto.conf'."""
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001074
Joe Hershberger25400092015-05-19 13:21:23 -05001075 self.cross_compile = self.parser.get_cross_compile()
Masahiro Yamada90ed6cb2016-05-19 15:51:53 +09001076 if self.cross_compile is None:
Masahiro Yamada1d085562016-05-19 15:52:02 +09001077 self.log += color_text(self.options.color, COLOR_YELLOW,
1078 "Compiler is missing. Do nothing.\n")
Masahiro Yamada4efef992016-05-19 15:52:03 +09001079 self.finish(False)
Masahiro Yamadae307fa92016-06-08 11:47:37 +09001080 return
Masahiro Yamada90ed6cb2016-05-19 15:51:53 +09001081
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001082 cmd = list(self.make_cmd)
Joe Hershberger25400092015-05-19 13:21:23 -05001083 if self.cross_compile:
1084 cmd.append('CROSS_COMPILE=%s' % self.cross_compile)
Joe Hershberger7740f652015-05-19 13:21:18 -05001085 cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001086 cmd.append('include/config/auto.conf')
Joe Hershberger25400092015-05-19 13:21:23 -05001087 self.ps = subprocess.Popen(cmd, stdout=self.devnull,
Masahiro Yamadaf432c332016-06-15 14:33:52 +09001088 stderr=subprocess.PIPE,
1089 cwd=self.current_src_dir)
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001090 self.state = STATE_AUTOCONF
Masahiro Yamadae307fa92016-06-08 11:47:37 +09001091
1092 def do_savedefconfig(self):
1093 """Update the .config and run 'make savedefconfig'."""
1094
Masahiro Yamada916224c2016-08-22 22:18:21 +09001095 (updated, suspicious, log) = self.parser.update_dotconfig()
1096 if suspicious:
1097 self.suspicious_boards.add(self.defconfig)
Masahiro Yamadae307fa92016-06-08 11:47:37 +09001098 self.log += log
1099
1100 if not self.options.force_sync and not updated:
1101 self.finish(True)
1102 return
1103 if updated:
1104 self.log += color_text(self.options.color, COLOR_LIGHT_GREEN,
1105 "Syncing by savedefconfig...\n")
1106 else:
1107 self.log += "Syncing by savedefconfig (forced by option)...\n"
1108
1109 cmd = list(self.make_cmd)
1110 cmd.append('savedefconfig')
1111 self.ps = subprocess.Popen(cmd, stdout=self.devnull,
1112 stderr=subprocess.PIPE)
1113 self.state = STATE_SAVEDEFCONFIG
1114
1115 def update_defconfig(self):
1116 """Update the input defconfig and go back to the idle state."""
1117
Masahiro Yamadafc2661e2016-06-15 14:33:54 +09001118 log = self.parser.check_defconfig()
1119 if log:
Masahiro Yamada09c6c062016-08-22 22:18:20 +09001120 self.suspicious_boards.add(self.defconfig)
Masahiro Yamadafc2661e2016-06-15 14:33:54 +09001121 self.log += log
Masahiro Yamadae307fa92016-06-08 11:47:37 +09001122 orig_defconfig = os.path.join('configs', self.defconfig)
1123 new_defconfig = os.path.join(self.build_dir, 'defconfig')
1124 updated = not filecmp.cmp(orig_defconfig, new_defconfig)
1125
1126 if updated:
Joe Hershberger06cc1d32016-06-10 14:53:30 -05001127 self.log += color_text(self.options.color, COLOR_LIGHT_BLUE,
Masahiro Yamadae307fa92016-06-08 11:47:37 +09001128 "defconfig was updated.\n")
1129
1130 if not self.options.dry_run and updated:
1131 shutil.move(new_defconfig, orig_defconfig)
1132 self.finish(True)
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001133
Masahiro Yamada4efef992016-05-19 15:52:03 +09001134 def finish(self, success):
1135 """Display log along with progress and go to the idle state.
Masahiro Yamada1d085562016-05-19 15:52:02 +09001136
1137 Arguments:
Masahiro Yamada4efef992016-05-19 15:52:03 +09001138 success: Should be True when the defconfig was processed
1139 successfully, or False when it fails.
Masahiro Yamada1d085562016-05-19 15:52:02 +09001140 """
1141 # output at least 30 characters to hide the "* defconfigs out of *".
1142 log = self.defconfig.ljust(30) + '\n'
1143
1144 log += '\n'.join([ ' ' + s for s in self.log.split('\n') ])
1145 # Some threads are running in parallel.
1146 # Print log atomically to not mix up logs from different threads.
Masahiro Yamada4efef992016-05-19 15:52:03 +09001147 print >> (sys.stdout if success else sys.stderr), log
1148
1149 if not success:
1150 if self.options.exit_on_error:
1151 sys.exit("Exit on error.")
1152 # If --exit-on-error flag is not set, skip this board and continue.
1153 # Record the failed board.
Masahiro Yamada09c6c062016-08-22 22:18:20 +09001154 self.failed_boards.add(self.defconfig)
Masahiro Yamada4efef992016-05-19 15:52:03 +09001155
Masahiro Yamada1d085562016-05-19 15:52:02 +09001156 self.progress.inc()
1157 self.progress.show()
Masahiro Yamada4efef992016-05-19 15:52:03 +09001158 self.state = STATE_IDLE
Masahiro Yamada1d085562016-05-19 15:52:02 +09001159
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001160 def get_failed_boards(self):
Masahiro Yamada09c6c062016-08-22 22:18:20 +09001161 """Returns a set of failed boards (defconfigs) in this slot.
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001162 """
1163 return self.failed_boards
1164
Masahiro Yamadafc2661e2016-06-15 14:33:54 +09001165 def get_suspicious_boards(self):
Masahiro Yamada09c6c062016-08-22 22:18:20 +09001166 """Returns a set of boards (defconfigs) with possible misconversion.
Masahiro Yamadafc2661e2016-06-15 14:33:54 +09001167 """
Masahiro Yamada916224c2016-08-22 22:18:21 +09001168 return self.suspicious_boards - self.failed_boards
Masahiro Yamadafc2661e2016-06-15 14:33:54 +09001169
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001170class Slots:
1171
1172 """Controller of the array of subprocess slots."""
1173
Joe Hershberger6b96c1a2016-06-10 14:53:32 -05001174 def __init__(self, configs, options, progress, reference_src_dir):
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001175 """Create a new slots controller.
1176
1177 Arguments:
Masahiro Yamadab134bc12016-05-19 15:51:57 +09001178 configs: A list of CONFIGs to move.
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001179 options: option flags.
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +09001180 progress: A progress indicator.
Joe Hershberger6b96c1a2016-06-10 14:53:32 -05001181 reference_src_dir: Determine the true starting config state from this
1182 source tree.
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001183 """
1184 self.options = options
1185 self.slots = []
1186 devnull = get_devnull()
1187 make_cmd = get_make_cmd()
1188 for i in range(options.jobs):
Masahiro Yamadab134bc12016-05-19 15:51:57 +09001189 self.slots.append(Slot(configs, options, progress, devnull,
Joe Hershberger6b96c1a2016-06-10 14:53:32 -05001190 make_cmd, reference_src_dir))
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001191
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +09001192 def add(self, defconfig):
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001193 """Add a new subprocess if a vacant slot is found.
1194
1195 Arguments:
1196 defconfig: defconfig name to be put into.
1197
1198 Returns:
1199 Return True on success or False on failure
1200 """
1201 for slot in self.slots:
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +09001202 if slot.add(defconfig):
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001203 return True
1204 return False
1205
1206 def available(self):
1207 """Check if there is a vacant slot.
1208
1209 Returns:
1210 Return True if at lease one vacant slot is found, False otherwise.
1211 """
1212 for slot in self.slots:
1213 if slot.poll():
1214 return True
1215 return False
1216
1217 def empty(self):
1218 """Check if all slots are vacant.
1219
1220 Returns:
1221 Return True if all the slots are vacant, False otherwise.
1222 """
1223 ret = True
1224 for slot in self.slots:
1225 if not slot.poll():
1226 ret = False
1227 return ret
1228
1229 def show_failed_boards(self):
1230 """Display all of the failed boards (defconfigs)."""
Masahiro Yamada09c6c062016-08-22 22:18:20 +09001231 boards = set()
Masahiro Yamada96dccd92016-06-15 14:33:53 +09001232 output_file = 'moveconfig.failed'
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001233
1234 for slot in self.slots:
Masahiro Yamada09c6c062016-08-22 22:18:20 +09001235 boards |= slot.get_failed_boards()
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001236
Masahiro Yamada96dccd92016-06-15 14:33:53 +09001237 if boards:
1238 boards = '\n'.join(boards) + '\n'
1239 msg = "The following boards were not processed due to error:\n"
1240 msg += boards
1241 msg += "(the list has been saved in %s)\n" % output_file
1242 print >> sys.stderr, color_text(self.options.color, COLOR_LIGHT_RED,
1243 msg)
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001244
Masahiro Yamada96dccd92016-06-15 14:33:53 +09001245 with open(output_file, 'w') as f:
1246 f.write(boards)
Joe Hershberger2559cd82015-05-19 13:21:22 -05001247
Masahiro Yamadafc2661e2016-06-15 14:33:54 +09001248 def show_suspicious_boards(self):
1249 """Display all boards (defconfigs) with possible misconversion."""
Masahiro Yamada09c6c062016-08-22 22:18:20 +09001250 boards = set()
Masahiro Yamadafc2661e2016-06-15 14:33:54 +09001251 output_file = 'moveconfig.suspicious'
1252
1253 for slot in self.slots:
Masahiro Yamada09c6c062016-08-22 22:18:20 +09001254 boards |= slot.get_suspicious_boards()
Masahiro Yamadafc2661e2016-06-15 14:33:54 +09001255
1256 if boards:
1257 boards = '\n'.join(boards) + '\n'
1258 msg = "The following boards might have been converted incorrectly.\n"
1259 msg += "It is highly recommended to check them manually:\n"
1260 msg += boards
1261 msg += "(the list has been saved in %s)\n" % output_file
1262 print >> sys.stderr, color_text(self.options.color, COLOR_YELLOW,
1263 msg)
1264
1265 with open(output_file, 'w') as f:
1266 f.write(boards)
1267
Masahiro Yamada5cc42a52016-06-15 14:33:51 +09001268class ReferenceSource:
1269
1270 """Reference source against which original configs should be parsed."""
1271
1272 def __init__(self, commit):
1273 """Create a reference source directory based on a specified commit.
1274
1275 Arguments:
1276 commit: commit to git-clone
1277 """
1278 self.src_dir = tempfile.mkdtemp()
1279 print "Cloning git repo to a separate work directory..."
1280 subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
1281 cwd=self.src_dir)
1282 print "Checkout '%s' to build the original autoconf.mk." % \
1283 subprocess.check_output(['git', 'rev-parse', '--short', commit]).strip()
1284 subprocess.check_output(['git', 'checkout', commit],
1285 stderr=subprocess.STDOUT, cwd=self.src_dir)
Joe Hershberger6b96c1a2016-06-10 14:53:32 -05001286
1287 def __del__(self):
Masahiro Yamada5cc42a52016-06-15 14:33:51 +09001288 """Delete the reference source directory
Joe Hershberger6b96c1a2016-06-10 14:53:32 -05001289
1290 This function makes sure the temporary directory is cleaned away
1291 even if Python suddenly dies due to error. It should be done in here
1292 because it is guaranteed the destructor is always invoked when the
1293 instance of the class gets unreferenced.
1294 """
Masahiro Yamada5cc42a52016-06-15 14:33:51 +09001295 shutil.rmtree(self.src_dir)
Joe Hershberger6b96c1a2016-06-10 14:53:32 -05001296
Masahiro Yamada5cc42a52016-06-15 14:33:51 +09001297 def get_dir(self):
1298 """Return the absolute path to the reference source directory."""
1299
1300 return self.src_dir
Joe Hershberger6b96c1a2016-06-10 14:53:32 -05001301
Masahiro Yamadab134bc12016-05-19 15:51:57 +09001302def move_config(configs, options):
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001303 """Move config options to defconfig files.
1304
1305 Arguments:
Masahiro Yamadab134bc12016-05-19 15:51:57 +09001306 configs: A list of CONFIGs to move.
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001307 options: option flags
1308 """
Masahiro Yamadab134bc12016-05-19 15:51:57 +09001309 if len(configs) == 0:
Masahiro Yamada6a9f79f2016-05-19 15:52:09 +09001310 if options.force_sync:
1311 print 'No CONFIG is specified. You are probably syncing defconfigs.',
1312 else:
1313 print 'Neither CONFIG nor --force-sync is specified. Nothing will happen.',
1314 else:
1315 print 'Move ' + ', '.join(configs),
1316 print '(jobs: %d)\n' % options.jobs
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001317
Joe Hershberger6b96c1a2016-06-10 14:53:32 -05001318 if options.git_ref:
Masahiro Yamada5cc42a52016-06-15 14:33:51 +09001319 reference_src = ReferenceSource(options.git_ref)
1320 reference_src_dir = reference_src.get_dir()
1321 else:
Masahiro Yamadaf432c332016-06-15 14:33:52 +09001322 reference_src_dir = None
Joe Hershberger6b96c1a2016-06-10 14:53:32 -05001323
Joe Hershberger91040e82015-05-19 13:21:19 -05001324 if options.defconfigs:
Masahiro Yamada0dbc9b52016-10-19 14:39:54 +09001325 defconfigs = get_matched_defconfigs(options.defconfigs)
Joe Hershberger91040e82015-05-19 13:21:19 -05001326 else:
Masahiro Yamada684c3062016-07-25 19:15:28 +09001327 defconfigs = get_all_defconfigs()
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001328
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +09001329 progress = Progress(len(defconfigs))
Joe Hershberger6b96c1a2016-06-10 14:53:32 -05001330 slots = Slots(configs, options, progress, reference_src_dir)
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001331
1332 # Main loop to process defconfig files:
1333 # Add a new subprocess into a vacant slot.
1334 # Sleep if there is no available slot.
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +09001335 for defconfig in defconfigs:
1336 while not slots.add(defconfig):
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001337 while not slots.available():
1338 # No available slot: sleep for a while
1339 time.sleep(SLEEP_TIME)
1340
1341 # wait until all the subprocesses finish
1342 while not slots.empty():
1343 time.sleep(SLEEP_TIME)
1344
Joe Hershberger2e2ce6c2015-05-19 13:21:25 -05001345 print ''
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001346 slots.show_failed_boards()
Masahiro Yamadafc2661e2016-06-15 14:33:54 +09001347 slots.show_suspicious_boards()
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001348
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001349def main():
1350 try:
1351 cpu_count = multiprocessing.cpu_count()
1352 except NotImplementedError:
1353 cpu_count = 1
1354
1355 parser = optparse.OptionParser()
1356 # Add options here
1357 parser.add_option('-c', '--color', action='store_true', default=False,
1358 help='display the log in color')
Simon Glass9ede2122016-09-12 23:18:21 -06001359 parser.add_option('-C', '--commit', action='store_true', default=False,
1360 help='Create a git commit for the operation')
Joe Hershberger91040e82015-05-19 13:21:19 -05001361 parser.add_option('-d', '--defconfigs', type='string',
Simon Glassee4e61b2017-06-01 19:38:59 -06001362 help='a file containing a list of defconfigs to move, '
1363 "one per line (for example 'snow_defconfig') "
1364 "or '-' to read from stdin")
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001365 parser.add_option('-n', '--dry-run', action='store_true', default=False,
1366 help='perform a trial run (show log with no changes)')
1367 parser.add_option('-e', '--exit-on-error', action='store_true',
1368 default=False,
1369 help='exit immediately on any error')
Masahiro Yamada8513dc02016-05-19 15:52:08 +09001370 parser.add_option('-s', '--force-sync', action='store_true', default=False,
1371 help='force sync by savedefconfig')
Masahiro Yamada07913d12016-08-22 22:18:22 +09001372 parser.add_option('-S', '--spl', action='store_true', default=False,
1373 help='parse config options defined for SPL build')
Joe Hershberger2144f882015-05-19 13:21:20 -05001374 parser.add_option('-H', '--headers-only', dest='cleanup_headers_only',
1375 action='store_true', default=False,
1376 help='only cleanup the headers')
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001377 parser.add_option('-j', '--jobs', type='int', default=cpu_count,
1378 help='the number of jobs to run simultaneously')
Joe Hershberger6b96c1a2016-06-10 14:53:32 -05001379 parser.add_option('-r', '--git-ref', type='string',
1380 help='the git ref to clone for building the autoconf.mk')
Simon Glass6b403df2016-09-12 23:18:20 -06001381 parser.add_option('-y', '--yes', action='store_true', default=False,
1382 help="respond 'yes' to any prompts")
Joe Hershberger95bf9c72015-05-19 13:21:24 -05001383 parser.add_option('-v', '--verbose', action='store_true', default=False,
1384 help='show any build errors as boards are built')
Masahiro Yamadab6ef3932016-05-19 15:51:58 +09001385 parser.usage += ' CONFIG ...'
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001386
Masahiro Yamadab6ef3932016-05-19 15:51:58 +09001387 (options, configs) = parser.parse_args()
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001388
Masahiro Yamada6a9f79f2016-05-19 15:52:09 +09001389 if len(configs) == 0 and not options.force_sync:
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001390 parser.print_usage()
1391 sys.exit(1)
1392
Masahiro Yamadab6ef3932016-05-19 15:51:58 +09001393 # prefix the option name with CONFIG_ if missing
1394 configs = [ config if config.startswith('CONFIG_') else 'CONFIG_' + config
1395 for config in configs ]
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001396
Joe Hershberger2144f882015-05-19 13:21:20 -05001397 check_top_directory()
1398
1399 if not options.cleanup_headers_only:
Masahiro Yamadaf7536f72016-07-25 19:15:23 +09001400 check_clean_directory()
1401 update_cross_compile(options.color)
Masahiro Yamadab134bc12016-05-19 15:51:57 +09001402 move_config(configs, options)
Joe Hershberger2144f882015-05-19 13:21:20 -05001403
Masahiro Yamada6a9f79f2016-05-19 15:52:09 +09001404 if configs:
Masahiro Yamadae9ea1222016-07-25 19:15:26 +09001405 cleanup_headers(configs, options)
Masahiro Yamada9ab02962016-07-25 19:15:29 +09001406 cleanup_extra_options(configs, options)
Chris Packhamca438342017-05-02 21:30:47 +12001407 cleanup_whitelist(configs, options)
Chris Packhamf90df592017-05-02 21:30:48 +12001408 cleanup_readme(configs, options)
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001409
Simon Glass9ede2122016-09-12 23:18:21 -06001410 if options.commit:
1411 subprocess.call(['git', 'add', '-u'])
1412 if configs:
1413 msg = 'Convert %s %sto Kconfig' % (configs[0],
1414 'et al ' if len(configs) > 1 else '')
1415 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' %
1416 '\n '.join(configs))
1417 else:
1418 msg = 'configs: Resync with savedefconfig'
1419 msg += '\n\nRsync all defconfig files using moveconfig.py'
1420 subprocess.call(['git', 'commit', '-s', '-m', msg])
1421
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001422if __name__ == '__main__':
1423 main()