blob: 2234f28a772446649d6472256d079ef0c048199e [file] [log] [blame]
Simon Glass793dca32019-10-31 07:42:57 -06001#!/usr/bin/env python3
Tom Rini83d290c2018-05-06 17:58:06 -04002# SPDX-License-Identifier: GPL-2.0+
Masahiro Yamada5a27c732015-05-20 11:36:07 +09003#
4# Author: Masahiro Yamada <yamada.masahiro@socionext.com>
5#
Masahiro Yamada5a27c732015-05-20 11:36:07 +09006
7"""
8Move config options from headers to defconfig files.
9
Simon Glass5c72c0e2021-07-21 21:35:51 -060010See doc/develop/moveconfig.rst for documentation.
Masahiro Yamada5a27c732015-05-20 11:36:07 +090011"""
12
Simon Glassb2e83c62021-12-18 14:54:31 -070013from argparse import ArgumentParser
Simon Glass99b66602017-06-01 19:39:03 -060014import collections
Simon Glass91197aa2021-12-18 14:54:35 -070015from contextlib import ExitStack
Simon Glass84067a52021-12-18 08:09:45 -070016import doctest
Masahiro Yamadac8e1b102016-05-19 15:52:07 +090017import filecmp
Masahiro Yamada5a27c732015-05-20 11:36:07 +090018import fnmatch
Masahiro Yamada0dbc9b52016-10-19 14:39:54 +090019import glob
Masahiro Yamada5a27c732015-05-20 11:36:07 +090020import multiprocessing
Masahiro Yamada5a27c732015-05-20 11:36:07 +090021import os
Simon Glass793dca32019-10-31 07:42:57 -060022import queue
Masahiro Yamada5a27c732015-05-20 11:36:07 +090023import re
24import shutil
25import subprocess
26import sys
27import tempfile
Simon Glassd73fcb12017-06-01 19:39:02 -060028import threading
Masahiro Yamada5a27c732015-05-20 11:36:07 +090029import time
Simon Glass84067a52021-12-18 08:09:45 -070030import unittest
Masahiro Yamada5a27c732015-05-20 11:36:07 +090031
Simon Glassb5aa5a32023-09-23 13:43:52 -060032import asteval
Simon Glass0ede00f2020-04-17 18:09:02 -060033from buildman import bsettings
34from buildman import kconfiglib
35from buildman import toolchain
Simon Glasscb008832017-06-15 21:39:33 -060036
Masahiro Yamada5a27c732015-05-20 11:36:07 +090037SHOW_GNU_MAKE = 'scripts/show-gnu-make'
38SLEEP_TIME=0.03
39
Masahiro Yamada5a27c732015-05-20 11:36:07 +090040STATE_IDLE = 0
41STATE_DEFCONFIG = 1
42STATE_AUTOCONF = 2
Joe Hershberger96464ba2015-05-19 13:21:17 -050043STATE_SAVEDEFCONFIG = 3
Masahiro Yamada5a27c732015-05-20 11:36:07 +090044
Masahiro Yamada5a27c732015-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 Glassf3b8e642017-06-01 19:39:01 -060062AUTO_CONF_PATH = 'include/config/auto.conf'
Simon Glassd73fcb12017-06-01 19:39:02 -060063CONFIG_DATABASE = 'moveconfig.db'
Simon Glassf3b8e642017-06-01 19:39:01 -060064
Simon Glasscb008832017-06-15 21:39:33 -060065CONFIG_LEN = len('CONFIG_')
Simon Glassf3b8e642017-06-01 19:39:01 -060066
Markus Klotzbuecherb237d352019-05-15 15:15:52 +020067SIZES = {
Simon Glassdaa694d2021-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 Klotzbuecherb237d352019-05-15 15:15:52 +020085}
86
Simon Glassb8d11da2022-02-08 11:49:45 -070087RE_REMOVE_DEFCONFIG = re.compile(r'(.*)_defconfig')
88
Simon Glass65e62032023-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 Yamada5a27c732015-05-20 11:36:07 +0900101### helper functions ###
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900102def check_top_directory():
103 """Exit if we are not at the top of source directory."""
Simon Glass91197aa2021-12-18 14:54:35 -0700104 for fname in 'README', 'Licenses':
105 if not os.path.exists(fname):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900106 sys.exit('Please run at the top of source directory.')
107
Masahiro Yamadabd63e5b2016-05-19 15:51:54 +0900108def check_clean_directory():
109 """Exit if the source tree is not clean."""
Simon Glass91197aa2021-12-18 14:54:35 -0700110 for fname in '.config', 'include/config':
111 if os.path.exists(fname):
Masahiro Yamadabd63e5b2016-05-19 15:51:54 +0900112 sys.exit("source tree is not clean, please run 'make mrproper'")
113
Masahiro Yamada5a27c732015-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 Glass91197aa2021-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 Yamada5a27c732015-05-20 11:36:07 +0900125 return ret[0].rstrip()
126
Simon Glass25f978c2017-06-01 19:38:58 -0600127def get_matched_defconfig(line):
128 """Get the defconfig files that match a pattern
129
130 Args:
Simon Glass91197aa2021-12-18 14:54:35 -0700131 line (str): Path or filename to match, e.g. 'configs/snow_defconfig' or
Simon Glass25f978c2017-06-01 19:38:58 -0600132 'k2*_defconfig'. If no directory is provided, 'configs/' is
133 prepended
134
135 Returns:
Simon Glass91197aa2021-12-18 14:54:35 -0700136 list of str: a list of matching defconfig files
Simon Glass25f978c2017-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 Yamada0dbc9b52016-10-19 14:39:54 +0900145def get_matched_defconfigs(defconfigs_file):
Simon Glassee4e61b2017-06-01 19:38:59 -0600146 """Get all the defconfig files that match the patterns in a file.
147
148 Args:
Simon Glass91197aa2021-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 Glassee4e61b2017-06-01 19:38:59 -0600151
152 Returns:
Simon Glass91197aa2021-12-18 14:54:35 -0700153 list of str: A list of paths to defconfig files, with no duplicates
Simon Glassee4e61b2017-06-01 19:38:59 -0600154 """
Masahiro Yamada0dbc9b52016-10-19 14:39:54 +0900155 defconfigs = []
Simon Glass91197aa2021-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 Yamada0dbc9b52016-10-19 14:39:54 +0900172
Simon Glass91197aa2021-12-18 14:54:35 -0700173 defconfigs += matched
Masahiro Yamada0dbc9b52016-10-19 14:39:54 +0900174
175 # use set() to drop multiple matching
Simon Glass91197aa2021-12-18 14:54:35 -0700176 return [defconfig[len('configs') + 1:] for defconfig in set(defconfigs)]
Masahiro Yamada0dbc9b52016-10-19 14:39:54 +0900177
Masahiro Yamada684c3062016-07-25 19:15:28 +0900178def get_all_defconfigs():
Simon Glass91197aa2021-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 Yamada684c3062016-07-25 19:15:28 +0900184 defconfigs = []
Simon Glass91197aa2021-12-18 14:54:35 -0700185 for (dirpath, _, filenames) in os.walk('configs'):
Masahiro Yamada684c3062016-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 Yamada5a27c732015-05-20 11:36:07 +0900192def color_text(color_enabled, color, string):
193 """Return colored string."""
194 if color_enabled:
Masahiro Yamada1d085562016-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 Glass91197aa2021-12-18 14:54:35 -0700199 return string
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900200
Simon Glass2fd85bd2021-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 Glass37f815c2021-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
Simon Glass549d4222023-09-23 13:43:58 -0600221 as_lines (bool): Return file contents as a list of lines
Simon Glass37f815c2021-12-18 14:54:34 -0700222 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()
Simon Glassa4c9d172023-09-23 13:44:01 -0600238 except UnicodeDecodeError as exc:
Simon Glass37f815c2021-12-18 14:54:34 -0700239 if not skip_unicode:
Simon Glass68a0b712022-02-11 13:23:22 -0700240 raise
Simon Glassa4c9d172023-09-23 13:44:01 -0600241 print(f"Failed on file '{fname}: {exc}")
Simon Glass37f815c2021-12-18 14:54:34 -0700242 return None
243
Markus Klotzbuecherb237d352019-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 Klotzbuecherb3192f42020-02-12 20:46:44 +0100252 aeval = asteval.Interpreter( usersyms=SIZES, minimal=True )
Markus Klotzbuecherb237d352019-05-15 15:15:52 +0200253 cfg, val = re.split("=", line)
254 val= val.strip('\"')
Simon Glassdaa694d2021-12-18 14:54:30 -0700255 if re.search(r'[*+-/]|<<|SZ_+|\(([^\)]+)\)', val):
Markus Klotzbuecherb3192f42020-02-12 20:46:44 +0100256 newval = hex(aeval(val))
Simon Glass1bd43062023-09-23 13:43:59 -0600257 print(f'\tExpanded expression {val} to {newval}')
Markus Klotzbuecherb237d352019-05-15 15:15:52 +0200258 return cfg+'='+newval
259 except:
Simon Glass1bd43062023-09-23 13:43:59 -0600260 print(f'\tFailed to expand expression in {line}')
Markus Klotzbuecherb237d352019-05-15 15:15:52 +0200261
262 return line
263
Chris Packhamca438342017-05-02 21:30:47 +1200264
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900265### classes ###
Masahiro Yamadac5e60fd2016-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 Glass91197aa2021-12-18 14:54:35 -0700273 Args:
Masahiro Yamadac5e60fd2016-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 Glass1bd43062023-09-23 13:43:59 -0600286 print(f' {self.current} defconfigs out of {self.total}\r', end=' ')
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900287 sys.stdout.flush()
288
Simon Glasscb008832017-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 Glass65e62032023-02-01 13:19:12 -0700299 os.environ['CC'] = 'gcc'
Tom Rini65e05dd2019-09-20 17:42:09 -0400300 self.conf = kconfiglib.Kconfig()
Simon Glasscb008832017-06-15 21:39:33 -0600301
302
Masahiro Yamada5a27c732015-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 Glass882c8e42023-09-23 13:43:54 -0600310 def __init__(self, args, build_dir):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900311 """Create a new parser.
312
Simon Glass91197aa2021-12-18 14:54:35 -0700313 Args:
Simon Glass91197aa2021-12-18 14:54:35 -0700314 args (Namespace): program arguments
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900315 build_dir: Build directory.
316 """
Simon Glassb2e83c62021-12-18 14:54:31 -0700317 self.args = args
Masahiro Yamada1f169922016-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 Yamada07913d12016-08-22 22:18:22 +0900320 self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
321 'autoconf.mk')
Simon Glassf3b8e642017-06-01 19:39:01 -0600322 self.config_autoconf = os.path.join(build_dir, AUTO_CONF_PATH)
Masahiro Yamada5da4f852016-05-19 15:52:06 +0900323 self.defconfig = os.path.join(build_dir, 'defconfig')
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900324
Simon Glass6821a742017-07-10 14:47:47 -0600325 def get_arch(self):
326 """Parse .config file and return the architecture.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900327
328 Returns:
Simon Glass6821a742017-07-10 14:47:47 -0600329 Architecture name (e.g. 'arm').
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900330 """
331 arch = ''
332 cpu = ''
Simon Glass37f815c2021-12-18 14:54:34 -0700333 for line in read_file(self.dotconfig):
Simon Glassa4c9d172023-09-23 13:44:01 -0600334 m_arch = self.re_arch.match(line)
335 if m_arch:
336 arch = m_arch.group(1)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900337 continue
Simon Glassa4c9d172023-09-23 13:44:01 -0600338 m_cpu = self.re_cpu.match(line)
339 if m_cpu:
340 cpu = m_cpu.group(1)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900341
Masahiro Yamada90ed6cb2016-05-19 15:51:53 +0900342 if not arch:
343 return None
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900344
345 # fix-up for aarch64
346 if arch == 'arm' and cpu == 'armv8':
347 arch = 'aarch64'
348
Simon Glass6821a742017-07-10 14:47:47 -0600349 return arch
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900350
Simon Glassd73fcb12017-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 Yamada5a27c732015-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 Glass882c8e42023-09-23 13:43:54 -0600388 def __init__(self, toolchains, args, progress, devnull,
Simon Glass6821a742017-07-10 14:47:47 -0600389 make_cmd, reference_src_dir, db_queue):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900390 """Create a new process slot.
391
Simon Glass91197aa2021-12-18 14:54:35 -0700392 Args:
Simon Glass6821a742017-07-10 14:47:47 -0600393 toolchains: Toolchains object containing toolchains.
Simon Glassb2e83c62021-12-18 14:54:31 -0700394 args: Program arguments
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900395 progress: A progress indicator.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900396 devnull: A file object of '/dev/null'.
397 make_cmd: command name of GNU Make.
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500398 reference_src_dir: Determine the true starting config state from this
399 source tree.
Simon Glassd73fcb12017-06-01 19:39:02 -0600400 db_queue: output queue to write config info for the database
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900401 """
Simon Glass6821a742017-07-10 14:47:47 -0600402 self.toolchains = toolchains
Simon Glassb2e83c62021-12-18 14:54:31 -0700403 self.args = args
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900404 self.progress = progress
Masahiro Yamada5a27c732015-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 Hershberger6b96c1a2016-06-10 14:53:32 -0500408 self.reference_src_dir = reference_src_dir
Simon Glassd73fcb12017-06-01 19:39:02 -0600409 self.db_queue = db_queue
Simon Glass882c8e42023-09-23 13:43:54 -0600410 self.parser = KconfigParser(args, self.build_dir)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900411 self.state = STATE_IDLE
Masahiro Yamada09c6c062016-08-22 22:18:20 +0900412 self.failed_boards = set()
Masahiro Yamada5a27c732015-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 Hershbergerf2dae752016-06-10 14:53:29 -0500419 because it is guaranteed the destructor is always invoked when the
Masahiro Yamada5a27c732015-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:
Simon Glassa4c9d172023-09-23 13:44:01 -0600425 while self.proc.poll() == None:
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900426 pass
427 shutil.rmtree(self.build_dir)
428
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900429 def add(self, defconfig):
Masahiro Yamada5a27c732015-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 Glass91197aa2021-12-18 14:54:35 -0700436 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600437 defconfig (str): defconfig name.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900438
439 Returns:
440 Return True on success or False on failure
441 """
442 if self.state != STATE_IDLE:
443 return False
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900444
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900445 self.defconfig = defconfig
Masahiro Yamada1d085562016-05-19 15:52:02 +0900446 self.log = ''
Masahiro Yamadaf432c332016-06-15 14:33:52 +0900447 self.current_src_dir = self.reference_src_dir
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900448 self.do_defconfig()
Masahiro Yamada5a27c732015-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 Yamada7fb0bac2016-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 Yamada5a27c732015-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
Simon Glassa4c9d172023-09-23 13:44:01 -0600470 if self.proc.poll() == None:
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900471 return False
472
Simon Glassa4c9d172023-09-23 13:44:01 -0600473 if self.proc.poll() != 0:
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900474 self.handle_error()
475 elif self.state == STATE_DEFCONFIG:
Masahiro Yamadaf432c332016-06-15 14:33:52 +0900476 if self.reference_src_dir and not self.current_src_dir:
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500477 self.do_savedefconfig()
478 else:
479 self.do_autoconf()
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900480 elif self.state == STATE_AUTOCONF:
Masahiro Yamadaf432c332016-06-15 14:33:52 +0900481 if self.current_src_dir:
482 self.current_src_dir = None
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500483 self.do_defconfig()
Simon Glassb2e83c62021-12-18 14:54:31 -0700484 elif self.args.build_db:
Simon Glassd73fcb12017-06-01 19:39:02 -0600485 self.do_build_db()
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500486 else:
487 self.do_savedefconfig()
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900488 elif self.state == STATE_SAVEDEFCONFIG:
489 self.update_defconfig()
490 else:
Simon Glassdaa694d2021-12-18 14:54:30 -0700491 sys.exit('Internal Error. This should not happen.')
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900492
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900493 return True if self.state == STATE_IDLE else False
Joe Hershberger96464ba2015-05-19 13:21:17 -0500494
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900495 def handle_error(self):
496 """Handle error cases."""
Masahiro Yamada8513dc02016-05-19 15:52:08 +0900497
Simon Glassb2e83c62021-12-18 14:54:31 -0700498 self.log += color_text(self.args.color, COLOR_LIGHT_RED,
Simon Glassdaa694d2021-12-18 14:54:30 -0700499 'Failed to process.\n')
Simon Glassb2e83c62021-12-18 14:54:31 -0700500 if self.args.verbose:
501 self.log += color_text(self.args.color, COLOR_LIGHT_CYAN,
Simon Glassa4c9d172023-09-23 13:44:01 -0600502 self.proc.stderr.read().decode())
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900503 self.finish(False)
Joe Hershberger96464ba2015-05-19 13:21:17 -0500504
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900505 def do_defconfig(self):
506 """Run 'make <board>_defconfig' to create the .config file."""
Masahiro Yamadac8e1b102016-05-19 15:52:07 +0900507
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900508 cmd = list(self.make_cmd)
509 cmd.append(self.defconfig)
Simon Glassa4c9d172023-09-23 13:44:01 -0600510 self.proc = subprocess.Popen(cmd, stdout=self.devnull,
511 stderr=subprocess.PIPE,
512 cwd=self.current_src_dir)
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900513 self.state = STATE_DEFCONFIG
Masahiro Yamadac8e1b102016-05-19 15:52:07 +0900514
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900515 def do_autoconf(self):
Simon Glassf3b8e642017-06-01 19:39:01 -0600516 """Run 'make AUTO_CONF_PATH'."""
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900517
Simon Glass6821a742017-07-10 14:47:47 -0600518 arch = self.parser.get_arch()
519 try:
520 toolchain = self.toolchains.Select(arch)
521 except ValueError:
Simon Glassb2e83c62021-12-18 14:54:31 -0700522 self.log += color_text(self.args.color, COLOR_YELLOW,
Simon Glass1bd43062023-09-23 13:43:59 -0600523 f"Tool chain for '{arch}' is missing. Do nothing.\n")
Masahiro Yamada4efef992016-05-19 15:52:03 +0900524 self.finish(False)
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900525 return
Simon Glass793dca32019-10-31 07:42:57 -0600526 env = toolchain.MakeEnvironment(False)
Masahiro Yamada90ed6cb2016-05-19 15:51:53 +0900527
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900528 cmd = list(self.make_cmd)
Joe Hershberger7740f652015-05-19 13:21:18 -0500529 cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
Simon Glassf3b8e642017-06-01 19:39:01 -0600530 cmd.append(AUTO_CONF_PATH)
Simon Glassa4c9d172023-09-23 13:44:01 -0600531 self.proc = subprocess.Popen(cmd, stdout=self.devnull, env=env,
532 stderr=subprocess.PIPE,
533 cwd=self.current_src_dir)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900534 self.state = STATE_AUTOCONF
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900535
Simon Glassd73fcb12017-06-01 19:39:02 -0600536 def do_build_db(self):
537 """Add the board to the database"""
538 configs = {}
Simon Glass37f815c2021-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 Glassd73fcb12017-06-01 19:39:02 -0600543 self.db_queue.put([self.defconfig, configs])
544 self.finish(True)
545
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900546 def do_savedefconfig(self):
547 """Update the .config and run 'make savedefconfig'."""
Simon Glassc7345612023-09-23 13:43:55 -0600548 if not self.args.force_sync:
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900549 self.finish(True)
550 return
Simon Glassc7345612023-09-23 13:43:55 -0600551 self.log += 'Syncing by savedefconfig (forced by option)...\n'
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900552
553 cmd = list(self.make_cmd)
554 cmd.append('savedefconfig')
Simon Glassa4c9d172023-09-23 13:44:01 -0600555 self.proc = subprocess.Popen(cmd, stdout=self.devnull,
556 stderr=subprocess.PIPE)
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900557 self.state = STATE_SAVEDEFCONFIG
558
559 def update_defconfig(self):
560 """Update the input defconfig and go back to the idle state."""
Masahiro Yamadae307fa92016-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 Glassb2e83c62021-12-18 14:54:31 -0700566 self.log += color_text(self.args.color, COLOR_LIGHT_BLUE,
Simon Glassdaa694d2021-12-18 14:54:30 -0700567 'defconfig was updated.\n')
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900568
Simon Glassb2e83c62021-12-18 14:54:31 -0700569 if not self.args.dry_run and updated:
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900570 shutil.move(new_defconfig, orig_defconfig)
571 self.finish(True)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900572
Masahiro Yamada4efef992016-05-19 15:52:03 +0900573 def finish(self, success):
574 """Display log along with progress and go to the idle state.
Masahiro Yamada1d085562016-05-19 15:52:02 +0900575
Simon Glass91197aa2021-12-18 14:54:35 -0700576 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600577 success (bool): Should be True when the defconfig was processed
Masahiro Yamada4efef992016-05-19 15:52:03 +0900578 successfully, or False when it fails.
Masahiro Yamada1d085562016-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 Glass793dca32019-10-31 07:42:57 -0600586 print(log, file=(sys.stdout if success else sys.stderr))
Masahiro Yamada4efef992016-05-19 15:52:03 +0900587
588 if not success:
Simon Glassb2e83c62021-12-18 14:54:31 -0700589 if self.args.exit_on_error:
Simon Glassdaa694d2021-12-18 14:54:30 -0700590 sys.exit('Exit on error.')
Masahiro Yamada4efef992016-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 Yamada09c6c062016-08-22 22:18:20 +0900593 self.failed_boards.add(self.defconfig)
Masahiro Yamada4efef992016-05-19 15:52:03 +0900594
Masahiro Yamada1d085562016-05-19 15:52:02 +0900595 self.progress.inc()
596 self.progress.show()
Masahiro Yamada4efef992016-05-19 15:52:03 +0900597 self.state = STATE_IDLE
Masahiro Yamada1d085562016-05-19 15:52:02 +0900598
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900599 def get_failed_boards(self):
Masahiro Yamada09c6c062016-08-22 22:18:20 +0900600 """Returns a set of failed boards (defconfigs) in this slot.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900601 """
602 return self.failed_boards
603
604class Slots:
605
606 """Controller of the array of subprocess slots."""
607
Simon Glass882c8e42023-09-23 13:43:54 -0600608 def __init__(self, toolchains, args, progress, reference_src_dir, db_queue):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900609 """Create a new slots controller.
610
Simon Glass91197aa2021-12-18 14:54:35 -0700611 Args:
Simon Glass6821a742017-07-10 14:47:47 -0600612 toolchains: Toolchains object containing toolchains.
Simon Glassb2e83c62021-12-18 14:54:31 -0700613 args: Program arguments
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900614 progress: A progress indicator.
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500615 reference_src_dir: Determine the true starting config state from this
616 source tree.
Simon Glassd73fcb12017-06-01 19:39:02 -0600617 db_queue: output queue to write config info for the database
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900618 """
Simon Glassb2e83c62021-12-18 14:54:31 -0700619 self.args = args
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900620 self.slots = []
Simon Glass478920d2021-12-18 14:54:32 -0700621 devnull = subprocess.DEVNULL
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900622 make_cmd = get_make_cmd()
Simon Glass62fae4b2023-09-23 13:44:00 -0600623 for _ in range(args.jobs):
Simon Glass882c8e42023-09-23 13:43:54 -0600624 self.slots.append(Slot(toolchains, args, progress, devnull,
625 make_cmd, reference_src_dir, db_queue))
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900626
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900627 def add(self, defconfig):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900628 """Add a new subprocess if a vacant slot is found.
629
Simon Glass91197aa2021-12-18 14:54:35 -0700630 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600631 defconfig (str): defconfig name to be put into.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900632
633 Returns:
634 Return True on success or False on failure
635 """
636 for slot in self.slots:
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900637 if slot.add(defconfig):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900638 return True
639 return False
640
641 def available(self):
642 """Check if there is a vacant slot.
643
644 Returns:
645 Return True if at lease one vacant slot is found, False otherwise.
646 """
647 for slot in self.slots:
648 if slot.poll():
649 return True
650 return False
651
652 def empty(self):
653 """Check if all slots are vacant.
654
655 Returns:
656 Return True if all the slots are vacant, False otherwise.
657 """
658 ret = True
659 for slot in self.slots:
660 if not slot.poll():
661 ret = False
662 return ret
663
664 def show_failed_boards(self):
665 """Display all of the failed boards (defconfigs)."""
Masahiro Yamada09c6c062016-08-22 22:18:20 +0900666 boards = set()
Masahiro Yamada96dccd92016-06-15 14:33:53 +0900667 output_file = 'moveconfig.failed'
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900668
669 for slot in self.slots:
Masahiro Yamada09c6c062016-08-22 22:18:20 +0900670 boards |= slot.get_failed_boards()
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900671
Masahiro Yamada96dccd92016-06-15 14:33:53 +0900672 if boards:
673 boards = '\n'.join(boards) + '\n'
Simon Glassdaa694d2021-12-18 14:54:30 -0700674 msg = 'The following boards were not processed due to error:\n'
Masahiro Yamada96dccd92016-06-15 14:33:53 +0900675 msg += boards
Simon Glass1bd43062023-09-23 13:43:59 -0600676 msg += f'(the list has been saved in {output_file})\n'
Simon Glassb2e83c62021-12-18 14:54:31 -0700677 print(color_text(self.args.color, COLOR_LIGHT_RED,
Simon Glass793dca32019-10-31 07:42:57 -0600678 msg), file=sys.stderr)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900679
Simon Glass2fd85bd2021-12-18 14:54:33 -0700680 write_file(output_file, boards)
Joe Hershberger2559cd82015-05-19 13:21:22 -0500681
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900682class ReferenceSource:
683
684 """Reference source against which original configs should be parsed."""
685
686 def __init__(self, commit):
687 """Create a reference source directory based on a specified commit.
688
Simon Glass91197aa2021-12-18 14:54:35 -0700689 Args:
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900690 commit: commit to git-clone
691 """
692 self.src_dir = tempfile.mkdtemp()
Simon Glassdaa694d2021-12-18 14:54:30 -0700693 print('Cloning git repo to a separate work directory...')
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900694 subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
695 cwd=self.src_dir)
Simon Glass1bd43062023-09-23 13:43:59 -0600696 rev = subprocess.check_output(['git', 'rev-parse', '--short',
697 commit]).strip()
698 print(f"Checkout '{rev}' to build the original autoconf.mk.")
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900699 subprocess.check_output(['git', 'checkout', commit],
700 stderr=subprocess.STDOUT, cwd=self.src_dir)
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500701
702 def __del__(self):
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900703 """Delete the reference source directory
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500704
705 This function makes sure the temporary directory is cleaned away
706 even if Python suddenly dies due to error. It should be done in here
707 because it is guaranteed the destructor is always invoked when the
708 instance of the class gets unreferenced.
709 """
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900710 shutil.rmtree(self.src_dir)
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500711
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900712 def get_dir(self):
713 """Return the absolute path to the reference source directory."""
714
715 return self.src_dir
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500716
Simon Glass882c8e42023-09-23 13:43:54 -0600717def move_config(toolchains, args, db_queue):
718 """Build database or sync config options to defconfig files.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900719
Simon Glass91197aa2021-12-18 14:54:35 -0700720 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600721 toolchains (Toolchains): Toolchains to use
722 args (Namespace): Program arguments
723 db_queue (Queue): Queue for database updates
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900724 """
Simon Glass882c8e42023-09-23 13:43:54 -0600725 if args.force_sync:
726 print('Syncing defconfigs', end=' ')
727 elif args.build_db:
Simon Glass1bd43062023-09-23 13:43:59 -0600728 print(f'Building {CONFIG_DATABASE} database')
729 print(f'(jobs: {args.jobs})\n')
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900730
Simon Glassb2e83c62021-12-18 14:54:31 -0700731 if args.git_ref:
732 reference_src = ReferenceSource(args.git_ref)
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900733 reference_src_dir = reference_src.get_dir()
734 else:
Masahiro Yamadaf432c332016-06-15 14:33:52 +0900735 reference_src_dir = None
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500736
Simon Glassb2e83c62021-12-18 14:54:31 -0700737 if args.defconfigs:
738 defconfigs = get_matched_defconfigs(args.defconfigs)
Joe Hershberger91040e82015-05-19 13:21:19 -0500739 else:
Masahiro Yamada684c3062016-07-25 19:15:28 +0900740 defconfigs = get_all_defconfigs()
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900741
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900742 progress = Progress(len(defconfigs))
Simon Glass882c8e42023-09-23 13:43:54 -0600743 slots = Slots(toolchains, args, progress, reference_src_dir, db_queue)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900744
745 # Main loop to process defconfig files:
746 # Add a new subprocess into a vacant slot.
747 # Sleep if there is no available slot.
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900748 for defconfig in defconfigs:
749 while not slots.add(defconfig):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900750 while not slots.available():
751 # No available slot: sleep for a while
752 time.sleep(SLEEP_TIME)
753
754 # wait until all the subprocesses finish
755 while not slots.empty():
756 time.sleep(SLEEP_TIME)
757
Simon Glass793dca32019-10-31 07:42:57 -0600758 print('')
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900759 slots.show_failed_boards()
760
Simon Glasscb008832017-06-15 21:39:33 -0600761def find_kconfig_rules(kconf, config, imply_config):
762 """Check whether a config has a 'select' or 'imply' keyword
763
764 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600765 kconf (Kconfiglib.Kconfig): Kconfig object
766 config (str): Name of config to check (without CONFIG_ prefix)
767 imply_config (str): Implying config (without CONFIG_ prefix) which may
768 or may not have an 'imply' for 'config')
Simon Glasscb008832017-06-15 21:39:33 -0600769
770 Returns:
771 Symbol object for 'config' if found, else None
772 """
Tom Rini65e05dd2019-09-20 17:42:09 -0400773 sym = kconf.syms.get(imply_config)
Simon Glasscb008832017-06-15 21:39:33 -0600774 if sym:
Simon Glass62fae4b2023-09-23 13:44:00 -0600775 for sel, _ in (sym.selects + sym.implies):
Simon Glassa3627082021-12-18 08:09:42 -0700776 if sel.name == config:
Simon Glasscb008832017-06-15 21:39:33 -0600777 return sym
778 return None
779
780def check_imply_rule(kconf, config, imply_config):
781 """Check if we can add an 'imply' option
782
783 This finds imply_config in the Kconfig and looks to see if it is possible
784 to add an 'imply' for 'config' to that part of the Kconfig.
785
786 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600787 kconf (Kconfiglib.Kconfig): Kconfig object
788 config (str): Name of config to check (without CONFIG_ prefix)
789 imply_config (str): Implying config (without CONFIG_ prefix) which may
790 or may not have an 'imply' for 'config')
Simon Glasscb008832017-06-15 21:39:33 -0600791
792 Returns:
793 tuple:
Simon Glass549d4222023-09-23 13:43:58 -0600794 str: filename of Kconfig file containing imply_config, or None if
795 none
796 int: line number within the Kconfig file, or 0 if none
797 str: message indicating the result
Simon Glasscb008832017-06-15 21:39:33 -0600798 """
Tom Rini65e05dd2019-09-20 17:42:09 -0400799 sym = kconf.syms.get(imply_config)
Simon Glasscb008832017-06-15 21:39:33 -0600800 if not sym:
801 return 'cannot find sym'
Simon Glassea40b202021-07-21 21:35:53 -0600802 nodes = sym.nodes
803 if len(nodes) != 1:
Simon Glass1bd43062023-09-23 13:43:59 -0600804 return f'{len(nodes)} locations'
Simon Glassa3627082021-12-18 08:09:42 -0700805 node = nodes[0]
806 fname, linenum = node.filename, node.linenr
Simon Glasscb008832017-06-15 21:39:33 -0600807 cwd = os.getcwd()
808 if cwd and fname.startswith(cwd):
809 fname = fname[len(cwd) + 1:]
Simon Glass1bd43062023-09-23 13:43:59 -0600810 file_line = f' at {fname}:{linenum}'
Simon Glass37f815c2021-12-18 14:54:34 -0700811 data = read_file(fname)
Simon Glass1bd43062023-09-23 13:43:59 -0600812 if data[linenum - 1] != f'config {imply_config}':
813 return None, 0, f'bad sym format {data[linenum]}{file_line})'
814 return fname, linenum, f'adding{file_line}'
Simon Glasscb008832017-06-15 21:39:33 -0600815
816def add_imply_rule(config, fname, linenum):
817 """Add a new 'imply' option to a Kconfig
818
819 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600820 config (str): config option to add an imply for (without CONFIG_ prefix)
821 fname (str): Kconfig filename to update
822 linenum (int): Line number to place the 'imply' before
Simon Glasscb008832017-06-15 21:39:33 -0600823
824 Returns:
825 Message indicating the result
826 """
Simon Glass1bd43062023-09-23 13:43:59 -0600827 file_line = f' at {fname}:{linenum}'
Simon Glass37f815c2021-12-18 14:54:34 -0700828 data = read_file(fname)
Simon Glasscb008832017-06-15 21:39:33 -0600829 linenum -= 1
830
831 for offset, line in enumerate(data[linenum:]):
832 if line.strip().startswith('help') or not line:
Simon Glass1bd43062023-09-23 13:43:59 -0600833 data.insert(linenum + offset, f'\timply {config}')
Simon Glass2fd85bd2021-12-18 14:54:33 -0700834 write_file(fname, data)
Simon Glass1bd43062023-09-23 13:43:59 -0600835 return f'added{file_line}'
Simon Glasscb008832017-06-15 21:39:33 -0600836
837 return 'could not insert%s'
838
839(IMPLY_MIN_2, IMPLY_TARGET, IMPLY_CMD, IMPLY_NON_ARCH_BOARD) = (
840 1, 2, 4, 8)
Simon Glass9b2a2e82017-06-15 21:39:32 -0600841
842IMPLY_FLAGS = {
843 'min2': [IMPLY_MIN_2, 'Show options which imply >2 boards (normally >5)'],
844 'target': [IMPLY_TARGET, 'Allow CONFIG_TARGET_... options to imply'],
845 'cmd': [IMPLY_CMD, 'Allow CONFIG_CMD_... to imply'],
Simon Glasscb008832017-06-15 21:39:33 -0600846 'non-arch-board': [
847 IMPLY_NON_ARCH_BOARD,
848 'Allow Kconfig options outside arch/ and /board/ to imply'],
Simon Glass91197aa2021-12-18 14:54:35 -0700849}
Simon Glass9b2a2e82017-06-15 21:39:32 -0600850
Simon Glass9d603392021-12-18 08:09:43 -0700851
852def read_database():
853 """Read in the config database
854
855 Returns:
856 tuple:
857 set of all config options seen (each a str)
858 set of all defconfigs seen (each a str)
859 dict of configs for each defconfig:
860 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
861 value: dict:
862 key: CONFIG option
863 value: Value of option
864 dict of defconfigs for each config:
865 key: CONFIG option
866 value: set of boards using that option
867
868 """
869 configs = {}
870
871 # key is defconfig name, value is dict of (CONFIG_xxx, value)
872 config_db = {}
873
874 # Set of all config options we have seen
875 all_configs = set()
876
877 # Set of all defconfigs we have seen
878 all_defconfigs = set()
879
880 defconfig_db = collections.defaultdict(set)
Simon Glass37f815c2021-12-18 14:54:34 -0700881 for line in read_file(CONFIG_DATABASE):
882 line = line.rstrip()
883 if not line: # Separator between defconfigs
884 config_db[defconfig] = configs
885 all_defconfigs.add(defconfig)
886 configs = {}
887 elif line[0] == ' ': # CONFIG line
888 config, value = line.strip().split('=', 1)
889 configs[config] = value
890 defconfig_db[config].add(defconfig)
891 all_configs.add(config)
892 else: # New defconfig
893 defconfig = line
Simon Glass9d603392021-12-18 08:09:43 -0700894
895 return all_configs, all_defconfigs, config_db, defconfig_db
896
897
Simon Glasscb008832017-06-15 21:39:33 -0600898def do_imply_config(config_list, add_imply, imply_flags, skip_added,
899 check_kconfig=True, find_superset=False):
Simon Glass99b66602017-06-01 19:39:03 -0600900 """Find CONFIG options which imply those in the list
901
902 Some CONFIG options can be implied by others and this can help to reduce
903 the size of the defconfig files. For example, CONFIG_X86 implies
904 CONFIG_CMD_IRQ, so we can put 'imply CMD_IRQ' under 'config X86' and
905 all x86 boards will have that option, avoiding adding CONFIG_CMD_IRQ to
906 each of the x86 defconfig files.
907
908 This function uses the moveconfig database to find such options. It
909 displays a list of things that could possibly imply those in the list.
910 The algorithm ignores any that start with CONFIG_TARGET since these
911 typically refer to only a few defconfigs (often one). It also does not
912 display a config with less than 5 defconfigs.
913
914 The algorithm works using sets. For each target config in config_list:
915 - Get the set 'defconfigs' which use that target config
916 - For each config (from a list of all configs):
917 - Get the set 'imply_defconfig' of defconfigs which use that config
918 -
919 - If imply_defconfigs contains anything not in defconfigs then
920 this config does not imply the target config
921
922 Params:
923 config_list: List of CONFIG options to check (each a string)
Simon Glasscb008832017-06-15 21:39:33 -0600924 add_imply: Automatically add an 'imply' for each config.
Simon Glass9b2a2e82017-06-15 21:39:32 -0600925 imply_flags: Flags which control which implying configs are allowed
926 (IMPLY_...)
Simon Glasscb008832017-06-15 21:39:33 -0600927 skip_added: Don't show options which already have an imply added.
928 check_kconfig: Check if implied symbols already have an 'imply' or
929 'select' for the target config, and show this information if so.
Simon Glass99b66602017-06-01 19:39:03 -0600930 find_superset: True to look for configs which are a superset of those
931 already found. So for example if CONFIG_EXYNOS5 implies an option,
932 but CONFIG_EXYNOS covers a larger set of defconfigs and also
933 implies that option, this will drop the former in favour of the
934 latter. In practice this option has not proved very used.
935
936 Note the terminoloy:
937 config - a CONFIG_XXX options (a string, e.g. 'CONFIG_CMD_EEPROM')
938 defconfig - a defconfig file (a string, e.g. 'configs/snow_defconfig')
939 """
Simon Glasscb008832017-06-15 21:39:33 -0600940 kconf = KconfigScanner().conf if check_kconfig else None
941 if add_imply and add_imply != 'all':
Simon Glassa3627082021-12-18 08:09:42 -0700942 add_imply = add_imply.split(',')
Simon Glasscb008832017-06-15 21:39:33 -0600943
Simon Glass62fae4b2023-09-23 13:44:00 -0600944 all_configs, all_defconfigs, _, defconfig_db = read_database()
Simon Glass99b66602017-06-01 19:39:03 -0600945
Simon Glassa3627082021-12-18 08:09:42 -0700946 # Work through each target config option in turn, independently
Simon Glass99b66602017-06-01 19:39:03 -0600947 for config in config_list:
948 defconfigs = defconfig_db.get(config)
949 if not defconfigs:
Simon Glass1bd43062023-09-23 13:43:59 -0600950 print(f'{config} not found in any defconfig')
Simon Glass99b66602017-06-01 19:39:03 -0600951 continue
952
953 # Get the set of defconfigs without this one (since a config cannot
954 # imply itself)
955 non_defconfigs = all_defconfigs - defconfigs
956 num_defconfigs = len(defconfigs)
Simon Glass1bd43062023-09-23 13:43:59 -0600957 print(f'{config} found in {num_defconfigs}/{len(all_configs)} defconfigs')
Simon Glass99b66602017-06-01 19:39:03 -0600958
959 # This will hold the results: key=config, value=defconfigs containing it
960 imply_configs = {}
961 rest_configs = all_configs - set([config])
962
963 # Look at every possible config, except the target one
964 for imply_config in rest_configs:
Simon Glass9b2a2e82017-06-15 21:39:32 -0600965 if 'ERRATUM' in imply_config:
Simon Glass99b66602017-06-01 19:39:03 -0600966 continue
Simon Glass91197aa2021-12-18 14:54:35 -0700967 if not imply_flags & IMPLY_CMD:
Simon Glass9b2a2e82017-06-15 21:39:32 -0600968 if 'CONFIG_CMD' in imply_config:
969 continue
Simon Glass91197aa2021-12-18 14:54:35 -0700970 if not imply_flags & IMPLY_TARGET:
Simon Glass9b2a2e82017-06-15 21:39:32 -0600971 if 'CONFIG_TARGET' in imply_config:
972 continue
Simon Glass99b66602017-06-01 19:39:03 -0600973
974 # Find set of defconfigs that have this config
975 imply_defconfig = defconfig_db[imply_config]
976
977 # Get the intersection of this with defconfigs containing the
978 # target config
979 common_defconfigs = imply_defconfig & defconfigs
980
981 # Get the set of defconfigs containing this config which DO NOT
982 # also contain the taret config. If this set is non-empty it means
983 # that this config affects other defconfigs as well as (possibly)
984 # the ones affected by the target config. This means it implies
985 # things we don't want to imply.
986 not_common_defconfigs = imply_defconfig & non_defconfigs
987 if not_common_defconfigs:
988 continue
989
990 # If there are common defconfigs, imply_config may be useful
991 if common_defconfigs:
992 skip = False
993 if find_superset:
Simon Glass793dca32019-10-31 07:42:57 -0600994 for prev in list(imply_configs.keys()):
Simon Glass99b66602017-06-01 19:39:03 -0600995 prev_count = len(imply_configs[prev])
996 count = len(common_defconfigs)
997 if (prev_count > count and
998 (imply_configs[prev] & common_defconfigs ==
999 common_defconfigs)):
1000 # skip imply_config because prev is a superset
1001 skip = True
1002 break
1003 elif count > prev_count:
1004 # delete prev because imply_config is a superset
1005 del imply_configs[prev]
1006 if not skip:
1007 imply_configs[imply_config] = common_defconfigs
1008
1009 # Now we have a dict imply_configs of configs which imply each config
1010 # The value of each dict item is the set of defconfigs containing that
1011 # config. Rank them so that we print the configs that imply the largest
1012 # number of defconfigs first.
Simon Glasscb008832017-06-15 21:39:33 -06001013 ranked_iconfigs = sorted(imply_configs,
Simon Glass99b66602017-06-01 19:39:03 -06001014 key=lambda k: len(imply_configs[k]), reverse=True)
Simon Glasscb008832017-06-15 21:39:33 -06001015 kconfig_info = ''
1016 cwd = os.getcwd()
1017 add_list = collections.defaultdict(list)
1018 for iconfig in ranked_iconfigs:
1019 num_common = len(imply_configs[iconfig])
Simon Glass99b66602017-06-01 19:39:03 -06001020
1021 # Don't bother if there are less than 5 defconfigs affected.
Simon Glass9b2a2e82017-06-15 21:39:32 -06001022 if num_common < (2 if imply_flags & IMPLY_MIN_2 else 5):
Simon Glass99b66602017-06-01 19:39:03 -06001023 continue
Simon Glasscb008832017-06-15 21:39:33 -06001024 missing = defconfigs - imply_configs[iconfig]
Simon Glass99b66602017-06-01 19:39:03 -06001025 missing_str = ', '.join(missing) if missing else 'all'
1026 missing_str = ''
Simon Glasscb008832017-06-15 21:39:33 -06001027 show = True
1028 if kconf:
1029 sym = find_kconfig_rules(kconf, config[CONFIG_LEN:],
1030 iconfig[CONFIG_LEN:])
1031 kconfig_info = ''
1032 if sym:
Simon Glassea40b202021-07-21 21:35:53 -06001033 nodes = sym.nodes
1034 if len(nodes) == 1:
1035 fname, linenum = nodes[0].filename, nodes[0].linenr
Simon Glasscb008832017-06-15 21:39:33 -06001036 if cwd and fname.startswith(cwd):
1037 fname = fname[len(cwd) + 1:]
Simon Glass1bd43062023-09-23 13:43:59 -06001038 kconfig_info = f'{fname}:{linenum}'
Simon Glasscb008832017-06-15 21:39:33 -06001039 if skip_added:
1040 show = False
1041 else:
Tom Rini65e05dd2019-09-20 17:42:09 -04001042 sym = kconf.syms.get(iconfig[CONFIG_LEN:])
Simon Glasscb008832017-06-15 21:39:33 -06001043 fname = ''
1044 if sym:
Simon Glassea40b202021-07-21 21:35:53 -06001045 nodes = sym.nodes
1046 if len(nodes) == 1:
1047 fname, linenum = nodes[0].filename, nodes[0].linenr
Simon Glasscb008832017-06-15 21:39:33 -06001048 if cwd and fname.startswith(cwd):
1049 fname = fname[len(cwd) + 1:]
1050 in_arch_board = not sym or (fname.startswith('arch') or
1051 fname.startswith('board'))
1052 if (not in_arch_board and
Simon Glass91197aa2021-12-18 14:54:35 -07001053 not imply_flags & IMPLY_NON_ARCH_BOARD):
Simon Glasscb008832017-06-15 21:39:33 -06001054 continue
1055
1056 if add_imply and (add_imply == 'all' or
1057 iconfig in add_imply):
1058 fname, linenum, kconfig_info = (check_imply_rule(kconf,
1059 config[CONFIG_LEN:], iconfig[CONFIG_LEN:]))
1060 if fname:
1061 add_list[fname].append(linenum)
1062
1063 if show and kconfig_info != 'skip':
Simon Glass1bd43062023-09-23 13:43:59 -06001064 print(f'{num_common:5d} : '
1065 f'{iconfig.ljust(30):-30s}{kconfig_info:-25s} {missing_str}')
Simon Glasscb008832017-06-15 21:39:33 -06001066
1067 # Having collected a list of things to add, now we add them. We process
1068 # each file from the largest line number to the smallest so that
1069 # earlier additions do not affect our line numbers. E.g. if we added an
1070 # imply at line 20 it would change the position of each line after
1071 # that.
Simon Glass793dca32019-10-31 07:42:57 -06001072 for fname, linenums in add_list.items():
Simon Glasscb008832017-06-15 21:39:33 -06001073 for linenum in sorted(linenums, reverse=True):
1074 add_imply_rule(config[CONFIG_LEN:], fname, linenum)
Simon Glass99b66602017-06-01 19:39:03 -06001075
Simon Glass941671a2022-02-08 11:49:46 -07001076def defconfig_matches(configs, re_match):
1077 """Check if any CONFIG option matches a regex
1078
1079 The match must be complete, i.e. from the start to end of the CONFIG option.
1080
1081 Args:
1082 configs (dict): Dict of CONFIG options:
1083 key: CONFIG option
1084 value: Value of option
1085 re_match (re.Pattern): Match to check
1086
1087 Returns:
1088 bool: True if any CONFIG matches the regex
1089 """
1090 for cfg in configs:
Simon Glassd9c958f2022-03-05 20:18:54 -07001091 if re_match.fullmatch(cfg):
Simon Glass941671a2022-02-08 11:49:46 -07001092 return True
1093 return False
Simon Glass99b66602017-06-01 19:39:03 -06001094
Simon Glass65d7fce2021-12-18 08:09:46 -07001095def do_find_config(config_list):
1096 """Find boards with a given combination of CONFIGs
1097
1098 Params:
Simon Glass941671a2022-02-08 11:49:46 -07001099 config_list: List of CONFIG options to check (each a regex consisting
Simon Glass65d7fce2021-12-18 08:09:46 -07001100 of a config option, with or without a CONFIG_ prefix. If an option
1101 is preceded by a tilde (~) then it must be false, otherwise it must
1102 be true)
1103 """
Simon Glass62fae4b2023-09-23 13:44:00 -06001104 _, all_defconfigs, config_db, _ = read_database()
Simon Glass65d7fce2021-12-18 08:09:46 -07001105
Simon Glass65d7fce2021-12-18 08:09:46 -07001106 # Start with all defconfigs
1107 out = all_defconfigs
1108
1109 # Work through each config in turn
Simon Glass65d7fce2021-12-18 08:09:46 -07001110 for item in config_list:
1111 # Get the real config name and whether we want this config or not
1112 cfg = item
1113 want = True
1114 if cfg[0] == '~':
1115 want = False
1116 cfg = cfg[1:]
1117
Simon Glass65d7fce2021-12-18 08:09:46 -07001118 # Search everything that is still in the running. If it has a config
1119 # that we want, or doesn't have one that we don't, add it into the
1120 # running for the next stage
1121 in_list = out
1122 out = set()
Simon Glass941671a2022-02-08 11:49:46 -07001123 re_match = re.compile(cfg)
Simon Glass65d7fce2021-12-18 08:09:46 -07001124 for defc in in_list:
Simon Glass941671a2022-02-08 11:49:46 -07001125 has_cfg = defconfig_matches(config_db[defc], re_match)
Simon Glass65d7fce2021-12-18 08:09:46 -07001126 if has_cfg == want:
1127 out.add(defc)
Tom Rini9ef3ba82022-12-04 10:14:16 -05001128 print(f'{len(out)} matches')
1129 print(' '.join(item.split('_defconfig')[0] for item in out))
Simon Glass65d7fce2021-12-18 08:09:46 -07001130
1131
1132def prefix_config(cfg):
1133 """Prefix a config with CONFIG_ if needed
1134
1135 This handles ~ operator, which indicates that the CONFIG should be disabled
1136
1137 >>> prefix_config('FRED')
1138 'CONFIG_FRED'
1139 >>> prefix_config('CONFIG_FRED')
1140 'CONFIG_FRED'
1141 >>> prefix_config('~FRED')
1142 '~CONFIG_FRED'
1143 >>> prefix_config('~CONFIG_FRED')
1144 '~CONFIG_FRED'
1145 >>> prefix_config('A123')
1146 'CONFIG_A123'
1147 """
Simon Glassa4c9d172023-09-23 13:44:01 -06001148 oper = ''
Simon Glass65d7fce2021-12-18 08:09:46 -07001149 if cfg[0] == '~':
Simon Glassa4c9d172023-09-23 13:44:01 -06001150 oper = cfg[0]
Simon Glass65d7fce2021-12-18 08:09:46 -07001151 cfg = cfg[1:]
1152 if not cfg.startswith('CONFIG_'):
1153 cfg = 'CONFIG_' + cfg
Simon Glassa4c9d172023-09-23 13:44:01 -06001154 return oper + cfg
Simon Glass65d7fce2021-12-18 08:09:46 -07001155
1156
Simon Glass98275712023-09-23 13:43:57 -06001157RE_MK_CONFIGS = re.compile(r'CONFIG_(\$\(SPL_(?:TPL_)?\))?([A-Za-z0-9_]*)')
1158RE_IFDEF = re.compile(r'(ifdef|ifndef)')
1159RE_C_CONFIGS = re.compile(r'CONFIG_([A-Za-z0-9_]*)')
1160RE_CONFIG_IS = re.compile(r'CONFIG_IS_ENABLED\(([A-Za-z0-9_]*)\)')
Simon Glass65e62032023-02-01 13:19:12 -07001161
1162class ConfigUse:
1163 def __init__(self, cfg, is_spl, fname, rest):
1164 self.cfg = cfg
1165 self.is_spl = is_spl
1166 self.fname = fname
1167 self.rest = rest
1168
1169 def __hash__(self):
1170 return hash((self.cfg, self.is_spl))
1171
1172def scan_makefiles(fnames):
1173 """Scan Makefiles looking for Kconfig options
1174
1175 Looks for uses of CONFIG options in Makefiles
1176
1177 Args:
1178 fnames (list of tuple):
1179 str: Makefile filename where the option was found
1180 str: Line of the Makefile
1181
1182 Returns:
1183 tuple:
1184 dict: all_uses
1185 key (ConfigUse): object
1186 value (list of str): matching lines
1187 dict: Uses by filename
1188 key (str): filename
1189 value (set of ConfigUse): uses in that filename
1190
1191 >>> RE_MK_CONFIGS.search('CONFIG_FRED').groups()
1192 (None, 'FRED')
1193 >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_)MARY').groups()
1194 ('$(SPL_)', 'MARY')
1195 >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_TPL_)MARY').groups()
1196 ('$(SPL_TPL_)', 'MARY')
1197 """
1198 all_uses = collections.defaultdict(list)
1199 fname_uses = {}
1200 for fname, rest in fnames:
1201 m_iter = RE_MK_CONFIGS.finditer(rest)
Simon Glassa4c9d172023-09-23 13:44:01 -06001202 for mat in m_iter:
1203 real_opt = mat.group(2)
Simon Glass65e62032023-02-01 13:19:12 -07001204 if real_opt == '':
1205 continue
1206 is_spl = False
Simon Glassa4c9d172023-09-23 13:44:01 -06001207 if mat.group(1):
Simon Glass65e62032023-02-01 13:19:12 -07001208 is_spl = True
1209 use = ConfigUse(real_opt, is_spl, fname, rest)
1210 if fname not in fname_uses:
1211 fname_uses[fname] = set()
1212 fname_uses[fname].add(use)
1213 all_uses[use].append(rest)
1214 return all_uses, fname_uses
1215
1216
1217def scan_src_files(fnames):
1218 """Scan source files (other than Makefiles) looking for Kconfig options
1219
1220 Looks for uses of CONFIG options
1221
1222 Args:
1223 fnames (list of tuple):
1224 str: Makefile filename where the option was found
1225 str: Line of the Makefile
1226
1227 Returns:
1228 tuple:
1229 dict: all_uses
1230 key (ConfigUse): object
1231 value (list of str): matching lines
1232 dict: Uses by filename
1233 key (str): filename
1234 value (set of ConfigUse): uses in that filename
1235
1236 >>> RE_C_CONFIGS.search('CONFIG_FRED').groups()
1237 ('FRED',)
1238 >>> RE_CONFIG_IS.search('CONFIG_IS_ENABLED(MARY)').groups()
1239 ('MARY',)
1240 >>> RE_CONFIG_IS.search('#if CONFIG_IS_ENABLED(OF_PLATDATA)').groups()
1241 ('OF_PLATDATA',)
1242 """
Simon Glass62fae4b2023-09-23 13:44:00 -06001243 fname = None
1244 rest = None
1245
Simon Glass65e62032023-02-01 13:19:12 -07001246 def add_uses(m_iter, is_spl):
Simon Glassa4c9d172023-09-23 13:44:01 -06001247 for mat in m_iter:
1248 real_opt = mat.group(1)
Simon Glass65e62032023-02-01 13:19:12 -07001249 if real_opt == '':
1250 continue
1251 use = ConfigUse(real_opt, is_spl, fname, rest)
1252 if fname not in fname_uses:
1253 fname_uses[fname] = set()
1254 fname_uses[fname].add(use)
1255 all_uses[use].append(rest)
1256
1257 all_uses = collections.defaultdict(list)
1258 fname_uses = {}
1259 for fname, rest in fnames:
1260 m_iter = RE_C_CONFIGS.finditer(rest)
1261 add_uses(m_iter, False)
1262
1263 m_iter2 = RE_CONFIG_IS.finditer(rest)
1264 add_uses(m_iter2, True)
1265
1266 return all_uses, fname_uses
1267
1268
1269MODE_NORMAL, MODE_SPL, MODE_PROPER = range(3)
1270
1271def do_scan_source(path, do_update):
1272 """Scan the source tree for Kconfig inconsistencies
1273
1274 Args:
1275 path (str): Path to source tree
1276 do_update (bool) : True to write to scripts/kconf_... files
1277 """
1278 def is_not_proper(name):
1279 for prefix in SPL_PREFIXES:
1280 if name.startswith(prefix):
1281 return name[len(prefix):]
1282 return False
1283
1284 def check_not_found(all_uses, spl_mode):
1285 """Check for Kconfig options mentioned in the source but not in Kconfig
1286
1287 Args:
1288 all_uses (dict):
1289 key (ConfigUse): object
1290 value (list of str): matching lines
1291 spl_mode (int): If MODE_SPL, look at source code which implies
1292 an SPL_ option, but for which there is none;
1293 for MOD_PROPER, look at source code which implies a Proper
1294 option (i.e. use of CONFIG_IS_ENABLED() or $(SPL_) or
1295 $(SPL_TPL_) but for which there none;
1296 if MODE_NORMAL, ignore SPL
1297
1298 Returns:
1299 dict:
1300 key (str): CONFIG name (without 'CONFIG_' prefix
1301 value (list of ConfigUse): List of uses of this CONFIG
1302 """
1303 # Make sure we know about all the options
1304 not_found = collections.defaultdict(list)
Simon Glass62fae4b2023-09-23 13:44:00 -06001305 for use, _ in all_uses.items():
Simon Glass65e62032023-02-01 13:19:12 -07001306 name = use.cfg
1307 if name in IGNORE_SYMS:
1308 continue
1309 check = True
1310
1311 if spl_mode == MODE_SPL:
1312 check = use.is_spl
1313
1314 # If it is an SPL symbol, try prepending all SPL_ prefixes to
1315 # find at least one SPL symbol
1316 if use.is_spl:
Simon Glass65e62032023-02-01 13:19:12 -07001317 for prefix in SPL_PREFIXES:
1318 try_name = prefix + name
1319 sym = kconf.syms.get(try_name)
1320 if sym:
1321 break
1322 if not sym:
1323 not_found[f'SPL_{name}'].append(use)
1324 continue
1325 elif spl_mode == MODE_PROPER:
1326 # Try to find the Proper version of this symbol, i.e. without
1327 # the SPL_ prefix
1328 proper_name = is_not_proper(name)
1329 if proper_name:
1330 name = proper_name
1331 elif not use.is_spl:
1332 check = False
1333 else: # MODE_NORMAL
Simon Glass65e62032023-02-01 13:19:12 -07001334 sym = kconf.syms.get(name)
1335 if not sym:
1336 proper_name = is_not_proper(name)
1337 if proper_name:
1338 name = proper_name
1339 sym = kconf.syms.get(name)
1340 if not sym:
1341 for prefix in SPL_PREFIXES:
1342 try_name = prefix + name
1343 sym = kconf.syms.get(try_name)
1344 if sym:
1345 break
1346 if not sym:
1347 not_found[name].append(use)
1348 continue
1349
1350 sym = kconf.syms.get(name)
1351 if not sym and check:
1352 not_found[name].append(use)
1353 return not_found
1354
1355 def show_uses(uses):
1356 """Show a list of uses along with their filename and code snippet
1357
1358 Args:
1359 uses (dict):
1360 key (str): CONFIG name (without 'CONFIG_' prefix
1361 value (list of ConfigUse): List of uses of this CONFIG
1362 """
1363 for name in sorted(uses):
1364 print(f'{name}: ', end='')
1365 for i, use in enumerate(uses[name]):
1366 print(f'{" " if i else ""}{use.fname}: {use.rest.strip()}')
1367
1368
1369 print('Scanning Kconfig')
1370 kconf = KconfigScanner().conf
1371 print(f'Scanning source in {path}')
1372 args = ['git', 'grep', '-E', r'IS_ENABLED|\bCONFIG']
1373 with subprocess.Popen(args, stdout=subprocess.PIPE) as proc:
Simon Glass62fae4b2023-09-23 13:44:00 -06001374 out, _ = proc.communicate()
Simon Glass65e62032023-02-01 13:19:12 -07001375 lines = out.splitlines()
1376 re_fname = re.compile('^([^:]*):(.*)')
1377 src_list = []
1378 mk_list = []
1379 for line in lines:
1380 linestr = line.decode('utf-8')
1381 m_fname = re_fname.search(linestr)
1382 if not m_fname:
1383 continue
1384 fname, rest = m_fname.groups()
1385 dirname, leaf = os.path.split(fname)
1386 root, ext = os.path.splitext(leaf)
1387 if ext == '.autoconf':
1388 pass
1389 elif ext in ['.c', '.h', '.S', '.lds', '.dts', '.dtsi', '.asl', '.cfg',
1390 '.env', '.tmpl']:
1391 src_list.append([fname, rest])
1392 elif 'Makefile' in root or ext == '.mk':
1393 mk_list.append([fname, rest])
1394 elif ext in ['.yml', '.sh', '.py', '.awk', '.pl', '.rst', '', '.sed']:
1395 pass
1396 elif 'Kconfig' in root or 'Kbuild' in root:
1397 pass
1398 elif 'README' in root:
1399 pass
1400 elif dirname in ['configs']:
1401 pass
1402 elif dirname.startswith('doc') or dirname.startswith('scripts/kconfig'):
1403 pass
1404 else:
1405 print(f'Not sure how to handle file {fname}')
1406
1407 # Scan the Makefiles
Simon Glass62fae4b2023-09-23 13:44:00 -06001408 all_uses, _ = scan_makefiles(mk_list)
Simon Glass65e62032023-02-01 13:19:12 -07001409
1410 spl_not_found = set()
1411 proper_not_found = set()
1412
1413 # Make sure we know about all the options
1414 print('\nCONFIG options present in Makefiles but not Kconfig:')
1415 not_found = check_not_found(all_uses, MODE_NORMAL)
1416 show_uses(not_found)
1417
1418 print('\nCONFIG options present in Makefiles but not Kconfig (SPL):')
1419 not_found = check_not_found(all_uses, MODE_SPL)
1420 show_uses(not_found)
1421 spl_not_found |= set([is_not_proper(key) or key for key in not_found.keys()])
1422
1423 print('\nCONFIG options used as Proper in Makefiles but without a non-SPL_ variant:')
1424 not_found = check_not_found(all_uses, MODE_PROPER)
1425 show_uses(not_found)
1426 proper_not_found |= set([key for key in not_found.keys()])
1427
1428 # Scan the source code
Simon Glass62fae4b2023-09-23 13:44:00 -06001429 all_uses, _ = scan_src_files(src_list)
Simon Glass65e62032023-02-01 13:19:12 -07001430
1431 # Make sure we know about all the options
1432 print('\nCONFIG options present in source but not Kconfig:')
1433 not_found = check_not_found(all_uses, MODE_NORMAL)
1434 show_uses(not_found)
1435
1436 print('\nCONFIG options present in source but not Kconfig (SPL):')
1437 not_found = check_not_found(all_uses, MODE_SPL)
1438 show_uses(not_found)
1439 spl_not_found |= set([is_not_proper(key) or key for key in not_found.keys()])
1440
1441 print('\nCONFIG options used as Proper in source but without a non-SPL_ variant:')
1442 not_found = check_not_found(all_uses, MODE_PROPER)
1443 show_uses(not_found)
1444 proper_not_found |= set([key for key in not_found.keys()])
1445
1446 print('\nCONFIG options used as SPL but without an SPL_ variant:')
1447 for item in sorted(spl_not_found):
1448 print(f' {item}')
1449
1450 print('\nCONFIG options used as Proper but without a non-SPL_ variant:')
1451 for item in sorted(proper_not_found):
1452 print(f' {item}')
1453
1454 # Write out the updated information
1455 if do_update:
1456 with open(os.path.join(path, 'scripts', 'conf_nospl'), 'w') as out:
1457 print('# These options should not be enabled in SPL builds\n',
1458 file=out)
1459 for item in sorted(spl_not_found):
1460 print(item, file=out)
1461 with open(os.path.join(path, 'scripts', 'conf_noproper'), 'w') as out:
1462 print('# These options should not be enabled in Proper builds\n',
1463 file=out)
1464 for item in sorted(proper_not_found):
1465 print(item, file=out)
1466
1467
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001468def main():
1469 try:
1470 cpu_count = multiprocessing.cpu_count()
1471 except NotImplementedError:
1472 cpu_count = 1
1473
Simon Glassb2e83c62021-12-18 14:54:31 -07001474 epilog = '''Move config options from headers to defconfig files. See
1475doc/develop/moveconfig.rst for documentation.'''
1476
1477 parser = ArgumentParser(epilog=epilog)
1478 # Add arguments here
1479 parser.add_argument('-a', '--add-imply', type=str, default='',
Simon Glasscb008832017-06-15 21:39:33 -06001480 help='comma-separated list of CONFIG options to add '
1481 "an 'imply' statement to for the CONFIG in -i")
Simon Glassb2e83c62021-12-18 14:54:31 -07001482 parser.add_argument('-A', '--skip-added', action='store_true', default=False,
Simon Glasscb008832017-06-15 21:39:33 -06001483 help="don't show options which are already marked as "
1484 'implying others')
Simon Glassb2e83c62021-12-18 14:54:31 -07001485 parser.add_argument('-b', '--build-db', action='store_true', default=False,
Simon Glassd73fcb12017-06-01 19:39:02 -06001486 help='build a CONFIG database')
Simon Glassb2e83c62021-12-18 14:54:31 -07001487 parser.add_argument('-c', '--color', action='store_true', default=False,
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001488 help='display the log in color')
Simon Glassb2e83c62021-12-18 14:54:31 -07001489 parser.add_argument('-C', '--commit', action='store_true', default=False,
Simon Glass9ede2122016-09-12 23:18:21 -06001490 help='Create a git commit for the operation')
Simon Glassb2e83c62021-12-18 14:54:31 -07001491 parser.add_argument('-d', '--defconfigs', type=str,
Simon Glassee4e61b2017-06-01 19:38:59 -06001492 help='a file containing a list of defconfigs to move, '
1493 "one per line (for example 'snow_defconfig') "
1494 "or '-' to read from stdin")
Simon Glassb2e83c62021-12-18 14:54:31 -07001495 parser.add_argument('-e', '--exit-on-error', action='store_true',
Simon Glasse1ae5632021-12-18 08:09:44 -07001496 default=False,
1497 help='exit immediately on any error')
Simon Glassb2e83c62021-12-18 14:54:31 -07001498 parser.add_argument('-f', '--find', action='store_true', default=False,
Simon Glass65d7fce2021-12-18 08:09:46 -07001499 help='Find boards with a given config combination')
Simon Glassb2e83c62021-12-18 14:54:31 -07001500 parser.add_argument('-i', '--imply', action='store_true', default=False,
Simon Glass99b66602017-06-01 19:39:03 -06001501 help='find options which imply others')
Simon Glassb2e83c62021-12-18 14:54:31 -07001502 parser.add_argument('-I', '--imply-flags', type=str, default='',
Simon Glass9b2a2e82017-06-15 21:39:32 -06001503 help="control the -i option ('help' for help")
Simon Glassb2e83c62021-12-18 14:54:31 -07001504 parser.add_argument('-j', '--jobs', type=int, default=cpu_count,
Simon Glasse1ae5632021-12-18 08:09:44 -07001505 help='the number of jobs to run simultaneously')
Simon Glassb2e83c62021-12-18 14:54:31 -07001506 parser.add_argument('-n', '--dry-run', action='store_true', default=False,
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001507 help='perform a trial run (show log with no changes)')
Simon Glassb2e83c62021-12-18 14:54:31 -07001508 parser.add_argument('-r', '--git-ref', type=str,
Simon Glasse1ae5632021-12-18 08:09:44 -07001509 help='the git ref to clone for building the autoconf.mk')
Simon Glassb2e83c62021-12-18 14:54:31 -07001510 parser.add_argument('-s', '--force-sync', action='store_true', default=False,
Masahiro Yamada8513dc02016-05-19 15:52:08 +09001511 help='force sync by savedefconfig')
Simon Glassb2e83c62021-12-18 14:54:31 -07001512 parser.add_argument('-S', '--spl', action='store_true', default=False,
Masahiro Yamada07913d12016-08-22 22:18:22 +09001513 help='parse config options defined for SPL build')
Simon Glass65e62032023-02-01 13:19:12 -07001514 parser.add_argument('--scan-source', action='store_true', default=False,
1515 help='scan source for uses of CONFIG options')
Simon Glassb2e83c62021-12-18 14:54:31 -07001516 parser.add_argument('-t', '--test', action='store_true', default=False,
Simon Glasse1ae5632021-12-18 08:09:44 -07001517 help='run unit tests')
Simon Glassb2e83c62021-12-18 14:54:31 -07001518 parser.add_argument('-y', '--yes', action='store_true', default=False,
Simon Glass6b403df2016-09-12 23:18:20 -06001519 help="respond 'yes' to any prompts")
Simon Glass65e62032023-02-01 13:19:12 -07001520 parser.add_argument('-u', '--update', action='store_true', default=False,
1521 help="update scripts/ files (use with --scan-source)")
Simon Glassb2e83c62021-12-18 14:54:31 -07001522 parser.add_argument('-v', '--verbose', action='store_true', default=False,
Joe Hershberger95bf9c72015-05-19 13:21:24 -05001523 help='show any build errors as boards are built')
Simon Glassb2e83c62021-12-18 14:54:31 -07001524 parser.add_argument('configs', nargs='*')
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001525
Simon Glassb2e83c62021-12-18 14:54:31 -07001526 args = parser.parse_args()
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001527
Simon Glassb2e83c62021-12-18 14:54:31 -07001528 if args.test:
Simon Glass84067a52021-12-18 08:09:45 -07001529 sys.argv = [sys.argv[0]]
Simon Glass62fae4b2023-09-23 13:44:00 -06001530 fail, _ = doctest.testmod()
Simon Glass84067a52021-12-18 08:09:45 -07001531 if fail:
1532 return 1
1533 unittest.main()
1534
Simon Glass65e62032023-02-01 13:19:12 -07001535 if args.scan_source:
1536 do_scan_source(os.getcwd(), args.update)
1537 return
1538
Simon Glass882c8e42023-09-23 13:43:54 -06001539 if not any((args.force_sync, args.build_db, args.imply, args.find)):
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001540 parser.print_usage()
1541 sys.exit(1)
1542
Masahiro Yamadab6ef3932016-05-19 15:51:58 +09001543 # prefix the option name with CONFIG_ if missing
Simon Glass882c8e42023-09-23 13:43:54 -06001544 configs = [prefix_config(cfg) for cfg in args.configs]
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001545
Joe Hershberger2144f882015-05-19 13:21:20 -05001546 check_top_directory()
1547
Simon Glassb2e83c62021-12-18 14:54:31 -07001548 if args.imply:
Simon Glass9b2a2e82017-06-15 21:39:32 -06001549 imply_flags = 0
Simon Glassb2e83c62021-12-18 14:54:31 -07001550 if args.imply_flags == 'all':
Simon Glassdee36c72017-07-10 14:47:46 -06001551 imply_flags = -1
1552
Simon Glassb2e83c62021-12-18 14:54:31 -07001553 elif args.imply_flags:
1554 for flag in args.imply_flags.split(','):
Simon Glassdee36c72017-07-10 14:47:46 -06001555 bad = flag not in IMPLY_FLAGS
1556 if bad:
Simon Glass1bd43062023-09-23 13:43:59 -06001557 print(f"Invalid flag '{flag}'")
Simon Glassdee36c72017-07-10 14:47:46 -06001558 if flag == 'help' or bad:
Simon Glass793dca32019-10-31 07:42:57 -06001559 print("Imply flags: (separate with ',')")
1560 for name, info in IMPLY_FLAGS.items():
Simon Glass1bd43062023-09-23 13:43:59 -06001561 print(f' {name:-15s}: {info[1]}')
Simon Glassdee36c72017-07-10 14:47:46 -06001562 parser.print_usage()
1563 sys.exit(1)
1564 imply_flags |= IMPLY_FLAGS[flag][0]
Simon Glass9b2a2e82017-06-15 21:39:32 -06001565
Simon Glassb2e83c62021-12-18 14:54:31 -07001566 do_imply_config(configs, args.add_imply, imply_flags, args.skip_added)
Simon Glass99b66602017-06-01 19:39:03 -06001567 return
1568
Simon Glassb2e83c62021-12-18 14:54:31 -07001569 if args.find:
Simon Glass65d7fce2021-12-18 08:09:46 -07001570 do_find_config(configs)
1571 return
1572
Simon Glass3481e892023-09-23 13:43:53 -06001573 # We are either building the database or forcing a sync of defconfigs
Simon Glassd73fcb12017-06-01 19:39:02 -06001574 config_db = {}
Simon Glass793dca32019-10-31 07:42:57 -06001575 db_queue = queue.Queue()
Simon Glassa4c9d172023-09-23 13:44:01 -06001576 dbt = DatabaseThread(config_db, db_queue)
1577 dbt.daemon = True
1578 dbt.start()
Simon Glassd73fcb12017-06-01 19:39:02 -06001579
Simon Glass63df2022023-09-23 13:43:50 -06001580 check_clean_directory()
1581 bsettings.setup('')
1582 toolchains = toolchain.Toolchains()
1583 toolchains.GetSettings()
1584 toolchains.Scan(verbose=False)
Simon Glass882c8e42023-09-23 13:43:54 -06001585 move_config(toolchains, args, db_queue)
Simon Glass63df2022023-09-23 13:43:50 -06001586 db_queue.join()
Joe Hershberger2144f882015-05-19 13:21:20 -05001587
Simon Glassb2e83c62021-12-18 14:54:31 -07001588 if args.commit:
Simon Glass9ede2122016-09-12 23:18:21 -06001589 subprocess.call(['git', 'add', '-u'])
1590 if configs:
1591 msg = 'Convert %s %sto Kconfig' % (configs[0],
1592 'et al ' if len(configs) > 1 else '')
1593 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' %
1594 '\n '.join(configs))
1595 else:
1596 msg = 'configs: Resync with savedefconfig'
1597 msg += '\n\nRsync all defconfig files using moveconfig.py'
1598 subprocess.call(['git', 'commit', '-s', '-m', msg])
1599
Simon Glassb2e83c62021-12-18 14:54:31 -07001600 if args.build_db:
Simon Glassa4c9d172023-09-23 13:44:01 -06001601 with open(CONFIG_DATABASE, 'w', encoding='utf-8') as outf:
Simon Glass793dca32019-10-31 07:42:57 -06001602 for defconfig, configs in config_db.items():
Simon Glassa4c9d172023-09-23 13:44:01 -06001603 outf.write(f'{defconfig}\n')
Simon Glassd73fcb12017-06-01 19:39:02 -06001604 for config in sorted(configs.keys()):
Simon Glassa4c9d172023-09-23 13:44:01 -06001605 outf.write(f' {config}={configs[config]}\n')
1606 outf.write('\n')
Simon Glassd73fcb12017-06-01 19:39:02 -06001607
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001608if __name__ == '__main__':
Simon Glass65d7fce2021-12-18 08:09:46 -07001609 sys.exit(main())