blob: dc5b7691064abbe7ee65f1c12332591e78eac761 [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
Simon Glass28155572024-07-17 16:56:52 +01004"""Build and query a Kconfig database for boards.
Masahiro Yamadab6160812015-05-20 11:36:07 +09005
Simon Glass46960942024-07-17 16:56:54 +01006See doc/develop/qconfig.rst for documentation.
Simon Glass28155572024-07-17 16:56:52 +01007
8Author: Masahiro Yamada <yamada.masahiro@socionext.com>
9Author: Simon Glass <sjg@chromium.org>
Masahiro Yamadab6160812015-05-20 11:36:07 +090010"""
11
Simon Glassd9c1da22021-12-18 14:54:31 -070012from argparse import ArgumentParser
Simon Glassc6e73cf2017-06-01 19:39:03 -060013import collections
Simon Glassb3464eb2021-12-18 14:54:35 -070014from contextlib import ExitStack
Simon Glassbb57be72021-12-18 08:09:45 -070015import doctest
Masahiro Yamada0f6beda2016-05-19 15:52:07 +090016import filecmp
Masahiro Yamadab6160812015-05-20 11:36:07 +090017import fnmatch
Masahiro Yamada3984d6e2016-10-19 14:39:54 +090018import glob
Masahiro Yamadab6160812015-05-20 11:36:07 +090019import multiprocessing
Masahiro Yamadab6160812015-05-20 11:36:07 +090020import os
Simon Glass1f701862019-10-31 07:42:57 -060021import queue
Masahiro Yamadab6160812015-05-20 11:36:07 +090022import re
23import shutil
24import subprocess
25import sys
26import tempfile
Simon Glass43cf08f2017-06-01 19:39:02 -060027import threading
Masahiro Yamadab6160812015-05-20 11:36:07 +090028import time
Simon Glassbb57be72021-12-18 08:09:45 -070029import unittest
Masahiro Yamadab6160812015-05-20 11:36:07 +090030
Simon Glassf0d9c102020-04-17 18:09:02 -060031from buildman import bsettings
32from buildman import kconfiglib
33from buildman import toolchain
Simon Glass9b191102023-09-23 13:44:09 -060034from u_boot_pylib import terminal
Simon Glass44116332017-06-15 21:39:33 -060035
Masahiro Yamadab6160812015-05-20 11:36:07 +090036SHOW_GNU_MAKE = 'scripts/show-gnu-make'
37SLEEP_TIME=0.03
38
Masahiro Yamadab6160812015-05-20 11:36:07 +090039STATE_IDLE = 0
40STATE_DEFCONFIG = 1
41STATE_AUTOCONF = 2
Joe Hershberger166edec2015-05-19 13:21:17 -050042STATE_SAVEDEFCONFIG = 3
Masahiro Yamadab6160812015-05-20 11:36:07 +090043
Simon Glass8fb5bd02017-06-01 19:39:01 -060044AUTO_CONF_PATH = 'include/config/auto.conf'
Simon Glass8a372032023-09-23 13:44:15 -060045CONFIG_DATABASE = 'qconfig.db'
46FAILED_LIST = 'qconfig.failed'
Simon Glass8fb5bd02017-06-01 19:39:01 -060047
Simon Glass44116332017-06-15 21:39:33 -060048CONFIG_LEN = len('CONFIG_')
Simon Glass8fb5bd02017-06-01 19:39:01 -060049
Markus Klotzbuecher3d5d4182019-05-15 15:15:52 +020050SIZES = {
Simon Glassdc634d92021-12-18 14:54:30 -070051 'SZ_1': 0x00000001, 'SZ_2': 0x00000002,
52 'SZ_4': 0x00000004, 'SZ_8': 0x00000008,
53 'SZ_16': 0x00000010, 'SZ_32': 0x00000020,
54 'SZ_64': 0x00000040, 'SZ_128': 0x00000080,
55 'SZ_256': 0x00000100, 'SZ_512': 0x00000200,
56 'SZ_1K': 0x00000400, 'SZ_2K': 0x00000800,
57 'SZ_4K': 0x00001000, 'SZ_8K': 0x00002000,
58 'SZ_16K': 0x00004000, 'SZ_32K': 0x00008000,
59 'SZ_64K': 0x00010000, 'SZ_128K': 0x00020000,
60 'SZ_256K': 0x00040000, 'SZ_512K': 0x00080000,
61 'SZ_1M': 0x00100000, 'SZ_2M': 0x00200000,
62 'SZ_4M': 0x00400000, 'SZ_8M': 0x00800000,
63 'SZ_16M': 0x01000000, 'SZ_32M': 0x02000000,
64 'SZ_64M': 0x04000000, 'SZ_128M': 0x08000000,
65 'SZ_256M': 0x10000000, 'SZ_512M': 0x20000000,
66 'SZ_1G': 0x40000000, 'SZ_2G': 0x80000000,
67 'SZ_4G': 0x100000000
Markus Klotzbuecher3d5d4182019-05-15 15:15:52 +020068}
69
Simon Glasse8037552022-02-08 11:49:45 -070070RE_REMOVE_DEFCONFIG = re.compile(r'(.*)_defconfig')
71
Simon Glass4c4eb7c2023-02-01 13:19:12 -070072# CONFIG symbols present in the build system (from Linux) but not actually used
73# in U-Boot; KCONFIG symbols
74IGNORE_SYMS = ['DEBUG_SECTION_MISMATCH', 'FTRACE_MCOUNT_RECORD', 'GCOV_KERNEL',
75 'GCOV_PROFILE_ALL', 'KALLSYMS', 'KASAN', 'MODVERSIONS', 'SHELL',
76 'TPL_BUILD', 'VPL_BUILD', 'IS_ENABLED', 'FOO', 'IF_ENABLED_INT',
77 'IS_ENABLED_', 'IS_ENABLED_1', 'IS_ENABLED_2', 'IS_ENABLED_3',
78 'SPL_', 'TPL_', 'SPL_FOO', 'TPL_FOO', 'TOOLS_FOO',
79 'ACME', 'SPL_ACME', 'TPL_ACME', 'TRACE_BRANCH_PROFILING',
80 'VAL', '_UNDEFINED', 'SPL_BUILD', ]
81
82SPL_PREFIXES = ['SPL_', 'TPL_', 'VPL_', 'TOOLS_']
83
Masahiro Yamadab6160812015-05-20 11:36:07 +090084### helper functions ###
Masahiro Yamadab6160812015-05-20 11:36:07 +090085def check_top_directory():
86 """Exit if we are not at the top of source directory."""
Simon Glassb3464eb2021-12-18 14:54:35 -070087 for fname in 'README', 'Licenses':
88 if not os.path.exists(fname):
Masahiro Yamadab6160812015-05-20 11:36:07 +090089 sys.exit('Please run at the top of source directory.')
90
Masahiro Yamada990e6772016-05-19 15:51:54 +090091def check_clean_directory():
92 """Exit if the source tree is not clean."""
Simon Glassb3464eb2021-12-18 14:54:35 -070093 for fname in '.config', 'include/config':
94 if os.path.exists(fname):
Masahiro Yamada990e6772016-05-19 15:51:54 +090095 sys.exit("source tree is not clean, please run 'make mrproper'")
96
Masahiro Yamadab6160812015-05-20 11:36:07 +090097def get_make_cmd():
98 """Get the command name of GNU Make.
99
100 U-Boot needs GNU Make for building, but the command name is not
101 necessarily "make". (for example, "gmake" on FreeBSD).
102 Returns the most appropriate command name on your system.
103 """
Simon Glassb3464eb2021-12-18 14:54:35 -0700104 with subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE) as proc:
105 ret = proc.communicate()
106 if proc.returncode:
107 sys.exit('GNU Make not found')
Masahiro Yamadab6160812015-05-20 11:36:07 +0900108 return ret[0].rstrip()
109
Simon Glass18774bc2017-06-01 19:38:58 -0600110def get_matched_defconfig(line):
111 """Get the defconfig files that match a pattern
112
113 Args:
Simon Glassb3464eb2021-12-18 14:54:35 -0700114 line (str): Path or filename to match, e.g. 'configs/snow_defconfig' or
Simon Glass18774bc2017-06-01 19:38:58 -0600115 'k2*_defconfig'. If no directory is provided, 'configs/' is
116 prepended
117
118 Returns:
Simon Glassb3464eb2021-12-18 14:54:35 -0700119 list of str: a list of matching defconfig files
Simon Glass18774bc2017-06-01 19:38:58 -0600120 """
121 dirname = os.path.dirname(line)
122 if dirname:
123 pattern = line
124 else:
125 pattern = os.path.join('configs', line)
126 return glob.glob(pattern) + glob.glob(pattern + '_defconfig')
127
Masahiro Yamada3984d6e2016-10-19 14:39:54 +0900128def get_matched_defconfigs(defconfigs_file):
Simon Glass8f3cf312017-06-01 19:38:59 -0600129 """Get all the defconfig files that match the patterns in a file.
130
131 Args:
Simon Glassb3464eb2021-12-18 14:54:35 -0700132 defconfigs_file (str): File containing a list of defconfigs to process,
133 or '-' to read the list from stdin
Simon Glass8f3cf312017-06-01 19:38:59 -0600134
135 Returns:
Simon Glassb3464eb2021-12-18 14:54:35 -0700136 list of str: A list of paths to defconfig files, with no duplicates
Simon Glass8f3cf312017-06-01 19:38:59 -0600137 """
Masahiro Yamada3984d6e2016-10-19 14:39:54 +0900138 defconfigs = []
Simon Glassb3464eb2021-12-18 14:54:35 -0700139 with ExitStack() as stack:
140 if defconfigs_file == '-':
141 inf = sys.stdin
142 defconfigs_file = 'stdin'
143 else:
144 inf = stack.enter_context(open(defconfigs_file, encoding='utf-8'))
145 for i, line in enumerate(inf):
146 line = line.strip()
147 if not line:
148 continue # skip blank lines silently
149 if ' ' in line:
150 line = line.split(' ')[0] # handle 'git log' input
151 matched = get_matched_defconfig(line)
152 if not matched:
153 print(f"warning: {defconfigs_file}:{i + 1}: no defconfig matched '{line}'",
154 file=sys.stderr)
Masahiro Yamada3984d6e2016-10-19 14:39:54 +0900155
Simon Glassb3464eb2021-12-18 14:54:35 -0700156 defconfigs += matched
Masahiro Yamada3984d6e2016-10-19 14:39:54 +0900157
158 # use set() to drop multiple matching
Simon Glassb3464eb2021-12-18 14:54:35 -0700159 return [defconfig[len('configs') + 1:] for defconfig in set(defconfigs)]
Masahiro Yamada3984d6e2016-10-19 14:39:54 +0900160
Masahiro Yamada58175e32016-07-25 19:15:28 +0900161def get_all_defconfigs():
Simon Glassb3464eb2021-12-18 14:54:35 -0700162 """Get all the defconfig files under the configs/ directory.
163
164 Returns:
165 list of str: List of paths to defconfig files
166 """
Masahiro Yamada58175e32016-07-25 19:15:28 +0900167 defconfigs = []
Simon Glassb3464eb2021-12-18 14:54:35 -0700168 for (dirpath, _, filenames) in os.walk('configs'):
Masahiro Yamada58175e32016-07-25 19:15:28 +0900169 dirpath = dirpath[len('configs') + 1:]
170 for filename in fnmatch.filter(filenames, '*_defconfig'):
171 defconfigs.append(os.path.join(dirpath, filename))
172
173 return defconfigs
174
Simon Glassb09ae452021-12-18 14:54:33 -0700175def write_file(fname, data):
176 """Write data to a file
177
178 Args:
179 fname (str): Filename to write to
180 data (list of str): Lines to write (with or without trailing newline);
181 or str to write
182 """
183 with open(fname, 'w', encoding='utf-8') as out:
184 if isinstance(data, list):
185 for line in data:
186 print(line.rstrip('\n'), file=out)
187 else:
188 out.write(data)
189
Simon Glassaba238f2021-12-18 14:54:34 -0700190def read_file(fname, as_lines=True, skip_unicode=False):
191 """Read a file and return the contents
192
193 Args:
194 fname (str): Filename to read from
Simon Glassc89a2962023-09-23 13:43:58 -0600195 as_lines (bool): Return file contents as a list of lines
Simon Glassaba238f2021-12-18 14:54:34 -0700196 skip_unicode (bool): True to report unicode errors and continue
197
198 Returns:
199 iter of str: List of ;ines from the file with newline removed; str if
200 as_lines is False with newlines intact; or None if a unicode error
201 occurred
202
203 Raises:
204 UnicodeDecodeError: Unicode error occurred when reading
205 """
206 with open(fname, encoding='utf-8') as inf:
207 try:
208 if as_lines:
209 return [line.rstrip('\n') for line in inf.readlines()]
Simon Glasse19a9cd2023-09-23 13:44:05 -0600210 return inf.read()
Simon Glass4f6725c2023-09-23 13:44:01 -0600211 except UnicodeDecodeError as exc:
Simon Glassaba238f2021-12-18 14:54:34 -0700212 if not skip_unicode:
Simon Glassafaddc72022-02-11 13:23:22 -0700213 raise
Simon Glass4f6725c2023-09-23 13:44:01 -0600214 print(f"Failed on file '{fname}: {exc}")
Simon Glassaba238f2021-12-18 14:54:34 -0700215 return None
216
Chris Packham9d5274f2017-05-02 21:30:47 +1200217
Masahiro Yamadab6160812015-05-20 11:36:07 +0900218### classes ###
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900219class Progress:
220
221 """Progress Indicator"""
222
Simon Glassbef67362023-09-23 13:44:10 -0600223 def __init__(self, col, total):
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900224 """Create a new progress indicator.
225
Simon Glassb3464eb2021-12-18 14:54:35 -0700226 Args:
Simon Glassbef67362023-09-23 13:44:10 -0600227 color_enabled (bool): True for colour output
228 total (int): A number of defconfig files to process.
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900229 """
Simon Glassbef67362023-09-23 13:44:10 -0600230 self.col = col
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900231 self.current = 0
Simon Glassbef67362023-09-23 13:44:10 -0600232 self.good = 0
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900233 self.total = total
234
Simon Glassbef67362023-09-23 13:44:10 -0600235 def inc(self, success):
236 """Increment the number of processed defconfig files.
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900237
Simon Glassbef67362023-09-23 13:44:10 -0600238 Args:
239 success (bool): True if processing succeeded
240 """
241 self.good += success
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900242 self.current += 1
243
244 def show(self):
245 """Display the progress."""
Simon Glass99c28cd2023-09-23 13:44:08 -0600246 if self.current != self.total:
Simon Glassbef67362023-09-23 13:44:10 -0600247 line = self.col.build(self.col.GREEN, f'{self.good:5d}')
248 line += self.col.build(self.col.RED,
249 f'{self.current - self.good:5d}')
250 line += self.col.build(self.col.MAGENTA,
251 f'/{self.total - self.current}')
252 print(f'{line} \r', end='')
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900253 sys.stdout.flush()
254
Simon Glass44116332017-06-15 21:39:33 -0600255
Simon Glassa0a61602024-07-17 16:56:51 +0100256def scan_kconfig():
257 """Scan all the Kconfig files and create a Config object
Simon Glass44116332017-06-15 21:39:33 -0600258
Simon Glassa0a61602024-07-17 16:56:51 +0100259 Returns:
260 Kconfig object
261 """
262 # Define environment variables referenced from Kconfig
263 os.environ['srctree'] = os.getcwd()
264 os.environ['UBOOTVERSION'] = 'dummy'
265 os.environ['KCONFIG_OBJDIR'] = ''
266 os.environ['CC'] = 'gcc'
267 return kconfiglib.Kconfig()
Simon Glass44116332017-06-15 21:39:33 -0600268
269
Simon Glass28155572024-07-17 16:56:52 +0100270# pylint: disable=R0903
Masahiro Yamadab6160812015-05-20 11:36:07 +0900271class KconfigParser:
Masahiro Yamadab6160812015-05-20 11:36:07 +0900272 """A parser of .config and include/autoconf.mk."""
273
274 re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
275 re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
276
Simon Glass08d148a2023-09-23 13:43:54 -0600277 def __init__(self, args, build_dir):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900278 """Create a new parser.
279
Simon Glassb3464eb2021-12-18 14:54:35 -0700280 Args:
Simon Glassb3464eb2021-12-18 14:54:35 -0700281 args (Namespace): program arguments
Masahiro Yamadab6160812015-05-20 11:36:07 +0900282 build_dir: Build directory.
283 """
Simon Glassd9c1da22021-12-18 14:54:31 -0700284 self.args = args
Masahiro Yamada5393b612016-05-19 15:52:00 +0900285 self.dotconfig = os.path.join(build_dir, '.config')
286 self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
Masahiro Yamada6d139172016-08-22 22:18:22 +0900287 self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
288 'autoconf.mk')
Simon Glass8fb5bd02017-06-01 19:39:01 -0600289 self.config_autoconf = os.path.join(build_dir, AUTO_CONF_PATH)
Masahiro Yamada07f98522016-05-19 15:52:06 +0900290 self.defconfig = os.path.join(build_dir, 'defconfig')
Masahiro Yamadab6160812015-05-20 11:36:07 +0900291
Simon Glass257f5232017-07-10 14:47:47 -0600292 def get_arch(self):
293 """Parse .config file and return the architecture.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900294
295 Returns:
Simon Glass257f5232017-07-10 14:47:47 -0600296 Architecture name (e.g. 'arm').
Masahiro Yamadab6160812015-05-20 11:36:07 +0900297 """
298 arch = ''
299 cpu = ''
Simon Glassaba238f2021-12-18 14:54:34 -0700300 for line in read_file(self.dotconfig):
Simon Glass4f6725c2023-09-23 13:44:01 -0600301 m_arch = self.re_arch.match(line)
302 if m_arch:
303 arch = m_arch.group(1)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900304 continue
Simon Glass4f6725c2023-09-23 13:44:01 -0600305 m_cpu = self.re_cpu.match(line)
306 if m_cpu:
307 cpu = m_cpu.group(1)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900308
Masahiro Yamadac4d76eb2016-05-19 15:51:53 +0900309 if not arch:
310 return None
Masahiro Yamadab6160812015-05-20 11:36:07 +0900311
312 # fix-up for aarch64
313 if arch == 'arm' and cpu == 'armv8':
314 arch = 'aarch64'
315
Simon Glass257f5232017-07-10 14:47:47 -0600316 return arch
Masahiro Yamadab6160812015-05-20 11:36:07 +0900317
Simon Glass43cf08f2017-06-01 19:39:02 -0600318
319class DatabaseThread(threading.Thread):
320 """This thread processes results from Slot threads.
321
322 It collects the data in the master config directary. There is only one
323 result thread, and this helps to serialise the build output.
324 """
325 def __init__(self, config_db, db_queue):
326 """Set up a new result thread
327
328 Args:
329 builder: Builder which will be sent each result
330 """
331 threading.Thread.__init__(self)
332 self.config_db = config_db
333 self.db_queue= db_queue
334
335 def run(self):
336 """Called to start up the result thread.
337
338 We collect the next result job and pass it on to the build.
339 """
340 while True:
341 defconfig, configs = self.db_queue.get()
342 self.config_db[defconfig] = configs
343 self.db_queue.task_done()
344
345
Masahiro Yamadab6160812015-05-20 11:36:07 +0900346class Slot:
347
348 """A slot to store a subprocess.
349
350 Each instance of this class handles one subprocess.
351 This class is useful to control multiple threads
352 for faster processing.
353 """
354
Simon Glass9b191102023-09-23 13:44:09 -0600355 def __init__(self, toolchains, args, progress, devnull, make_cmd,
356 reference_src_dir, db_queue, col):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900357 """Create a new process slot.
358
Simon Glassb3464eb2021-12-18 14:54:35 -0700359 Args:
Simon Glass257f5232017-07-10 14:47:47 -0600360 toolchains: Toolchains object containing toolchains.
Simon Glassd9c1da22021-12-18 14:54:31 -0700361 args: Program arguments
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900362 progress: A progress indicator.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900363 devnull: A file object of '/dev/null'.
364 make_cmd: command name of GNU Make.
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500365 reference_src_dir: Determine the true starting config state from this
366 source tree.
Simon Glass43cf08f2017-06-01 19:39:02 -0600367 db_queue: output queue to write config info for the database
Simon Glass9b191102023-09-23 13:44:09 -0600368 col (terminal.Color): Colour object
Masahiro Yamadab6160812015-05-20 11:36:07 +0900369 """
Simon Glass257f5232017-07-10 14:47:47 -0600370 self.toolchains = toolchains
Simon Glassd9c1da22021-12-18 14:54:31 -0700371 self.args = args
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900372 self.progress = progress
Masahiro Yamadab6160812015-05-20 11:36:07 +0900373 self.build_dir = tempfile.mkdtemp()
374 self.devnull = devnull
375 self.make_cmd = (make_cmd, 'O=' + self.build_dir)
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500376 self.reference_src_dir = reference_src_dir
Simon Glass43cf08f2017-06-01 19:39:02 -0600377 self.db_queue = db_queue
Simon Glass9b191102023-09-23 13:44:09 -0600378 self.col = col
Simon Glass08d148a2023-09-23 13:43:54 -0600379 self.parser = KconfigParser(args, self.build_dir)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900380 self.state = STATE_IDLE
Masahiro Yamada1271b672016-08-22 22:18:20 +0900381 self.failed_boards = set()
Simon Glass67ee0112023-09-23 13:44:02 -0600382 self.defconfig = None
Simon Glasse24ac992023-09-23 13:44:07 -0600383 self.log = []
Simon Glass67ee0112023-09-23 13:44:02 -0600384 self.current_src_dir = None
385 self.proc = None
Masahiro Yamadab6160812015-05-20 11:36:07 +0900386
387 def __del__(self):
388 """Delete the working directory
389
390 This function makes sure the temporary directory is cleaned away
391 even if Python suddenly dies due to error. It should be done in here
Joe Hershberger640de872016-06-10 14:53:29 -0500392 because it is guaranteed the destructor is always invoked when the
Masahiro Yamadab6160812015-05-20 11:36:07 +0900393 instance of the class gets unreferenced.
394
395 If the subprocess is still running, wait until it finishes.
396 """
397 if self.state != STATE_IDLE:
Simon Glasse19a9cd2023-09-23 13:44:05 -0600398 while self.proc.poll() is None:
Masahiro Yamadab6160812015-05-20 11:36:07 +0900399 pass
400 shutil.rmtree(self.build_dir)
401
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900402 def add(self, defconfig):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900403 """Assign a new subprocess for defconfig and add it to the slot.
404
405 If the slot is vacant, create a new subprocess for processing the
406 given defconfig and add it to the slot. Just returns False if
407 the slot is occupied (i.e. the current subprocess is still running).
408
Simon Glassb3464eb2021-12-18 14:54:35 -0700409 Args:
Simon Glassc89a2962023-09-23 13:43:58 -0600410 defconfig (str): defconfig name.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900411
412 Returns:
413 Return True on success or False on failure
414 """
415 if self.state != STATE_IDLE:
416 return False
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900417
Masahiro Yamadab6160812015-05-20 11:36:07 +0900418 self.defconfig = defconfig
Simon Glasse24ac992023-09-23 13:44:07 -0600419 self.log = []
Masahiro Yamada8f5256a2016-06-15 14:33:52 +0900420 self.current_src_dir = self.reference_src_dir
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900421 self.do_defconfig()
Masahiro Yamadab6160812015-05-20 11:36:07 +0900422 return True
423
424 def poll(self):
425 """Check the status of the subprocess and handle it as needed.
426
427 Returns True if the slot is vacant (i.e. in idle state).
428 If the configuration is successfully finished, assign a new
429 subprocess to build include/autoconf.mk.
430 If include/autoconf.mk is generated, invoke the parser to
Masahiro Yamada263d1372016-05-19 15:52:04 +0900431 parse the .config and the include/autoconf.mk, moving
432 config options to the .config as needed.
433 If the .config was updated, run "make savedefconfig" to sync
434 it, update the original defconfig, and then set the slot back
435 to the idle state.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900436
437 Returns:
438 Return True if the subprocess is terminated, False otherwise
439 """
440 if self.state == STATE_IDLE:
441 return True
442
Simon Glasse19a9cd2023-09-23 13:44:05 -0600443 if self.proc.poll() is None:
Masahiro Yamadab6160812015-05-20 11:36:07 +0900444 return False
445
Simon Glass4f6725c2023-09-23 13:44:01 -0600446 if self.proc.poll() != 0:
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900447 self.handle_error()
448 elif self.state == STATE_DEFCONFIG:
Masahiro Yamada8f5256a2016-06-15 14:33:52 +0900449 if self.reference_src_dir and not self.current_src_dir:
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500450 self.do_savedefconfig()
451 else:
452 self.do_autoconf()
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900453 elif self.state == STATE_AUTOCONF:
Masahiro Yamada8f5256a2016-06-15 14:33:52 +0900454 if self.current_src_dir:
455 self.current_src_dir = None
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500456 self.do_defconfig()
Simon Glassd9c1da22021-12-18 14:54:31 -0700457 elif self.args.build_db:
Simon Glass43cf08f2017-06-01 19:39:02 -0600458 self.do_build_db()
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500459 else:
460 self.do_savedefconfig()
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900461 elif self.state == STATE_SAVEDEFCONFIG:
462 self.update_defconfig()
463 else:
Simon Glassdc634d92021-12-18 14:54:30 -0700464 sys.exit('Internal Error. This should not happen.')
Masahiro Yamadab6160812015-05-20 11:36:07 +0900465
Simon Glasse19a9cd2023-09-23 13:44:05 -0600466 return self.state == STATE_IDLE
Joe Hershberger166edec2015-05-19 13:21:17 -0500467
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900468 def handle_error(self):
469 """Handle error cases."""
Masahiro Yamada83c17672016-05-19 15:52:08 +0900470
Simon Glass9b191102023-09-23 13:44:09 -0600471 self.log.append(self.col.build(self.col.RED, 'Failed to process',
472 bright=True))
Simon Glassd9c1da22021-12-18 14:54:31 -0700473 if self.args.verbose:
Simon Glasse24ac992023-09-23 13:44:07 -0600474 for line in self.proc.stderr.read().decode().splitlines():
Simon Glass9b191102023-09-23 13:44:09 -0600475 self.log.append(self.col.build(self.col.CYAN, line, True))
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900476 self.finish(False)
Joe Hershberger166edec2015-05-19 13:21:17 -0500477
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900478 def do_defconfig(self):
479 """Run 'make <board>_defconfig' to create the .config file."""
Masahiro Yamada0f6beda2016-05-19 15:52:07 +0900480
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900481 cmd = list(self.make_cmd)
482 cmd.append(self.defconfig)
Simon Glass28155572024-07-17 16:56:52 +0100483 # pylint: disable=R1732
Simon Glass4f6725c2023-09-23 13:44:01 -0600484 self.proc = subprocess.Popen(cmd, stdout=self.devnull,
485 stderr=subprocess.PIPE,
486 cwd=self.current_src_dir)
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900487 self.state = STATE_DEFCONFIG
Masahiro Yamada0f6beda2016-05-19 15:52:07 +0900488
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900489 def do_autoconf(self):
Simon Glass8fb5bd02017-06-01 19:39:01 -0600490 """Run 'make AUTO_CONF_PATH'."""
Masahiro Yamadab6160812015-05-20 11:36:07 +0900491
Simon Glass257f5232017-07-10 14:47:47 -0600492 arch = self.parser.get_arch()
493 try:
Simon Glasse19a9cd2023-09-23 13:44:05 -0600494 tchain = self.toolchains.Select(arch)
Simon Glass257f5232017-07-10 14:47:47 -0600495 except ValueError:
Simon Glass9b191102023-09-23 13:44:09 -0600496 self.log.append(self.col.build(
497 self.col.YELLOW,
498 f"Tool chain for '{arch}' is missing: do nothing"))
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900499 self.finish(False)
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900500 return
Simon Glasse19a9cd2023-09-23 13:44:05 -0600501 env = tchain.MakeEnvironment(False)
Masahiro Yamadac4d76eb2016-05-19 15:51:53 +0900502
Masahiro Yamadab6160812015-05-20 11:36:07 +0900503 cmd = list(self.make_cmd)
Joe Hershberger765442b2015-05-19 13:21:18 -0500504 cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
Simon Glass8fb5bd02017-06-01 19:39:01 -0600505 cmd.append(AUTO_CONF_PATH)
Simon Glass28155572024-07-17 16:56:52 +0100506 # pylint: disable=R1732
Simon Glass4f6725c2023-09-23 13:44:01 -0600507 self.proc = subprocess.Popen(cmd, stdout=self.devnull, env=env,
508 stderr=subprocess.PIPE,
509 cwd=self.current_src_dir)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900510 self.state = STATE_AUTOCONF
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900511
Simon Glass43cf08f2017-06-01 19:39:02 -0600512 def do_build_db(self):
513 """Add the board to the database"""
514 configs = {}
Simon Glassaba238f2021-12-18 14:54:34 -0700515 for line in read_file(os.path.join(self.build_dir, AUTO_CONF_PATH)):
516 if line.startswith('CONFIG'):
517 config, value = line.split('=', 1)
518 configs[config] = value.rstrip()
Simon Glass43cf08f2017-06-01 19:39:02 -0600519 self.db_queue.put([self.defconfig, configs])
520 self.finish(True)
521
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900522 def do_savedefconfig(self):
523 """Update the .config and run 'make savedefconfig'."""
Simon Glassc1c10c22023-09-23 13:43:55 -0600524 if not self.args.force_sync:
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900525 self.finish(True)
526 return
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900527
528 cmd = list(self.make_cmd)
529 cmd.append('savedefconfig')
Simon Glass28155572024-07-17 16:56:52 +0100530 # pylint: disable=R1732
Simon Glass4f6725c2023-09-23 13:44:01 -0600531 self.proc = subprocess.Popen(cmd, stdout=self.devnull,
532 stderr=subprocess.PIPE)
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900533 self.state = STATE_SAVEDEFCONFIG
534
535 def update_defconfig(self):
536 """Update the input defconfig and go back to the idle state."""
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900537 orig_defconfig = os.path.join('configs', self.defconfig)
538 new_defconfig = os.path.join(self.build_dir, 'defconfig')
539 updated = not filecmp.cmp(orig_defconfig, new_defconfig)
540
541 if updated:
Simon Glass9b191102023-09-23 13:44:09 -0600542 self.log.append(
543 self.col.build(self.col.BLUE, 'defconfig updated', bright=True))
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900544
Simon Glassd9c1da22021-12-18 14:54:31 -0700545 if not self.args.dry_run and updated:
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900546 shutil.move(new_defconfig, orig_defconfig)
547 self.finish(True)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900548
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900549 def finish(self, success):
550 """Display log along with progress and go to the idle state.
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900551
Simon Glassb3464eb2021-12-18 14:54:35 -0700552 Args:
Simon Glassc89a2962023-09-23 13:43:58 -0600553 success (bool): Should be True when the defconfig was processed
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900554 successfully, or False when it fails.
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900555 """
556 # output at least 30 characters to hide the "* defconfigs out of *".
Simon Glass29790432023-09-23 13:44:11 -0600557 name = self.defconfig[:-len('_defconfig')]
Simon Glass4fd16e62023-09-23 13:44:06 -0600558 if self.log:
Simon Glasse24ac992023-09-23 13:44:07 -0600559
560 # Put the first log line on the first line
561 log = name.ljust(20) + ' ' + self.log[0]
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900562
Simon Glasse24ac992023-09-23 13:44:07 -0600563 if len(self.log) > 1:
564 log += '\n' + '\n'.join([' ' + s for s in self.log[1:]])
Simon Glass4fd16e62023-09-23 13:44:06 -0600565 # Some threads are running in parallel.
566 # Print log atomically to not mix up logs from different threads.
567 print(log, file=(sys.stdout if success else sys.stderr))
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900568
569 if not success:
Simon Glassd9c1da22021-12-18 14:54:31 -0700570 if self.args.exit_on_error:
Simon Glassdc634d92021-12-18 14:54:30 -0700571 sys.exit('Exit on error.')
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900572 # If --exit-on-error flag is not set, skip this board and continue.
573 # Record the failed board.
Simon Glass29790432023-09-23 13:44:11 -0600574 self.failed_boards.add(name)
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900575
Simon Glassbef67362023-09-23 13:44:10 -0600576 self.progress.inc(success)
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900577 self.progress.show()
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900578 self.state = STATE_IDLE
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900579
Masahiro Yamadab6160812015-05-20 11:36:07 +0900580 def get_failed_boards(self):
Masahiro Yamada1271b672016-08-22 22:18:20 +0900581 """Returns a set of failed boards (defconfigs) in this slot.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900582 """
583 return self.failed_boards
584
585class Slots:
586
587 """Controller of the array of subprocess slots."""
588
Simon Glass9b191102023-09-23 13:44:09 -0600589 def __init__(self, toolchains, args, progress, reference_src_dir, db_queue,
590 col):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900591 """Create a new slots controller.
592
Simon Glassb3464eb2021-12-18 14:54:35 -0700593 Args:
Simon Glass9b191102023-09-23 13:44:09 -0600594 toolchains (Toolchains): Toolchains object containing toolchains
595 args (Namespace): Program arguments
596 progress (Progress): A progress indicator.
597 reference_src_dir (str): Determine the true starting config state
598 from this source tree (None for none)
599 db_queue (Queue): output queue to write config info for the database
600 col (terminal.Color): Colour object
Masahiro Yamadab6160812015-05-20 11:36:07 +0900601 """
Simon Glassd9c1da22021-12-18 14:54:31 -0700602 self.args = args
Masahiro Yamadab6160812015-05-20 11:36:07 +0900603 self.slots = []
Simon Glass29790432023-09-23 13:44:11 -0600604 self.progress = progress
Simon Glass9b191102023-09-23 13:44:09 -0600605 self.col = col
Simon Glass34c505f2021-12-18 14:54:32 -0700606 devnull = subprocess.DEVNULL
Masahiro Yamadab6160812015-05-20 11:36:07 +0900607 make_cmd = get_make_cmd()
Simon Glassbeb825d2023-09-23 13:44:00 -0600608 for _ in range(args.jobs):
Simon Glass08d148a2023-09-23 13:43:54 -0600609 self.slots.append(Slot(toolchains, args, progress, devnull,
Simon Glass9b191102023-09-23 13:44:09 -0600610 make_cmd, reference_src_dir, db_queue, col))
Masahiro Yamadab6160812015-05-20 11:36:07 +0900611
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900612 def add(self, defconfig):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900613 """Add a new subprocess if a vacant slot is found.
614
Simon Glassb3464eb2021-12-18 14:54:35 -0700615 Args:
Simon Glassc89a2962023-09-23 13:43:58 -0600616 defconfig (str): defconfig name to be put into.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900617
618 Returns:
619 Return True on success or False on failure
620 """
621 for slot in self.slots:
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900622 if slot.add(defconfig):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900623 return True
624 return False
625
626 def available(self):
627 """Check if there is a vacant slot.
628
629 Returns:
630 Return True if at lease one vacant slot is found, False otherwise.
631 """
632 for slot in self.slots:
633 if slot.poll():
634 return True
635 return False
636
637 def empty(self):
638 """Check if all slots are vacant.
639
640 Returns:
641 Return True if all the slots are vacant, False otherwise.
642 """
643 ret = True
644 for slot in self.slots:
645 if not slot.poll():
646 ret = False
647 return ret
648
Simon Glass65709242023-09-23 13:44:13 -0600649 def write_failed_boards(self):
Simon Glass29790432023-09-23 13:44:11 -0600650 """Show the results of processing"""
Masahiro Yamada1271b672016-08-22 22:18:20 +0900651 boards = set()
Masahiro Yamadab6160812015-05-20 11:36:07 +0900652
653 for slot in self.slots:
Masahiro Yamada1271b672016-08-22 22:18:20 +0900654 boards |= slot.get_failed_boards()
Masahiro Yamadab6160812015-05-20 11:36:07 +0900655
Masahiro Yamada0153f032016-06-15 14:33:53 +0900656 if boards:
Simon Glass29790432023-09-23 13:44:11 -0600657 boards = '\n'.join(sorted(boards)) + '\n'
Simon Glass65709242023-09-23 13:44:13 -0600658 write_file(FAILED_LIST, boards)
659
Joe Hershbergerdade12e2015-05-19 13:21:22 -0500660
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900661class ReferenceSource:
662
663 """Reference source against which original configs should be parsed."""
664
665 def __init__(self, commit):
666 """Create a reference source directory based on a specified commit.
667
Simon Glassb3464eb2021-12-18 14:54:35 -0700668 Args:
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900669 commit: commit to git-clone
670 """
671 self.src_dir = tempfile.mkdtemp()
Simon Glassdc634d92021-12-18 14:54:30 -0700672 print('Cloning git repo to a separate work directory...')
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900673 subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
674 cwd=self.src_dir)
Simon Glass96f8f312023-09-23 13:43:59 -0600675 rev = subprocess.check_output(['git', 'rev-parse', '--short',
676 commit]).strip()
677 print(f"Checkout '{rev}' to build the original autoconf.mk.")
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900678 subprocess.check_output(['git', 'checkout', commit],
679 stderr=subprocess.STDOUT, cwd=self.src_dir)
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500680
681 def __del__(self):
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900682 """Delete the reference source directory
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500683
684 This function makes sure the temporary directory is cleaned away
685 even if Python suddenly dies due to error. It should be done in here
686 because it is guaranteed the destructor is always invoked when the
687 instance of the class gets unreferenced.
688 """
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900689 shutil.rmtree(self.src_dir)
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500690
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900691 def get_dir(self):
692 """Return the absolute path to the reference source directory."""
693
694 return self.src_dir
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500695
Simon Glass4c059032024-07-17 16:57:05 +0100696def move_config(args, col):
Simon Glass08d148a2023-09-23 13:43:54 -0600697 """Build database or sync config options to defconfig files.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900698
Simon Glassb3464eb2021-12-18 14:54:35 -0700699 Args:
Simon Glass9b191102023-09-23 13:44:09 -0600700 args (Namespace): Program arguments
Simon Glass9b191102023-09-23 13:44:09 -0600701 col (terminal.Color): Colour object
Simon Glass65709242023-09-23 13:44:13 -0600702
703 Returns:
Simon Glass4c059032024-07-17 16:57:05 +0100704 tuple:
705 config_db (dict of configs for each defconfig):
706 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
707 value: dict:
708 key: CONFIG option
709 value: Value of option
710 Progress: Progress indicator
Masahiro Yamadab6160812015-05-20 11:36:07 +0900711 """
Simon Glass4c059032024-07-17 16:57:05 +0100712 config_db = {}
713 db_queue = queue.Queue()
714 dbt = DatabaseThread(config_db, db_queue)
715 dbt.daemon = True
716 dbt.start()
717
718 check_clean_directory()
719 bsettings.setup('')
720
721 # Get toolchains to use
722 toolchains = toolchain.Toolchains()
723 toolchains.GetSettings()
724 toolchains.Scan(verbose=False)
725
Simon Glassd9c1da22021-12-18 14:54:31 -0700726 if args.git_ref:
727 reference_src = ReferenceSource(args.git_ref)
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900728 reference_src_dir = reference_src.get_dir()
729 else:
Masahiro Yamada8f5256a2016-06-15 14:33:52 +0900730 reference_src_dir = None
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500731
Simon Glassd9c1da22021-12-18 14:54:31 -0700732 if args.defconfigs:
733 defconfigs = get_matched_defconfigs(args.defconfigs)
Joe Hershbergerc6e043a2015-05-19 13:21:19 -0500734 else:
Masahiro Yamada58175e32016-07-25 19:15:28 +0900735 defconfigs = get_all_defconfigs()
Masahiro Yamadab6160812015-05-20 11:36:07 +0900736
Simon Glassbef67362023-09-23 13:44:10 -0600737 progress = Progress(col, len(defconfigs))
Simon Glass9b191102023-09-23 13:44:09 -0600738 slots = Slots(toolchains, args, progress, reference_src_dir, db_queue, col)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900739
740 # Main loop to process defconfig files:
741 # Add a new subprocess into a vacant slot.
742 # Sleep if there is no available slot.
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900743 for defconfig in defconfigs:
744 while not slots.add(defconfig):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900745 while not slots.available():
746 # No available slot: sleep for a while
747 time.sleep(SLEEP_TIME)
748
749 # wait until all the subprocesses finish
750 while not slots.empty():
751 time.sleep(SLEEP_TIME)
752
Simon Glass65709242023-09-23 13:44:13 -0600753 slots.write_failed_boards()
Simon Glass4c059032024-07-17 16:57:05 +0100754 db_queue.join()
755 return config_db, progress
Masahiro Yamadab6160812015-05-20 11:36:07 +0900756
Simon Glass44116332017-06-15 21:39:33 -0600757def find_kconfig_rules(kconf, config, imply_config):
758 """Check whether a config has a 'select' or 'imply' keyword
759
760 Args:
Simon Glassc89a2962023-09-23 13:43:58 -0600761 kconf (Kconfiglib.Kconfig): Kconfig object
762 config (str): Name of config to check (without CONFIG_ prefix)
763 imply_config (str): Implying config (without CONFIG_ prefix) which may
764 or may not have an 'imply' for 'config')
Simon Glass44116332017-06-15 21:39:33 -0600765
766 Returns:
767 Symbol object for 'config' if found, else None
768 """
Tom Rini3c5f4152019-09-20 17:42:09 -0400769 sym = kconf.syms.get(imply_config)
Simon Glass44116332017-06-15 21:39:33 -0600770 if sym:
Simon Glassbeb825d2023-09-23 13:44:00 -0600771 for sel, _ in (sym.selects + sym.implies):
Simon Glass93c0a9e2021-12-18 08:09:42 -0700772 if sel.name == config:
Simon Glass44116332017-06-15 21:39:33 -0600773 return sym
774 return None
775
Simon Glass28155572024-07-17 16:56:52 +0100776def check_imply_rule(kconf, imply_config):
Simon Glass44116332017-06-15 21:39:33 -0600777 """Check if we can add an 'imply' option
778
779 This finds imply_config in the Kconfig and looks to see if it is possible
780 to add an 'imply' for 'config' to that part of the Kconfig.
781
782 Args:
Simon Glassc89a2962023-09-23 13:43:58 -0600783 kconf (Kconfiglib.Kconfig): Kconfig object
Simon Glassc89a2962023-09-23 13:43:58 -0600784 imply_config (str): Implying config (without CONFIG_ prefix) which may
785 or may not have an 'imply' for 'config')
Simon Glass44116332017-06-15 21:39:33 -0600786
787 Returns:
788 tuple:
Simon Glassc89a2962023-09-23 13:43:58 -0600789 str: filename of Kconfig file containing imply_config, or None if
790 none
791 int: line number within the Kconfig file, or 0 if none
792 str: message indicating the result
Simon Glass44116332017-06-15 21:39:33 -0600793 """
Tom Rini3c5f4152019-09-20 17:42:09 -0400794 sym = kconf.syms.get(imply_config)
Simon Glass44116332017-06-15 21:39:33 -0600795 if not sym:
796 return 'cannot find sym'
Simon Glass520b47a2021-07-21 21:35:53 -0600797 nodes = sym.nodes
798 if len(nodes) != 1:
Simon Glass96f8f312023-09-23 13:43:59 -0600799 return f'{len(nodes)} locations'
Simon Glass93c0a9e2021-12-18 08:09:42 -0700800 node = nodes[0]
801 fname, linenum = node.filename, node.linenr
Simon Glass44116332017-06-15 21:39:33 -0600802 cwd = os.getcwd()
803 if cwd and fname.startswith(cwd):
804 fname = fname[len(cwd) + 1:]
Simon Glass96f8f312023-09-23 13:43:59 -0600805 file_line = f' at {fname}:{linenum}'
Simon Glassaba238f2021-12-18 14:54:34 -0700806 data = read_file(fname)
Simon Glass96f8f312023-09-23 13:43:59 -0600807 if data[linenum - 1] != f'config {imply_config}':
808 return None, 0, f'bad sym format {data[linenum]}{file_line})'
809 return fname, linenum, f'adding{file_line}'
Simon Glass44116332017-06-15 21:39:33 -0600810
811def add_imply_rule(config, fname, linenum):
812 """Add a new 'imply' option to a Kconfig
813
814 Args:
Simon Glassc89a2962023-09-23 13:43:58 -0600815 config (str): config option to add an imply for (without CONFIG_ prefix)
816 fname (str): Kconfig filename to update
817 linenum (int): Line number to place the 'imply' before
Simon Glass44116332017-06-15 21:39:33 -0600818
819 Returns:
820 Message indicating the result
821 """
Simon Glass96f8f312023-09-23 13:43:59 -0600822 file_line = f' at {fname}:{linenum}'
Simon Glassaba238f2021-12-18 14:54:34 -0700823 data = read_file(fname)
Simon Glass44116332017-06-15 21:39:33 -0600824 linenum -= 1
825
826 for offset, line in enumerate(data[linenum:]):
827 if line.strip().startswith('help') or not line:
Simon Glass96f8f312023-09-23 13:43:59 -0600828 data.insert(linenum + offset, f'\timply {config}')
Simon Glassb09ae452021-12-18 14:54:33 -0700829 write_file(fname, data)
Simon Glass96f8f312023-09-23 13:43:59 -0600830 return f'added{file_line}'
Simon Glass44116332017-06-15 21:39:33 -0600831
832 return 'could not insert%s'
833
834(IMPLY_MIN_2, IMPLY_TARGET, IMPLY_CMD, IMPLY_NON_ARCH_BOARD) = (
835 1, 2, 4, 8)
Simon Glass92e55582017-06-15 21:39:32 -0600836
837IMPLY_FLAGS = {
838 'min2': [IMPLY_MIN_2, 'Show options which imply >2 boards (normally >5)'],
839 'target': [IMPLY_TARGET, 'Allow CONFIG_TARGET_... options to imply'],
840 'cmd': [IMPLY_CMD, 'Allow CONFIG_CMD_... to imply'],
Simon Glass44116332017-06-15 21:39:33 -0600841 'non-arch-board': [
842 IMPLY_NON_ARCH_BOARD,
843 'Allow Kconfig options outside arch/ and /board/ to imply'],
Simon Glassb3464eb2021-12-18 14:54:35 -0700844}
Simon Glass92e55582017-06-15 21:39:32 -0600845
Simon Glassf931c2f2021-12-18 08:09:43 -0700846
847def read_database():
848 """Read in the config database
849
850 Returns:
851 tuple:
852 set of all config options seen (each a str)
853 set of all defconfigs seen (each a str)
854 dict of configs for each defconfig:
855 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
856 value: dict:
857 key: CONFIG option
858 value: Value of option
859 dict of defconfigs for each config:
860 key: CONFIG option
861 value: set of boards using that option
862
863 """
864 configs = {}
865
866 # key is defconfig name, value is dict of (CONFIG_xxx, value)
867 config_db = {}
868
869 # Set of all config options we have seen
870 all_configs = set()
871
872 # Set of all defconfigs we have seen
873 all_defconfigs = set()
874
875 defconfig_db = collections.defaultdict(set)
Simon Glass3a315fa2024-07-17 16:56:49 +0100876 defconfig = None
Simon Glassaba238f2021-12-18 14:54:34 -0700877 for line in read_file(CONFIG_DATABASE):
878 line = line.rstrip()
879 if not line: # Separator between defconfigs
880 config_db[defconfig] = configs
881 all_defconfigs.add(defconfig)
882 configs = {}
883 elif line[0] == ' ': # CONFIG line
884 config, value = line.strip().split('=', 1)
885 configs[config] = value
886 defconfig_db[config].add(defconfig)
887 all_configs.add(config)
888 else: # New defconfig
889 defconfig = line
Simon Glassf931c2f2021-12-18 08:09:43 -0700890
891 return all_configs, all_defconfigs, config_db, defconfig_db
892
893
Simon Glass44116332017-06-15 21:39:33 -0600894def do_imply_config(config_list, add_imply, imply_flags, skip_added,
895 check_kconfig=True, find_superset=False):
Simon Glassc6e73cf2017-06-01 19:39:03 -0600896 """Find CONFIG options which imply those in the list
897
898 Some CONFIG options can be implied by others and this can help to reduce
899 the size of the defconfig files. For example, CONFIG_X86 implies
900 CONFIG_CMD_IRQ, so we can put 'imply CMD_IRQ' under 'config X86' and
901 all x86 boards will have that option, avoiding adding CONFIG_CMD_IRQ to
902 each of the x86 defconfig files.
903
Simon Glassbf0e11a2023-09-23 13:44:14 -0600904 This function uses the qconfig database to find such options. It
Simon Glassc6e73cf2017-06-01 19:39:03 -0600905 displays a list of things that could possibly imply those in the list.
906 The algorithm ignores any that start with CONFIG_TARGET since these
907 typically refer to only a few defconfigs (often one). It also does not
908 display a config with less than 5 defconfigs.
909
910 The algorithm works using sets. For each target config in config_list:
911 - Get the set 'defconfigs' which use that target config
912 - For each config (from a list of all configs):
913 - Get the set 'imply_defconfig' of defconfigs which use that config
914 -
915 - If imply_defconfigs contains anything not in defconfigs then
916 this config does not imply the target config
917
Simon Glass18352112024-07-17 16:57:03 +0100918 Args:
919 config_list (list of str): List of CONFIG options to check
920 add_imply (bool): Automatically add an 'imply' for each config.
921 imply_flags (int): Flags which control which implying configs are allowed
Simon Glass92e55582017-06-15 21:39:32 -0600922 (IMPLY_...)
Simon Glass18352112024-07-17 16:57:03 +0100923 skip_added (bool): Don't show options which already have an imply added.
924 check_kconfig (bool): Check if implied symbols already have an 'imply' or
Simon Glass44116332017-06-15 21:39:33 -0600925 'select' for the target config, and show this information if so.
Simon Glass18352112024-07-17 16:57:03 +0100926 find_superset (bool): True to look for configs which are a superset of those
Simon Glassc6e73cf2017-06-01 19:39:03 -0600927 already found. So for example if CONFIG_EXYNOS5 implies an option,
928 but CONFIG_EXYNOS covers a larger set of defconfigs and also
929 implies that option, this will drop the former in favour of the
930 latter. In practice this option has not proved very used.
931
932 Note the terminoloy:
933 config - a CONFIG_XXX options (a string, e.g. 'CONFIG_CMD_EEPROM')
934 defconfig - a defconfig file (a string, e.g. 'configs/snow_defconfig')
935 """
Simon Glassa0a61602024-07-17 16:56:51 +0100936 kconf = scan_kconfig() if check_kconfig else None
Simon Glass44116332017-06-15 21:39:33 -0600937 if add_imply and add_imply != 'all':
Simon Glass93c0a9e2021-12-18 08:09:42 -0700938 add_imply = add_imply.split(',')
Simon Glass44116332017-06-15 21:39:33 -0600939
Simon Glassbeb825d2023-09-23 13:44:00 -0600940 all_configs, all_defconfigs, _, defconfig_db = read_database()
Simon Glassc6e73cf2017-06-01 19:39:03 -0600941
Simon Glass93c0a9e2021-12-18 08:09:42 -0700942 # Work through each target config option in turn, independently
Simon Glassc6e73cf2017-06-01 19:39:03 -0600943 for config in config_list:
944 defconfigs = defconfig_db.get(config)
945 if not defconfigs:
Simon Glass96f8f312023-09-23 13:43:59 -0600946 print(f'{config} not found in any defconfig')
Simon Glassc6e73cf2017-06-01 19:39:03 -0600947 continue
948
949 # Get the set of defconfigs without this one (since a config cannot
950 # imply itself)
951 non_defconfigs = all_defconfigs - defconfigs
952 num_defconfigs = len(defconfigs)
Simon Glass96f8f312023-09-23 13:43:59 -0600953 print(f'{config} found in {num_defconfigs}/{len(all_configs)} defconfigs')
Simon Glassc6e73cf2017-06-01 19:39:03 -0600954
955 # This will hold the results: key=config, value=defconfigs containing it
956 imply_configs = {}
957 rest_configs = all_configs - set([config])
958
959 # Look at every possible config, except the target one
960 for imply_config in rest_configs:
Simon Glass92e55582017-06-15 21:39:32 -0600961 if 'ERRATUM' in imply_config:
Simon Glassc6e73cf2017-06-01 19:39:03 -0600962 continue
Simon Glassb3464eb2021-12-18 14:54:35 -0700963 if not imply_flags & IMPLY_CMD:
Simon Glass92e55582017-06-15 21:39:32 -0600964 if 'CONFIG_CMD' in imply_config:
965 continue
Simon Glassb3464eb2021-12-18 14:54:35 -0700966 if not imply_flags & IMPLY_TARGET:
Simon Glass92e55582017-06-15 21:39:32 -0600967 if 'CONFIG_TARGET' in imply_config:
968 continue
Simon Glassc6e73cf2017-06-01 19:39:03 -0600969
970 # Find set of defconfigs that have this config
971 imply_defconfig = defconfig_db[imply_config]
972
973 # Get the intersection of this with defconfigs containing the
974 # target config
975 common_defconfigs = imply_defconfig & defconfigs
976
977 # Get the set of defconfigs containing this config which DO NOT
978 # also contain the taret config. If this set is non-empty it means
979 # that this config affects other defconfigs as well as (possibly)
980 # the ones affected by the target config. This means it implies
981 # things we don't want to imply.
982 not_common_defconfigs = imply_defconfig & non_defconfigs
983 if not_common_defconfigs:
984 continue
985
986 # If there are common defconfigs, imply_config may be useful
987 if common_defconfigs:
988 skip = False
989 if find_superset:
Simon Glass1f701862019-10-31 07:42:57 -0600990 for prev in list(imply_configs.keys()):
Simon Glassc6e73cf2017-06-01 19:39:03 -0600991 prev_count = len(imply_configs[prev])
992 count = len(common_defconfigs)
993 if (prev_count > count and
994 (imply_configs[prev] & common_defconfigs ==
995 common_defconfigs)):
996 # skip imply_config because prev is a superset
997 skip = True
998 break
Simon Glasse19a9cd2023-09-23 13:44:05 -0600999 if count > prev_count:
Simon Glassc6e73cf2017-06-01 19:39:03 -06001000 # delete prev because imply_config is a superset
1001 del imply_configs[prev]
1002 if not skip:
1003 imply_configs[imply_config] = common_defconfigs
1004
1005 # Now we have a dict imply_configs of configs which imply each config
1006 # The value of each dict item is the set of defconfigs containing that
1007 # config. Rank them so that we print the configs that imply the largest
1008 # number of defconfigs first.
Simon Glass44116332017-06-15 21:39:33 -06001009 ranked_iconfigs = sorted(imply_configs,
Simon Glassc6e73cf2017-06-01 19:39:03 -06001010 key=lambda k: len(imply_configs[k]), reverse=True)
Simon Glass44116332017-06-15 21:39:33 -06001011 kconfig_info = ''
1012 cwd = os.getcwd()
1013 add_list = collections.defaultdict(list)
1014 for iconfig in ranked_iconfigs:
1015 num_common = len(imply_configs[iconfig])
Simon Glassc6e73cf2017-06-01 19:39:03 -06001016
1017 # Don't bother if there are less than 5 defconfigs affected.
Simon Glass92e55582017-06-15 21:39:32 -06001018 if num_common < (2 if imply_flags & IMPLY_MIN_2 else 5):
Simon Glassc6e73cf2017-06-01 19:39:03 -06001019 continue
Simon Glass44116332017-06-15 21:39:33 -06001020 missing = defconfigs - imply_configs[iconfig]
Simon Glassc6e73cf2017-06-01 19:39:03 -06001021 missing_str = ', '.join(missing) if missing else 'all'
1022 missing_str = ''
Simon Glass44116332017-06-15 21:39:33 -06001023 show = True
1024 if kconf:
1025 sym = find_kconfig_rules(kconf, config[CONFIG_LEN:],
1026 iconfig[CONFIG_LEN:])
1027 kconfig_info = ''
1028 if sym:
Simon Glass520b47a2021-07-21 21:35:53 -06001029 nodes = sym.nodes
1030 if len(nodes) == 1:
1031 fname, linenum = nodes[0].filename, nodes[0].linenr
Simon Glass44116332017-06-15 21:39:33 -06001032 if cwd and fname.startswith(cwd):
1033 fname = fname[len(cwd) + 1:]
Simon Glass96f8f312023-09-23 13:43:59 -06001034 kconfig_info = f'{fname}:{linenum}'
Simon Glass44116332017-06-15 21:39:33 -06001035 if skip_added:
1036 show = False
1037 else:
Tom Rini3c5f4152019-09-20 17:42:09 -04001038 sym = kconf.syms.get(iconfig[CONFIG_LEN:])
Simon Glass44116332017-06-15 21:39:33 -06001039 fname = ''
1040 if sym:
Simon Glass520b47a2021-07-21 21:35:53 -06001041 nodes = sym.nodes
1042 if len(nodes) == 1:
1043 fname, linenum = nodes[0].filename, nodes[0].linenr
Simon Glass44116332017-06-15 21:39:33 -06001044 if cwd and fname.startswith(cwd):
1045 fname = fname[len(cwd) + 1:]
1046 in_arch_board = not sym or (fname.startswith('arch') or
1047 fname.startswith('board'))
1048 if (not in_arch_board and
Simon Glassb3464eb2021-12-18 14:54:35 -07001049 not imply_flags & IMPLY_NON_ARCH_BOARD):
Simon Glass44116332017-06-15 21:39:33 -06001050 continue
1051
1052 if add_imply and (add_imply == 'all' or
1053 iconfig in add_imply):
1054 fname, linenum, kconfig_info = (check_imply_rule(kconf,
Simon Glass28155572024-07-17 16:56:52 +01001055 iconfig[CONFIG_LEN:]))
Simon Glass44116332017-06-15 21:39:33 -06001056 if fname:
1057 add_list[fname].append(linenum)
Simon Glassc6e73cf2017-06-01 19:39:03 -06001058
Simon Glass44116332017-06-15 21:39:33 -06001059 if show and kconfig_info != 'skip':
Simon Glass0f439202024-07-17 16:56:53 +01001060 print(f'{num_common:5} : '
1061 f'{iconfig.ljust(30)}{kconfig_info.ljust(25)} {missing_str}')
Simon Glass44116332017-06-15 21:39:33 -06001062
1063 # Having collected a list of things to add, now we add them. We process
1064 # each file from the largest line number to the smallest so that
1065 # earlier additions do not affect our line numbers. E.g. if we added an
1066 # imply at line 20 it would change the position of each line after
1067 # that.
Simon Glass1f701862019-10-31 07:42:57 -06001068 for fname, linenums in add_list.items():
Simon Glass44116332017-06-15 21:39:33 -06001069 for linenum in sorted(linenums, reverse=True):
1070 add_imply_rule(config[CONFIG_LEN:], fname, linenum)
1071
Simon Glass99f79422022-02-08 11:49:46 -07001072def defconfig_matches(configs, re_match):
1073 """Check if any CONFIG option matches a regex
1074
1075 The match must be complete, i.e. from the start to end of the CONFIG option.
1076
1077 Args:
1078 configs (dict): Dict of CONFIG options:
1079 key: CONFIG option
1080 value: Value of option
1081 re_match (re.Pattern): Match to check
1082
1083 Returns:
1084 bool: True if any CONFIG matches the regex
1085 """
1086 for cfg in configs:
Simon Glassfea71c92022-03-05 20:18:54 -07001087 if re_match.fullmatch(cfg):
Simon Glass99f79422022-02-08 11:49:46 -07001088 return True
1089 return False
Simon Glassc6e73cf2017-06-01 19:39:03 -06001090
Simon Glass0082b2e2021-12-18 08:09:46 -07001091def do_find_config(config_list):
1092 """Find boards with a given combination of CONFIGs
1093
Simon Glass3c4cb202024-07-17 16:57:04 +01001094 Args:
1095 config_list (list of str): List of CONFIG options to check (each a regex
1096 consisting of a config option, with or without a CONFIG_ prefix. If
1097 an option is preceded by a tilde (~) then it must be false,
1098 otherwise it must be true)
1099
1100 Returns:
1101 int: exit code (0 for success)
Simon Glass0082b2e2021-12-18 08:09:46 -07001102 """
Simon Glassbeb825d2023-09-23 13:44:00 -06001103 _, all_defconfigs, config_db, _ = read_database()
Simon Glass0082b2e2021-12-18 08:09:46 -07001104
Simon Glass0082b2e2021-12-18 08:09:46 -07001105 # Start with all defconfigs
1106 out = all_defconfigs
1107
1108 # Work through each config in turn
Simon Glass0082b2e2021-12-18 08:09:46 -07001109 for item in config_list:
1110 # Get the real config name and whether we want this config or not
1111 cfg = item
1112 want = True
1113 if cfg[0] == '~':
1114 want = False
1115 cfg = cfg[1:]
1116
Simon Glass0082b2e2021-12-18 08:09:46 -07001117 # Search everything that is still in the running. If it has a config
1118 # that we want, or doesn't have one that we don't, add it into the
1119 # running for the next stage
1120 in_list = out
1121 out = set()
Simon Glass99f79422022-02-08 11:49:46 -07001122 re_match = re.compile(cfg)
Simon Glass0082b2e2021-12-18 08:09:46 -07001123 for defc in in_list:
Simon Glass99f79422022-02-08 11:49:46 -07001124 has_cfg = defconfig_matches(config_db[defc], re_match)
Simon Glass0082b2e2021-12-18 08:09:46 -07001125 if has_cfg == want:
1126 out.add(defc)
Tom Rinic1b64aa2022-12-04 10:14:16 -05001127 print(f'{len(out)} matches')
1128 print(' '.join(item.split('_defconfig')[0] for item in out))
Simon Glass3c4cb202024-07-17 16:57:04 +01001129 return 0
Simon Glass0082b2e2021-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 Glass4f6725c2023-09-23 13:44:01 -06001148 oper = ''
Simon Glass0082b2e2021-12-18 08:09:46 -07001149 if cfg[0] == '~':
Simon Glass4f6725c2023-09-23 13:44:01 -06001150 oper = cfg[0]
Simon Glass0082b2e2021-12-18 08:09:46 -07001151 cfg = cfg[1:]
1152 if not cfg.startswith('CONFIG_'):
1153 cfg = 'CONFIG_' + cfg
Simon Glass4f6725c2023-09-23 13:44:01 -06001154 return oper + cfg
Simon Glass0082b2e2021-12-18 08:09:46 -07001155
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001156
Simon Glass2deee262023-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 Glass4c4eb7c2023-02-01 13:19:12 -07001161
1162class ConfigUse:
Simon Glass28155572024-07-17 16:56:52 +01001163 """Tracks whether a config relates to SPL or not"""
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001164 def __init__(self, cfg, is_spl, fname, rest):
Simon Glass28155572024-07-17 16:56:52 +01001165 """Set up a new ConfigUse
1166
1167 Args:
1168 cfg (str): CONFIG option, without any CONFIG_ or SPL_ prefix
1169 is_spl (bool): True if this option relates to SPL
1170 fname (str): Makefile filename where the CONFIG option was found
1171 rest (str): Line of the Makefile
1172 """
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001173 self.cfg = cfg
1174 self.is_spl = is_spl
1175 self.fname = fname
1176 self.rest = rest
1177
1178 def __hash__(self):
1179 return hash((self.cfg, self.is_spl))
1180
1181def scan_makefiles(fnames):
1182 """Scan Makefiles looking for Kconfig options
1183
1184 Looks for uses of CONFIG options in Makefiles
1185
1186 Args:
1187 fnames (list of tuple):
1188 str: Makefile filename where the option was found
1189 str: Line of the Makefile
1190
1191 Returns:
1192 tuple:
1193 dict: all_uses
1194 key (ConfigUse): object
1195 value (list of str): matching lines
1196 dict: Uses by filename
1197 key (str): filename
1198 value (set of ConfigUse): uses in that filename
1199
1200 >>> RE_MK_CONFIGS.search('CONFIG_FRED').groups()
1201 (None, 'FRED')
1202 >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_)MARY').groups()
1203 ('$(SPL_)', 'MARY')
1204 >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_TPL_)MARY').groups()
1205 ('$(SPL_TPL_)', 'MARY')
1206 """
1207 all_uses = collections.defaultdict(list)
1208 fname_uses = {}
1209 for fname, rest in fnames:
1210 m_iter = RE_MK_CONFIGS.finditer(rest)
Simon Glass4f6725c2023-09-23 13:44:01 -06001211 for mat in m_iter:
1212 real_opt = mat.group(2)
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001213 if real_opt == '':
1214 continue
1215 is_spl = False
Simon Glass4f6725c2023-09-23 13:44:01 -06001216 if mat.group(1):
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001217 is_spl = True
1218 use = ConfigUse(real_opt, is_spl, fname, rest)
1219 if fname not in fname_uses:
1220 fname_uses[fname] = set()
1221 fname_uses[fname].add(use)
1222 all_uses[use].append(rest)
1223 return all_uses, fname_uses
1224
1225
1226def scan_src_files(fnames):
1227 """Scan source files (other than Makefiles) looking for Kconfig options
1228
1229 Looks for uses of CONFIG options
1230
1231 Args:
1232 fnames (list of tuple):
1233 str: Makefile filename where the option was found
1234 str: Line of the Makefile
1235
1236 Returns:
1237 tuple:
1238 dict: all_uses
1239 key (ConfigUse): object
1240 value (list of str): matching lines
1241 dict: Uses by filename
1242 key (str): filename
1243 value (set of ConfigUse): uses in that filename
1244
1245 >>> RE_C_CONFIGS.search('CONFIG_FRED').groups()
1246 ('FRED',)
1247 >>> RE_CONFIG_IS.search('CONFIG_IS_ENABLED(MARY)').groups()
1248 ('MARY',)
1249 >>> RE_CONFIG_IS.search('#if CONFIG_IS_ENABLED(OF_PLATDATA)').groups()
1250 ('OF_PLATDATA',)
1251 """
Simon Glassbeb825d2023-09-23 13:44:00 -06001252 fname = None
1253 rest = None
1254
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001255 def add_uses(m_iter, is_spl):
Simon Glass4f6725c2023-09-23 13:44:01 -06001256 for mat in m_iter:
1257 real_opt = mat.group(1)
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001258 if real_opt == '':
1259 continue
1260 use = ConfigUse(real_opt, is_spl, fname, rest)
1261 if fname not in fname_uses:
1262 fname_uses[fname] = set()
1263 fname_uses[fname].add(use)
1264 all_uses[use].append(rest)
1265
1266 all_uses = collections.defaultdict(list)
1267 fname_uses = {}
1268 for fname, rest in fnames:
1269 m_iter = RE_C_CONFIGS.finditer(rest)
1270 add_uses(m_iter, False)
1271
1272 m_iter2 = RE_CONFIG_IS.finditer(rest)
1273 add_uses(m_iter2, True)
1274
1275 return all_uses, fname_uses
1276
1277
1278MODE_NORMAL, MODE_SPL, MODE_PROPER = range(3)
1279
1280def do_scan_source(path, do_update):
1281 """Scan the source tree for Kconfig inconsistencies
1282
1283 Args:
1284 path (str): Path to source tree
1285 do_update (bool) : True to write to scripts/kconf_... files
1286 """
1287 def is_not_proper(name):
1288 for prefix in SPL_PREFIXES:
1289 if name.startswith(prefix):
1290 return name[len(prefix):]
1291 return False
1292
1293 def check_not_found(all_uses, spl_mode):
1294 """Check for Kconfig options mentioned in the source but not in Kconfig
1295
1296 Args:
1297 all_uses (dict):
1298 key (ConfigUse): object
1299 value (list of str): matching lines
1300 spl_mode (int): If MODE_SPL, look at source code which implies
1301 an SPL_ option, but for which there is none;
1302 for MOD_PROPER, look at source code which implies a Proper
1303 option (i.e. use of CONFIG_IS_ENABLED() or $(SPL_) or
1304 $(SPL_TPL_) but for which there none;
1305 if MODE_NORMAL, ignore SPL
1306
1307 Returns:
1308 dict:
1309 key (str): CONFIG name (without 'CONFIG_' prefix
1310 value (list of ConfigUse): List of uses of this CONFIG
1311 """
1312 # Make sure we know about all the options
1313 not_found = collections.defaultdict(list)
Simon Glassbeb825d2023-09-23 13:44:00 -06001314 for use, _ in all_uses.items():
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001315 name = use.cfg
1316 if name in IGNORE_SYMS:
1317 continue
1318 check = True
1319
1320 if spl_mode == MODE_SPL:
1321 check = use.is_spl
1322
1323 # If it is an SPL symbol, try prepending all SPL_ prefixes to
1324 # find at least one SPL symbol
1325 if use.is_spl:
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001326 for prefix in SPL_PREFIXES:
1327 try_name = prefix + name
1328 sym = kconf.syms.get(try_name)
1329 if sym:
1330 break
1331 if not sym:
1332 not_found[f'SPL_{name}'].append(use)
1333 continue
1334 elif spl_mode == MODE_PROPER:
1335 # Try to find the Proper version of this symbol, i.e. without
1336 # the SPL_ prefix
1337 proper_name = is_not_proper(name)
1338 if proper_name:
1339 name = proper_name
1340 elif not use.is_spl:
1341 check = False
1342 else: # MODE_NORMAL
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001343 sym = kconf.syms.get(name)
1344 if not sym:
1345 proper_name = is_not_proper(name)
1346 if proper_name:
1347 name = proper_name
1348 sym = kconf.syms.get(name)
1349 if not sym:
1350 for prefix in SPL_PREFIXES:
1351 try_name = prefix + name
1352 sym = kconf.syms.get(try_name)
1353 if sym:
1354 break
1355 if not sym:
1356 not_found[name].append(use)
1357 continue
1358
1359 sym = kconf.syms.get(name)
1360 if not sym and check:
1361 not_found[name].append(use)
1362 return not_found
1363
1364 def show_uses(uses):
1365 """Show a list of uses along with their filename and code snippet
1366
1367 Args:
1368 uses (dict):
1369 key (str): CONFIG name (without 'CONFIG_' prefix
1370 value (list of ConfigUse): List of uses of this CONFIG
1371 """
1372 for name in sorted(uses):
1373 print(f'{name}: ', end='')
1374 for i, use in enumerate(uses[name]):
1375 print(f'{" " if i else ""}{use.fname}: {use.rest.strip()}')
1376
1377
1378 print('Scanning Kconfig')
Simon Glassa0a61602024-07-17 16:56:51 +01001379 kconf = scan_kconfig()
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001380 print(f'Scanning source in {path}')
1381 args = ['git', 'grep', '-E', r'IS_ENABLED|\bCONFIG']
1382 with subprocess.Popen(args, stdout=subprocess.PIPE) as proc:
Simon Glassbeb825d2023-09-23 13:44:00 -06001383 out, _ = proc.communicate()
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001384 lines = out.splitlines()
1385 re_fname = re.compile('^([^:]*):(.*)')
1386 src_list = []
1387 mk_list = []
1388 for line in lines:
1389 linestr = line.decode('utf-8')
1390 m_fname = re_fname.search(linestr)
1391 if not m_fname:
1392 continue
1393 fname, rest = m_fname.groups()
1394 dirname, leaf = os.path.split(fname)
1395 root, ext = os.path.splitext(leaf)
1396 if ext == '.autoconf':
1397 pass
1398 elif ext in ['.c', '.h', '.S', '.lds', '.dts', '.dtsi', '.asl', '.cfg',
1399 '.env', '.tmpl']:
1400 src_list.append([fname, rest])
1401 elif 'Makefile' in root or ext == '.mk':
1402 mk_list.append([fname, rest])
1403 elif ext in ['.yml', '.sh', '.py', '.awk', '.pl', '.rst', '', '.sed']:
1404 pass
1405 elif 'Kconfig' in root or 'Kbuild' in root:
1406 pass
1407 elif 'README' in root:
1408 pass
1409 elif dirname in ['configs']:
1410 pass
1411 elif dirname.startswith('doc') or dirname.startswith('scripts/kconfig'):
1412 pass
1413 else:
1414 print(f'Not sure how to handle file {fname}')
1415
1416 # Scan the Makefiles
Simon Glassbeb825d2023-09-23 13:44:00 -06001417 all_uses, _ = scan_makefiles(mk_list)
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001418
1419 spl_not_found = set()
1420 proper_not_found = set()
1421
1422 # Make sure we know about all the options
1423 print('\nCONFIG options present in Makefiles but not Kconfig:')
1424 not_found = check_not_found(all_uses, MODE_NORMAL)
1425 show_uses(not_found)
1426
1427 print('\nCONFIG options present in Makefiles but not Kconfig (SPL):')
1428 not_found = check_not_found(all_uses, MODE_SPL)
1429 show_uses(not_found)
Simon Glassd708cf72023-09-23 13:44:03 -06001430 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001431
1432 print('\nCONFIG options used as Proper in Makefiles but without a non-SPL_ variant:')
1433 not_found = check_not_found(all_uses, MODE_PROPER)
1434 show_uses(not_found)
Simon Glassd708cf72023-09-23 13:44:03 -06001435 proper_not_found |= {not_found.keys()}
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001436
1437 # Scan the source code
Simon Glassbeb825d2023-09-23 13:44:00 -06001438 all_uses, _ = scan_src_files(src_list)
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001439
1440 # Make sure we know about all the options
1441 print('\nCONFIG options present in source but not Kconfig:')
1442 not_found = check_not_found(all_uses, MODE_NORMAL)
1443 show_uses(not_found)
1444
1445 print('\nCONFIG options present in source but not Kconfig (SPL):')
1446 not_found = check_not_found(all_uses, MODE_SPL)
1447 show_uses(not_found)
Simon Glassd708cf72023-09-23 13:44:03 -06001448 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001449
1450 print('\nCONFIG options used as Proper in source but without a non-SPL_ variant:')
1451 not_found = check_not_found(all_uses, MODE_PROPER)
1452 show_uses(not_found)
Simon Glassd708cf72023-09-23 13:44:03 -06001453 proper_not_found |= {not_found.keys()}
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001454
1455 print('\nCONFIG options used as SPL but without an SPL_ variant:')
1456 for item in sorted(spl_not_found):
1457 print(f' {item}')
1458
1459 print('\nCONFIG options used as Proper but without a non-SPL_ variant:')
1460 for item in sorted(proper_not_found):
1461 print(f' {item}')
1462
1463 # Write out the updated information
1464 if do_update:
Simon Glass47f22892023-09-23 13:44:04 -06001465 with open(os.path.join(path, 'scripts', 'conf_nospl'), 'w',
1466 encoding='utf-8') as out:
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001467 print('# These options should not be enabled in SPL builds\n',
1468 file=out)
1469 for item in sorted(spl_not_found):
1470 print(item, file=out)
Simon Glass47f22892023-09-23 13:44:04 -06001471 with open(os.path.join(path, 'scripts', 'conf_noproper'), 'w',
1472 encoding='utf-8') as out:
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001473 print('# These options should not be enabled in Proper builds\n',
1474 file=out)
1475 for item in sorted(proper_not_found):
1476 print(item, file=out)
Simon Glass6901a642024-07-17 16:57:02 +01001477 return 0
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001478
Simon Glass0082b2e2021-12-18 08:09:46 -07001479
Simon Glassfe11dcf2024-07-17 16:56:55 +01001480def parse_args():
1481 """Parse the program arguments
1482
1483 Returns:
1484 tuple:
1485 argparse.ArgumentParser: parser
1486 argparse.Namespace: Parsed arguments
1487 """
Masahiro Yamadab6160812015-05-20 11:36:07 +09001488 try:
1489 cpu_count = multiprocessing.cpu_count()
1490 except NotImplementedError:
1491 cpu_count = 1
1492
Simon Glassd9c1da22021-12-18 14:54:31 -07001493 epilog = '''Move config options from headers to defconfig files. See
1494doc/develop/moveconfig.rst for documentation.'''
1495
1496 parser = ArgumentParser(epilog=epilog)
1497 # Add arguments here
1498 parser.add_argument('-a', '--add-imply', type=str, default='',
Simon Glass44116332017-06-15 21:39:33 -06001499 help='comma-separated list of CONFIG options to add '
1500 "an 'imply' statement to for the CONFIG in -i")
Simon Glassd9c1da22021-12-18 14:54:31 -07001501 parser.add_argument('-A', '--skip-added', action='store_true', default=False,
Simon Glass44116332017-06-15 21:39:33 -06001502 help="don't show options which are already marked as "
1503 'implying others')
Simon Glassd9c1da22021-12-18 14:54:31 -07001504 parser.add_argument('-b', '--build-db', action='store_true', default=False,
Simon Glass43cf08f2017-06-01 19:39:02 -06001505 help='build a CONFIG database')
Simon Glassd9c1da22021-12-18 14:54:31 -07001506 parser.add_argument('-C', '--commit', action='store_true', default=False,
Simon Glass8bf41c22016-09-12 23:18:21 -06001507 help='Create a git commit for the operation')
Simon Glass9b191102023-09-23 13:44:09 -06001508 parser.add_argument('--nocolour', action='store_true', default=False,
1509 help="don't display the log in colour")
Simon Glassd9c1da22021-12-18 14:54:31 -07001510 parser.add_argument('-d', '--defconfigs', type=str,
Simon Glass8f3cf312017-06-01 19:38:59 -06001511 help='a file containing a list of defconfigs to move, '
1512 "one per line (for example 'snow_defconfig') "
1513 "or '-' to read from stdin")
Simon Glassd9c1da22021-12-18 14:54:31 -07001514 parser.add_argument('-e', '--exit-on-error', action='store_true',
Masahiro Yamadab6160812015-05-20 11:36:07 +09001515 default=False,
1516 help='exit immediately on any error')
Simon Glassd9c1da22021-12-18 14:54:31 -07001517 parser.add_argument('-f', '--find', action='store_true', default=False,
Simon Glass0082b2e2021-12-18 08:09:46 -07001518 help='Find boards with a given config combination')
Simon Glassd9c1da22021-12-18 14:54:31 -07001519 parser.add_argument('-i', '--imply', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001520 help='find options which imply others')
Simon Glassd9c1da22021-12-18 14:54:31 -07001521 parser.add_argument('-I', '--imply-flags', type=str, default='',
Simon Glass0559a742021-12-18 08:09:44 -07001522 help="control the -i option ('help' for help")
Simon Glassd9c1da22021-12-18 14:54:31 -07001523 parser.add_argument('-j', '--jobs', type=int, default=cpu_count,
Masahiro Yamadab6160812015-05-20 11:36:07 +09001524 help='the number of jobs to run simultaneously')
Simon Glassd9c1da22021-12-18 14:54:31 -07001525 parser.add_argument('-n', '--dry-run', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001526 help='perform a trial run (show log with no changes)')
Simon Glassd9c1da22021-12-18 14:54:31 -07001527 parser.add_argument('-r', '--git-ref', type=str,
Joe Hershbergerb1a570f2016-06-10 14:53:32 -05001528 help='the git ref to clone for building the autoconf.mk')
Simon Glassd9c1da22021-12-18 14:54:31 -07001529 parser.add_argument('-s', '--force-sync', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001530 help='force sync by savedefconfig')
Simon Glassd9c1da22021-12-18 14:54:31 -07001531 parser.add_argument('-S', '--spl', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001532 help='parse config options defined for SPL build')
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001533 parser.add_argument('--scan-source', action='store_true', default=False,
1534 help='scan source for uses of CONFIG options')
Simon Glassd9c1da22021-12-18 14:54:31 -07001535 parser.add_argument('-t', '--test', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001536 help='run unit tests')
Simon Glassd9c1da22021-12-18 14:54:31 -07001537 parser.add_argument('-y', '--yes', action='store_true', default=False,
Simon Glass13e05a02016-09-12 23:18:20 -06001538 help="respond 'yes' to any prompts")
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001539 parser.add_argument('-u', '--update', action='store_true', default=False,
1540 help="update scripts/ files (use with --scan-source)")
Simon Glassd9c1da22021-12-18 14:54:31 -07001541 parser.add_argument('-v', '--verbose', action='store_true', default=False,
Joe Hershberger808b63f2015-05-19 13:21:24 -05001542 help='show any build errors as boards are built')
Simon Glassd9c1da22021-12-18 14:54:31 -07001543 parser.add_argument('configs', nargs='*')
Masahiro Yamadab6160812015-05-20 11:36:07 +09001544
Simon Glassfe11dcf2024-07-17 16:56:55 +01001545 return parser, parser.parse_args()
Masahiro Yamadab6160812015-05-20 11:36:07 +09001546
Simon Glassfe11dcf2024-07-17 16:56:55 +01001547
Simon Glass18352112024-07-17 16:57:03 +01001548def imply(args):
1549 """Handle checking for flags which imply others
1550
1551 Args:
1552 args (argparse.Namespace): Program arguments
1553
1554 Returns:
1555 int: exit code (0 for success)
1556 """
1557 imply_flags = 0
1558 if args.imply_flags == 'all':
1559 imply_flags = -1
1560
1561 elif args.imply_flags:
1562 for flag in args.imply_flags.split(','):
1563 bad = flag not in IMPLY_FLAGS
1564 if bad:
1565 print(f"Invalid flag '{flag}'")
1566 if flag == 'help' or bad:
1567 print("Imply flags: (separate with ',')")
1568 for name, info in IMPLY_FLAGS.items():
1569 print(f' {name:-15s}: {info[1]}')
1570 return 1
1571 imply_flags |= IMPLY_FLAGS[flag][0]
1572
1573 do_imply_config(args.configs, args.add_imply, imply_flags, args.skip_added)
1574 return 0
1575
1576
Simon Glasse054a752024-07-17 16:57:06 +01001577def add_commit(configs):
1578 """Add a commit indicating which CONFIG options were converted
1579
1580 Args:
1581 configs (list of str) List of CONFIG_... options to process
1582 """
1583 subprocess.call(['git', 'add', '-u'])
1584 if configs:
1585 part = 'et al ' if len(configs) > 1 else ''
1586 msg = f'Convert {configs[0]} {part}to Kconfig'
1587 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' %
1588 '\n '.join(configs))
1589 else:
1590 msg = 'configs: Resync with savedefconfig'
1591 msg += '\n\nRsync all defconfig files using moveconfig.py'
1592 subprocess.call(['git', 'commit', '-s', '-m', msg])
1593
1594
Simon Glassac261cc2024-07-17 16:57:01 +01001595def do_tests():
1596 """Run doctests and unit tests (so far there are no unit tests)"""
1597 sys.argv = [sys.argv[0]]
1598 fail, _ = doctest.testmod()
1599 if fail:
1600 return 1
1601 unittest.main()
1602 return 0
1603
1604
Simon Glassfe11dcf2024-07-17 16:56:55 +01001605def main():
1606 """Main program"""
1607 parser, args = parse_args()
Simon Glass855a0ce2024-07-17 16:56:57 +01001608 if not any((args.force_sync, args.build_db, args.imply, args.find,
1609 args.scan_source, args.test)):
1610 parser.print_usage()
1611 sys.exit(1)
Simon Glass9a042c52024-07-17 16:56:59 +01001612
1613 check_top_directory()
1614
Simon Glass84f541a2024-07-17 16:57:00 +01001615 # prefix the option name with CONFIG_ if missing
1616 args.configs = [prefix_config(cfg) for cfg in args.configs]
1617
Simon Glassd9c1da22021-12-18 14:54:31 -07001618 if args.test:
Simon Glassac261cc2024-07-17 16:57:01 +01001619 return do_tests()
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001620 if args.scan_source:
Simon Glass6901a642024-07-17 16:57:02 +01001621 return do_scan_source(os.getcwd(), args.update)
Simon Glassd9c1da22021-12-18 14:54:31 -07001622 if args.imply:
Simon Glass18352112024-07-17 16:57:03 +01001623 if imply(args):
1624 parser.print_usage()
1625 sys.exit(1)
Simon Glasse19a9cd2023-09-23 13:44:05 -06001626 return 0
Simon Glassd9c1da22021-12-18 14:54:31 -07001627 if args.find:
Simon Glass3c4cb202024-07-17 16:57:04 +01001628 return do_find_config(args.configs)
Simon Glass0082b2e2021-12-18 08:09:46 -07001629
Simon Glass704dd892024-07-17 16:56:58 +01001630 col = terminal.Color(terminal.COLOR_NEVER if args.nocolour
1631 else terminal.COLOR_IF_TERMINAL)
Simon Glass4c059032024-07-17 16:57:05 +01001632 config_db, progress = move_config(args, col)
Joe Hershberger23475932015-05-19 13:21:20 -05001633
Simon Glassd9c1da22021-12-18 14:54:31 -07001634 if args.commit:
Simon Glasse054a752024-07-17 16:57:06 +01001635 add_commit(args.configs)
Simon Glass8bf41c22016-09-12 23:18:21 -06001636
Simon Glass65709242023-09-23 13:44:13 -06001637 failed = progress.total - progress.good
1638 failure = f'{failed} failed, ' if failed else ''
Simon Glassd9c1da22021-12-18 14:54:31 -07001639 if args.build_db:
Simon Glasse054a752024-07-17 16:57:06 +01001640 configs = args.configs
Simon Glass4f6725c2023-09-23 13:44:01 -06001641 with open(CONFIG_DATABASE, 'w', encoding='utf-8') as outf:
Simon Glass1f701862019-10-31 07:42:57 -06001642 for defconfig, configs in config_db.items():
Simon Glass4f6725c2023-09-23 13:44:01 -06001643 outf.write(f'{defconfig}\n')
Simon Glass43cf08f2017-06-01 19:39:02 -06001644 for config in sorted(configs.keys()):
Simon Glass4f6725c2023-09-23 13:44:01 -06001645 outf.write(f' {config}={configs[config]}\n')
1646 outf.write('\n')
Simon Glass65709242023-09-23 13:44:13 -06001647 print(col.build(
1648 col.RED if failed else col.GREEN,
1649 f'{failure}{len(config_db)} boards written to {CONFIG_DATABASE}'))
1650 else:
1651 if failed:
1652 print(col.build(col.RED, f'{failure}see {FAILED_LIST}', True))
1653 else:
1654 # Add enough spaces to overwrite the progress indicator
1655 print(col.build(
1656 col.GREEN, f'{progress.total} processed ', bright=True))
1657
Simon Glasse19a9cd2023-09-23 13:44:05 -06001658 return 0
1659
Simon Glass43cf08f2017-06-01 19:39:02 -06001660
Masahiro Yamadab6160812015-05-20 11:36:07 +09001661if __name__ == '__main__':
Simon Glass0082b2e2021-12-18 08:09:46 -07001662 sys.exit(main())