]> Git Repo - J-u-boot.git/blame - tools/moveconfig.py
stm32mp: stm32prog: handle U-Boot script in flashlayout alternate
[J-u-boot.git] / tools / moveconfig.py
CommitLineData
793dca34 1#!/usr/bin/env python3
83d290c5 2# SPDX-License-Identifier: GPL-2.0+
5a27c734
MY
3#
4# Author: Masahiro Yamada <[email protected]>
5#
5a27c734
MY
6
7"""
8Move config options from headers to defconfig files.
9
5c72c0e0 10See doc/develop/moveconfig.rst for documentation.
5a27c734
MY
11"""
12
b2e83c63 13from argparse import ArgumentParser
b3192f48 14import asteval
99b66605 15import collections
91197aa6 16from contextlib import ExitStack
8ba1f5de 17import copy
f2f6981a 18import difflib
84067a58 19import doctest
c8e1b10d 20import filecmp
5a27c734 21import fnmatch
0dbc9b59 22import glob
5a27c734 23import multiprocessing
5a27c734 24import os
793dca34 25import queue
5a27c734
MY
26import re
27import shutil
28import subprocess
29import sys
30import tempfile
d73fcb12 31import threading
5a27c734 32import time
84067a58 33import unittest
5a27c734 34
0ede00fd
SG
35from buildman import bsettings
36from buildman import kconfiglib
37from buildman import toolchain
cb008830 38
5a27c734
MY
39SHOW_GNU_MAKE = 'scripts/show-gnu-make'
40SLEEP_TIME=0.03
41
5a27c734
MY
42STATE_IDLE = 0
43STATE_DEFCONFIG = 1
44STATE_AUTOCONF = 2
96464bad 45STATE_SAVEDEFCONFIG = 3
5a27c734
MY
46
47ACTION_MOVE = 0
cc008299 48ACTION_NO_ENTRY = 1
916224c3
MY
49ACTION_NO_ENTRY_WARN = 2
50ACTION_NO_CHANGE = 3
5a27c734
MY
51
52COLOR_BLACK = '0;30'
53COLOR_RED = '0;31'
54COLOR_GREEN = '0;32'
55COLOR_BROWN = '0;33'
56COLOR_BLUE = '0;34'
57COLOR_PURPLE = '0;35'
58COLOR_CYAN = '0;36'
59COLOR_LIGHT_GRAY = '0;37'
60COLOR_DARK_GRAY = '1;30'
61COLOR_LIGHT_RED = '1;31'
62COLOR_LIGHT_GREEN = '1;32'
63COLOR_YELLOW = '1;33'
64COLOR_LIGHT_BLUE = '1;34'
65COLOR_LIGHT_PURPLE = '1;35'
66COLOR_LIGHT_CYAN = '1;36'
67COLOR_WHITE = '1;37'
68
f3b8e647 69AUTO_CONF_PATH = 'include/config/auto.conf'
d73fcb12 70CONFIG_DATABASE = 'moveconfig.db'
f3b8e647 71
cb008830 72CONFIG_LEN = len('CONFIG_')
f3b8e647 73
b237d358 74SIZES = {
daa694d3
SG
75 'SZ_1': 0x00000001, 'SZ_2': 0x00000002,
76 'SZ_4': 0x00000004, 'SZ_8': 0x00000008,
77 'SZ_16': 0x00000010, 'SZ_32': 0x00000020,
78 'SZ_64': 0x00000040, 'SZ_128': 0x00000080,
79 'SZ_256': 0x00000100, 'SZ_512': 0x00000200,
80 'SZ_1K': 0x00000400, 'SZ_2K': 0x00000800,
81 'SZ_4K': 0x00001000, 'SZ_8K': 0x00002000,
82 'SZ_16K': 0x00004000, 'SZ_32K': 0x00008000,
83 'SZ_64K': 0x00010000, 'SZ_128K': 0x00020000,
84 'SZ_256K': 0x00040000, 'SZ_512K': 0x00080000,
85 'SZ_1M': 0x00100000, 'SZ_2M': 0x00200000,
86 'SZ_4M': 0x00400000, 'SZ_8M': 0x00800000,
87 'SZ_16M': 0x01000000, 'SZ_32M': 0x02000000,
88 'SZ_64M': 0x04000000, 'SZ_128M': 0x08000000,
89 'SZ_256M': 0x10000000, 'SZ_512M': 0x20000000,
90 'SZ_1G': 0x40000000, 'SZ_2G': 0x80000000,
91 'SZ_4G': 0x100000000
b237d358
MK
92}
93
b8d11da0
SG
94RE_REMOVE_DEFCONFIG = re.compile(r'(.*)_defconfig')
95
5a27c734 96### helper functions ###
5a27c734
MY
97def check_top_directory():
98 """Exit if we are not at the top of source directory."""
91197aa6
SG
99 for fname in 'README', 'Licenses':
100 if not os.path.exists(fname):
5a27c734
MY
101 sys.exit('Please run at the top of source directory.')
102
bd63e5ba
MY
103def check_clean_directory():
104 """Exit if the source tree is not clean."""
91197aa6
SG
105 for fname in '.config', 'include/config':
106 if os.path.exists(fname):
bd63e5ba
MY
107 sys.exit("source tree is not clean, please run 'make mrproper'")
108
5a27c734
MY
109def get_make_cmd():
110 """Get the command name of GNU Make.
111
112 U-Boot needs GNU Make for building, but the command name is not
113 necessarily "make". (for example, "gmake" on FreeBSD).
114 Returns the most appropriate command name on your system.
115 """
91197aa6
SG
116 with subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE) as proc:
117 ret = proc.communicate()
118 if proc.returncode:
119 sys.exit('GNU Make not found')
5a27c734
MY
120 return ret[0].rstrip()
121
25f978cb
SG
122def get_matched_defconfig(line):
123 """Get the defconfig files that match a pattern
124
125 Args:
91197aa6 126 line (str): Path or filename to match, e.g. 'configs/snow_defconfig' or
25f978cb
SG
127 'k2*_defconfig'. If no directory is provided, 'configs/' is
128 prepended
129
130 Returns:
91197aa6 131 list of str: a list of matching defconfig files
25f978cb
SG
132 """
133 dirname = os.path.dirname(line)
134 if dirname:
135 pattern = line
136 else:
137 pattern = os.path.join('configs', line)
138 return glob.glob(pattern) + glob.glob(pattern + '_defconfig')
139
0dbc9b59 140def get_matched_defconfigs(defconfigs_file):
ee4e61bd
SG
141 """Get all the defconfig files that match the patterns in a file.
142
143 Args:
91197aa6
SG
144 defconfigs_file (str): File containing a list of defconfigs to process,
145 or '-' to read the list from stdin
ee4e61bd
SG
146
147 Returns:
91197aa6 148 list of str: A list of paths to defconfig files, with no duplicates
ee4e61bd 149 """
0dbc9b59 150 defconfigs = []
91197aa6
SG
151 with ExitStack() as stack:
152 if defconfigs_file == '-':
153 inf = sys.stdin
154 defconfigs_file = 'stdin'
155 else:
156 inf = stack.enter_context(open(defconfigs_file, encoding='utf-8'))
157 for i, line in enumerate(inf):
158 line = line.strip()
159 if not line:
160 continue # skip blank lines silently
161 if ' ' in line:
162 line = line.split(' ')[0] # handle 'git log' input
163 matched = get_matched_defconfig(line)
164 if not matched:
165 print(f"warning: {defconfigs_file}:{i + 1}: no defconfig matched '{line}'",
166 file=sys.stderr)
167
168 defconfigs += matched
0dbc9b59
MY
169
170 # use set() to drop multiple matching
91197aa6 171 return [defconfig[len('configs') + 1:] for defconfig in set(defconfigs)]
0dbc9b59 172
684c306e 173def get_all_defconfigs():
91197aa6
SG
174 """Get all the defconfig files under the configs/ directory.
175
176 Returns:
177 list of str: List of paths to defconfig files
178 """
684c306e 179 defconfigs = []
91197aa6 180 for (dirpath, _, filenames) in os.walk('configs'):
684c306e
MY
181 dirpath = dirpath[len('configs') + 1:]
182 for filename in fnmatch.filter(filenames, '*_defconfig'):
183 defconfigs.append(os.path.join(dirpath, filename))
184
185 return defconfigs
186
5a27c734
MY
187def color_text(color_enabled, color, string):
188 """Return colored string."""
189 if color_enabled:
1d085568
MY
190 # LF should not be surrounded by the escape sequence.
191 # Otherwise, additional whitespace or line-feed might be printed.
192 return '\n'.join([ '\033[' + color + 'm' + s + '\033[0m' if s else ''
193 for s in string.split('\n') ])
91197aa6 194 return string
5a27c734 195
91197aa6 196def show_diff(alines, blines, file_path, color_enabled):
f2f6981a
MY
197 """Show unidified diff.
198
91197aa6
SG
199 Args:
200 alines (list of str): A list of lines (before)
201 blines (list of str): A list of lines (after)
202 file_path (str): Path to the file
203 color_enabled (bool): Display the diff in color
f2f6981a 204 """
91197aa6 205 diff = difflib.unified_diff(alines, blines,
f2f6981a
MY
206 fromfile=os.path.join('a', file_path),
207 tofile=os.path.join('b', file_path))
208
209 for line in diff:
6c928c63
ANY
210 if line.startswith('-') and not line.startswith('--'):
211 print(color_text(color_enabled, COLOR_RED, line))
212 elif line.startswith('+') and not line.startswith('++'):
213 print(color_text(color_enabled, COLOR_GREEN, line))
e9ea1221 214 else:
6c928c63 215 print(line)
f2f6981a 216
91197aa6
SG
217def extend_matched_lines(lines, matched, pre_patterns, post_patterns,
218 extend_pre, extend_post):
8ba1f5de
MY
219 """Extend matched lines if desired patterns are found before/after already
220 matched lines.
221
91197aa6
SG
222 Args:
223 lines (list of str): list of lines handled.
224 matched (list of int): list of line numbers that have been already
225 matched (will be updated by this function)
226 pre_patterns (list of re.Pattern): list of regular expression that should
227 be matched as preamble
228 post_patterns (list of re.Pattern): list of regular expression that should
229 be matched as postamble
230 extend_pre (bool): Add the line number of matched preamble to the matched
231 list
232 extend_post (bool): Add the line number of matched postamble to the
233 matched list
8ba1f5de
MY
234 """
235 extended_matched = []
236
237 j = matched[0]
238
239 for i in matched:
240 if i == 0 or i < j:
241 continue
242 j = i
243 while j in matched:
244 j += 1
245 if j >= len(lines):
246 break
247
91197aa6
SG
248 for pat in pre_patterns:
249 if pat.search(lines[i - 1]):
8ba1f5de
MY
250 break
251 else:
252 # not matched
253 continue
254
91197aa6
SG
255 for pat in post_patterns:
256 if pat.search(lines[j]):
8ba1f5de
MY
257 break
258 else:
259 # not matched
260 continue
261
262 if extend_pre:
263 extended_matched.append(i - 1)
264 if extend_post:
265 extended_matched.append(j)
266
267 matched += extended_matched
268 matched.sort()
269
b2e83c63 270def confirm(args, prompt):
91197aa6
SG
271 """Ask the user to confirm something
272
273 Args:
274 args (Namespace ): program arguments
275
276 Returns:
277 bool: True to confirm, False to cancel/stop
278 """
b2e83c63 279 if not args.yes:
85edfc1f 280 while True:
91197aa6 281 choice = input(f'{prompt} [y/n]: ')
85edfc1f 282 choice = choice.lower()
793dca34 283 print(choice)
91197aa6 284 if choice in ('y', 'n'):
85edfc1f
CP
285 break
286
287 if choice == 'n':
288 return False
289
290 return True
291
2fd85bd3
SG
292def write_file(fname, data):
293 """Write data to a file
294
295 Args:
296 fname (str): Filename to write to
297 data (list of str): Lines to write (with or without trailing newline);
298 or str to write
299 """
300 with open(fname, 'w', encoding='utf-8') as out:
301 if isinstance(data, list):
302 for line in data:
303 print(line.rstrip('\n'), file=out)
304 else:
305 out.write(data)
306
37f815ca
SG
307def read_file(fname, as_lines=True, skip_unicode=False):
308 """Read a file and return the contents
309
310 Args:
311 fname (str): Filename to read from
312 as_lines: Return file contents as a list of lines
313 skip_unicode (bool): True to report unicode errors and continue
314
315 Returns:
316 iter of str: List of ;ines from the file with newline removed; str if
317 as_lines is False with newlines intact; or None if a unicode error
318 occurred
319
320 Raises:
321 UnicodeDecodeError: Unicode error occurred when reading
322 """
323 with open(fname, encoding='utf-8') as inf:
324 try:
325 if as_lines:
326 return [line.rstrip('\n') for line in inf.readlines()]
327 else:
328 return inf.read()
329 except UnicodeDecodeError as e:
330 if not skip_unicode:
68a0b715 331 raise
37f815ca
SG
332 print("Failed on file %s': %s" % (fname, e))
333 return None
334
b2e83c63 335def cleanup_empty_blocks(header_path, args):
4d9dbb1f
CP
336 """Clean up empty conditional blocks
337
91197aa6
SG
338 Args:
339 header_path (str): path to the cleaned file.
340 args (Namespace): program arguments
4d9dbb1f
CP
341 """
342 pattern = re.compile(r'^\s*#\s*if.*$\n^\s*#\s*endif.*$\n*', flags=re.M)
37f815ca
SG
343 data = read_file(header_path, as_lines=False, skip_unicode=True)
344 if data is None:
345 return
4d9dbb1f
CP
346
347 new_data = pattern.sub('\n', data)
348
349 show_diff(data.splitlines(True), new_data.splitlines(True), header_path,
b2e83c63 350 args.color)
4d9dbb1f 351
b2e83c63 352 if args.dry_run:
4d9dbb1f
CP
353 return
354
37f815ca
SG
355 if new_data != data:
356 write_file(header_path, new_data)
4d9dbb1f 357
b2e83c63 358def cleanup_one_header(header_path, patterns, args):
5a27c734
MY
359 """Clean regex-matched lines away from a file.
360
91197aa6 361 Args:
5a27c734
MY
362 header_path: path to the cleaned file.
363 patterns: list of regex patterns. Any lines matching to these
364 patterns are deleted.
91197aa6 365 args (Namespace): program arguments
5a27c734 366 """
37f815ca
SG
367 lines = read_file(header_path, skip_unicode=True)
368 if lines is None:
369 return
5a27c734
MY
370
371 matched = []
372 for i, line in enumerate(lines):
6c928c63 373 if i - 1 in matched and lines[i - 1].endswith('\\'):
a3a779f7
MY
374 matched.append(i)
375 continue
5a27c734 376 for pattern in patterns:
8ba1f5de 377 if pattern.search(line):
5a27c734
MY
378 matched.append(i)
379 break
380
8ba1f5de
MY
381 if not matched:
382 return
383
384 # remove empty #ifdef ... #endif, successive blank lines
6c928c63
ANY
385 pattern_if = re.compile(r'#\s*if(def|ndef)?\b') # #if, #ifdef, #ifndef
386 pattern_elif = re.compile(r'#\s*el(if|se)\b') # #elif, #else
387 pattern_endif = re.compile(r'#\s*endif\b') # #endif
8ba1f5de
MY
388 pattern_blank = re.compile(r'^\s*$') # empty line
389
390 while True:
391 old_matched = copy.copy(matched)
392 extend_matched_lines(lines, matched, [pattern_if],
393 [pattern_endif], True, True)
394 extend_matched_lines(lines, matched, [pattern_elif],
395 [pattern_elif, pattern_endif], True, False)
396 extend_matched_lines(lines, matched, [pattern_if, pattern_elif],
397 [pattern_blank], False, True)
398 extend_matched_lines(lines, matched, [pattern_blank],
399 [pattern_elif, pattern_endif], True, False)
400 extend_matched_lines(lines, matched, [pattern_blank],
401 [pattern_blank], True, False)
402 if matched == old_matched:
403 break
404
f2f6981a
MY
405 tolines = copy.copy(lines)
406
407 for i in reversed(matched):
408 tolines.pop(i)
409
b2e83c63 410 show_diff(lines, tolines, header_path, args.color)
8ba1f5de 411
b2e83c63 412 if args.dry_run:
5a27c734
MY
413 return
414
2fd85bd3 415 write_file(header_path, tolines)
5a27c734 416
b2e83c63 417def cleanup_headers(configs, args):
5a27c734
MY
418 """Delete config defines from board headers.
419
91197aa6 420 Args:
b134bc13 421 configs: A list of CONFIGs to remove.
91197aa6 422 args (Namespace): program arguments
5a27c734 423 """
b2e83c63 424 if not confirm(args, 'Clean up headers?'):
85edfc1f 425 return
5a27c734
MY
426
427 patterns = []
b134bc13 428 for config in configs:
6c928c63
ANY
429 patterns.append(re.compile(r'#\s*define\s+%s\b' % config))
430 patterns.append(re.compile(r'#\s*undef\s+%s\b' % config))
5a27c734 431
60727f51
JH
432 for dir in 'include', 'arch', 'board':
433 for (dirpath, dirnames, filenames) in os.walk(dir):
dc6de50b
MY
434 if dirpath == os.path.join('include', 'generated'):
435 continue
60727f51 436 for filename in filenames:
a38cc172 437 if not filename.endswith(('~', '.dts', '.dtsi', '.bin',
dc514d7e 438 '.elf','.aml','.dat')):
4d9dbb1f 439 header_path = os.path.join(dirpath, filename)
02b5670e
TR
440 # This file contains UTF-16 data and no CONFIG symbols
441 if header_path == 'include/video_font_data.h':
442 continue
b2e83c63
SG
443 cleanup_one_header(header_path, patterns, args)
444 cleanup_empty_blocks(header_path, args)
5a27c734 445
25b8acee
TR
446def cleanup_one_extra_option(defconfig_path, configs, args):
447 """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in one defconfig file.
448
449 Args:
450 defconfig_path: path to the cleaned defconfig file.
451 configs: A list of CONFIGs to remove.
452 args (Namespace): program arguments
453 """
454
455 start = 'CONFIG_SYS_EXTRA_OPTIONS="'
456 end = '"'
457
458 lines = read_file(defconfig_path)
459
460 for i, line in enumerate(lines):
461 if line.startswith(start) and line.endswith(end):
462 break
463 else:
464 # CONFIG_SYS_EXTRA_OPTIONS was not found in this defconfig
465 return
466
467 old_tokens = line[len(start):-len(end)].split(',')
468 new_tokens = []
469
470 for token in old_tokens:
471 pos = token.find('=')
472 if not (token[:pos] if pos >= 0 else token) in configs:
473 new_tokens.append(token)
474
475 if new_tokens == old_tokens:
476 return
477
478 tolines = copy.copy(lines)
479
480 if new_tokens:
481 tolines[i] = start + ','.join(new_tokens) + end
482 else:
483 tolines.pop(i)
484
485 show_diff(lines, tolines, defconfig_path, args.color)
486
487 if args.dry_run:
488 return
489
490 write_file(defconfig_path, tolines)
491
492def cleanup_extra_options(configs, args):
493 """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in defconfig files.
494
495 Args:
496 configs: A list of CONFIGs to remove.
497 args (Namespace): program arguments
498 """
499 if not confirm(args, 'Clean up CONFIG_SYS_EXTRA_OPTIONS?'):
500 return
501
502 configs = [ config[len('CONFIG_'):] for config in configs ]
503
504 defconfigs = get_all_defconfigs()
505
506 for defconfig in defconfigs:
507 cleanup_one_extra_option(os.path.join('configs', defconfig), configs,
508 args)
509
b2e83c63 510def cleanup_whitelist(configs, args):
ca43834d
CP
511 """Delete config whitelist entries
512
91197aa6 513 Args:
ca43834d 514 configs: A list of CONFIGs to remove.
91197aa6 515 args (Namespace): program arguments
ca43834d 516 """
b2e83c63 517 if not confirm(args, 'Clean up whitelist entries?'):
ca43834d
CP
518 return
519
37f815ca 520 lines = read_file(os.path.join('scripts', 'config_whitelist.txt'))
ca43834d
CP
521
522 lines = [x for x in lines if x.strip() not in configs]
523
2fd85bd3 524 write_file(os.path.join('scripts', 'config_whitelist.txt'), lines)
ca43834d 525
f90df596
CP
526def find_matching(patterns, line):
527 for pat in patterns:
528 if pat.search(line):
529 return True
530 return False
531
b2e83c63 532def cleanup_readme(configs, args):
f90df596
CP
533 """Delete config description in README
534
91197aa6 535 Args:
f90df596 536 configs: A list of CONFIGs to remove.
91197aa6 537 args (Namespace): program arguments
f90df596 538 """
b2e83c63 539 if not confirm(args, 'Clean up README?'):
f90df596
CP
540 return
541
542 patterns = []
543 for config in configs:
544 patterns.append(re.compile(r'^\s+%s' % config))
545
37f815ca 546 lines = read_file('README')
f90df596
CP
547
548 found = False
549 newlines = []
550 for line in lines:
551 if not found:
552 found = find_matching(patterns, line)
553 if found:
554 continue
555
556 if found and re.search(r'^\s+CONFIG', line):
557 found = False
558
559 if not found:
560 newlines.append(line)
561
2fd85bd3 562 write_file('README', newlines)
f90df596 563
b237d358
MK
564def try_expand(line):
565 """If value looks like an expression, try expanding it
566 Otherwise just return the existing value
567 """
568 if line.find('=') == -1:
569 return line
570
571 try:
b3192f48 572 aeval = asteval.Interpreter( usersyms=SIZES, minimal=True )
b237d358
MK
573 cfg, val = re.split("=", line)
574 val= val.strip('\"')
daa694d3 575 if re.search(r'[*+-/]|<<|SZ_+|\(([^\)]+)\)', val):
b3192f48 576 newval = hex(aeval(val))
daa694d3 577 print('\tExpanded expression %s to %s' % (val, newval))
b237d358
MK
578 return cfg+'='+newval
579 except:
daa694d3 580 print('\tFailed to expand expression in %s' % line)
b237d358
MK
581
582 return line
583
ca43834d 584
5a27c734 585### classes ###
c5e60fd4
MY
586class Progress:
587
588 """Progress Indicator"""
589
590 def __init__(self, total):
591 """Create a new progress indicator.
592
91197aa6 593 Args:
c5e60fd4
MY
594 total: A number of defconfig files to process.
595 """
596 self.current = 0
597 self.total = total
598
599 def inc(self):
600 """Increment the number of processed defconfig files."""
601
602 self.current += 1
603
604 def show(self):
605 """Display the progress."""
793dca34 606 print(' %d defconfigs out of %d\r' % (self.current, self.total), end=' ')
c5e60fd4
MY
607 sys.stdout.flush()
608
cb008830
SG
609
610class KconfigScanner:
611 """Kconfig scanner."""
612
613 def __init__(self):
614 """Scan all the Kconfig files and create a Config object."""
615 # Define environment variables referenced from Kconfig
616 os.environ['srctree'] = os.getcwd()
617 os.environ['UBOOTVERSION'] = 'dummy'
618 os.environ['KCONFIG_OBJDIR'] = ''
65e05ddc 619 self.conf = kconfiglib.Kconfig()
cb008830
SG
620
621
5a27c734
MY
622class KconfigParser:
623
624 """A parser of .config and include/autoconf.mk."""
625
626 re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
627 re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
628
b2e83c63 629 def __init__(self, configs, args, build_dir):
5a27c734
MY
630 """Create a new parser.
631
91197aa6 632 Args:
b134bc13 633 configs: A list of CONFIGs to move.
91197aa6 634 args (Namespace): program arguments
5a27c734
MY
635 build_dir: Build directory.
636 """
b134bc13 637 self.configs = configs
b2e83c63 638 self.args = args
1f16992e
MY
639 self.dotconfig = os.path.join(build_dir, '.config')
640 self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
07913d1e
MY
641 self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
642 'autoconf.mk')
f3b8e647 643 self.config_autoconf = os.path.join(build_dir, AUTO_CONF_PATH)
5da4f857 644 self.defconfig = os.path.join(build_dir, 'defconfig')
5a27c734 645
6821a745
SG
646 def get_arch(self):
647 """Parse .config file and return the architecture.
5a27c734
MY
648
649 Returns:
6821a745 650 Architecture name (e.g. 'arm').
5a27c734
MY
651 """
652 arch = ''
653 cpu = ''
37f815ca 654 for line in read_file(self.dotconfig):
5a27c734
MY
655 m = self.re_arch.match(line)
656 if m:
657 arch = m.group(1)
658 continue
659 m = self.re_cpu.match(line)
660 if m:
661 cpu = m.group(1)
662
90ed6cba
MY
663 if not arch:
664 return None
5a27c734
MY
665
666 # fix-up for aarch64
667 if arch == 'arm' and cpu == 'armv8':
668 arch = 'aarch64'
669
6821a745 670 return arch
5a27c734 671
b134bc13 672 def parse_one_config(self, config, dotconfig_lines, autoconf_lines):
5a27c734
MY
673 """Parse .config, defconfig, include/autoconf.mk for one config.
674
675 This function looks for the config options in the lines from
676 defconfig, .config, and include/autoconf.mk in order to decide
677 which action should be taken for this defconfig.
678
91197aa6 679 Args:
b134bc13 680 config: CONFIG name to parse.
cc008299 681 dotconfig_lines: lines from the .config file.
5a27c734
MY
682 autoconf_lines: lines from the include/autoconf.mk file.
683
684 Returns:
685 A tupple of the action for this defconfig and the line
686 matched for the config.
687 """
5a27c734
MY
688 not_set = '# %s is not set' % config
689
5a27c734
MY
690 for line in autoconf_lines:
691 line = line.rstrip()
692 if line.startswith(config + '='):
cc008299 693 new_val = line
5a27c734 694 break
5a27c734 695 else:
cc008299
MY
696 new_val = not_set
697
b237d358
MK
698 new_val = try_expand(new_val)
699
916224c3
MY
700 for line in dotconfig_lines:
701 line = line.rstrip()
702 if line.startswith(config + '=') or line == not_set:
703 old_val = line
704 break
705 else:
706 if new_val == not_set:
707 return (ACTION_NO_ENTRY, config)
708 else:
709 return (ACTION_NO_ENTRY_WARN, config)
710
cc008299
MY
711 # If this CONFIG is neither bool nor trisate
712 if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set:
713 # tools/scripts/define2mk.sed changes '1' to 'y'.
714 # This is a problem if the CONFIG is int type.
715 # Check the type in Kconfig and handle it correctly.
716 if new_val[-2:] == '=y':
717 new_val = new_val[:-1] + '1'
5a27c734 718
5030159e
MY
719 return (ACTION_NO_CHANGE if old_val == new_val else ACTION_MOVE,
720 new_val)
5a27c734 721
1d085568 722 def update_dotconfig(self):
6ff36d21 723 """Parse files for the config options and update the .config.
5a27c734 724
cc008299
MY
725 This function parses the generated .config and include/autoconf.mk
726 searching the target options.
6ff36d21 727 Move the config option(s) to the .config as needed.
5a27c734 728
91197aa6 729 Args:
5a27c734 730 defconfig: defconfig name.
522e8dcb
MY
731
732 Returns:
7fb0bacd
MY
733 Return a tuple of (updated flag, log string).
734 The "updated flag" is True if the .config was updated, False
735 otherwise. The "log string" shows what happend to the .config.
5a27c734
MY
736 """
737
5a27c734 738 results = []
7fb0bacd 739 updated = False
916224c3 740 suspicious = False
07913d1e
MY
741 rm_files = [self.config_autoconf, self.autoconf]
742
b2e83c63 743 if self.args.spl:
07913d1e
MY
744 if os.path.exists(self.spl_autoconf):
745 autoconf_path = self.spl_autoconf
746 rm_files.append(self.spl_autoconf)
747 else:
748 for f in rm_files:
749 os.remove(f)
750 return (updated, suspicious,
b2e83c63 751 color_text(self.args.color, COLOR_BROWN,
07913d1e
MY
752 "SPL is not enabled. Skipped.") + '\n')
753 else:
754 autoconf_path = self.autoconf
5a27c734 755
37f815ca 756 dotconfig_lines = read_file(self.dotconfig)
5a27c734 757
37f815ca 758 autoconf_lines = read_file(autoconf_path)
5a27c734 759
b134bc13
MY
760 for config in self.configs:
761 result = self.parse_one_config(config, dotconfig_lines,
96464bad 762 autoconf_lines)
5a27c734
MY
763 results.append(result)
764
765 log = ''
766
767 for (action, value) in results:
768 if action == ACTION_MOVE:
769 actlog = "Move '%s'" % value
770 log_color = COLOR_LIGHT_GREEN
cc008299 771 elif action == ACTION_NO_ENTRY:
daa694d3 772 actlog = '%s is not defined in Kconfig. Do nothing.' % value
5a27c734 773 log_color = COLOR_LIGHT_BLUE
916224c3 774 elif action == ACTION_NO_ENTRY_WARN:
daa694d3 775 actlog = '%s is not defined in Kconfig (suspicious). Do nothing.' % value
916224c3
MY
776 log_color = COLOR_YELLOW
777 suspicious = True
cc008299
MY
778 elif action == ACTION_NO_CHANGE:
779 actlog = "'%s' is the same as the define in Kconfig. Do nothing." \
780 % value
5a27c734 781 log_color = COLOR_LIGHT_PURPLE
5a27c734 782 else:
daa694d3 783 sys.exit('Internal Error. This should not happen.')
5a27c734 784
b2e83c63 785 log += color_text(self.args.color, log_color, actlog) + '\n'
5a27c734 786
91197aa6 787 with open(self.dotconfig, 'a', encoding='utf-8') as out:
e423d17f
MY
788 for (action, value) in results:
789 if action == ACTION_MOVE:
91197aa6 790 out.write(value + '\n')
7fb0bacd 791 updated = True
5a27c734 792
5da4f857 793 self.results = results
07913d1e
MY
794 for f in rm_files:
795 os.remove(f)
5a27c734 796
916224c3 797 return (updated, suspicious, log)
522e8dcb 798
5da4f857
MY
799 def check_defconfig(self):
800 """Check the defconfig after savedefconfig
801
802 Returns:
803 Return additional log if moved CONFIGs were removed again by
804 'make savedefconfig'.
805 """
806
807 log = ''
808
37f815ca 809 defconfig_lines = read_file(self.defconfig)
5da4f857
MY
810
811 for (action, value) in self.results:
812 if action != ACTION_MOVE:
813 continue
6c928c63 814 if not value in defconfig_lines:
b2e83c63 815 log += color_text(self.args.color, COLOR_YELLOW,
5da4f857
MY
816 "'%s' was removed by savedefconfig.\n" %
817 value)
818
819 return log
820
d73fcb12
SG
821
822class DatabaseThread(threading.Thread):
823 """This thread processes results from Slot threads.
824
825 It collects the data in the master config directary. There is only one
826 result thread, and this helps to serialise the build output.
827 """
828 def __init__(self, config_db, db_queue):
829 """Set up a new result thread
830
831 Args:
832 builder: Builder which will be sent each result
833 """
834 threading.Thread.__init__(self)
835 self.config_db = config_db
836 self.db_queue= db_queue
837
838 def run(self):
839 """Called to start up the result thread.
840
841 We collect the next result job and pass it on to the build.
842 """
843 while True:
844 defconfig, configs = self.db_queue.get()
845 self.config_db[defconfig] = configs
846 self.db_queue.task_done()
847
848
5a27c734
MY
849class Slot:
850
851 """A slot to store a subprocess.
852
853 Each instance of this class handles one subprocess.
854 This class is useful to control multiple threads
855 for faster processing.
856 """
857
b2e83c63 858 def __init__(self, toolchains, configs, args, progress, devnull,
6821a745 859 make_cmd, reference_src_dir, db_queue):
5a27c734
MY
860 """Create a new process slot.
861
91197aa6 862 Args:
6821a745 863 toolchains: Toolchains object containing toolchains.
b134bc13 864 configs: A list of CONFIGs to move.
b2e83c63 865 args: Program arguments
c5e60fd4 866 progress: A progress indicator.
5a27c734
MY
867 devnull: A file object of '/dev/null'.
868 make_cmd: command name of GNU Make.
6b96c1a1
JH
869 reference_src_dir: Determine the true starting config state from this
870 source tree.
d73fcb12 871 db_queue: output queue to write config info for the database
5a27c734 872 """
6821a745 873 self.toolchains = toolchains
b2e83c63 874 self.args = args
c5e60fd4 875 self.progress = progress
5a27c734
MY
876 self.build_dir = tempfile.mkdtemp()
877 self.devnull = devnull
878 self.make_cmd = (make_cmd, 'O=' + self.build_dir)
6b96c1a1 879 self.reference_src_dir = reference_src_dir
d73fcb12 880 self.db_queue = db_queue
b2e83c63 881 self.parser = KconfigParser(configs, args, self.build_dir)
5a27c734 882 self.state = STATE_IDLE
09c6c066
MY
883 self.failed_boards = set()
884 self.suspicious_boards = set()
5a27c734
MY
885
886 def __del__(self):
887 """Delete the working directory
888
889 This function makes sure the temporary directory is cleaned away
890 even if Python suddenly dies due to error. It should be done in here
f2dae751 891 because it is guaranteed the destructor is always invoked when the
5a27c734
MY
892 instance of the class gets unreferenced.
893
894 If the subprocess is still running, wait until it finishes.
895 """
896 if self.state != STATE_IDLE:
897 while self.ps.poll() == None:
898 pass
899 shutil.rmtree(self.build_dir)
900
c5e60fd4 901 def add(self, defconfig):
5a27c734
MY
902 """Assign a new subprocess for defconfig and add it to the slot.
903
904 If the slot is vacant, create a new subprocess for processing the
905 given defconfig and add it to the slot. Just returns False if
906 the slot is occupied (i.e. the current subprocess is still running).
907
91197aa6 908 Args:
5a27c734
MY
909 defconfig: defconfig name.
910
911 Returns:
912 Return True on success or False on failure
913 """
914 if self.state != STATE_IDLE:
915 return False
e307fa9d 916
5a27c734 917 self.defconfig = defconfig
1d085568 918 self.log = ''
f432c33f 919 self.current_src_dir = self.reference_src_dir
e307fa9d 920 self.do_defconfig()
5a27c734
MY
921 return True
922
923 def poll(self):
924 """Check the status of the subprocess and handle it as needed.
925
926 Returns True if the slot is vacant (i.e. in idle state).
927 If the configuration is successfully finished, assign a new
928 subprocess to build include/autoconf.mk.
929 If include/autoconf.mk is generated, invoke the parser to
7fb0bacd
MY
930 parse the .config and the include/autoconf.mk, moving
931 config options to the .config as needed.
932 If the .config was updated, run "make savedefconfig" to sync
933 it, update the original defconfig, and then set the slot back
934 to the idle state.
5a27c734
MY
935
936 Returns:
937 Return True if the subprocess is terminated, False otherwise
938 """
939 if self.state == STATE_IDLE:
940 return True
941
942 if self.ps.poll() == None:
943 return False
944
945 if self.ps.poll() != 0:
e307fa9d
MY
946 self.handle_error()
947 elif self.state == STATE_DEFCONFIG:
f432c33f 948 if self.reference_src_dir and not self.current_src_dir:
6b96c1a1
JH
949 self.do_savedefconfig()
950 else:
951 self.do_autoconf()
e307fa9d 952 elif self.state == STATE_AUTOCONF:
f432c33f
MY
953 if self.current_src_dir:
954 self.current_src_dir = None
6b96c1a1 955 self.do_defconfig()
b2e83c63 956 elif self.args.build_db:
d73fcb12 957 self.do_build_db()
6b96c1a1
JH
958 else:
959 self.do_savedefconfig()
e307fa9d
MY
960 elif self.state == STATE_SAVEDEFCONFIG:
961 self.update_defconfig()
962 else:
daa694d3 963 sys.exit('Internal Error. This should not happen.')
5a27c734 964
e307fa9d 965 return True if self.state == STATE_IDLE else False
96464bad 966
e307fa9d
MY
967 def handle_error(self):
968 """Handle error cases."""
8513dc04 969
b2e83c63 970 self.log += color_text(self.args.color, COLOR_LIGHT_RED,
daa694d3 971 'Failed to process.\n')
b2e83c63
SG
972 if self.args.verbose:
973 self.log += color_text(self.args.color, COLOR_LIGHT_CYAN,
4f5c5e99 974 self.ps.stderr.read().decode())
e307fa9d 975 self.finish(False)
96464bad 976
e307fa9d
MY
977 def do_defconfig(self):
978 """Run 'make <board>_defconfig' to create the .config file."""
c8e1b10d 979
e307fa9d
MY
980 cmd = list(self.make_cmd)
981 cmd.append(self.defconfig)
982 self.ps = subprocess.Popen(cmd, stdout=self.devnull,
f432c33f
MY
983 stderr=subprocess.PIPE,
984 cwd=self.current_src_dir)
e307fa9d 985 self.state = STATE_DEFCONFIG
c8e1b10d 986
e307fa9d 987 def do_autoconf(self):
f3b8e647 988 """Run 'make AUTO_CONF_PATH'."""
5a27c734 989
6821a745
SG
990 arch = self.parser.get_arch()
991 try:
992 toolchain = self.toolchains.Select(arch)
993 except ValueError:
b2e83c63 994 self.log += color_text(self.args.color, COLOR_YELLOW,
ce3ba458 995 "Tool chain for '%s' is missing. Do nothing.\n" % arch)
4efef998 996 self.finish(False)
e307fa9d 997 return
793dca34 998 env = toolchain.MakeEnvironment(False)
90ed6cba 999
5a27c734 1000 cmd = list(self.make_cmd)
7740f653 1001 cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
f3b8e647 1002 cmd.append(AUTO_CONF_PATH)
6821a745 1003 self.ps = subprocess.Popen(cmd, stdout=self.devnull, env=env,
f432c33f
MY
1004 stderr=subprocess.PIPE,
1005 cwd=self.current_src_dir)
5a27c734 1006 self.state = STATE_AUTOCONF
e307fa9d 1007
d73fcb12
SG
1008 def do_build_db(self):
1009 """Add the board to the database"""
1010 configs = {}
37f815ca
SG
1011 for line in read_file(os.path.join(self.build_dir, AUTO_CONF_PATH)):
1012 if line.startswith('CONFIG'):
1013 config, value = line.split('=', 1)
1014 configs[config] = value.rstrip()
d73fcb12
SG
1015 self.db_queue.put([self.defconfig, configs])
1016 self.finish(True)
1017
e307fa9d
MY
1018 def do_savedefconfig(self):
1019 """Update the .config and run 'make savedefconfig'."""
1020
916224c3
MY
1021 (updated, suspicious, log) = self.parser.update_dotconfig()
1022 if suspicious:
1023 self.suspicious_boards.add(self.defconfig)
e307fa9d
MY
1024 self.log += log
1025
b2e83c63 1026 if not self.args.force_sync and not updated:
e307fa9d
MY
1027 self.finish(True)
1028 return
1029 if updated:
b2e83c63 1030 self.log += color_text(self.args.color, COLOR_LIGHT_GREEN,
daa694d3 1031 'Syncing by savedefconfig...\n')
e307fa9d 1032 else:
daa694d3 1033 self.log += 'Syncing by savedefconfig (forced by option)...\n'
e307fa9d
MY
1034
1035 cmd = list(self.make_cmd)
1036 cmd.append('savedefconfig')
1037 self.ps = subprocess.Popen(cmd, stdout=self.devnull,
1038 stderr=subprocess.PIPE)
1039 self.state = STATE_SAVEDEFCONFIG
1040
1041 def update_defconfig(self):
1042 """Update the input defconfig and go back to the idle state."""
1043
fc2661ee
MY
1044 log = self.parser.check_defconfig()
1045 if log:
09c6c066 1046 self.suspicious_boards.add(self.defconfig)
fc2661ee 1047 self.log += log
e307fa9d
MY
1048 orig_defconfig = os.path.join('configs', self.defconfig)
1049 new_defconfig = os.path.join(self.build_dir, 'defconfig')
1050 updated = not filecmp.cmp(orig_defconfig, new_defconfig)
1051
1052 if updated:
b2e83c63 1053 self.log += color_text(self.args.color, COLOR_LIGHT_BLUE,
daa694d3 1054 'defconfig was updated.\n')
e307fa9d 1055
b2e83c63 1056 if not self.args.dry_run and updated:
e307fa9d
MY
1057 shutil.move(new_defconfig, orig_defconfig)
1058 self.finish(True)
5a27c734 1059
4efef998
MY
1060 def finish(self, success):
1061 """Display log along with progress and go to the idle state.
1d085568 1062
91197aa6 1063 Args:
4efef998
MY
1064 success: Should be True when the defconfig was processed
1065 successfully, or False when it fails.
1d085568
MY
1066 """
1067 # output at least 30 characters to hide the "* defconfigs out of *".
1068 log = self.defconfig.ljust(30) + '\n'
1069
1070 log += '\n'.join([ ' ' + s for s in self.log.split('\n') ])
1071 # Some threads are running in parallel.
1072 # Print log atomically to not mix up logs from different threads.
793dca34 1073 print(log, file=(sys.stdout if success else sys.stderr))
4efef998
MY
1074
1075 if not success:
b2e83c63 1076 if self.args.exit_on_error:
daa694d3 1077 sys.exit('Exit on error.')
4efef998
MY
1078 # If --exit-on-error flag is not set, skip this board and continue.
1079 # Record the failed board.
09c6c066 1080 self.failed_boards.add(self.defconfig)
4efef998 1081
1d085568
MY
1082 self.progress.inc()
1083 self.progress.show()
4efef998 1084 self.state = STATE_IDLE
1d085568 1085
5a27c734 1086 def get_failed_boards(self):
09c6c066 1087 """Returns a set of failed boards (defconfigs) in this slot.
5a27c734
MY
1088 """
1089 return self.failed_boards
1090
fc2661ee 1091 def get_suspicious_boards(self):
09c6c066 1092 """Returns a set of boards (defconfigs) with possible misconversion.
fc2661ee 1093 """
916224c3 1094 return self.suspicious_boards - self.failed_boards
fc2661ee 1095
5a27c734
MY
1096class Slots:
1097
1098 """Controller of the array of subprocess slots."""
1099
b2e83c63 1100 def __init__(self, toolchains, configs, args, progress,
6821a745 1101 reference_src_dir, db_queue):
5a27c734
MY
1102 """Create a new slots controller.
1103
91197aa6 1104 Args:
6821a745 1105 toolchains: Toolchains object containing toolchains.
b134bc13 1106 configs: A list of CONFIGs to move.
b2e83c63 1107 args: Program arguments
c5e60fd4 1108 progress: A progress indicator.
6b96c1a1
JH
1109 reference_src_dir: Determine the true starting config state from this
1110 source tree.
d73fcb12 1111 db_queue: output queue to write config info for the database
5a27c734 1112 """
b2e83c63 1113 self.args = args
5a27c734 1114 self.slots = []
478920dc 1115 devnull = subprocess.DEVNULL
5a27c734 1116 make_cmd = get_make_cmd()
b2e83c63
SG
1117 for i in range(args.jobs):
1118 self.slots.append(Slot(toolchains, configs, args, progress,
6821a745
SG
1119 devnull, make_cmd, reference_src_dir,
1120 db_queue))
5a27c734 1121
c5e60fd4 1122 def add(self, defconfig):
5a27c734
MY
1123 """Add a new subprocess if a vacant slot is found.
1124
91197aa6 1125 Args:
5a27c734
MY
1126 defconfig: defconfig name to be put into.
1127
1128 Returns:
1129 Return True on success or False on failure
1130 """
1131 for slot in self.slots:
c5e60fd4 1132 if slot.add(defconfig):
5a27c734
MY
1133 return True
1134 return False
1135
1136 def available(self):
1137 """Check if there is a vacant slot.
1138
1139 Returns:
1140 Return True if at lease one vacant slot is found, False otherwise.
1141 """
1142 for slot in self.slots:
1143 if slot.poll():
1144 return True
1145 return False
1146
1147 def empty(self):
1148 """Check if all slots are vacant.
1149
1150 Returns:
1151 Return True if all the slots are vacant, False otherwise.
1152 """
1153 ret = True
1154 for slot in self.slots:
1155 if not slot.poll():
1156 ret = False
1157 return ret
1158
1159 def show_failed_boards(self):
1160 """Display all of the failed boards (defconfigs)."""
09c6c066 1161 boards = set()
96dccd97 1162 output_file = 'moveconfig.failed'
5a27c734
MY
1163
1164 for slot in self.slots:
09c6c066 1165 boards |= slot.get_failed_boards()
96dccd97
MY
1166
1167 if boards:
1168 boards = '\n'.join(boards) + '\n'
daa694d3 1169 msg = 'The following boards were not processed due to error:\n'
96dccd97 1170 msg += boards
daa694d3 1171 msg += '(the list has been saved in %s)\n' % output_file
b2e83c63 1172 print(color_text(self.args.color, COLOR_LIGHT_RED,
793dca34 1173 msg), file=sys.stderr)
96dccd97 1174
2fd85bd3 1175 write_file(output_file, boards)
2559cd89 1176
fc2661ee
MY
1177 def show_suspicious_boards(self):
1178 """Display all boards (defconfigs) with possible misconversion."""
09c6c066 1179 boards = set()
fc2661ee
MY
1180 output_file = 'moveconfig.suspicious'
1181
1182 for slot in self.slots:
09c6c066 1183 boards |= slot.get_suspicious_boards()
fc2661ee
MY
1184
1185 if boards:
1186 boards = '\n'.join(boards) + '\n'
daa694d3
SG
1187 msg = 'The following boards might have been converted incorrectly.\n'
1188 msg += 'It is highly recommended to check them manually:\n'
fc2661ee 1189 msg += boards
daa694d3 1190 msg += '(the list has been saved in %s)\n' % output_file
b2e83c63 1191 print(color_text(self.args.color, COLOR_YELLOW,
793dca34 1192 msg), file=sys.stderr)
fc2661ee 1193
2fd85bd3 1194 write_file(output_file, boards)
fc2661ee 1195
5cc42a51
MY
1196class ReferenceSource:
1197
1198 """Reference source against which original configs should be parsed."""
1199
1200 def __init__(self, commit):
1201 """Create a reference source directory based on a specified commit.
1202
91197aa6 1203 Args:
5cc42a51
MY
1204 commit: commit to git-clone
1205 """
1206 self.src_dir = tempfile.mkdtemp()
daa694d3 1207 print('Cloning git repo to a separate work directory...')
5cc42a51
MY
1208 subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
1209 cwd=self.src_dir)
793dca34
SG
1210 print("Checkout '%s' to build the original autoconf.mk." % \
1211 subprocess.check_output(['git', 'rev-parse', '--short', commit]).strip())
5cc42a51
MY
1212 subprocess.check_output(['git', 'checkout', commit],
1213 stderr=subprocess.STDOUT, cwd=self.src_dir)
6b96c1a1
JH
1214
1215 def __del__(self):
5cc42a51 1216 """Delete the reference source directory
6b96c1a1
JH
1217
1218 This function makes sure the temporary directory is cleaned away
1219 even if Python suddenly dies due to error. It should be done in here
1220 because it is guaranteed the destructor is always invoked when the
1221 instance of the class gets unreferenced.
1222 """
5cc42a51
MY
1223 shutil.rmtree(self.src_dir)
1224
1225 def get_dir(self):
1226 """Return the absolute path to the reference source directory."""
6b96c1a1 1227
5cc42a51 1228 return self.src_dir
6b96c1a1 1229
b2e83c63 1230def move_config(toolchains, configs, args, db_queue):
5a27c734
MY
1231 """Move config options to defconfig files.
1232
91197aa6 1233 Args:
b134bc13 1234 configs: A list of CONFIGs to move.
b2e83c63 1235 args: Program arguments
5a27c734 1236 """
b134bc13 1237 if len(configs) == 0:
b2e83c63 1238 if args.force_sync:
793dca34 1239 print('No CONFIG is specified. You are probably syncing defconfigs.', end=' ')
b2e83c63 1240 elif args.build_db:
793dca34 1241 print('Building %s database' % CONFIG_DATABASE)
6a9f79f7 1242 else:
793dca34 1243 print('Neither CONFIG nor --force-sync is specified. Nothing will happen.', end=' ')
6a9f79f7 1244 else:
793dca34 1245 print('Move ' + ', '.join(configs), end=' ')
b2e83c63 1246 print('(jobs: %d)\n' % args.jobs)
5a27c734 1247
b2e83c63
SG
1248 if args.git_ref:
1249 reference_src = ReferenceSource(args.git_ref)
5cc42a51
MY
1250 reference_src_dir = reference_src.get_dir()
1251 else:
f432c33f 1252 reference_src_dir = None
6b96c1a1 1253
b2e83c63
SG
1254 if args.defconfigs:
1255 defconfigs = get_matched_defconfigs(args.defconfigs)
91040e85 1256 else:
684c306e 1257 defconfigs = get_all_defconfigs()
5a27c734 1258
c5e60fd4 1259 progress = Progress(len(defconfigs))
b2e83c63 1260 slots = Slots(toolchains, configs, args, progress, reference_src_dir,
91197aa6 1261 db_queue)
5a27c734
MY
1262
1263 # Main loop to process defconfig files:
1264 # Add a new subprocess into a vacant slot.
1265 # Sleep if there is no available slot.
c5e60fd4
MY
1266 for defconfig in defconfigs:
1267 while not slots.add(defconfig):
5a27c734
MY
1268 while not slots.available():
1269 # No available slot: sleep for a while
1270 time.sleep(SLEEP_TIME)
1271
1272 # wait until all the subprocesses finish
1273 while not slots.empty():
1274 time.sleep(SLEEP_TIME)
1275
793dca34 1276 print('')
5a27c734 1277 slots.show_failed_boards()
fc2661ee 1278 slots.show_suspicious_boards()
5a27c734 1279
cb008830
SG
1280def find_kconfig_rules(kconf, config, imply_config):
1281 """Check whether a config has a 'select' or 'imply' keyword
1282
1283 Args:
65e05ddc 1284 kconf: Kconfiglib.Kconfig object
cb008830
SG
1285 config: Name of config to check (without CONFIG_ prefix)
1286 imply_config: Implying config (without CONFIG_ prefix) which may or
1287 may not have an 'imply' for 'config')
1288
1289 Returns:
1290 Symbol object for 'config' if found, else None
1291 """
65e05ddc 1292 sym = kconf.syms.get(imply_config)
cb008830 1293 if sym:
ea40b204 1294 for sel, cond in (sym.selects + sym.implies):
a3627086 1295 if sel.name == config:
cb008830
SG
1296 return sym
1297 return None
1298
1299def check_imply_rule(kconf, config, imply_config):
1300 """Check if we can add an 'imply' option
1301
1302 This finds imply_config in the Kconfig and looks to see if it is possible
1303 to add an 'imply' for 'config' to that part of the Kconfig.
1304
1305 Args:
65e05ddc 1306 kconf: Kconfiglib.Kconfig object
cb008830
SG
1307 config: Name of config to check (without CONFIG_ prefix)
1308 imply_config: Implying config (without CONFIG_ prefix) which may or
1309 may not have an 'imply' for 'config')
1310
1311 Returns:
1312 tuple:
1313 filename of Kconfig file containing imply_config, or None if none
1314 line number within the Kconfig file, or 0 if none
1315 message indicating the result
1316 """
65e05ddc 1317 sym = kconf.syms.get(imply_config)
cb008830
SG
1318 if not sym:
1319 return 'cannot find sym'
ea40b204
SG
1320 nodes = sym.nodes
1321 if len(nodes) != 1:
1322 return '%d locations' % len(nodes)
a3627086
SG
1323 node = nodes[0]
1324 fname, linenum = node.filename, node.linenr
cb008830
SG
1325 cwd = os.getcwd()
1326 if cwd and fname.startswith(cwd):
1327 fname = fname[len(cwd) + 1:]
1328 file_line = ' at %s:%d' % (fname, linenum)
37f815ca 1329 data = read_file(fname)
cb008830
SG
1330 if data[linenum - 1] != 'config %s' % imply_config:
1331 return None, 0, 'bad sym format %s%s' % (data[linenum], file_line)
1332 return fname, linenum, 'adding%s' % file_line
1333
1334def add_imply_rule(config, fname, linenum):
1335 """Add a new 'imply' option to a Kconfig
1336
1337 Args:
1338 config: config option to add an imply for (without CONFIG_ prefix)
1339 fname: Kconfig filename to update
1340 linenum: Line number to place the 'imply' before
1341
1342 Returns:
1343 Message indicating the result
1344 """
1345 file_line = ' at %s:%d' % (fname, linenum)
37f815ca 1346 data = read_file(fname)
cb008830
SG
1347 linenum -= 1
1348
1349 for offset, line in enumerate(data[linenum:]):
1350 if line.strip().startswith('help') or not line:
1351 data.insert(linenum + offset, '\timply %s' % config)
2fd85bd3 1352 write_file(fname, data)
cb008830
SG
1353 return 'added%s' % file_line
1354
1355 return 'could not insert%s'
1356
1357(IMPLY_MIN_2, IMPLY_TARGET, IMPLY_CMD, IMPLY_NON_ARCH_BOARD) = (
1358 1, 2, 4, 8)
9b2a2e87
SG
1359
1360IMPLY_FLAGS = {
1361 'min2': [IMPLY_MIN_2, 'Show options which imply >2 boards (normally >5)'],
1362 'target': [IMPLY_TARGET, 'Allow CONFIG_TARGET_... options to imply'],
1363 'cmd': [IMPLY_CMD, 'Allow CONFIG_CMD_... to imply'],
cb008830
SG
1364 'non-arch-board': [
1365 IMPLY_NON_ARCH_BOARD,
1366 'Allow Kconfig options outside arch/ and /board/ to imply'],
91197aa6 1367}
9b2a2e87 1368
9d603391
SG
1369
1370def read_database():
1371 """Read in the config database
1372
1373 Returns:
1374 tuple:
1375 set of all config options seen (each a str)
1376 set of all defconfigs seen (each a str)
1377 dict of configs for each defconfig:
1378 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
1379 value: dict:
1380 key: CONFIG option
1381 value: Value of option
1382 dict of defconfigs for each config:
1383 key: CONFIG option
1384 value: set of boards using that option
1385
1386 """
1387 configs = {}
1388
1389 # key is defconfig name, value is dict of (CONFIG_xxx, value)
1390 config_db = {}
1391
1392 # Set of all config options we have seen
1393 all_configs = set()
1394
1395 # Set of all defconfigs we have seen
1396 all_defconfigs = set()
1397
1398 defconfig_db = collections.defaultdict(set)
37f815ca
SG
1399 for line in read_file(CONFIG_DATABASE):
1400 line = line.rstrip()
1401 if not line: # Separator between defconfigs
1402 config_db[defconfig] = configs
1403 all_defconfigs.add(defconfig)
1404 configs = {}
1405 elif line[0] == ' ': # CONFIG line
1406 config, value = line.strip().split('=', 1)
1407 configs[config] = value
1408 defconfig_db[config].add(defconfig)
1409 all_configs.add(config)
1410 else: # New defconfig
1411 defconfig = line
9d603391
SG
1412
1413 return all_configs, all_defconfigs, config_db, defconfig_db
1414
1415
cb008830
SG
1416def do_imply_config(config_list, add_imply, imply_flags, skip_added,
1417 check_kconfig=True, find_superset=False):
99b66605
SG
1418 """Find CONFIG options which imply those in the list
1419
1420 Some CONFIG options can be implied by others and this can help to reduce
1421 the size of the defconfig files. For example, CONFIG_X86 implies
1422 CONFIG_CMD_IRQ, so we can put 'imply CMD_IRQ' under 'config X86' and
1423 all x86 boards will have that option, avoiding adding CONFIG_CMD_IRQ to
1424 each of the x86 defconfig files.
1425
1426 This function uses the moveconfig database to find such options. It
1427 displays a list of things that could possibly imply those in the list.
1428 The algorithm ignores any that start with CONFIG_TARGET since these
1429 typically refer to only a few defconfigs (often one). It also does not
1430 display a config with less than 5 defconfigs.
1431
1432 The algorithm works using sets. For each target config in config_list:
1433 - Get the set 'defconfigs' which use that target config
1434 - For each config (from a list of all configs):
1435 - Get the set 'imply_defconfig' of defconfigs which use that config
1436 -
1437 - If imply_defconfigs contains anything not in defconfigs then
1438 this config does not imply the target config
1439
1440 Params:
1441 config_list: List of CONFIG options to check (each a string)
cb008830 1442 add_imply: Automatically add an 'imply' for each config.
9b2a2e87
SG
1443 imply_flags: Flags which control which implying configs are allowed
1444 (IMPLY_...)
cb008830
SG
1445 skip_added: Don't show options which already have an imply added.
1446 check_kconfig: Check if implied symbols already have an 'imply' or
1447 'select' for the target config, and show this information if so.
99b66605
SG
1448 find_superset: True to look for configs which are a superset of those
1449 already found. So for example if CONFIG_EXYNOS5 implies an option,
1450 but CONFIG_EXYNOS covers a larger set of defconfigs and also
1451 implies that option, this will drop the former in favour of the
1452 latter. In practice this option has not proved very used.
1453
1454 Note the terminoloy:
1455 config - a CONFIG_XXX options (a string, e.g. 'CONFIG_CMD_EEPROM')
1456 defconfig - a defconfig file (a string, e.g. 'configs/snow_defconfig')
1457 """
cb008830
SG
1458 kconf = KconfigScanner().conf if check_kconfig else None
1459 if add_imply and add_imply != 'all':
a3627086 1460 add_imply = add_imply.split(',')
cb008830 1461
9d603391 1462 all_configs, all_defconfigs, config_db, defconfig_db = read_database()
99b66605 1463
a3627086 1464 # Work through each target config option in turn, independently
99b66605
SG
1465 for config in config_list:
1466 defconfigs = defconfig_db.get(config)
1467 if not defconfigs:
793dca34 1468 print('%s not found in any defconfig' % config)
99b66605
SG
1469 continue
1470
1471 # Get the set of defconfigs without this one (since a config cannot
1472 # imply itself)
1473 non_defconfigs = all_defconfigs - defconfigs
1474 num_defconfigs = len(defconfigs)
793dca34
SG
1475 print('%s found in %d/%d defconfigs' % (config, num_defconfigs,
1476 len(all_configs)))
99b66605
SG
1477
1478 # This will hold the results: key=config, value=defconfigs containing it
1479 imply_configs = {}
1480 rest_configs = all_configs - set([config])
1481
1482 # Look at every possible config, except the target one
1483 for imply_config in rest_configs:
9b2a2e87 1484 if 'ERRATUM' in imply_config:
99b66605 1485 continue
91197aa6 1486 if not imply_flags & IMPLY_CMD:
9b2a2e87
SG
1487 if 'CONFIG_CMD' in imply_config:
1488 continue
91197aa6 1489 if not imply_flags & IMPLY_TARGET:
9b2a2e87
SG
1490 if 'CONFIG_TARGET' in imply_config:
1491 continue
99b66605
SG
1492
1493 # Find set of defconfigs that have this config
1494 imply_defconfig = defconfig_db[imply_config]
1495
1496 # Get the intersection of this with defconfigs containing the
1497 # target config
1498 common_defconfigs = imply_defconfig & defconfigs
1499
1500 # Get the set of defconfigs containing this config which DO NOT
1501 # also contain the taret config. If this set is non-empty it means
1502 # that this config affects other defconfigs as well as (possibly)
1503 # the ones affected by the target config. This means it implies
1504 # things we don't want to imply.
1505 not_common_defconfigs = imply_defconfig & non_defconfigs
1506 if not_common_defconfigs:
1507 continue
1508
1509 # If there are common defconfigs, imply_config may be useful
1510 if common_defconfigs:
1511 skip = False
1512 if find_superset:
793dca34 1513 for prev in list(imply_configs.keys()):
99b66605
SG
1514 prev_count = len(imply_configs[prev])
1515 count = len(common_defconfigs)
1516 if (prev_count > count and
1517 (imply_configs[prev] & common_defconfigs ==
1518 common_defconfigs)):
1519 # skip imply_config because prev is a superset
1520 skip = True
1521 break
1522 elif count > prev_count:
1523 # delete prev because imply_config is a superset
1524 del imply_configs[prev]
1525 if not skip:
1526 imply_configs[imply_config] = common_defconfigs
1527
1528 # Now we have a dict imply_configs of configs which imply each config
1529 # The value of each dict item is the set of defconfigs containing that
1530 # config. Rank them so that we print the configs that imply the largest
1531 # number of defconfigs first.
cb008830 1532 ranked_iconfigs = sorted(imply_configs,
99b66605 1533 key=lambda k: len(imply_configs[k]), reverse=True)
cb008830
SG
1534 kconfig_info = ''
1535 cwd = os.getcwd()
1536 add_list = collections.defaultdict(list)
1537 for iconfig in ranked_iconfigs:
1538 num_common = len(imply_configs[iconfig])
99b66605
SG
1539
1540 # Don't bother if there are less than 5 defconfigs affected.
9b2a2e87 1541 if num_common < (2 if imply_flags & IMPLY_MIN_2 else 5):
99b66605 1542 continue
cb008830 1543 missing = defconfigs - imply_configs[iconfig]
99b66605
SG
1544 missing_str = ', '.join(missing) if missing else 'all'
1545 missing_str = ''
cb008830
SG
1546 show = True
1547 if kconf:
1548 sym = find_kconfig_rules(kconf, config[CONFIG_LEN:],
1549 iconfig[CONFIG_LEN:])
1550 kconfig_info = ''
1551 if sym:
ea40b204
SG
1552 nodes = sym.nodes
1553 if len(nodes) == 1:
1554 fname, linenum = nodes[0].filename, nodes[0].linenr
cb008830
SG
1555 if cwd and fname.startswith(cwd):
1556 fname = fname[len(cwd) + 1:]
1557 kconfig_info = '%s:%d' % (fname, linenum)
1558 if skip_added:
1559 show = False
1560 else:
65e05ddc 1561 sym = kconf.syms.get(iconfig[CONFIG_LEN:])
cb008830
SG
1562 fname = ''
1563 if sym:
ea40b204
SG
1564 nodes = sym.nodes
1565 if len(nodes) == 1:
1566 fname, linenum = nodes[0].filename, nodes[0].linenr
cb008830
SG
1567 if cwd and fname.startswith(cwd):
1568 fname = fname[len(cwd) + 1:]
1569 in_arch_board = not sym or (fname.startswith('arch') or
1570 fname.startswith('board'))
1571 if (not in_arch_board and
91197aa6 1572 not imply_flags & IMPLY_NON_ARCH_BOARD):
cb008830
SG
1573 continue
1574
1575 if add_imply and (add_imply == 'all' or
1576 iconfig in add_imply):
1577 fname, linenum, kconfig_info = (check_imply_rule(kconf,
1578 config[CONFIG_LEN:], iconfig[CONFIG_LEN:]))
1579 if fname:
1580 add_list[fname].append(linenum)
1581
1582 if show and kconfig_info != 'skip':
793dca34
SG
1583 print('%5d : %-30s%-25s %s' % (num_common, iconfig.ljust(30),
1584 kconfig_info, missing_str))
cb008830
SG
1585
1586 # Having collected a list of things to add, now we add them. We process
1587 # each file from the largest line number to the smallest so that
1588 # earlier additions do not affect our line numbers. E.g. if we added an
1589 # imply at line 20 it would change the position of each line after
1590 # that.
793dca34 1591 for fname, linenums in add_list.items():
cb008830
SG
1592 for linenum in sorted(linenums, reverse=True):
1593 add_imply_rule(config[CONFIG_LEN:], fname, linenum)
99b66605 1594
941671a1
SG
1595def defconfig_matches(configs, re_match):
1596 """Check if any CONFIG option matches a regex
1597
1598 The match must be complete, i.e. from the start to end of the CONFIG option.
1599
1600 Args:
1601 configs (dict): Dict of CONFIG options:
1602 key: CONFIG option
1603 value: Value of option
1604 re_match (re.Pattern): Match to check
1605
1606 Returns:
1607 bool: True if any CONFIG matches the regex
1608 """
1609 for cfg in configs:
d9c958f4 1610 if re_match.fullmatch(cfg):
941671a1
SG
1611 return True
1612 return False
99b66605 1613
65d7fcec
SG
1614def do_find_config(config_list):
1615 """Find boards with a given combination of CONFIGs
1616
1617 Params:
941671a1 1618 config_list: List of CONFIG options to check (each a regex consisting
65d7fcec
SG
1619 of a config option, with or without a CONFIG_ prefix. If an option
1620 is preceded by a tilde (~) then it must be false, otherwise it must
1621 be true)
1622 """
1623 all_configs, all_defconfigs, config_db, defconfig_db = read_database()
1624
1625 # Get the whitelist
37f815ca 1626 adhoc_configs = set(read_file('scripts/config_whitelist.txt'))
65d7fcec
SG
1627
1628 # Start with all defconfigs
1629 out = all_defconfigs
1630
1631 # Work through each config in turn
1632 adhoc = []
1633 for item in config_list:
1634 # Get the real config name and whether we want this config or not
1635 cfg = item
1636 want = True
1637 if cfg[0] == '~':
1638 want = False
1639 cfg = cfg[1:]
1640
1641 if cfg in adhoc_configs:
1642 adhoc.append(cfg)
1643 continue
1644
1645 # Search everything that is still in the running. If it has a config
1646 # that we want, or doesn't have one that we don't, add it into the
1647 # running for the next stage
1648 in_list = out
1649 out = set()
941671a1 1650 re_match = re.compile(cfg)
65d7fcec 1651 for defc in in_list:
941671a1 1652 has_cfg = defconfig_matches(config_db[defc], re_match)
65d7fcec
SG
1653 if has_cfg == want:
1654 out.add(defc)
1655 if adhoc:
1656 print(f"Error: Not in Kconfig: %s" % ' '.join(adhoc))
1657 else:
1658 print(f'{len(out)} matches')
78f12e53 1659 print(' '.join(item.split('_defconfig')[0] for item in out))
65d7fcec
SG
1660
1661
1662def prefix_config(cfg):
1663 """Prefix a config with CONFIG_ if needed
1664
1665 This handles ~ operator, which indicates that the CONFIG should be disabled
1666
1667 >>> prefix_config('FRED')
1668 'CONFIG_FRED'
1669 >>> prefix_config('CONFIG_FRED')
1670 'CONFIG_FRED'
1671 >>> prefix_config('~FRED')
1672 '~CONFIG_FRED'
1673 >>> prefix_config('~CONFIG_FRED')
1674 '~CONFIG_FRED'
1675 >>> prefix_config('A123')
1676 'CONFIG_A123'
1677 """
1678 op = ''
1679 if cfg[0] == '~':
1680 op = cfg[0]
1681 cfg = cfg[1:]
1682 if not cfg.startswith('CONFIG_'):
1683 cfg = 'CONFIG_' + cfg
1684 return op + cfg
1685
1686
5a27c734
MY
1687def main():
1688 try:
1689 cpu_count = multiprocessing.cpu_count()
1690 except NotImplementedError:
1691 cpu_count = 1
1692
b2e83c63
SG
1693 epilog = '''Move config options from headers to defconfig files. See
1694doc/develop/moveconfig.rst for documentation.'''
1695
1696 parser = ArgumentParser(epilog=epilog)
1697 # Add arguments here
1698 parser.add_argument('-a', '--add-imply', type=str, default='',
cb008830
SG
1699 help='comma-separated list of CONFIG options to add '
1700 "an 'imply' statement to for the CONFIG in -i")
b2e83c63 1701 parser.add_argument('-A', '--skip-added', action='store_true', default=False,
cb008830
SG
1702 help="don't show options which are already marked as "
1703 'implying others')
b2e83c63 1704 parser.add_argument('-b', '--build-db', action='store_true', default=False,
d73fcb12 1705 help='build a CONFIG database')
b2e83c63 1706 parser.add_argument('-c', '--color', action='store_true', default=False,
5a27c734 1707 help='display the log in color')
b2e83c63 1708 parser.add_argument('-C', '--commit', action='store_true', default=False,
9ede2123 1709 help='Create a git commit for the operation')
b2e83c63 1710 parser.add_argument('-d', '--defconfigs', type=str,
ee4e61bd
SG
1711 help='a file containing a list of defconfigs to move, '
1712 "one per line (for example 'snow_defconfig') "
1713 "or '-' to read from stdin")
b2e83c63 1714 parser.add_argument('-e', '--exit-on-error', action='store_true',
5a27c734
MY
1715 default=False,
1716 help='exit immediately on any error')
b2e83c63 1717 parser.add_argument('-f', '--find', action='store_true', default=False,
65d7fcec 1718 help='Find boards with a given config combination')
b2e83c63 1719 parser.add_argument('-H', '--headers-only', dest='cleanup_headers_only',
2144f880
JH
1720 action='store_true', default=False,
1721 help='only cleanup the headers')
b2e83c63 1722 parser.add_argument('-i', '--imply', action='store_true', default=False,
e1ae5632 1723 help='find options which imply others')
b2e83c63 1724 parser.add_argument('-I', '--imply-flags', type=str, default='',
e1ae5632 1725 help="control the -i option ('help' for help")
b2e83c63 1726 parser.add_argument('-j', '--jobs', type=int, default=cpu_count,
5a27c734 1727 help='the number of jobs to run simultaneously')
b2e83c63 1728 parser.add_argument('-n', '--dry-run', action='store_true', default=False,
e1ae5632 1729 help='perform a trial run (show log with no changes)')
b2e83c63 1730 parser.add_argument('-r', '--git-ref', type=str,
6b96c1a1 1731 help='the git ref to clone for building the autoconf.mk')
b2e83c63 1732 parser.add_argument('-s', '--force-sync', action='store_true', default=False,
e1ae5632 1733 help='force sync by savedefconfig')
b2e83c63 1734 parser.add_argument('-S', '--spl', action='store_true', default=False,
e1ae5632 1735 help='parse config options defined for SPL build')
b2e83c63 1736 parser.add_argument('-t', '--test', action='store_true', default=False,
e1ae5632 1737 help='run unit tests')
b2e83c63 1738 parser.add_argument('-y', '--yes', action='store_true', default=False,
6b403dfd 1739 help="respond 'yes' to any prompts")
b2e83c63 1740 parser.add_argument('-v', '--verbose', action='store_true', default=False,
95bf9c7e 1741 help='show any build errors as boards are built')
b2e83c63 1742 parser.add_argument('configs', nargs='*')
5a27c734 1743
b2e83c63
SG
1744 args = parser.parse_args()
1745 configs = args.configs
5a27c734 1746
b2e83c63 1747 if args.test:
84067a58
SG
1748 sys.argv = [sys.argv[0]]
1749 fail, count = doctest.testmod()
1750 if fail:
1751 return 1
1752 unittest.main()
1753
b2e83c63
SG
1754 if not any((len(configs), args.force_sync, args.build_db, args.imply,
1755 args.find)):
5a27c734
MY
1756 parser.print_usage()
1757 sys.exit(1)
1758
b6ef393a 1759 # prefix the option name with CONFIG_ if missing
65d7fcec 1760 configs = [prefix_config(cfg) for cfg in configs]
5a27c734 1761
2144f880
JH
1762 check_top_directory()
1763
b2e83c63 1764 if args.imply:
9b2a2e87 1765 imply_flags = 0
b2e83c63 1766 if args.imply_flags == 'all':
dee36c74
SG
1767 imply_flags = -1
1768
b2e83c63
SG
1769 elif args.imply_flags:
1770 for flag in args.imply_flags.split(','):
dee36c74
SG
1771 bad = flag not in IMPLY_FLAGS
1772 if bad:
793dca34 1773 print("Invalid flag '%s'" % flag)
dee36c74 1774 if flag == 'help' or bad:
793dca34
SG
1775 print("Imply flags: (separate with ',')")
1776 for name, info in IMPLY_FLAGS.items():
1777 print(' %-15s: %s' % (name, info[1]))
dee36c74
SG
1778 parser.print_usage()
1779 sys.exit(1)
1780 imply_flags |= IMPLY_FLAGS[flag][0]
9b2a2e87 1781
b2e83c63 1782 do_imply_config(configs, args.add_imply, imply_flags, args.skip_added)
99b66605
SG
1783 return
1784
b2e83c63 1785 if args.find:
65d7fcec
SG
1786 do_find_config(configs)
1787 return
1788
d73fcb12 1789 config_db = {}
793dca34 1790 db_queue = queue.Queue()
d73fcb12
SG
1791 t = DatabaseThread(config_db, db_queue)
1792 t.setDaemon(True)
1793 t.start()
1794
b2e83c63 1795 if not args.cleanup_headers_only:
f7536f79 1796 check_clean_directory()
793dca34 1797 bsettings.Setup('')
6821a745
SG
1798 toolchains = toolchain.Toolchains()
1799 toolchains.GetSettings()
1800 toolchains.Scan(verbose=False)
b2e83c63 1801 move_config(toolchains, configs, args, db_queue)
d73fcb12 1802 db_queue.join()
2144f880 1803
6a9f79f7 1804 if configs:
b2e83c63 1805 cleanup_headers(configs, args)
25b8acee 1806 cleanup_extra_options(configs, args)
b2e83c63
SG
1807 cleanup_whitelist(configs, args)
1808 cleanup_readme(configs, args)
5a27c734 1809
b2e83c63 1810 if args.commit:
9ede2123
SG
1811 subprocess.call(['git', 'add', '-u'])
1812 if configs:
1813 msg = 'Convert %s %sto Kconfig' % (configs[0],
1814 'et al ' if len(configs) > 1 else '')
1815 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' %
1816 '\n '.join(configs))
1817 else:
1818 msg = 'configs: Resync with savedefconfig'
1819 msg += '\n\nRsync all defconfig files using moveconfig.py'
1820 subprocess.call(['git', 'commit', '-s', '-m', msg])
1821
b2e83c63 1822 if args.build_db:
91197aa6 1823 with open(CONFIG_DATABASE, 'w', encoding='utf-8') as fd:
793dca34 1824 for defconfig, configs in config_db.items():
c79d18c4 1825 fd.write('%s\n' % defconfig)
d73fcb12 1826 for config in sorted(configs.keys()):
c79d18c4
SG
1827 fd.write(' %s=%s\n' % (config, configs[config]))
1828 fd.write('\n')
d73fcb12 1829
5a27c734 1830if __name__ == '__main__':
65d7fcec 1831 sys.exit(main())
This page took 0.605614 seconds and 4 git commands to generate.