blob: 87a670f6e85556e5647f837505c40142b0d06ecd [file] [log] [blame]
Simon Glass1f701862019-10-31 07:42:57 -06001#!/usr/bin/env python3
Tom Rini10e47792018-05-06 17:58:06 -04002# SPDX-License-Identifier: GPL-2.0+
Masahiro Yamadab6160812015-05-20 11:36:07 +09003#
4# Author: Masahiro Yamada <yamada.masahiro@socionext.com>
5#
Masahiro Yamadab6160812015-05-20 11:36:07 +09006
7"""
8Move config options from headers to defconfig files.
9
Simon Glass83cc72e2021-07-21 21:35:51 -060010See doc/develop/moveconfig.rst for documentation.
Masahiro Yamadab6160812015-05-20 11:36:07 +090011"""
12
Simon Glassd9c1da22021-12-18 14:54:31 -070013from argparse import ArgumentParser
Simon Glassc6e73cf2017-06-01 19:39:03 -060014import collections
Simon Glassb3464eb2021-12-18 14:54:35 -070015from contextlib import ExitStack
Simon Glassbb57be72021-12-18 08:09:45 -070016import doctest
Masahiro Yamada0f6beda2016-05-19 15:52:07 +090017import filecmp
Masahiro Yamadab6160812015-05-20 11:36:07 +090018import fnmatch
Masahiro Yamada3984d6e2016-10-19 14:39:54 +090019import glob
Masahiro Yamadab6160812015-05-20 11:36:07 +090020import multiprocessing
Masahiro Yamadab6160812015-05-20 11:36:07 +090021import os
Simon Glass1f701862019-10-31 07:42:57 -060022import queue
Masahiro Yamadab6160812015-05-20 11:36:07 +090023import re
24import shutil
25import subprocess
26import sys
27import tempfile
Simon Glass43cf08f2017-06-01 19:39:02 -060028import threading
Masahiro Yamadab6160812015-05-20 11:36:07 +090029import time
Simon Glassbb57be72021-12-18 08:09:45 -070030import unittest
Masahiro Yamadab6160812015-05-20 11:36:07 +090031
Simon Glass5367e572023-09-23 13:43:52 -060032import asteval
Simon Glassf0d9c102020-04-17 18:09:02 -060033from buildman import bsettings
34from buildman import kconfiglib
35from buildman import toolchain
Simon Glass44116332017-06-15 21:39:33 -060036
Masahiro Yamadab6160812015-05-20 11:36:07 +090037SHOW_GNU_MAKE = 'scripts/show-gnu-make'
38SLEEP_TIME=0.03
39
Masahiro Yamadab6160812015-05-20 11:36:07 +090040STATE_IDLE = 0
41STATE_DEFCONFIG = 1
42STATE_AUTOCONF = 2
Joe Hershberger166edec2015-05-19 13:21:17 -050043STATE_SAVEDEFCONFIG = 3
Masahiro Yamadab6160812015-05-20 11:36:07 +090044
Masahiro Yamadab6160812015-05-20 11:36:07 +090045COLOR_BLACK = '0;30'
46COLOR_RED = '0;31'
47COLOR_GREEN = '0;32'
48COLOR_BROWN = '0;33'
49COLOR_BLUE = '0;34'
50COLOR_PURPLE = '0;35'
51COLOR_CYAN = '0;36'
52COLOR_LIGHT_GRAY = '0;37'
53COLOR_DARK_GRAY = '1;30'
54COLOR_LIGHT_RED = '1;31'
55COLOR_LIGHT_GREEN = '1;32'
56COLOR_YELLOW = '1;33'
57COLOR_LIGHT_BLUE = '1;34'
58COLOR_LIGHT_PURPLE = '1;35'
59COLOR_LIGHT_CYAN = '1;36'
60COLOR_WHITE = '1;37'
61
Simon Glass8fb5bd02017-06-01 19:39:01 -060062AUTO_CONF_PATH = 'include/config/auto.conf'
Simon Glass43cf08f2017-06-01 19:39:02 -060063CONFIG_DATABASE = 'moveconfig.db'
Simon Glass8fb5bd02017-06-01 19:39:01 -060064
Simon Glass44116332017-06-15 21:39:33 -060065CONFIG_LEN = len('CONFIG_')
Simon Glass8fb5bd02017-06-01 19:39:01 -060066
Markus Klotzbuecher3d5d4182019-05-15 15:15:52 +020067SIZES = {
Simon Glassdc634d92021-12-18 14:54:30 -070068 'SZ_1': 0x00000001, 'SZ_2': 0x00000002,
69 'SZ_4': 0x00000004, 'SZ_8': 0x00000008,
70 'SZ_16': 0x00000010, 'SZ_32': 0x00000020,
71 'SZ_64': 0x00000040, 'SZ_128': 0x00000080,
72 'SZ_256': 0x00000100, 'SZ_512': 0x00000200,
73 'SZ_1K': 0x00000400, 'SZ_2K': 0x00000800,
74 'SZ_4K': 0x00001000, 'SZ_8K': 0x00002000,
75 'SZ_16K': 0x00004000, 'SZ_32K': 0x00008000,
76 'SZ_64K': 0x00010000, 'SZ_128K': 0x00020000,
77 'SZ_256K': 0x00040000, 'SZ_512K': 0x00080000,
78 'SZ_1M': 0x00100000, 'SZ_2M': 0x00200000,
79 'SZ_4M': 0x00400000, 'SZ_8M': 0x00800000,
80 'SZ_16M': 0x01000000, 'SZ_32M': 0x02000000,
81 'SZ_64M': 0x04000000, 'SZ_128M': 0x08000000,
82 'SZ_256M': 0x10000000, 'SZ_512M': 0x20000000,
83 'SZ_1G': 0x40000000, 'SZ_2G': 0x80000000,
84 'SZ_4G': 0x100000000
Markus Klotzbuecher3d5d4182019-05-15 15:15:52 +020085}
86
Simon Glasse8037552022-02-08 11:49:45 -070087RE_REMOVE_DEFCONFIG = re.compile(r'(.*)_defconfig')
88
Simon Glass4c4eb7c2023-02-01 13:19:12 -070089# CONFIG symbols present in the build system (from Linux) but not actually used
90# in U-Boot; KCONFIG symbols
91IGNORE_SYMS = ['DEBUG_SECTION_MISMATCH', 'FTRACE_MCOUNT_RECORD', 'GCOV_KERNEL',
92 'GCOV_PROFILE_ALL', 'KALLSYMS', 'KASAN', 'MODVERSIONS', 'SHELL',
93 'TPL_BUILD', 'VPL_BUILD', 'IS_ENABLED', 'FOO', 'IF_ENABLED_INT',
94 'IS_ENABLED_', 'IS_ENABLED_1', 'IS_ENABLED_2', 'IS_ENABLED_3',
95 'SPL_', 'TPL_', 'SPL_FOO', 'TPL_FOO', 'TOOLS_FOO',
96 'ACME', 'SPL_ACME', 'TPL_ACME', 'TRACE_BRANCH_PROFILING',
97 'VAL', '_UNDEFINED', 'SPL_BUILD', ]
98
99SPL_PREFIXES = ['SPL_', 'TPL_', 'VPL_', 'TOOLS_']
100
Masahiro Yamadab6160812015-05-20 11:36:07 +0900101### helper functions ###
Masahiro Yamadab6160812015-05-20 11:36:07 +0900102def check_top_directory():
103 """Exit if we are not at the top of source directory."""
Simon Glassb3464eb2021-12-18 14:54:35 -0700104 for fname in 'README', 'Licenses':
105 if not os.path.exists(fname):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900106 sys.exit('Please run at the top of source directory.')
107
Masahiro Yamada990e6772016-05-19 15:51:54 +0900108def check_clean_directory():
109 """Exit if the source tree is not clean."""
Simon Glassb3464eb2021-12-18 14:54:35 -0700110 for fname in '.config', 'include/config':
111 if os.path.exists(fname):
Masahiro Yamada990e6772016-05-19 15:51:54 +0900112 sys.exit("source tree is not clean, please run 'make mrproper'")
113
Masahiro Yamadab6160812015-05-20 11:36:07 +0900114def get_make_cmd():
115 """Get the command name of GNU Make.
116
117 U-Boot needs GNU Make for building, but the command name is not
118 necessarily "make". (for example, "gmake" on FreeBSD).
119 Returns the most appropriate command name on your system.
120 """
Simon Glassb3464eb2021-12-18 14:54:35 -0700121 with subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE) as proc:
122 ret = proc.communicate()
123 if proc.returncode:
124 sys.exit('GNU Make not found')
Masahiro Yamadab6160812015-05-20 11:36:07 +0900125 return ret[0].rstrip()
126
Simon Glass18774bc2017-06-01 19:38:58 -0600127def get_matched_defconfig(line):
128 """Get the defconfig files that match a pattern
129
130 Args:
Simon Glassb3464eb2021-12-18 14:54:35 -0700131 line (str): Path or filename to match, e.g. 'configs/snow_defconfig' or
Simon Glass18774bc2017-06-01 19:38:58 -0600132 'k2*_defconfig'. If no directory is provided, 'configs/' is
133 prepended
134
135 Returns:
Simon Glassb3464eb2021-12-18 14:54:35 -0700136 list of str: a list of matching defconfig files
Simon Glass18774bc2017-06-01 19:38:58 -0600137 """
138 dirname = os.path.dirname(line)
139 if dirname:
140 pattern = line
141 else:
142 pattern = os.path.join('configs', line)
143 return glob.glob(pattern) + glob.glob(pattern + '_defconfig')
144
Masahiro Yamada3984d6e2016-10-19 14:39:54 +0900145def get_matched_defconfigs(defconfigs_file):
Simon Glass8f3cf312017-06-01 19:38:59 -0600146 """Get all the defconfig files that match the patterns in a file.
147
148 Args:
Simon Glassb3464eb2021-12-18 14:54:35 -0700149 defconfigs_file (str): File containing a list of defconfigs to process,
150 or '-' to read the list from stdin
Simon Glass8f3cf312017-06-01 19:38:59 -0600151
152 Returns:
Simon Glassb3464eb2021-12-18 14:54:35 -0700153 list of str: A list of paths to defconfig files, with no duplicates
Simon Glass8f3cf312017-06-01 19:38:59 -0600154 """
Masahiro Yamada3984d6e2016-10-19 14:39:54 +0900155 defconfigs = []
Simon Glassb3464eb2021-12-18 14:54:35 -0700156 with ExitStack() as stack:
157 if defconfigs_file == '-':
158 inf = sys.stdin
159 defconfigs_file = 'stdin'
160 else:
161 inf = stack.enter_context(open(defconfigs_file, encoding='utf-8'))
162 for i, line in enumerate(inf):
163 line = line.strip()
164 if not line:
165 continue # skip blank lines silently
166 if ' ' in line:
167 line = line.split(' ')[0] # handle 'git log' input
168 matched = get_matched_defconfig(line)
169 if not matched:
170 print(f"warning: {defconfigs_file}:{i + 1}: no defconfig matched '{line}'",
171 file=sys.stderr)
Masahiro Yamada3984d6e2016-10-19 14:39:54 +0900172
Simon Glassb3464eb2021-12-18 14:54:35 -0700173 defconfigs += matched
Masahiro Yamada3984d6e2016-10-19 14:39:54 +0900174
175 # use set() to drop multiple matching
Simon Glassb3464eb2021-12-18 14:54:35 -0700176 return [defconfig[len('configs') + 1:] for defconfig in set(defconfigs)]
Masahiro Yamada3984d6e2016-10-19 14:39:54 +0900177
Masahiro Yamada58175e32016-07-25 19:15:28 +0900178def get_all_defconfigs():
Simon Glassb3464eb2021-12-18 14:54:35 -0700179 """Get all the defconfig files under the configs/ directory.
180
181 Returns:
182 list of str: List of paths to defconfig files
183 """
Masahiro Yamada58175e32016-07-25 19:15:28 +0900184 defconfigs = []
Simon Glassb3464eb2021-12-18 14:54:35 -0700185 for (dirpath, _, filenames) in os.walk('configs'):
Masahiro Yamada58175e32016-07-25 19:15:28 +0900186 dirpath = dirpath[len('configs') + 1:]
187 for filename in fnmatch.filter(filenames, '*_defconfig'):
188 defconfigs.append(os.path.join(dirpath, filename))
189
190 return defconfigs
191
Masahiro Yamadab6160812015-05-20 11:36:07 +0900192def color_text(color_enabled, color, string):
193 """Return colored string."""
194 if color_enabled:
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900195 # LF should not be surrounded by the escape sequence.
196 # Otherwise, additional whitespace or line-feed might be printed.
197 return '\n'.join([ '\033[' + color + 'm' + s + '\033[0m' if s else ''
198 for s in string.split('\n') ])
Simon Glassb3464eb2021-12-18 14:54:35 -0700199 return string
Masahiro Yamadab6160812015-05-20 11:36:07 +0900200
Simon Glassb09ae452021-12-18 14:54:33 -0700201def write_file(fname, data):
202 """Write data to a file
203
204 Args:
205 fname (str): Filename to write to
206 data (list of str): Lines to write (with or without trailing newline);
207 or str to write
208 """
209 with open(fname, 'w', encoding='utf-8') as out:
210 if isinstance(data, list):
211 for line in data:
212 print(line.rstrip('\n'), file=out)
213 else:
214 out.write(data)
215
Simon Glassaba238f2021-12-18 14:54:34 -0700216def read_file(fname, as_lines=True, skip_unicode=False):
217 """Read a file and return the contents
218
219 Args:
220 fname (str): Filename to read from
221 as_lines: Return file contents as a list of lines
222 skip_unicode (bool): True to report unicode errors and continue
223
224 Returns:
225 iter of str: List of ;ines from the file with newline removed; str if
226 as_lines is False with newlines intact; or None if a unicode error
227 occurred
228
229 Raises:
230 UnicodeDecodeError: Unicode error occurred when reading
231 """
232 with open(fname, encoding='utf-8') as inf:
233 try:
234 if as_lines:
235 return [line.rstrip('\n') for line in inf.readlines()]
236 else:
237 return inf.read()
238 except UnicodeDecodeError as e:
239 if not skip_unicode:
Simon Glassafaddc72022-02-11 13:23:22 -0700240 raise
Simon Glassaba238f2021-12-18 14:54:34 -0700241 print("Failed on file %s': %s" % (fname, e))
242 return None
243
Markus Klotzbuecher3d5d4182019-05-15 15:15:52 +0200244def try_expand(line):
245 """If value looks like an expression, try expanding it
246 Otherwise just return the existing value
247 """
248 if line.find('=') == -1:
249 return line
250
251 try:
Markus Klotzbuecherbac0bb52020-02-12 20:46:44 +0100252 aeval = asteval.Interpreter( usersyms=SIZES, minimal=True )
Markus Klotzbuecher3d5d4182019-05-15 15:15:52 +0200253 cfg, val = re.split("=", line)
254 val= val.strip('\"')
Simon Glassdc634d92021-12-18 14:54:30 -0700255 if re.search(r'[*+-/]|<<|SZ_+|\(([^\)]+)\)', val):
Markus Klotzbuecherbac0bb52020-02-12 20:46:44 +0100256 newval = hex(aeval(val))
Simon Glassdc634d92021-12-18 14:54:30 -0700257 print('\tExpanded expression %s to %s' % (val, newval))
Markus Klotzbuecher3d5d4182019-05-15 15:15:52 +0200258 return cfg+'='+newval
259 except:
Simon Glassdc634d92021-12-18 14:54:30 -0700260 print('\tFailed to expand expression in %s' % line)
Markus Klotzbuecher3d5d4182019-05-15 15:15:52 +0200261
262 return line
263
Chris Packham9d5274f2017-05-02 21:30:47 +1200264
Masahiro Yamadab6160812015-05-20 11:36:07 +0900265### classes ###
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900266class Progress:
267
268 """Progress Indicator"""
269
270 def __init__(self, total):
271 """Create a new progress indicator.
272
Simon Glassb3464eb2021-12-18 14:54:35 -0700273 Args:
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900274 total: A number of defconfig files to process.
275 """
276 self.current = 0
277 self.total = total
278
279 def inc(self):
280 """Increment the number of processed defconfig files."""
281
282 self.current += 1
283
284 def show(self):
285 """Display the progress."""
Simon Glass1f701862019-10-31 07:42:57 -0600286 print(' %d defconfigs out of %d\r' % (self.current, self.total), end=' ')
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900287 sys.stdout.flush()
288
Simon Glass44116332017-06-15 21:39:33 -0600289
290class KconfigScanner:
291 """Kconfig scanner."""
292
293 def __init__(self):
294 """Scan all the Kconfig files and create a Config object."""
295 # Define environment variables referenced from Kconfig
296 os.environ['srctree'] = os.getcwd()
297 os.environ['UBOOTVERSION'] = 'dummy'
298 os.environ['KCONFIG_OBJDIR'] = ''
Simon Glass4c4eb7c2023-02-01 13:19:12 -0700299 os.environ['CC'] = 'gcc'
Tom Rini3c5f4152019-09-20 17:42:09 -0400300 self.conf = kconfiglib.Kconfig()
Simon Glass44116332017-06-15 21:39:33 -0600301
302
Masahiro Yamadab6160812015-05-20 11:36:07 +0900303class KconfigParser:
304
305 """A parser of .config and include/autoconf.mk."""
306
307 re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
308 re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
309
Simon Glass08d148a2023-09-23 13:43:54 -0600310 def __init__(self, args, build_dir):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900311 """Create a new parser.
312
Simon Glassb3464eb2021-12-18 14:54:35 -0700313 Args:
Simon Glassb3464eb2021-12-18 14:54:35 -0700314 args (Namespace): program arguments
Masahiro Yamadab6160812015-05-20 11:36:07 +0900315 build_dir: Build directory.
316 """
Simon Glassd9c1da22021-12-18 14:54:31 -0700317 self.args = args
Masahiro Yamada5393b612016-05-19 15:52:00 +0900318 self.dotconfig = os.path.join(build_dir, '.config')
319 self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
Masahiro Yamada6d139172016-08-22 22:18:22 +0900320 self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
321 'autoconf.mk')
Simon Glass8fb5bd02017-06-01 19:39:01 -0600322 self.config_autoconf = os.path.join(build_dir, AUTO_CONF_PATH)
Masahiro Yamada07f98522016-05-19 15:52:06 +0900323 self.defconfig = os.path.join(build_dir, 'defconfig')
Masahiro Yamadab6160812015-05-20 11:36:07 +0900324
Simon Glass257f5232017-07-10 14:47:47 -0600325 def get_arch(self):
326 """Parse .config file and return the architecture.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900327
328 Returns:
Simon Glass257f5232017-07-10 14:47:47 -0600329 Architecture name (e.g. 'arm').
Masahiro Yamadab6160812015-05-20 11:36:07 +0900330 """
331 arch = ''
332 cpu = ''
Simon Glassaba238f2021-12-18 14:54:34 -0700333 for line in read_file(self.dotconfig):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900334 m = self.re_arch.match(line)
335 if m:
336 arch = m.group(1)
337 continue
338 m = self.re_cpu.match(line)
339 if m:
340 cpu = m.group(1)
341
Masahiro Yamadac4d76eb2016-05-19 15:51:53 +0900342 if not arch:
343 return None
Masahiro Yamadab6160812015-05-20 11:36:07 +0900344
345 # fix-up for aarch64
346 if arch == 'arm' and cpu == 'armv8':
347 arch = 'aarch64'
348
Simon Glass257f5232017-07-10 14:47:47 -0600349 return arch
Masahiro Yamadab6160812015-05-20 11:36:07 +0900350
Simon Glass43cf08f2017-06-01 19:39:02 -0600351
352class DatabaseThread(threading.Thread):
353 """This thread processes results from Slot threads.
354
355 It collects the data in the master config directary. There is only one
356 result thread, and this helps to serialise the build output.
357 """
358 def __init__(self, config_db, db_queue):
359 """Set up a new result thread
360
361 Args:
362 builder: Builder which will be sent each result
363 """
364 threading.Thread.__init__(self)
365 self.config_db = config_db
366 self.db_queue= db_queue
367
368 def run(self):
369 """Called to start up the result thread.
370
371 We collect the next result job and pass it on to the build.
372 """
373 while True:
374 defconfig, configs = self.db_queue.get()
375 self.config_db[defconfig] = configs
376 self.db_queue.task_done()
377
378
Masahiro Yamadab6160812015-05-20 11:36:07 +0900379class Slot:
380
381 """A slot to store a subprocess.
382
383 Each instance of this class handles one subprocess.
384 This class is useful to control multiple threads
385 for faster processing.
386 """
387
Simon Glass08d148a2023-09-23 13:43:54 -0600388 def __init__(self, toolchains, args, progress, devnull,
Simon Glass257f5232017-07-10 14:47:47 -0600389 make_cmd, reference_src_dir, db_queue):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900390 """Create a new process slot.
391
Simon Glassb3464eb2021-12-18 14:54:35 -0700392 Args:
Simon Glass257f5232017-07-10 14:47:47 -0600393 toolchains: Toolchains object containing toolchains.
Simon Glassd9c1da22021-12-18 14:54:31 -0700394 args: Program arguments
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900395 progress: A progress indicator.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900396 devnull: A file object of '/dev/null'.
397 make_cmd: command name of GNU Make.
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500398 reference_src_dir: Determine the true starting config state from this
399 source tree.
Simon Glass43cf08f2017-06-01 19:39:02 -0600400 db_queue: output queue to write config info for the database
Masahiro Yamadab6160812015-05-20 11:36:07 +0900401 """
Simon Glass257f5232017-07-10 14:47:47 -0600402 self.toolchains = toolchains
Simon Glassd9c1da22021-12-18 14:54:31 -0700403 self.args = args
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900404 self.progress = progress
Masahiro Yamadab6160812015-05-20 11:36:07 +0900405 self.build_dir = tempfile.mkdtemp()
406 self.devnull = devnull
407 self.make_cmd = (make_cmd, 'O=' + self.build_dir)
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500408 self.reference_src_dir = reference_src_dir
Simon Glass43cf08f2017-06-01 19:39:02 -0600409 self.db_queue = db_queue
Simon Glass08d148a2023-09-23 13:43:54 -0600410 self.parser = KconfigParser(args, self.build_dir)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900411 self.state = STATE_IDLE
Masahiro Yamada1271b672016-08-22 22:18:20 +0900412 self.failed_boards = set()
Masahiro Yamadab6160812015-05-20 11:36:07 +0900413
414 def __del__(self):
415 """Delete the working directory
416
417 This function makes sure the temporary directory is cleaned away
418 even if Python suddenly dies due to error. It should be done in here
Joe Hershberger640de872016-06-10 14:53:29 -0500419 because it is guaranteed the destructor is always invoked when the
Masahiro Yamadab6160812015-05-20 11:36:07 +0900420 instance of the class gets unreferenced.
421
422 If the subprocess is still running, wait until it finishes.
423 """
424 if self.state != STATE_IDLE:
425 while self.ps.poll() == None:
426 pass
427 shutil.rmtree(self.build_dir)
428
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900429 def add(self, defconfig):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900430 """Assign a new subprocess for defconfig and add it to the slot.
431
432 If the slot is vacant, create a new subprocess for processing the
433 given defconfig and add it to the slot. Just returns False if
434 the slot is occupied (i.e. the current subprocess is still running).
435
Simon Glassb3464eb2021-12-18 14:54:35 -0700436 Args:
Masahiro Yamadab6160812015-05-20 11:36:07 +0900437 defconfig: defconfig name.
438
439 Returns:
440 Return True on success or False on failure
441 """
442 if self.state != STATE_IDLE:
443 return False
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900444
Masahiro Yamadab6160812015-05-20 11:36:07 +0900445 self.defconfig = defconfig
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900446 self.log = ''
Masahiro Yamada8f5256a2016-06-15 14:33:52 +0900447 self.current_src_dir = self.reference_src_dir
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900448 self.do_defconfig()
Masahiro Yamadab6160812015-05-20 11:36:07 +0900449 return True
450
451 def poll(self):
452 """Check the status of the subprocess and handle it as needed.
453
454 Returns True if the slot is vacant (i.e. in idle state).
455 If the configuration is successfully finished, assign a new
456 subprocess to build include/autoconf.mk.
457 If include/autoconf.mk is generated, invoke the parser to
Masahiro Yamada263d1372016-05-19 15:52:04 +0900458 parse the .config and the include/autoconf.mk, moving
459 config options to the .config as needed.
460 If the .config was updated, run "make savedefconfig" to sync
461 it, update the original defconfig, and then set the slot back
462 to the idle state.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900463
464 Returns:
465 Return True if the subprocess is terminated, False otherwise
466 """
467 if self.state == STATE_IDLE:
468 return True
469
470 if self.ps.poll() == None:
471 return False
472
473 if self.ps.poll() != 0:
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900474 self.handle_error()
475 elif self.state == STATE_DEFCONFIG:
Masahiro Yamada8f5256a2016-06-15 14:33:52 +0900476 if self.reference_src_dir and not self.current_src_dir:
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500477 self.do_savedefconfig()
478 else:
479 self.do_autoconf()
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900480 elif self.state == STATE_AUTOCONF:
Masahiro Yamada8f5256a2016-06-15 14:33:52 +0900481 if self.current_src_dir:
482 self.current_src_dir = None
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500483 self.do_defconfig()
Simon Glassd9c1da22021-12-18 14:54:31 -0700484 elif self.args.build_db:
Simon Glass43cf08f2017-06-01 19:39:02 -0600485 self.do_build_db()
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500486 else:
487 self.do_savedefconfig()
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900488 elif self.state == STATE_SAVEDEFCONFIG:
489 self.update_defconfig()
490 else:
Simon Glassdc634d92021-12-18 14:54:30 -0700491 sys.exit('Internal Error. This should not happen.')
Masahiro Yamadab6160812015-05-20 11:36:07 +0900492
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900493 return True if self.state == STATE_IDLE else False
Joe Hershberger166edec2015-05-19 13:21:17 -0500494
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900495 def handle_error(self):
496 """Handle error cases."""
Masahiro Yamada83c17672016-05-19 15:52:08 +0900497
Simon Glassd9c1da22021-12-18 14:54:31 -0700498 self.log += color_text(self.args.color, COLOR_LIGHT_RED,
Simon Glassdc634d92021-12-18 14:54:30 -0700499 'Failed to process.\n')
Simon Glassd9c1da22021-12-18 14:54:31 -0700500 if self.args.verbose:
501 self.log += color_text(self.args.color, COLOR_LIGHT_CYAN,
Markus Klotzbuecher8773f352020-02-12 20:46:45 +0100502 self.ps.stderr.read().decode())
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900503 self.finish(False)
Joe Hershberger166edec2015-05-19 13:21:17 -0500504
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900505 def do_defconfig(self):
506 """Run 'make <board>_defconfig' to create the .config file."""
Masahiro Yamada0f6beda2016-05-19 15:52:07 +0900507
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900508 cmd = list(self.make_cmd)
509 cmd.append(self.defconfig)
510 self.ps = subprocess.Popen(cmd, stdout=self.devnull,
Masahiro Yamada8f5256a2016-06-15 14:33:52 +0900511 stderr=subprocess.PIPE,
512 cwd=self.current_src_dir)
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900513 self.state = STATE_DEFCONFIG
Masahiro Yamada0f6beda2016-05-19 15:52:07 +0900514
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900515 def do_autoconf(self):
Simon Glass8fb5bd02017-06-01 19:39:01 -0600516 """Run 'make AUTO_CONF_PATH'."""
Masahiro Yamadab6160812015-05-20 11:36:07 +0900517
Simon Glass257f5232017-07-10 14:47:47 -0600518 arch = self.parser.get_arch()
519 try:
520 toolchain = self.toolchains.Select(arch)
521 except ValueError:
Simon Glassd9c1da22021-12-18 14:54:31 -0700522 self.log += color_text(self.args.color, COLOR_YELLOW,
Chris Packham1ebcbd12017-08-27 20:00:51 +1200523 "Tool chain for '%s' is missing. Do nothing.\n" % arch)
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900524 self.finish(False)
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900525 return
Simon Glass1f701862019-10-31 07:42:57 -0600526 env = toolchain.MakeEnvironment(False)
Masahiro Yamadac4d76eb2016-05-19 15:51:53 +0900527
Masahiro Yamadab6160812015-05-20 11:36:07 +0900528 cmd = list(self.make_cmd)
Joe Hershberger765442b2015-05-19 13:21:18 -0500529 cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
Simon Glass8fb5bd02017-06-01 19:39:01 -0600530 cmd.append(AUTO_CONF_PATH)
Simon Glass257f5232017-07-10 14:47:47 -0600531 self.ps = subprocess.Popen(cmd, stdout=self.devnull, env=env,
Masahiro Yamada8f5256a2016-06-15 14:33:52 +0900532 stderr=subprocess.PIPE,
533 cwd=self.current_src_dir)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900534 self.state = STATE_AUTOCONF
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900535
Simon Glass43cf08f2017-06-01 19:39:02 -0600536 def do_build_db(self):
537 """Add the board to the database"""
538 configs = {}
Simon Glassaba238f2021-12-18 14:54:34 -0700539 for line in read_file(os.path.join(self.build_dir, AUTO_CONF_PATH)):
540 if line.startswith('CONFIG'):
541 config, value = line.split('=', 1)
542 configs[config] = value.rstrip()
Simon Glass43cf08f2017-06-01 19:39:02 -0600543 self.db_queue.put([self.defconfig, configs])
544 self.finish(True)
545
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900546 def do_savedefconfig(self):
547 """Update the .config and run 'make savedefconfig'."""
Simon Glassc1c10c22023-09-23 13:43:55 -0600548 if not self.args.force_sync:
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900549 self.finish(True)
550 return
Simon Glassc1c10c22023-09-23 13:43:55 -0600551 self.log += 'Syncing by savedefconfig (forced by option)...\n'
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900552
553 cmd = list(self.make_cmd)
554 cmd.append('savedefconfig')
555 self.ps = subprocess.Popen(cmd, stdout=self.devnull,
556 stderr=subprocess.PIPE)
557 self.state = STATE_SAVEDEFCONFIG
558
559 def update_defconfig(self):
560 """Update the input defconfig and go back to the idle state."""
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900561 orig_defconfig = os.path.join('configs', self.defconfig)
562 new_defconfig = os.path.join(self.build_dir, 'defconfig')
563 updated = not filecmp.cmp(orig_defconfig, new_defconfig)
564
565 if updated:
Simon Glassd9c1da22021-12-18 14:54:31 -0700566 self.log += color_text(self.args.color, COLOR_LIGHT_BLUE,
Simon Glassdc634d92021-12-18 14:54:30 -0700567 'defconfig was updated.\n')
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900568
Simon Glassd9c1da22021-12-18 14:54:31 -0700569 if not self.args.dry_run and updated:
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900570 shutil.move(new_defconfig, orig_defconfig)
571 self.finish(True)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900572
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900573 def finish(self, success):
574 """Display log along with progress and go to the idle state.
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900575
Simon Glassb3464eb2021-12-18 14:54:35 -0700576 Args:
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900577 success: Should be True when the defconfig was processed
578 successfully, or False when it fails.
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900579 """
580 # output at least 30 characters to hide the "* defconfigs out of *".
581 log = self.defconfig.ljust(30) + '\n'
582
583 log += '\n'.join([ ' ' + s for s in self.log.split('\n') ])
584 # Some threads are running in parallel.
585 # Print log atomically to not mix up logs from different threads.
Simon Glass1f701862019-10-31 07:42:57 -0600586 print(log, file=(sys.stdout if success else sys.stderr))
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900587
588 if not success:
Simon Glassd9c1da22021-12-18 14:54:31 -0700589 if self.args.exit_on_error:
Simon Glassdc634d92021-12-18 14:54:30 -0700590 sys.exit('Exit on error.')
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900591 # If --exit-on-error flag is not set, skip this board and continue.
592 # Record the failed board.
Masahiro Yamada1271b672016-08-22 22:18:20 +0900593 self.failed_boards.add(self.defconfig)
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900594
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900595 self.progress.inc()
596 self.progress.show()
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900597 self.state = STATE_IDLE
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900598
Masahiro Yamadab6160812015-05-20 11:36:07 +0900599 def get_failed_boards(self):
Masahiro Yamada1271b672016-08-22 22:18:20 +0900600 """Returns a set of failed boards (defconfigs) in this slot.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900601 """
602 return self.failed_boards
603
Masahiro Yamada3c9bfea2016-06-15 14:33:54 +0900604 def get_suspicious_boards(self):
Masahiro Yamada1271b672016-08-22 22:18:20 +0900605 """Returns a set of boards (defconfigs) with possible misconversion.
Masahiro Yamada3c9bfea2016-06-15 14:33:54 +0900606 """
Masahiro Yamada35204d92016-08-22 22:18:21 +0900607 return self.suspicious_boards - self.failed_boards
Masahiro Yamada3c9bfea2016-06-15 14:33:54 +0900608
Masahiro Yamadab6160812015-05-20 11:36:07 +0900609class Slots:
610
611 """Controller of the array of subprocess slots."""
612
Simon Glass08d148a2023-09-23 13:43:54 -0600613 def __init__(self, toolchains, args, progress, reference_src_dir, db_queue):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900614 """Create a new slots controller.
615
Simon Glassb3464eb2021-12-18 14:54:35 -0700616 Args:
Simon Glass257f5232017-07-10 14:47:47 -0600617 toolchains: Toolchains object containing toolchains.
Simon Glassd9c1da22021-12-18 14:54:31 -0700618 args: Program arguments
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900619 progress: A progress indicator.
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500620 reference_src_dir: Determine the true starting config state from this
621 source tree.
Simon Glass43cf08f2017-06-01 19:39:02 -0600622 db_queue: output queue to write config info for the database
Masahiro Yamadab6160812015-05-20 11:36:07 +0900623 """
Simon Glassd9c1da22021-12-18 14:54:31 -0700624 self.args = args
Masahiro Yamadab6160812015-05-20 11:36:07 +0900625 self.slots = []
Simon Glass34c505f2021-12-18 14:54:32 -0700626 devnull = subprocess.DEVNULL
Masahiro Yamadab6160812015-05-20 11:36:07 +0900627 make_cmd = get_make_cmd()
Simon Glassd9c1da22021-12-18 14:54:31 -0700628 for i in range(args.jobs):
Simon Glass08d148a2023-09-23 13:43:54 -0600629 self.slots.append(Slot(toolchains, args, progress, devnull,
630 make_cmd, reference_src_dir, db_queue))
Masahiro Yamadab6160812015-05-20 11:36:07 +0900631
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900632 def add(self, defconfig):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900633 """Add a new subprocess if a vacant slot is found.
634
Simon Glassb3464eb2021-12-18 14:54:35 -0700635 Args:
Masahiro Yamadab6160812015-05-20 11:36:07 +0900636 defconfig: defconfig name to be put into.
637
638 Returns:
639 Return True on success or False on failure
640 """
641 for slot in self.slots:
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900642 if slot.add(defconfig):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900643 return True
644 return False
645
646 def available(self):
647 """Check if there is a vacant slot.
648
649 Returns:
650 Return True if at lease one vacant slot is found, False otherwise.
651 """
652 for slot in self.slots:
653 if slot.poll():
654 return True
655 return False
656
657 def empty(self):
658 """Check if all slots are vacant.
659
660 Returns:
661 Return True if all the slots are vacant, False otherwise.
662 """
663 ret = True
664 for slot in self.slots:
665 if not slot.poll():
666 ret = False
667 return ret
668
669 def show_failed_boards(self):
670 """Display all of the failed boards (defconfigs)."""
Masahiro Yamada1271b672016-08-22 22:18:20 +0900671 boards = set()
Masahiro Yamada0153f032016-06-15 14:33:53 +0900672 output_file = 'moveconfig.failed'
Masahiro Yamadab6160812015-05-20 11:36:07 +0900673
674 for slot in self.slots:
Masahiro Yamada1271b672016-08-22 22:18:20 +0900675 boards |= slot.get_failed_boards()
Masahiro Yamadab6160812015-05-20 11:36:07 +0900676
Masahiro Yamada0153f032016-06-15 14:33:53 +0900677 if boards:
678 boards = '\n'.join(boards) + '\n'
Simon Glassdc634d92021-12-18 14:54:30 -0700679 msg = 'The following boards were not processed due to error:\n'
Masahiro Yamada0153f032016-06-15 14:33:53 +0900680 msg += boards
Simon Glassdc634d92021-12-18 14:54:30 -0700681 msg += '(the list has been saved in %s)\n' % output_file
Simon Glassd9c1da22021-12-18 14:54:31 -0700682 print(color_text(self.args.color, COLOR_LIGHT_RED,
Simon Glass1f701862019-10-31 07:42:57 -0600683 msg), file=sys.stderr)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900684
Simon Glassb09ae452021-12-18 14:54:33 -0700685 write_file(output_file, boards)
Joe Hershbergerdade12e2015-05-19 13:21:22 -0500686
Masahiro Yamada3c9bfea2016-06-15 14:33:54 +0900687 def show_suspicious_boards(self):
688 """Display all boards (defconfigs) with possible misconversion."""
Masahiro Yamada1271b672016-08-22 22:18:20 +0900689 boards = set()
Masahiro Yamada3c9bfea2016-06-15 14:33:54 +0900690 output_file = 'moveconfig.suspicious'
691
692 for slot in self.slots:
Masahiro Yamada1271b672016-08-22 22:18:20 +0900693 boards |= slot.get_suspicious_boards()
Masahiro Yamada3c9bfea2016-06-15 14:33:54 +0900694
695 if boards:
696 boards = '\n'.join(boards) + '\n'
Simon Glassdc634d92021-12-18 14:54:30 -0700697 msg = 'The following boards might have been converted incorrectly.\n'
698 msg += 'It is highly recommended to check them manually:\n'
Masahiro Yamada3c9bfea2016-06-15 14:33:54 +0900699 msg += boards
Simon Glassdc634d92021-12-18 14:54:30 -0700700 msg += '(the list has been saved in %s)\n' % output_file
Simon Glassd9c1da22021-12-18 14:54:31 -0700701 print(color_text(self.args.color, COLOR_YELLOW,
Simon Glass1f701862019-10-31 07:42:57 -0600702 msg), file=sys.stderr)
Masahiro Yamada3c9bfea2016-06-15 14:33:54 +0900703
Simon Glassb09ae452021-12-18 14:54:33 -0700704 write_file(output_file, boards)
Masahiro Yamada3c9bfea2016-06-15 14:33:54 +0900705
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900706class ReferenceSource:
707
708 """Reference source against which original configs should be parsed."""
709
710 def __init__(self, commit):
711 """Create a reference source directory based on a specified commit.
712
Simon Glassb3464eb2021-12-18 14:54:35 -0700713 Args:
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900714 commit: commit to git-clone
715 """
716 self.src_dir = tempfile.mkdtemp()
Simon Glassdc634d92021-12-18 14:54:30 -0700717 print('Cloning git repo to a separate work directory...')
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900718 subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
719 cwd=self.src_dir)
Simon Glass1f701862019-10-31 07:42:57 -0600720 print("Checkout '%s' to build the original autoconf.mk." % \
721 subprocess.check_output(['git', 'rev-parse', '--short', commit]).strip())
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900722 subprocess.check_output(['git', 'checkout', commit],
723 stderr=subprocess.STDOUT, cwd=self.src_dir)
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500724
725 def __del__(self):
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900726 """Delete the reference source directory
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500727
728 This function makes sure the temporary directory is cleaned away
729 even if Python suddenly dies due to error. It should be done in here
730 because it is guaranteed the destructor is always invoked when the
731 instance of the class gets unreferenced.
732 """
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900733 shutil.rmtree(self.src_dir)
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500734
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900735 def get_dir(self):
736 """Return the absolute path to the reference source directory."""
737
738 return self.src_dir
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500739
Simon Glass08d148a2023-09-23 13:43:54 -0600740def move_config(toolchains, args, db_queue):
741 """Build database or sync config options to defconfig files.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900742
Simon Glassb3464eb2021-12-18 14:54:35 -0700743 Args:
Simon Glass08d148a2023-09-23 13:43:54 -0600744 toolchains: Toolchains to use
Simon Glassd9c1da22021-12-18 14:54:31 -0700745 args: Program arguments
Simon Glass08d148a2023-09-23 13:43:54 -0600746 db_queue: Queue for database updates
Masahiro Yamadab6160812015-05-20 11:36:07 +0900747 """
Simon Glass08d148a2023-09-23 13:43:54 -0600748 if args.force_sync:
749 print('Syncing defconfigs', end=' ')
750 elif args.build_db:
751 print('Building %s database' % CONFIG_DATABASE)
Simon Glassd9c1da22021-12-18 14:54:31 -0700752 print('(jobs: %d)\n' % args.jobs)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900753
Simon Glassd9c1da22021-12-18 14:54:31 -0700754 if args.git_ref:
755 reference_src = ReferenceSource(args.git_ref)
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900756 reference_src_dir = reference_src.get_dir()
757 else:
Masahiro Yamada8f5256a2016-06-15 14:33:52 +0900758 reference_src_dir = None
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500759
Simon Glassd9c1da22021-12-18 14:54:31 -0700760 if args.defconfigs:
761 defconfigs = get_matched_defconfigs(args.defconfigs)
Joe Hershbergerc6e043a2015-05-19 13:21:19 -0500762 else:
Masahiro Yamada58175e32016-07-25 19:15:28 +0900763 defconfigs = get_all_defconfigs()
Masahiro Yamadab6160812015-05-20 11:36:07 +0900764
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900765 progress = Progress(len(defconfigs))
Simon Glass08d148a2023-09-23 13:43:54 -0600766 slots = Slots(toolchains, args, progress, reference_src_dir, db_queue)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900767
768 # Main loop to process defconfig files:
769 # Add a new subprocess into a vacant slot.
770 # Sleep if there is no available slot.
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900771 for defconfig in defconfigs:
772 while not slots.add(defconfig):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900773 while not slots.available():
774 # No available slot: sleep for a while
775 time.sleep(SLEEP_TIME)
776
777 # wait until all the subprocesses finish
778 while not slots.empty():
779 time.sleep(SLEEP_TIME)
780
Simon Glass1f701862019-10-31 07:42:57 -0600781 print('')
Masahiro Yamadab6160812015-05-20 11:36:07 +0900782 slots.show_failed_boards()
Masahiro Yamada3c9bfea2016-06-15 14:33:54 +0900783 slots.show_suspicious_boards()
Masahiro Yamadab6160812015-05-20 11:36:07 +0900784
Simon Glass44116332017-06-15 21:39:33 -0600785def find_kconfig_rules(kconf, config, imply_config):
786 """Check whether a config has a 'select' or 'imply' keyword
787
788 Args:
Tom Rini3c5f4152019-09-20 17:42:09 -0400789 kconf: Kconfiglib.Kconfig object
Simon Glass44116332017-06-15 21:39:33 -0600790 config: Name of config to check (without CONFIG_ prefix)
791 imply_config: Implying config (without CONFIG_ prefix) which may or
792 may not have an 'imply' for 'config')
793
794 Returns:
795 Symbol object for 'config' if found, else None
796 """
Tom Rini3c5f4152019-09-20 17:42:09 -0400797 sym = kconf.syms.get(imply_config)
Simon Glass44116332017-06-15 21:39:33 -0600798 if sym:
Simon Glass520b47a2021-07-21 21:35:53 -0600799 for sel, cond in (sym.selects + sym.implies):
Simon Glass93c0a9e2021-12-18 08:09:42 -0700800 if sel.name == config:
Simon Glass44116332017-06-15 21:39:33 -0600801 return sym
802 return None
803
804def check_imply_rule(kconf, config, imply_config):
805 """Check if we can add an 'imply' option
806
807 This finds imply_config in the Kconfig and looks to see if it is possible
808 to add an 'imply' for 'config' to that part of the Kconfig.
809
810 Args:
Tom Rini3c5f4152019-09-20 17:42:09 -0400811 kconf: Kconfiglib.Kconfig object
Simon Glass44116332017-06-15 21:39:33 -0600812 config: Name of config to check (without CONFIG_ prefix)
813 imply_config: Implying config (without CONFIG_ prefix) which may or
814 may not have an 'imply' for 'config')
815
816 Returns:
817 tuple:
818 filename of Kconfig file containing imply_config, or None if none
819 line number within the Kconfig file, or 0 if none
820 message indicating the result
821 """
Tom Rini3c5f4152019-09-20 17:42:09 -0400822 sym = kconf.syms.get(imply_config)
Simon Glass44116332017-06-15 21:39:33 -0600823 if not sym:
824 return 'cannot find sym'
Simon Glass520b47a2021-07-21 21:35:53 -0600825 nodes = sym.nodes
826 if len(nodes) != 1:
827 return '%d locations' % len(nodes)
Simon Glass93c0a9e2021-12-18 08:09:42 -0700828 node = nodes[0]
829 fname, linenum = node.filename, node.linenr
Simon Glass44116332017-06-15 21:39:33 -0600830 cwd = os.getcwd()
831 if cwd and fname.startswith(cwd):
832 fname = fname[len(cwd) + 1:]
833 file_line = ' at %s:%d' % (fname, linenum)
Simon Glassaba238f2021-12-18 14:54:34 -0700834 data = read_file(fname)
Simon Glass44116332017-06-15 21:39:33 -0600835 if data[linenum - 1] != 'config %s' % imply_config:
836 return None, 0, 'bad sym format %s%s' % (data[linenum], file_line)
837 return fname, linenum, 'adding%s' % file_line
838
839def add_imply_rule(config, fname, linenum):
840 """Add a new 'imply' option to a Kconfig
841
842 Args:
843 config: config option to add an imply for (without CONFIG_ prefix)
844 fname: Kconfig filename to update
845 linenum: Line number to place the 'imply' before
846
847 Returns:
848 Message indicating the result
849 """
850 file_line = ' at %s:%d' % (fname, linenum)
Simon Glassaba238f2021-12-18 14:54:34 -0700851 data = read_file(fname)
Simon Glass44116332017-06-15 21:39:33 -0600852 linenum -= 1
853
854 for offset, line in enumerate(data[linenum:]):
855 if line.strip().startswith('help') or not line:
856 data.insert(linenum + offset, '\timply %s' % config)
Simon Glassb09ae452021-12-18 14:54:33 -0700857 write_file(fname, data)
Simon Glass44116332017-06-15 21:39:33 -0600858 return 'added%s' % file_line
859
860 return 'could not insert%s'
861
862(IMPLY_MIN_2, IMPLY_TARGET, IMPLY_CMD, IMPLY_NON_ARCH_BOARD) = (
863 1, 2, 4, 8)
Simon Glass92e55582017-06-15 21:39:32 -0600864
865IMPLY_FLAGS = {
866 'min2': [IMPLY_MIN_2, 'Show options which imply >2 boards (normally >5)'],
867 'target': [IMPLY_TARGET, 'Allow CONFIG_TARGET_... options to imply'],
868 'cmd': [IMPLY_CMD, 'Allow CONFIG_CMD_... to imply'],
Simon Glass44116332017-06-15 21:39:33 -0600869 'non-arch-board': [
870 IMPLY_NON_ARCH_BOARD,
871 'Allow Kconfig options outside arch/ and /board/ to imply'],
Simon Glassb3464eb2021-12-18 14:54:35 -0700872}
Simon Glass92e55582017-06-15 21:39:32 -0600873
Simon Glassf931c2f2021-12-18 08:09:43 -0700874
875def read_database():
876 """Read in the config database
877
878 Returns:
879 tuple:
880 set of all config options seen (each a str)
881 set of all defconfigs seen (each a str)
882 dict of configs for each defconfig:
883 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
884 value: dict:
885 key: CONFIG option
886 value: Value of option
887 dict of defconfigs for each config:
888 key: CONFIG option
889 value: set of boards using that option
890
891 """
892 configs = {}
893
894 # key is defconfig name, value is dict of (CONFIG_xxx, value)
895 config_db = {}
896
897 # Set of all config options we have seen
898 all_configs = set()
899
900 # Set of all defconfigs we have seen
901 all_defconfigs = set()
902
903 defconfig_db = collections.defaultdict(set)
Simon Glassaba238f2021-12-18 14:54:34 -0700904 for line in read_file(CONFIG_DATABASE):
905 line = line.rstrip()
906 if not line: # Separator between defconfigs
907 config_db[defconfig] = configs
908 all_defconfigs.add(defconfig)
909 configs = {}
910 elif line[0] == ' ': # CONFIG line
911 config, value = line.strip().split('=', 1)
912 configs[config] = value
913 defconfig_db[config].add(defconfig)
914 all_configs.add(config)
915 else: # New defconfig
916 defconfig = line
Simon Glassf931c2f2021-12-18 08:09:43 -0700917
918 return all_configs, all_defconfigs, config_db, defconfig_db
919
920
Simon Glass44116332017-06-15 21:39:33 -0600921def do_imply_config(config_list, add_imply, imply_flags, skip_added,
922 check_kconfig=True, find_superset=False):
Simon Glassc6e73cf2017-06-01 19:39:03 -0600923 """Find CONFIG options which imply those in the list
924
925 Some CONFIG options can be implied by others and this can help to reduce
926 the size of the defconfig files. For example, CONFIG_X86 implies
927 CONFIG_CMD_IRQ, so we can put 'imply CMD_IRQ' under 'config X86' and
928 all x86 boards will have that option, avoiding adding CONFIG_CMD_IRQ to
929 each of the x86 defconfig files.
930
931 This function uses the moveconfig database to find such options. It
932 displays a list of things that could possibly imply those in the list.
933 The algorithm ignores any that start with CONFIG_TARGET since these
934 typically refer to only a few defconfigs (often one). It also does not
935 display a config with less than 5 defconfigs.
936
937 The algorithm works using sets. For each target config in config_list:
938 - Get the set 'defconfigs' which use that target config
939 - For each config (from a list of all configs):
940 - Get the set 'imply_defconfig' of defconfigs which use that config
941 -
942 - If imply_defconfigs contains anything not in defconfigs then
943 this config does not imply the target config
944
945 Params:
946 config_list: List of CONFIG options to check (each a string)
Simon Glass44116332017-06-15 21:39:33 -0600947 add_imply: Automatically add an 'imply' for each config.
Simon Glass92e55582017-06-15 21:39:32 -0600948 imply_flags: Flags which control which implying configs are allowed
949 (IMPLY_...)
Simon Glass44116332017-06-15 21:39:33 -0600950 skip_added: Don't show options which already have an imply added.
951 check_kconfig: Check if implied symbols already have an 'imply' or
952 'select' for the target config, and show this information if so.
Simon Glassc6e73cf2017-06-01 19:39:03 -0600953 find_superset: True to look for configs which are a superset of those
954 already found. So for example if CONFIG_EXYNOS5 implies an option,
955 but CONFIG_EXYNOS covers a larger set of defconfigs and also
956 implies that option, this will drop the former in favour of the
957 latter. In practice this option has not proved very used.
958
959 Note the terminoloy:
960 config - a CONFIG_XXX options (a string, e.g. 'CONFIG_CMD_EEPROM')
961 defconfig - a defconfig file (a string, e.g. 'configs/snow_defconfig')
962 """
Simon Glass44116332017-06-15 21:39:33 -0600963 kconf = KconfigScanner().conf if check_kconfig else None
964 if add_imply and add_imply != 'all':
Simon Glass93c0a9e2021-12-18 08:09:42 -0700965 add_imply = add_imply.split(',')
Simon Glass44116332017-06-15 21:39:33 -0600966
Simon Glassf931c2f2021-12-18 08:09:43 -0700967 all_configs, all_defconfigs, config_db, defconfig_db = read_database()
Simon Glassc6e73cf2017-06-01 19:39:03 -0600968
Simon Glass93c0a9e2021-12-18 08:09:42 -0700969 # Work through each target config option in turn, independently
Simon Glassc6e73cf2017-06-01 19:39:03 -0600970 for config in config_list:
971 defconfigs = defconfig_db.get(config)
972 if not defconfigs:
Simon Glass1f701862019-10-31 07:42:57 -0600973 print('%s not found in any defconfig' % config)
Simon Glassc6e73cf2017-06-01 19:39:03 -0600974 continue
975
976 # Get the set of defconfigs without this one (since a config cannot
977 # imply itself)
978 non_defconfigs = all_defconfigs - defconfigs
979 num_defconfigs = len(defconfigs)
Simon Glass1f701862019-10-31 07:42:57 -0600980 print('%s found in %d/%d defconfigs' % (config, num_defconfigs,
981 len(all_configs)))
Simon Glassc6e73cf2017-06-01 19:39:03 -0600982
983 # This will hold the results: key=config, value=defconfigs containing it
984 imply_configs = {}
985 rest_configs = all_configs - set([config])
986
987 # Look at every possible config, except the target one
988 for imply_config in rest_configs:
Simon Glass92e55582017-06-15 21:39:32 -0600989 if 'ERRATUM' in imply_config:
Simon Glassc6e73cf2017-06-01 19:39:03 -0600990 continue
Simon Glassb3464eb2021-12-18 14:54:35 -0700991 if not imply_flags & IMPLY_CMD:
Simon Glass92e55582017-06-15 21:39:32 -0600992 if 'CONFIG_CMD' in imply_config:
993 continue
Simon Glassb3464eb2021-12-18 14:54:35 -0700994 if not imply_flags & IMPLY_TARGET:
Simon Glass92e55582017-06-15 21:39:32 -0600995 if 'CONFIG_TARGET' in imply_config:
996 continue
Simon Glassc6e73cf2017-06-01 19:39:03 -0600997
998 # Find set of defconfigs that have this config
999 imply_defconfig = defconfig_db[imply_config]
1000
1001 # Get the intersection of this with defconfigs containing the
1002 # target config
1003 common_defconfigs = imply_defconfig & defconfigs
1004
1005 # Get the set of defconfigs containing this config which DO NOT
1006 # also contain the taret config. If this set is non-empty it means
1007 # that this config affects other defconfigs as well as (possibly)
1008 # the ones affected by the target config. This means it implies
1009 # things we don't want to imply.
1010 not_common_defconfigs = imply_defconfig & non_defconfigs
1011 if not_common_defconfigs:
1012 continue
1013
1014 # If there are common defconfigs, imply_config may be useful
1015 if common_defconfigs:
1016 skip = False
1017 if find_superset:
Simon Glass1f701862019-10-31 07:42:57 -06001018 for prev in list(imply_configs.keys()):
Simon Glassc6e73cf2017-06-01 19:39:03 -06001019 prev_count = len(imply_configs[prev])
1020 count = len(common_defconfigs)
1021 if (prev_count > count and
1022 (imply_configs[prev] & common_defconfigs ==
1023 common_defconfigs)):
1024 # skip imply_config because prev is a superset
1025 skip = True
1026 break
1027 elif count > prev_count:
1028 # delete prev because imply_config is a superset
1029 del imply_configs[prev]
1030 if not skip:
1031 imply_configs[imply_config] = common_defconfigs
1032
1033 # Now we have a dict imply_configs of configs which imply each config
1034 # The value of each dict item is the set of defconfigs containing that
1035 # config. Rank them so that we print the configs that imply the largest
1036 # number of defconfigs first.
Simon Glass44116332017-06-15 21:39:33 -06001037 ranked_iconfigs = sorted(imply_configs,
Simon Glassc6e73cf2017-06-01 19:39:03 -06001038 key=lambda k: len(imply_configs[k]), reverse=True)
Simon Glass44116332017-06-15 21:39:33 -06001039 kconfig_info = ''
1040 cwd = os.getcwd()
1041 add_list = collections.defaultdict(list)
1042 for iconfig in ranked_iconfigs:
1043 num_common = len(imply_configs[iconfig])
Simon Glassc6e73cf2017-06-01 19:39:03 -06001044
1045 # Don't bother if there are less than 5 defconfigs affected.
Simon Glass92e55582017-06-15 21:39:32 -06001046 if num_common < (2 if imply_flags & IMPLY_MIN_2 else 5):
Simon Glassc6e73cf2017-06-01 19:39:03 -06001047 continue
Simon Glass44116332017-06-15 21:39:33 -06001048 missing = defconfigs - imply_configs[iconfig]
Simon Glassc6e73cf2017-06-01 19:39:03 -06001049 missing_str = ', '.join(missing) if missing else 'all'
1050 missing_str = ''
Simon Glass44116332017-06-15 21:39:33 -06001051 show = True
1052 if kconf:
1053 sym = find_kconfig_rules(kconf, config[CONFIG_LEN:],
1054 iconfig[CONFIG_LEN:])
1055 kconfig_info = ''
1056 if sym:
Simon Glass520b47a2021-07-21 21:35:53 -06001057 nodes = sym.nodes
1058 if len(nodes) == 1:
1059 fname, linenum = nodes[0].filename, nodes[0].linenr
Simon Glass44116332017-06-15 21:39:33 -06001060 if cwd and fname.startswith(cwd):
1061 fname = fname[len(cwd) + 1:]
1062 kconfig_info = '%s:%d' % (fname, linenum)
1063 if skip_added:
1064 show = False
1065 else:
Tom Rini3c5f4152019-09-20 17:42:09 -04001066 sym = kconf.syms.get(iconfig[CONFIG_LEN:])
Simon Glass44116332017-06-15 21:39:33 -06001067 fname = ''
1068 if sym:
Simon Glass520b47a2021-07-21 21:35:53 -06001069 nodes = sym.nodes
1070 if len(nodes) == 1:
1071 fname, linenum = nodes[0].filename, nodes[0].linenr
Simon Glass44116332017-06-15 21:39:33 -06001072 if cwd and fname.startswith(cwd):
1073 fname = fname[len(cwd) + 1:]
1074 in_arch_board = not sym or (fname.startswith('arch') or
1075 fname.startswith('board'))
1076 if (not in_arch_board and
Simon Glassb3464eb2021-12-18 14:54:35 -07001077 not imply_flags & IMPLY_NON_ARCH_BOARD):
Simon Glass44116332017-06-15 21:39:33 -06001078 continue
1079
1080 if add_imply and (add_imply == 'all' or
1081 iconfig in add_imply):
1082 fname, linenum, kconfig_info = (check_imply_rule(kconf,
1083 config[CONFIG_LEN:], iconfig[CONFIG_LEN:]))
1084 if fname:
1085 add_list[fname].append(linenum)
Simon Glassc6e73cf2017-06-01 19:39:03 -06001086
Simon Glass44116332017-06-15 21:39:33 -06001087 if show and kconfig_info != 'skip':
Simon Glass1f701862019-10-31 07:42:57 -06001088 print('%5d : %-30s%-25s %s' % (num_common, iconfig.ljust(30),
1089 kconfig_info, missing_str))
Simon Glass44116332017-06-15 21:39:33 -06001090
1091 # Having collected a list of things to add, now we add them. We process
1092 # each file from the largest line number to the smallest so that
1093 # earlier additions do not affect our line numbers. E.g. if we added an
1094 # imply at line 20 it would change the position of each line after
1095 # that.
Simon Glass1f701862019-10-31 07:42:57 -06001096 for fname, linenums in add_list.items():
Simon Glass44116332017-06-15 21:39:33 -06001097 for linenum in sorted(linenums, reverse=True):
1098 add_imply_rule(config[CONFIG_LEN:], fname, linenum)
1099
Simon Glass99f79422022-02-08 11:49:46 -07001100def defconfig_matches(configs, re_match):
1101 """Check if any CONFIG option matches a regex
1102
1103 The match must be complete, i.e. from the start to end of the CONFIG option.
1104
1105 Args:
1106 configs (dict): Dict of CONFIG options:
1107 key: CONFIG option
1108 value: Value of option
1109 re_match (re.Pattern): Match to check
1110
1111 Returns:
1112 bool: True if any CONFIG matches the regex
1113 """
1114 for cfg in configs:
Simon Glassfea71c92022-03-05 20:18:54 -07001115 if re_match.fullmatch(cfg):
Simon Glass99f79422022-02-08 11:49:46 -07001116 return True
1117 return False
Simon Glassc6e73cf2017-06-01 19:39:03 -06001118
Simon Glass0082b2e2021-12-18 08:09:46 -07001119def do_find_config(config_list):
1120 """Find boards with a given combination of CONFIGs
1121
1122 Params:
Simon Glass99f79422022-02-08 11:49:46 -07001123 config_list: List of CONFIG options to check (each a regex consisting
Simon Glass0082b2e2021-12-18 08:09:46 -07001124 of a config option, with or without a CONFIG_ prefix. If an option
1125 is preceded by a tilde (~) then it must be false, otherwise it must
1126 be true)
1127 """
1128 all_configs, all_defconfigs, config_db, defconfig_db = read_database()
1129
Simon Glass0082b2e2021-12-18 08:09:46 -07001130 # Start with all defconfigs
1131 out = all_defconfigs
1132
1133 # Work through each config in turn
Simon Glass0082b2e2021-12-18 08:09:46 -07001134 for item in config_list:
1135 # Get the real config name and whether we want this config or not
1136 cfg = item
1137 want = True
1138 if cfg[0] == '~':
1139 want = False
1140 cfg = cfg[1:]
1141
Simon Glass0082b2e2021-12-18 08:09:46 -07001142 # Search everything that is still in the running. If it has a config
1143 # that we want, or doesn't have one that we don't, add it into the
1144 # running for the next stage
1145 in_list = out
1146 out = set()
Simon Glass99f79422022-02-08 11:49:46 -07001147 re_match = re.compile(cfg)
Simon Glass0082b2e2021-12-18 08:09:46 -07001148 for defc in in_list:
Simon Glass99f79422022-02-08 11:49:46 -07001149 has_cfg = defconfig_matches(config_db[defc], re_match)
Simon Glass0082b2e2021-12-18 08:09:46 -07001150 if has_cfg == want:
1151 out.add(defc)
Tom Rinic1b64aa2022-12-04 10:14:16 -05001152 print(f'{len(out)} matches')
1153 print(' '.join(item.split('_defconfig')[0] for item in out))
Simon Glass0082b2e2021-12-18 08:09:46 -07001154
1155
1156def prefix_config(cfg):
1157 """Prefix a config with CONFIG_ if needed
1158
1159 This handles ~ operator, which indicates that the CONFIG should be disabled
1160
1161 >>> prefix_config('FRED')
1162 'CONFIG_FRED'
1163 >>> prefix_config('CONFIG_FRED')
1164 'CONFIG_FRED'
1165 >>> prefix_config('~FRED')
1166 '~CONFIG_FRED'
1167 >>> prefix_config('~CONFIG_FRED')
1168 '~CONFIG_FRED'
1169 >>> prefix_config('A123')
1170 'CONFIG_A123'
1171 """
1172 op = ''
1173 if cfg[0] == '~':
1174 op = cfg[0]
1175 cfg = cfg[1:]
1176 if not cfg.startswith('CONFIG_'):
1177 cfg = 'CONFIG_' + cfg
1178 return op + cfg
1179
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001180
1181RE_MK_CONFIGS = re.compile('CONFIG_(\$\(SPL_(?:TPL_)?\))?([A-Za-z0-9_]*)')
1182RE_IFDEF = re.compile('(ifdef|ifndef)')
1183RE_C_CONFIGS = re.compile('CONFIG_([A-Za-z0-9_]*)')
1184RE_CONFIG_IS = re.compile('CONFIG_IS_ENABLED\(([A-Za-z0-9_]*)\)')
1185
1186class ConfigUse:
1187 def __init__(self, cfg, is_spl, fname, rest):
1188 self.cfg = cfg
1189 self.is_spl = is_spl
1190 self.fname = fname
1191 self.rest = rest
1192
1193 def __hash__(self):
1194 return hash((self.cfg, self.is_spl))
1195
1196def scan_makefiles(fnames):
1197 """Scan Makefiles looking for Kconfig options
1198
1199 Looks for uses of CONFIG options in Makefiles
1200
1201 Args:
1202 fnames (list of tuple):
1203 str: Makefile filename where the option was found
1204 str: Line of the Makefile
1205
1206 Returns:
1207 tuple:
1208 dict: all_uses
1209 key (ConfigUse): object
1210 value (list of str): matching lines
1211 dict: Uses by filename
1212 key (str): filename
1213 value (set of ConfigUse): uses in that filename
1214
1215 >>> RE_MK_CONFIGS.search('CONFIG_FRED').groups()
1216 (None, 'FRED')
1217 >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_)MARY').groups()
1218 ('$(SPL_)', 'MARY')
1219 >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_TPL_)MARY').groups()
1220 ('$(SPL_TPL_)', 'MARY')
1221 """
1222 all_uses = collections.defaultdict(list)
1223 fname_uses = {}
1224 for fname, rest in fnames:
1225 m_iter = RE_MK_CONFIGS.finditer(rest)
1226 found = False
1227 for m in m_iter:
1228 found = True
1229 real_opt = m.group(2)
1230 if real_opt == '':
1231 continue
1232 is_spl = False
1233 if m.group(1):
1234 is_spl = True
1235 use = ConfigUse(real_opt, is_spl, fname, rest)
1236 if fname not in fname_uses:
1237 fname_uses[fname] = set()
1238 fname_uses[fname].add(use)
1239 all_uses[use].append(rest)
1240 return all_uses, fname_uses
1241
1242
1243def scan_src_files(fnames):
1244 """Scan source files (other than Makefiles) looking for Kconfig options
1245
1246 Looks for uses of CONFIG options
1247
1248 Args:
1249 fnames (list of tuple):
1250 str: Makefile filename where the option was found
1251 str: Line of the Makefile
1252
1253 Returns:
1254 tuple:
1255 dict: all_uses
1256 key (ConfigUse): object
1257 value (list of str): matching lines
1258 dict: Uses by filename
1259 key (str): filename
1260 value (set of ConfigUse): uses in that filename
1261
1262 >>> RE_C_CONFIGS.search('CONFIG_FRED').groups()
1263 ('FRED',)
1264 >>> RE_CONFIG_IS.search('CONFIG_IS_ENABLED(MARY)').groups()
1265 ('MARY',)
1266 >>> RE_CONFIG_IS.search('#if CONFIG_IS_ENABLED(OF_PLATDATA)').groups()
1267 ('OF_PLATDATA',)
1268 """
1269 def add_uses(m_iter, is_spl):
1270 for m in m_iter:
1271 found = True
1272 real_opt = m.group(1)
1273 if real_opt == '':
1274 continue
1275 use = ConfigUse(real_opt, is_spl, fname, rest)
1276 if fname not in fname_uses:
1277 fname_uses[fname] = set()
1278 fname_uses[fname].add(use)
1279 all_uses[use].append(rest)
1280
1281 all_uses = collections.defaultdict(list)
1282 fname_uses = {}
1283 for fname, rest in fnames:
1284 m_iter = RE_C_CONFIGS.finditer(rest)
1285 add_uses(m_iter, False)
1286
1287 m_iter2 = RE_CONFIG_IS.finditer(rest)
1288 add_uses(m_iter2, True)
1289
1290 return all_uses, fname_uses
1291
1292
1293MODE_NORMAL, MODE_SPL, MODE_PROPER = range(3)
1294
1295def do_scan_source(path, do_update):
1296 """Scan the source tree for Kconfig inconsistencies
1297
1298 Args:
1299 path (str): Path to source tree
1300 do_update (bool) : True to write to scripts/kconf_... files
1301 """
1302 def is_not_proper(name):
1303 for prefix in SPL_PREFIXES:
1304 if name.startswith(prefix):
1305 return name[len(prefix):]
1306 return False
1307
1308 def check_not_found(all_uses, spl_mode):
1309 """Check for Kconfig options mentioned in the source but not in Kconfig
1310
1311 Args:
1312 all_uses (dict):
1313 key (ConfigUse): object
1314 value (list of str): matching lines
1315 spl_mode (int): If MODE_SPL, look at source code which implies
1316 an SPL_ option, but for which there is none;
1317 for MOD_PROPER, look at source code which implies a Proper
1318 option (i.e. use of CONFIG_IS_ENABLED() or $(SPL_) or
1319 $(SPL_TPL_) but for which there none;
1320 if MODE_NORMAL, ignore SPL
1321
1322 Returns:
1323 dict:
1324 key (str): CONFIG name (without 'CONFIG_' prefix
1325 value (list of ConfigUse): List of uses of this CONFIG
1326 """
1327 # Make sure we know about all the options
1328 not_found = collections.defaultdict(list)
1329 for use, rest in all_uses.items():
1330 name = use.cfg
1331 if name in IGNORE_SYMS:
1332 continue
1333 check = True
1334
1335 if spl_mode == MODE_SPL:
1336 check = use.is_spl
1337
1338 # If it is an SPL symbol, try prepending all SPL_ prefixes to
1339 # find at least one SPL symbol
1340 if use.is_spl:
1341 add_to_dict = False
1342 for prefix in SPL_PREFIXES:
1343 try_name = prefix + name
1344 sym = kconf.syms.get(try_name)
1345 if sym:
1346 break
1347 if not sym:
1348 not_found[f'SPL_{name}'].append(use)
1349 continue
1350 elif spl_mode == MODE_PROPER:
1351 # Try to find the Proper version of this symbol, i.e. without
1352 # the SPL_ prefix
1353 proper_name = is_not_proper(name)
1354 if proper_name:
1355 name = proper_name
1356 elif not use.is_spl:
1357 check = False
1358 else: # MODE_NORMAL
1359 debug = False
1360 sym = kconf.syms.get(name)
1361 if not sym:
1362 proper_name = is_not_proper(name)
1363 if proper_name:
1364 name = proper_name
1365 sym = kconf.syms.get(name)
1366 if not sym:
1367 for prefix in SPL_PREFIXES:
1368 try_name = prefix + name
1369 sym = kconf.syms.get(try_name)
1370 if sym:
1371 break
1372 if not sym:
1373 not_found[name].append(use)
1374 continue
1375
1376 sym = kconf.syms.get(name)
1377 if not sym and check:
1378 not_found[name].append(use)
1379 return not_found
1380
1381 def show_uses(uses):
1382 """Show a list of uses along with their filename and code snippet
1383
1384 Args:
1385 uses (dict):
1386 key (str): CONFIG name (without 'CONFIG_' prefix
1387 value (list of ConfigUse): List of uses of this CONFIG
1388 """
1389 for name in sorted(uses):
1390 print(f'{name}: ', end='')
1391 for i, use in enumerate(uses[name]):
1392 print(f'{" " if i else ""}{use.fname}: {use.rest.strip()}')
1393
1394
1395 print('Scanning Kconfig')
1396 kconf = KconfigScanner().conf
1397 print(f'Scanning source in {path}')
1398 args = ['git', 'grep', '-E', r'IS_ENABLED|\bCONFIG']
1399 with subprocess.Popen(args, stdout=subprocess.PIPE) as proc:
1400 out, err = proc.communicate()
1401 lines = out.splitlines()
1402 re_fname = re.compile('^([^:]*):(.*)')
1403 src_list = []
1404 mk_list = []
1405 for line in lines:
1406 linestr = line.decode('utf-8')
1407 m_fname = re_fname.search(linestr)
1408 if not m_fname:
1409 continue
1410 fname, rest = m_fname.groups()
1411 dirname, leaf = os.path.split(fname)
1412 root, ext = os.path.splitext(leaf)
1413 if ext == '.autoconf':
1414 pass
1415 elif ext in ['.c', '.h', '.S', '.lds', '.dts', '.dtsi', '.asl', '.cfg',
1416 '.env', '.tmpl']:
1417 src_list.append([fname, rest])
1418 elif 'Makefile' in root or ext == '.mk':
1419 mk_list.append([fname, rest])
1420 elif ext in ['.yml', '.sh', '.py', '.awk', '.pl', '.rst', '', '.sed']:
1421 pass
1422 elif 'Kconfig' in root or 'Kbuild' in root:
1423 pass
1424 elif 'README' in root:
1425 pass
1426 elif dirname in ['configs']:
1427 pass
1428 elif dirname.startswith('doc') or dirname.startswith('scripts/kconfig'):
1429 pass
1430 else:
1431 print(f'Not sure how to handle file {fname}')
1432
1433 # Scan the Makefiles
1434 all_uses, fname_uses = scan_makefiles(mk_list)
1435
1436 spl_not_found = set()
1437 proper_not_found = set()
1438
1439 # Make sure we know about all the options
1440 print('\nCONFIG options present in Makefiles but not Kconfig:')
1441 not_found = check_not_found(all_uses, MODE_NORMAL)
1442 show_uses(not_found)
1443
1444 print('\nCONFIG options present in Makefiles but not Kconfig (SPL):')
1445 not_found = check_not_found(all_uses, MODE_SPL)
1446 show_uses(not_found)
1447 spl_not_found |= set([is_not_proper(key) or key for key in not_found.keys()])
1448
1449 print('\nCONFIG options used as Proper in Makefiles but without a non-SPL_ variant:')
1450 not_found = check_not_found(all_uses, MODE_PROPER)
1451 show_uses(not_found)
1452 proper_not_found |= set([key for key in not_found.keys()])
1453
1454 # Scan the source code
1455 all_uses, fname_uses = scan_src_files(src_list)
1456
1457 # Make sure we know about all the options
1458 print('\nCONFIG options present in source but not Kconfig:')
1459 not_found = check_not_found(all_uses, MODE_NORMAL)
1460 show_uses(not_found)
1461
1462 print('\nCONFIG options present in source but not Kconfig (SPL):')
1463 not_found = check_not_found(all_uses, MODE_SPL)
1464 show_uses(not_found)
1465 spl_not_found |= set([is_not_proper(key) or key for key in not_found.keys()])
1466
1467 print('\nCONFIG options used as Proper in source but without a non-SPL_ variant:')
1468 not_found = check_not_found(all_uses, MODE_PROPER)
1469 show_uses(not_found)
1470 proper_not_found |= set([key for key in not_found.keys()])
1471
1472 print('\nCONFIG options used as SPL but without an SPL_ variant:')
1473 for item in sorted(spl_not_found):
1474 print(f' {item}')
1475
1476 print('\nCONFIG options used as Proper but without a non-SPL_ variant:')
1477 for item in sorted(proper_not_found):
1478 print(f' {item}')
1479
1480 # Write out the updated information
1481 if do_update:
1482 with open(os.path.join(path, 'scripts', 'conf_nospl'), 'w') as out:
1483 print('# These options should not be enabled in SPL builds\n',
1484 file=out)
1485 for item in sorted(spl_not_found):
1486 print(item, file=out)
1487 with open(os.path.join(path, 'scripts', 'conf_noproper'), 'w') as out:
1488 print('# These options should not be enabled in Proper builds\n',
1489 file=out)
1490 for item in sorted(proper_not_found):
1491 print(item, file=out)
1492
Simon Glass0082b2e2021-12-18 08:09:46 -07001493
Masahiro Yamadab6160812015-05-20 11:36:07 +09001494def main():
1495 try:
1496 cpu_count = multiprocessing.cpu_count()
1497 except NotImplementedError:
1498 cpu_count = 1
1499
Simon Glassd9c1da22021-12-18 14:54:31 -07001500 epilog = '''Move config options from headers to defconfig files. See
1501doc/develop/moveconfig.rst for documentation.'''
1502
1503 parser = ArgumentParser(epilog=epilog)
1504 # Add arguments here
1505 parser.add_argument('-a', '--add-imply', type=str, default='',
Simon Glass44116332017-06-15 21:39:33 -06001506 help='comma-separated list of CONFIG options to add '
1507 "an 'imply' statement to for the CONFIG in -i")
Simon Glassd9c1da22021-12-18 14:54:31 -07001508 parser.add_argument('-A', '--skip-added', action='store_true', default=False,
Simon Glass44116332017-06-15 21:39:33 -06001509 help="don't show options which are already marked as "
1510 'implying others')
Simon Glassd9c1da22021-12-18 14:54:31 -07001511 parser.add_argument('-b', '--build-db', action='store_true', default=False,
Simon Glass43cf08f2017-06-01 19:39:02 -06001512 help='build a CONFIG database')
Simon Glassd9c1da22021-12-18 14:54:31 -07001513 parser.add_argument('-c', '--color', action='store_true', default=False,
Masahiro Yamadab6160812015-05-20 11:36:07 +09001514 help='display the log in color')
Simon Glassd9c1da22021-12-18 14:54:31 -07001515 parser.add_argument('-C', '--commit', action='store_true', default=False,
Simon Glass8bf41c22016-09-12 23:18:21 -06001516 help='Create a git commit for the operation')
Simon Glassd9c1da22021-12-18 14:54:31 -07001517 parser.add_argument('-d', '--defconfigs', type=str,
Simon Glass8f3cf312017-06-01 19:38:59 -06001518 help='a file containing a list of defconfigs to move, '
1519 "one per line (for example 'snow_defconfig') "
1520 "or '-' to read from stdin")
Simon Glassd9c1da22021-12-18 14:54:31 -07001521 parser.add_argument('-e', '--exit-on-error', action='store_true',
Masahiro Yamadab6160812015-05-20 11:36:07 +09001522 default=False,
1523 help='exit immediately on any error')
Simon Glassd9c1da22021-12-18 14:54:31 -07001524 parser.add_argument('-f', '--find', action='store_true', default=False,
Simon Glass0082b2e2021-12-18 08:09:46 -07001525 help='Find boards with a given config combination')
Simon Glassd9c1da22021-12-18 14:54:31 -07001526 parser.add_argument('-i', '--imply', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001527 help='find options which imply others')
Simon Glassd9c1da22021-12-18 14:54:31 -07001528 parser.add_argument('-I', '--imply-flags', type=str, default='',
Simon Glass0559a742021-12-18 08:09:44 -07001529 help="control the -i option ('help' for help")
Simon Glassd9c1da22021-12-18 14:54:31 -07001530 parser.add_argument('-j', '--jobs', type=int, default=cpu_count,
Masahiro Yamadab6160812015-05-20 11:36:07 +09001531 help='the number of jobs to run simultaneously')
Simon Glassd9c1da22021-12-18 14:54:31 -07001532 parser.add_argument('-n', '--dry-run', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001533 help='perform a trial run (show log with no changes)')
Simon Glassd9c1da22021-12-18 14:54:31 -07001534 parser.add_argument('-r', '--git-ref', type=str,
Joe Hershbergerb1a570f2016-06-10 14:53:32 -05001535 help='the git ref to clone for building the autoconf.mk')
Simon Glassd9c1da22021-12-18 14:54:31 -07001536 parser.add_argument('-s', '--force-sync', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001537 help='force sync by savedefconfig')
Simon Glassd9c1da22021-12-18 14:54:31 -07001538 parser.add_argument('-S', '--spl', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001539 help='parse config options defined for SPL build')
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001540 parser.add_argument('--scan-source', action='store_true', default=False,
1541 help='scan source for uses of CONFIG options')
Simon Glassd9c1da22021-12-18 14:54:31 -07001542 parser.add_argument('-t', '--test', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001543 help='run unit tests')
Simon Glassd9c1da22021-12-18 14:54:31 -07001544 parser.add_argument('-y', '--yes', action='store_true', default=False,
Simon Glass13e05a02016-09-12 23:18:20 -06001545 help="respond 'yes' to any prompts")
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001546 parser.add_argument('-u', '--update', action='store_true', default=False,
1547 help="update scripts/ files (use with --scan-source)")
Simon Glassd9c1da22021-12-18 14:54:31 -07001548 parser.add_argument('-v', '--verbose', action='store_true', default=False,
Joe Hershberger808b63f2015-05-19 13:21:24 -05001549 help='show any build errors as boards are built')
Simon Glassd9c1da22021-12-18 14:54:31 -07001550 parser.add_argument('configs', nargs='*')
Masahiro Yamadab6160812015-05-20 11:36:07 +09001551
Simon Glassd9c1da22021-12-18 14:54:31 -07001552 args = parser.parse_args()
Masahiro Yamadab6160812015-05-20 11:36:07 +09001553
Simon Glassd9c1da22021-12-18 14:54:31 -07001554 if args.test:
Simon Glassbb57be72021-12-18 08:09:45 -07001555 sys.argv = [sys.argv[0]]
1556 fail, count = doctest.testmod()
1557 if fail:
1558 return 1
1559 unittest.main()
1560
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001561 if args.scan_source:
1562 do_scan_source(os.getcwd(), args.update)
1563 return
1564
Simon Glass08d148a2023-09-23 13:43:54 -06001565 if not any((args.force_sync, args.build_db, args.imply, args.find)):
Masahiro Yamadab6160812015-05-20 11:36:07 +09001566 parser.print_usage()
1567 sys.exit(1)
1568
Masahiro Yamadab903c4e2016-05-19 15:51:58 +09001569 # prefix the option name with CONFIG_ if missing
Simon Glass08d148a2023-09-23 13:43:54 -06001570 configs = [prefix_config(cfg) for cfg in args.configs]
Masahiro Yamadab6160812015-05-20 11:36:07 +09001571
Joe Hershberger23475932015-05-19 13:21:20 -05001572 check_top_directory()
1573
Simon Glassd9c1da22021-12-18 14:54:31 -07001574 if args.imply:
Simon Glass92e55582017-06-15 21:39:32 -06001575 imply_flags = 0
Simon Glassd9c1da22021-12-18 14:54:31 -07001576 if args.imply_flags == 'all':
Simon Glass5f096922017-07-10 14:47:46 -06001577 imply_flags = -1
1578
Simon Glassd9c1da22021-12-18 14:54:31 -07001579 elif args.imply_flags:
1580 for flag in args.imply_flags.split(','):
Simon Glass5f096922017-07-10 14:47:46 -06001581 bad = flag not in IMPLY_FLAGS
1582 if bad:
Simon Glass1f701862019-10-31 07:42:57 -06001583 print("Invalid flag '%s'" % flag)
Simon Glass5f096922017-07-10 14:47:46 -06001584 if flag == 'help' or bad:
Simon Glass1f701862019-10-31 07:42:57 -06001585 print("Imply flags: (separate with ',')")
1586 for name, info in IMPLY_FLAGS.items():
1587 print(' %-15s: %s' % (name, info[1]))
Simon Glass5f096922017-07-10 14:47:46 -06001588 parser.print_usage()
1589 sys.exit(1)
1590 imply_flags |= IMPLY_FLAGS[flag][0]
Simon Glass92e55582017-06-15 21:39:32 -06001591
Simon Glassd9c1da22021-12-18 14:54:31 -07001592 do_imply_config(configs, args.add_imply, imply_flags, args.skip_added)
Simon Glassc6e73cf2017-06-01 19:39:03 -06001593 return
1594
Simon Glassd9c1da22021-12-18 14:54:31 -07001595 if args.find:
Simon Glass0082b2e2021-12-18 08:09:46 -07001596 do_find_config(configs)
1597 return
1598
Simon Glass35688b62023-09-23 13:43:53 -06001599 # We are either building the database or forcing a sync of defconfigs
Simon Glass43cf08f2017-06-01 19:39:02 -06001600 config_db = {}
Simon Glass1f701862019-10-31 07:42:57 -06001601 db_queue = queue.Queue()
Simon Glass43cf08f2017-06-01 19:39:02 -06001602 t = DatabaseThread(config_db, db_queue)
Simon Glass5cda9022023-09-23 13:43:51 -06001603 t.daemon = True
Simon Glass43cf08f2017-06-01 19:39:02 -06001604 t.start()
1605
Simon Glass1572b3f2023-09-23 13:43:50 -06001606 check_clean_directory()
1607 bsettings.setup('')
1608 toolchains = toolchain.Toolchains()
1609 toolchains.GetSettings()
1610 toolchains.Scan(verbose=False)
Simon Glass08d148a2023-09-23 13:43:54 -06001611 move_config(toolchains, args, db_queue)
Simon Glass1572b3f2023-09-23 13:43:50 -06001612 db_queue.join()
Joe Hershberger23475932015-05-19 13:21:20 -05001613
Simon Glassd9c1da22021-12-18 14:54:31 -07001614 if args.commit:
Simon Glass8bf41c22016-09-12 23:18:21 -06001615 subprocess.call(['git', 'add', '-u'])
1616 if configs:
1617 msg = 'Convert %s %sto Kconfig' % (configs[0],
1618 'et al ' if len(configs) > 1 else '')
1619 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' %
1620 '\n '.join(configs))
1621 else:
1622 msg = 'configs: Resync with savedefconfig'
1623 msg += '\n\nRsync all defconfig files using moveconfig.py'
1624 subprocess.call(['git', 'commit', '-s', '-m', msg])
1625
Simon Glassd9c1da22021-12-18 14:54:31 -07001626 if args.build_db:
Simon Glassb3464eb2021-12-18 14:54:35 -07001627 with open(CONFIG_DATABASE, 'w', encoding='utf-8') as fd:
Simon Glass1f701862019-10-31 07:42:57 -06001628 for defconfig, configs in config_db.items():
Simon Glass1c879312017-08-13 16:02:54 -06001629 fd.write('%s\n' % defconfig)
Simon Glass43cf08f2017-06-01 19:39:02 -06001630 for config in sorted(configs.keys()):
Simon Glass1c879312017-08-13 16:02:54 -06001631 fd.write(' %s=%s\n' % (config, configs[config]))
1632 fd.write('\n')
Simon Glass43cf08f2017-06-01 19:39:02 -06001633
Masahiro Yamadab6160812015-05-20 11:36:07 +09001634if __name__ == '__main__':
Simon Glass0082b2e2021-12-18 08:09:46 -07001635 sys.exit(main())