blob: b148792861373c769a95e29223036f456408a838 [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 Glass9b191102023-09-23 13:44:09 -0600696def move_config(toolchains, args, db_queue, 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 toolchains (Toolchains): Toolchains to use
701 args (Namespace): Program arguments
702 db_queue (Queue): Queue for database updates
703 col (terminal.Color): Colour object
Simon Glass65709242023-09-23 13:44:13 -0600704
705 Returns:
706 Progress: Progress indicator
Masahiro Yamadab6160812015-05-20 11:36:07 +0900707 """
Simon Glassd9c1da22021-12-18 14:54:31 -0700708 if args.git_ref:
709 reference_src = ReferenceSource(args.git_ref)
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900710 reference_src_dir = reference_src.get_dir()
711 else:
Masahiro Yamada8f5256a2016-06-15 14:33:52 +0900712 reference_src_dir = None
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500713
Simon Glassd9c1da22021-12-18 14:54:31 -0700714 if args.defconfigs:
715 defconfigs = get_matched_defconfigs(args.defconfigs)
Joe Hershbergerc6e043a2015-05-19 13:21:19 -0500716 else:
Masahiro Yamada58175e32016-07-25 19:15:28 +0900717 defconfigs = get_all_defconfigs()
Masahiro Yamadab6160812015-05-20 11:36:07 +0900718
Simon Glassbef67362023-09-23 13:44:10 -0600719 progress = Progress(col, len(defconfigs))
Simon Glass9b191102023-09-23 13:44:09 -0600720 slots = Slots(toolchains, args, progress, reference_src_dir, db_queue, col)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900721
722 # Main loop to process defconfig files:
723 # Add a new subprocess into a vacant slot.
724 # Sleep if there is no available slot.
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900725 for defconfig in defconfigs:
726 while not slots.add(defconfig):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900727 while not slots.available():
728 # No available slot: sleep for a while
729 time.sleep(SLEEP_TIME)
730
731 # wait until all the subprocesses finish
732 while not slots.empty():
733 time.sleep(SLEEP_TIME)
734
Simon Glass65709242023-09-23 13:44:13 -0600735 slots.write_failed_boards()
736 return progress
Masahiro Yamadab6160812015-05-20 11:36:07 +0900737
Simon Glass44116332017-06-15 21:39:33 -0600738def find_kconfig_rules(kconf, config, imply_config):
739 """Check whether a config has a 'select' or 'imply' keyword
740
741 Args:
Simon Glassc89a2962023-09-23 13:43:58 -0600742 kconf (Kconfiglib.Kconfig): Kconfig object
743 config (str): Name of config to check (without CONFIG_ prefix)
744 imply_config (str): Implying config (without CONFIG_ prefix) which may
745 or may not have an 'imply' for 'config')
Simon Glass44116332017-06-15 21:39:33 -0600746
747 Returns:
748 Symbol object for 'config' if found, else None
749 """
Tom Rini3c5f4152019-09-20 17:42:09 -0400750 sym = kconf.syms.get(imply_config)
Simon Glass44116332017-06-15 21:39:33 -0600751 if sym:
Simon Glassbeb825d2023-09-23 13:44:00 -0600752 for sel, _ in (sym.selects + sym.implies):
Simon Glass93c0a9e2021-12-18 08:09:42 -0700753 if sel.name == config:
Simon Glass44116332017-06-15 21:39:33 -0600754 return sym
755 return None
756
Simon Glass28155572024-07-17 16:56:52 +0100757def check_imply_rule(kconf, imply_config):
Simon Glass44116332017-06-15 21:39:33 -0600758 """Check if we can add an 'imply' option
759
760 This finds imply_config in the Kconfig and looks to see if it is possible
761 to add an 'imply' for 'config' to that part of the Kconfig.
762
763 Args:
Simon Glassc89a2962023-09-23 13:43:58 -0600764 kconf (Kconfiglib.Kconfig): Kconfig object
Simon Glassc89a2962023-09-23 13:43:58 -0600765 imply_config (str): Implying config (without CONFIG_ prefix) which may
766 or may not have an 'imply' for 'config')
Simon Glass44116332017-06-15 21:39:33 -0600767
768 Returns:
769 tuple:
Simon Glassc89a2962023-09-23 13:43:58 -0600770 str: filename of Kconfig file containing imply_config, or None if
771 none
772 int: line number within the Kconfig file, or 0 if none
773 str: message indicating the result
Simon Glass44116332017-06-15 21:39:33 -0600774 """
Tom Rini3c5f4152019-09-20 17:42:09 -0400775 sym = kconf.syms.get(imply_config)
Simon Glass44116332017-06-15 21:39:33 -0600776 if not sym:
777 return 'cannot find sym'
Simon Glass520b47a2021-07-21 21:35:53 -0600778 nodes = sym.nodes
779 if len(nodes) != 1:
Simon Glass96f8f312023-09-23 13:43:59 -0600780 return f'{len(nodes)} locations'
Simon Glass93c0a9e2021-12-18 08:09:42 -0700781 node = nodes[0]
782 fname, linenum = node.filename, node.linenr
Simon Glass44116332017-06-15 21:39:33 -0600783 cwd = os.getcwd()
784 if cwd and fname.startswith(cwd):
785 fname = fname[len(cwd) + 1:]
Simon Glass96f8f312023-09-23 13:43:59 -0600786 file_line = f' at {fname}:{linenum}'
Simon Glassaba238f2021-12-18 14:54:34 -0700787 data = read_file(fname)
Simon Glass96f8f312023-09-23 13:43:59 -0600788 if data[linenum - 1] != f'config {imply_config}':
789 return None, 0, f'bad sym format {data[linenum]}{file_line})'
790 return fname, linenum, f'adding{file_line}'
Simon Glass44116332017-06-15 21:39:33 -0600791
792def add_imply_rule(config, fname, linenum):
793 """Add a new 'imply' option to a Kconfig
794
795 Args:
Simon Glassc89a2962023-09-23 13:43:58 -0600796 config (str): config option to add an imply for (without CONFIG_ prefix)
797 fname (str): Kconfig filename to update
798 linenum (int): Line number to place the 'imply' before
Simon Glass44116332017-06-15 21:39:33 -0600799
800 Returns:
801 Message indicating the result
802 """
Simon Glass96f8f312023-09-23 13:43:59 -0600803 file_line = f' at {fname}:{linenum}'
Simon Glassaba238f2021-12-18 14:54:34 -0700804 data = read_file(fname)
Simon Glass44116332017-06-15 21:39:33 -0600805 linenum -= 1
806
807 for offset, line in enumerate(data[linenum:]):
808 if line.strip().startswith('help') or not line:
Simon Glass96f8f312023-09-23 13:43:59 -0600809 data.insert(linenum + offset, f'\timply {config}')
Simon Glassb09ae452021-12-18 14:54:33 -0700810 write_file(fname, data)
Simon Glass96f8f312023-09-23 13:43:59 -0600811 return f'added{file_line}'
Simon Glass44116332017-06-15 21:39:33 -0600812
813 return 'could not insert%s'
814
815(IMPLY_MIN_2, IMPLY_TARGET, IMPLY_CMD, IMPLY_NON_ARCH_BOARD) = (
816 1, 2, 4, 8)
Simon Glass92e55582017-06-15 21:39:32 -0600817
818IMPLY_FLAGS = {
819 'min2': [IMPLY_MIN_2, 'Show options which imply >2 boards (normally >5)'],
820 'target': [IMPLY_TARGET, 'Allow CONFIG_TARGET_... options to imply'],
821 'cmd': [IMPLY_CMD, 'Allow CONFIG_CMD_... to imply'],
Simon Glass44116332017-06-15 21:39:33 -0600822 'non-arch-board': [
823 IMPLY_NON_ARCH_BOARD,
824 'Allow Kconfig options outside arch/ and /board/ to imply'],
Simon Glassb3464eb2021-12-18 14:54:35 -0700825}
Simon Glass92e55582017-06-15 21:39:32 -0600826
Simon Glassf931c2f2021-12-18 08:09:43 -0700827
828def read_database():
829 """Read in the config database
830
831 Returns:
832 tuple:
833 set of all config options seen (each a str)
834 set of all defconfigs seen (each a str)
835 dict of configs for each defconfig:
836 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
837 value: dict:
838 key: CONFIG option
839 value: Value of option
840 dict of defconfigs for each config:
841 key: CONFIG option
842 value: set of boards using that option
843
844 """
845 configs = {}
846
847 # key is defconfig name, value is dict of (CONFIG_xxx, value)
848 config_db = {}
849
850 # Set of all config options we have seen
851 all_configs = set()
852
853 # Set of all defconfigs we have seen
854 all_defconfigs = set()
855
856 defconfig_db = collections.defaultdict(set)
Simon Glass3a315fa2024-07-17 16:56:49 +0100857 defconfig = None
Simon Glassaba238f2021-12-18 14:54:34 -0700858 for line in read_file(CONFIG_DATABASE):
859 line = line.rstrip()
860 if not line: # Separator between defconfigs
861 config_db[defconfig] = configs
862 all_defconfigs.add(defconfig)
863 configs = {}
864 elif line[0] == ' ': # CONFIG line
865 config, value = line.strip().split('=', 1)
866 configs[config] = value
867 defconfig_db[config].add(defconfig)
868 all_configs.add(config)
869 else: # New defconfig
870 defconfig = line
Simon Glassf931c2f2021-12-18 08:09:43 -0700871
872 return all_configs, all_defconfigs, config_db, defconfig_db
873
874
Simon Glass44116332017-06-15 21:39:33 -0600875def do_imply_config(config_list, add_imply, imply_flags, skip_added,
876 check_kconfig=True, find_superset=False):
Simon Glassc6e73cf2017-06-01 19:39:03 -0600877 """Find CONFIG options which imply those in the list
878
879 Some CONFIG options can be implied by others and this can help to reduce
880 the size of the defconfig files. For example, CONFIG_X86 implies
881 CONFIG_CMD_IRQ, so we can put 'imply CMD_IRQ' under 'config X86' and
882 all x86 boards will have that option, avoiding adding CONFIG_CMD_IRQ to
883 each of the x86 defconfig files.
884
Simon Glassbf0e11a2023-09-23 13:44:14 -0600885 This function uses the qconfig database to find such options. It
Simon Glassc6e73cf2017-06-01 19:39:03 -0600886 displays a list of things that could possibly imply those in the list.
887 The algorithm ignores any that start with CONFIG_TARGET since these
888 typically refer to only a few defconfigs (often one). It also does not
889 display a config with less than 5 defconfigs.
890
891 The algorithm works using sets. For each target config in config_list:
892 - Get the set 'defconfigs' which use that target config
893 - For each config (from a list of all configs):
894 - Get the set 'imply_defconfig' of defconfigs which use that config
895 -
896 - If imply_defconfigs contains anything not in defconfigs then
897 this config does not imply the target config
898
Simon Glass18352112024-07-17 16:57:03 +0100899 Args:
900 config_list (list of str): List of CONFIG options to check
901 add_imply (bool): Automatically add an 'imply' for each config.
902 imply_flags (int): Flags which control which implying configs are allowed
Simon Glass92e55582017-06-15 21:39:32 -0600903 (IMPLY_...)
Simon Glass18352112024-07-17 16:57:03 +0100904 skip_added (bool): Don't show options which already have an imply added.
905 check_kconfig (bool): Check if implied symbols already have an 'imply' or
Simon Glass44116332017-06-15 21:39:33 -0600906 'select' for the target config, and show this information if so.
Simon Glass18352112024-07-17 16:57:03 +0100907 find_superset (bool): True to look for configs which are a superset of those
Simon Glassc6e73cf2017-06-01 19:39:03 -0600908 already found. So for example if CONFIG_EXYNOS5 implies an option,
909 but CONFIG_EXYNOS covers a larger set of defconfigs and also
910 implies that option, this will drop the former in favour of the
911 latter. In practice this option has not proved very used.
912
913 Note the terminoloy:
914 config - a CONFIG_XXX options (a string, e.g. 'CONFIG_CMD_EEPROM')
915 defconfig - a defconfig file (a string, e.g. 'configs/snow_defconfig')
916 """
Simon Glassa0a61602024-07-17 16:56:51 +0100917 kconf = scan_kconfig() if check_kconfig else None
Simon Glass44116332017-06-15 21:39:33 -0600918 if add_imply and add_imply != 'all':
Simon Glass93c0a9e2021-12-18 08:09:42 -0700919 add_imply = add_imply.split(',')
Simon Glass44116332017-06-15 21:39:33 -0600920
Simon Glassbeb825d2023-09-23 13:44:00 -0600921 all_configs, all_defconfigs, _, defconfig_db = read_database()
Simon Glassc6e73cf2017-06-01 19:39:03 -0600922
Simon Glass93c0a9e2021-12-18 08:09:42 -0700923 # Work through each target config option in turn, independently
Simon Glassc6e73cf2017-06-01 19:39:03 -0600924 for config in config_list:
925 defconfigs = defconfig_db.get(config)
926 if not defconfigs:
Simon Glass96f8f312023-09-23 13:43:59 -0600927 print(f'{config} not found in any defconfig')
Simon Glassc6e73cf2017-06-01 19:39:03 -0600928 continue
929
930 # Get the set of defconfigs without this one (since a config cannot
931 # imply itself)
932 non_defconfigs = all_defconfigs - defconfigs
933 num_defconfigs = len(defconfigs)
Simon Glass96f8f312023-09-23 13:43:59 -0600934 print(f'{config} found in {num_defconfigs}/{len(all_configs)} defconfigs')
Simon Glassc6e73cf2017-06-01 19:39:03 -0600935
936 # This will hold the results: key=config, value=defconfigs containing it
937 imply_configs = {}
938 rest_configs = all_configs - set([config])
939
940 # Look at every possible config, except the target one
941 for imply_config in rest_configs:
Simon Glass92e55582017-06-15 21:39:32 -0600942 if 'ERRATUM' in imply_config:
Simon Glassc6e73cf2017-06-01 19:39:03 -0600943 continue
Simon Glassb3464eb2021-12-18 14:54:35 -0700944 if not imply_flags & IMPLY_CMD:
Simon Glass92e55582017-06-15 21:39:32 -0600945 if 'CONFIG_CMD' in imply_config:
946 continue
Simon Glassb3464eb2021-12-18 14:54:35 -0700947 if not imply_flags & IMPLY_TARGET:
Simon Glass92e55582017-06-15 21:39:32 -0600948 if 'CONFIG_TARGET' in imply_config:
949 continue
Simon Glassc6e73cf2017-06-01 19:39:03 -0600950
951 # Find set of defconfigs that have this config
952 imply_defconfig = defconfig_db[imply_config]
953
954 # Get the intersection of this with defconfigs containing the
955 # target config
956 common_defconfigs = imply_defconfig & defconfigs
957
958 # Get the set of defconfigs containing this config which DO NOT
959 # also contain the taret config. If this set is non-empty it means
960 # that this config affects other defconfigs as well as (possibly)
961 # the ones affected by the target config. This means it implies
962 # things we don't want to imply.
963 not_common_defconfigs = imply_defconfig & non_defconfigs
964 if not_common_defconfigs:
965 continue
966
967 # If there are common defconfigs, imply_config may be useful
968 if common_defconfigs:
969 skip = False
970 if find_superset:
Simon Glass1f701862019-10-31 07:42:57 -0600971 for prev in list(imply_configs.keys()):
Simon Glassc6e73cf2017-06-01 19:39:03 -0600972 prev_count = len(imply_configs[prev])
973 count = len(common_defconfigs)
974 if (prev_count > count and
975 (imply_configs[prev] & common_defconfigs ==
976 common_defconfigs)):
977 # skip imply_config because prev is a superset
978 skip = True
979 break
Simon Glasse19a9cd2023-09-23 13:44:05 -0600980 if count > prev_count:
Simon Glassc6e73cf2017-06-01 19:39:03 -0600981 # delete prev because imply_config is a superset
982 del imply_configs[prev]
983 if not skip:
984 imply_configs[imply_config] = common_defconfigs
985
986 # Now we have a dict imply_configs of configs which imply each config
987 # The value of each dict item is the set of defconfigs containing that
988 # config. Rank them so that we print the configs that imply the largest
989 # number of defconfigs first.
Simon Glass44116332017-06-15 21:39:33 -0600990 ranked_iconfigs = sorted(imply_configs,
Simon Glassc6e73cf2017-06-01 19:39:03 -0600991 key=lambda k: len(imply_configs[k]), reverse=True)
Simon Glass44116332017-06-15 21:39:33 -0600992 kconfig_info = ''
993 cwd = os.getcwd()
994 add_list = collections.defaultdict(list)
995 for iconfig in ranked_iconfigs:
996 num_common = len(imply_configs[iconfig])
Simon Glassc6e73cf2017-06-01 19:39:03 -0600997
998 # Don't bother if there are less than 5 defconfigs affected.
Simon Glass92e55582017-06-15 21:39:32 -0600999 if num_common < (2 if imply_flags & IMPLY_MIN_2 else 5):
Simon Glassc6e73cf2017-06-01 19:39:03 -06001000 continue
Simon Glass44116332017-06-15 21:39:33 -06001001 missing = defconfigs - imply_configs[iconfig]
Simon Glassc6e73cf2017-06-01 19:39:03 -06001002 missing_str = ', '.join(missing) if missing else 'all'
1003 missing_str = ''
Simon Glass44116332017-06-15 21:39:33 -06001004 show = True
1005 if kconf:
1006 sym = find_kconfig_rules(kconf, config[CONFIG_LEN:],
1007 iconfig[CONFIG_LEN:])
1008 kconfig_info = ''
1009 if sym:
Simon Glass520b47a2021-07-21 21:35:53 -06001010 nodes = sym.nodes
1011 if len(nodes) == 1:
1012 fname, linenum = nodes[0].filename, nodes[0].linenr
Simon Glass44116332017-06-15 21:39:33 -06001013 if cwd and fname.startswith(cwd):
1014 fname = fname[len(cwd) + 1:]
Simon Glass96f8f312023-09-23 13:43:59 -06001015 kconfig_info = f'{fname}:{linenum}'
Simon Glass44116332017-06-15 21:39:33 -06001016 if skip_added:
1017 show = False
1018 else:
Tom Rini3c5f4152019-09-20 17:42:09 -04001019 sym = kconf.syms.get(iconfig[CONFIG_LEN:])
Simon Glass44116332017-06-15 21:39:33 -06001020 fname = ''
1021 if sym:
Simon Glass520b47a2021-07-21 21:35:53 -06001022 nodes = sym.nodes
1023 if len(nodes) == 1:
1024 fname, linenum = nodes[0].filename, nodes[0].linenr
Simon Glass44116332017-06-15 21:39:33 -06001025 if cwd and fname.startswith(cwd):
1026 fname = fname[len(cwd) + 1:]
1027 in_arch_board = not sym or (fname.startswith('arch') or
1028 fname.startswith('board'))
1029 if (not in_arch_board and
Simon Glassb3464eb2021-12-18 14:54:35 -07001030 not imply_flags & IMPLY_NON_ARCH_BOARD):
Simon Glass44116332017-06-15 21:39:33 -06001031 continue
1032
1033 if add_imply and (add_imply == 'all' or
1034 iconfig in add_imply):
1035 fname, linenum, kconfig_info = (check_imply_rule(kconf,
Simon Glass28155572024-07-17 16:56:52 +01001036 iconfig[CONFIG_LEN:]))
Simon Glass44116332017-06-15 21:39:33 -06001037 if fname:
1038 add_list[fname].append(linenum)
Simon Glassc6e73cf2017-06-01 19:39:03 -06001039
Simon Glass44116332017-06-15 21:39:33 -06001040 if show and kconfig_info != 'skip':
Simon Glass0f439202024-07-17 16:56:53 +01001041 print(f'{num_common:5} : '
1042 f'{iconfig.ljust(30)}{kconfig_info.ljust(25)} {missing_str}')
Simon Glass44116332017-06-15 21:39:33 -06001043
1044 # Having collected a list of things to add, now we add them. We process
1045 # each file from the largest line number to the smallest so that
1046 # earlier additions do not affect our line numbers. E.g. if we added an
1047 # imply at line 20 it would change the position of each line after
1048 # that.
Simon Glass1f701862019-10-31 07:42:57 -06001049 for fname, linenums in add_list.items():
Simon Glass44116332017-06-15 21:39:33 -06001050 for linenum in sorted(linenums, reverse=True):
1051 add_imply_rule(config[CONFIG_LEN:], fname, linenum)
1052
Simon Glass99f79422022-02-08 11:49:46 -07001053def defconfig_matches(configs, re_match):
1054 """Check if any CONFIG option matches a regex
1055
1056 The match must be complete, i.e. from the start to end of the CONFIG option.
1057
1058 Args:
1059 configs (dict): Dict of CONFIG options:
1060 key: CONFIG option
1061 value: Value of option
1062 re_match (re.Pattern): Match to check
1063
1064 Returns:
1065 bool: True if any CONFIG matches the regex
1066 """
1067 for cfg in configs:
Simon Glassfea71c92022-03-05 20:18:54 -07001068 if re_match.fullmatch(cfg):
Simon Glass99f79422022-02-08 11:49:46 -07001069 return True
1070 return False
Simon Glassc6e73cf2017-06-01 19:39:03 -06001071
Simon Glass0082b2e2021-12-18 08:09:46 -07001072def do_find_config(config_list):
1073 """Find boards with a given combination of CONFIGs
1074
Simon Glass3c4cb202024-07-17 16:57:04 +01001075 Args:
1076 config_list (list of str): List of CONFIG options to check (each a regex
1077 consisting of a config option, with or without a CONFIG_ prefix. If
1078 an option is preceded by a tilde (~) then it must be false,
1079 otherwise it must be true)
1080
1081 Returns:
1082 int: exit code (0 for success)
Simon Glass0082b2e2021-12-18 08:09:46 -07001083 """
Simon Glassbeb825d2023-09-23 13:44:00 -06001084 _, all_defconfigs, config_db, _ = read_database()
Simon Glass0082b2e2021-12-18 08:09:46 -07001085
Simon Glass0082b2e2021-12-18 08:09:46 -07001086 # Start with all defconfigs
1087 out = all_defconfigs
1088
1089 # Work through each config in turn
Simon Glass0082b2e2021-12-18 08:09:46 -07001090 for item in config_list:
1091 # Get the real config name and whether we want this config or not
1092 cfg = item
1093 want = True
1094 if cfg[0] == '~':
1095 want = False
1096 cfg = cfg[1:]
1097
Simon Glass0082b2e2021-12-18 08:09:46 -07001098 # Search everything that is still in the running. If it has a config
1099 # that we want, or doesn't have one that we don't, add it into the
1100 # running for the next stage
1101 in_list = out
1102 out = set()
Simon Glass99f79422022-02-08 11:49:46 -07001103 re_match = re.compile(cfg)
Simon Glass0082b2e2021-12-18 08:09:46 -07001104 for defc in in_list:
Simon Glass99f79422022-02-08 11:49:46 -07001105 has_cfg = defconfig_matches(config_db[defc], re_match)
Simon Glass0082b2e2021-12-18 08:09:46 -07001106 if has_cfg == want:
1107 out.add(defc)
Tom Rinic1b64aa2022-12-04 10:14:16 -05001108 print(f'{len(out)} matches')
1109 print(' '.join(item.split('_defconfig')[0] for item in out))
Simon Glass3c4cb202024-07-17 16:57:04 +01001110 return 0
Simon Glass0082b2e2021-12-18 08:09:46 -07001111
1112
1113def prefix_config(cfg):
1114 """Prefix a config with CONFIG_ if needed
1115
1116 This handles ~ operator, which indicates that the CONFIG should be disabled
1117
1118 >>> prefix_config('FRED')
1119 'CONFIG_FRED'
1120 >>> prefix_config('CONFIG_FRED')
1121 'CONFIG_FRED'
1122 >>> prefix_config('~FRED')
1123 '~CONFIG_FRED'
1124 >>> prefix_config('~CONFIG_FRED')
1125 '~CONFIG_FRED'
1126 >>> prefix_config('A123')
1127 'CONFIG_A123'
1128 """
Simon Glass4f6725c2023-09-23 13:44:01 -06001129 oper = ''
Simon Glass0082b2e2021-12-18 08:09:46 -07001130 if cfg[0] == '~':
Simon Glass4f6725c2023-09-23 13:44:01 -06001131 oper = cfg[0]
Simon Glass0082b2e2021-12-18 08:09:46 -07001132 cfg = cfg[1:]
1133 if not cfg.startswith('CONFIG_'):
1134 cfg = 'CONFIG_' + cfg
Simon Glass4f6725c2023-09-23 13:44:01 -06001135 return oper + cfg
Simon Glass0082b2e2021-12-18 08:09:46 -07001136
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001137
Simon Glass2deee262023-09-23 13:43:57 -06001138RE_MK_CONFIGS = re.compile(r'CONFIG_(\$\(SPL_(?:TPL_)?\))?([A-Za-z0-9_]*)')
1139RE_IFDEF = re.compile(r'(ifdef|ifndef)')
1140RE_C_CONFIGS = re.compile(r'CONFIG_([A-Za-z0-9_]*)')
1141RE_CONFIG_IS = re.compile(r'CONFIG_IS_ENABLED\(([A-Za-z0-9_]*)\)')
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001142
1143class ConfigUse:
Simon Glass28155572024-07-17 16:56:52 +01001144 """Tracks whether a config relates to SPL or not"""
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001145 def __init__(self, cfg, is_spl, fname, rest):
Simon Glass28155572024-07-17 16:56:52 +01001146 """Set up a new ConfigUse
1147
1148 Args:
1149 cfg (str): CONFIG option, without any CONFIG_ or SPL_ prefix
1150 is_spl (bool): True if this option relates to SPL
1151 fname (str): Makefile filename where the CONFIG option was found
1152 rest (str): Line of the Makefile
1153 """
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001154 self.cfg = cfg
1155 self.is_spl = is_spl
1156 self.fname = fname
1157 self.rest = rest
1158
1159 def __hash__(self):
1160 return hash((self.cfg, self.is_spl))
1161
1162def scan_makefiles(fnames):
1163 """Scan Makefiles looking for Kconfig options
1164
1165 Looks for uses of CONFIG options in Makefiles
1166
1167 Args:
1168 fnames (list of tuple):
1169 str: Makefile filename where the option was found
1170 str: Line of the Makefile
1171
1172 Returns:
1173 tuple:
1174 dict: all_uses
1175 key (ConfigUse): object
1176 value (list of str): matching lines
1177 dict: Uses by filename
1178 key (str): filename
1179 value (set of ConfigUse): uses in that filename
1180
1181 >>> RE_MK_CONFIGS.search('CONFIG_FRED').groups()
1182 (None, 'FRED')
1183 >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_)MARY').groups()
1184 ('$(SPL_)', 'MARY')
1185 >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_TPL_)MARY').groups()
1186 ('$(SPL_TPL_)', 'MARY')
1187 """
1188 all_uses = collections.defaultdict(list)
1189 fname_uses = {}
1190 for fname, rest in fnames:
1191 m_iter = RE_MK_CONFIGS.finditer(rest)
Simon Glass4f6725c2023-09-23 13:44:01 -06001192 for mat in m_iter:
1193 real_opt = mat.group(2)
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001194 if real_opt == '':
1195 continue
1196 is_spl = False
Simon Glass4f6725c2023-09-23 13:44:01 -06001197 if mat.group(1):
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001198 is_spl = True
1199 use = ConfigUse(real_opt, is_spl, fname, rest)
1200 if fname not in fname_uses:
1201 fname_uses[fname] = set()
1202 fname_uses[fname].add(use)
1203 all_uses[use].append(rest)
1204 return all_uses, fname_uses
1205
1206
1207def scan_src_files(fnames):
1208 """Scan source files (other than Makefiles) looking for Kconfig options
1209
1210 Looks for uses of CONFIG options
1211
1212 Args:
1213 fnames (list of tuple):
1214 str: Makefile filename where the option was found
1215 str: Line of the Makefile
1216
1217 Returns:
1218 tuple:
1219 dict: all_uses
1220 key (ConfigUse): object
1221 value (list of str): matching lines
1222 dict: Uses by filename
1223 key (str): filename
1224 value (set of ConfigUse): uses in that filename
1225
1226 >>> RE_C_CONFIGS.search('CONFIG_FRED').groups()
1227 ('FRED',)
1228 >>> RE_CONFIG_IS.search('CONFIG_IS_ENABLED(MARY)').groups()
1229 ('MARY',)
1230 >>> RE_CONFIG_IS.search('#if CONFIG_IS_ENABLED(OF_PLATDATA)').groups()
1231 ('OF_PLATDATA',)
1232 """
Simon Glassbeb825d2023-09-23 13:44:00 -06001233 fname = None
1234 rest = None
1235
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001236 def add_uses(m_iter, is_spl):
Simon Glass4f6725c2023-09-23 13:44:01 -06001237 for mat in m_iter:
1238 real_opt = mat.group(1)
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001239 if real_opt == '':
1240 continue
1241 use = ConfigUse(real_opt, is_spl, fname, rest)
1242 if fname not in fname_uses:
1243 fname_uses[fname] = set()
1244 fname_uses[fname].add(use)
1245 all_uses[use].append(rest)
1246
1247 all_uses = collections.defaultdict(list)
1248 fname_uses = {}
1249 for fname, rest in fnames:
1250 m_iter = RE_C_CONFIGS.finditer(rest)
1251 add_uses(m_iter, False)
1252
1253 m_iter2 = RE_CONFIG_IS.finditer(rest)
1254 add_uses(m_iter2, True)
1255
1256 return all_uses, fname_uses
1257
1258
1259MODE_NORMAL, MODE_SPL, MODE_PROPER = range(3)
1260
1261def do_scan_source(path, do_update):
1262 """Scan the source tree for Kconfig inconsistencies
1263
1264 Args:
1265 path (str): Path to source tree
1266 do_update (bool) : True to write to scripts/kconf_... files
1267 """
1268 def is_not_proper(name):
1269 for prefix in SPL_PREFIXES:
1270 if name.startswith(prefix):
1271 return name[len(prefix):]
1272 return False
1273
1274 def check_not_found(all_uses, spl_mode):
1275 """Check for Kconfig options mentioned in the source but not in Kconfig
1276
1277 Args:
1278 all_uses (dict):
1279 key (ConfigUse): object
1280 value (list of str): matching lines
1281 spl_mode (int): If MODE_SPL, look at source code which implies
1282 an SPL_ option, but for which there is none;
1283 for MOD_PROPER, look at source code which implies a Proper
1284 option (i.e. use of CONFIG_IS_ENABLED() or $(SPL_) or
1285 $(SPL_TPL_) but for which there none;
1286 if MODE_NORMAL, ignore SPL
1287
1288 Returns:
1289 dict:
1290 key (str): CONFIG name (without 'CONFIG_' prefix
1291 value (list of ConfigUse): List of uses of this CONFIG
1292 """
1293 # Make sure we know about all the options
1294 not_found = collections.defaultdict(list)
Simon Glassbeb825d2023-09-23 13:44:00 -06001295 for use, _ in all_uses.items():
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001296 name = use.cfg
1297 if name in IGNORE_SYMS:
1298 continue
1299 check = True
1300
1301 if spl_mode == MODE_SPL:
1302 check = use.is_spl
1303
1304 # If it is an SPL symbol, try prepending all SPL_ prefixes to
1305 # find at least one SPL symbol
1306 if use.is_spl:
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001307 for prefix in SPL_PREFIXES:
1308 try_name = prefix + name
1309 sym = kconf.syms.get(try_name)
1310 if sym:
1311 break
1312 if not sym:
1313 not_found[f'SPL_{name}'].append(use)
1314 continue
1315 elif spl_mode == MODE_PROPER:
1316 # Try to find the Proper version of this symbol, i.e. without
1317 # the SPL_ prefix
1318 proper_name = is_not_proper(name)
1319 if proper_name:
1320 name = proper_name
1321 elif not use.is_spl:
1322 check = False
1323 else: # MODE_NORMAL
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001324 sym = kconf.syms.get(name)
1325 if not sym:
1326 proper_name = is_not_proper(name)
1327 if proper_name:
1328 name = proper_name
1329 sym = kconf.syms.get(name)
1330 if not sym:
1331 for prefix in SPL_PREFIXES:
1332 try_name = prefix + name
1333 sym = kconf.syms.get(try_name)
1334 if sym:
1335 break
1336 if not sym:
1337 not_found[name].append(use)
1338 continue
1339
1340 sym = kconf.syms.get(name)
1341 if not sym and check:
1342 not_found[name].append(use)
1343 return not_found
1344
1345 def show_uses(uses):
1346 """Show a list of uses along with their filename and code snippet
1347
1348 Args:
1349 uses (dict):
1350 key (str): CONFIG name (without 'CONFIG_' prefix
1351 value (list of ConfigUse): List of uses of this CONFIG
1352 """
1353 for name in sorted(uses):
1354 print(f'{name}: ', end='')
1355 for i, use in enumerate(uses[name]):
1356 print(f'{" " if i else ""}{use.fname}: {use.rest.strip()}')
1357
1358
1359 print('Scanning Kconfig')
Simon Glassa0a61602024-07-17 16:56:51 +01001360 kconf = scan_kconfig()
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001361 print(f'Scanning source in {path}')
1362 args = ['git', 'grep', '-E', r'IS_ENABLED|\bCONFIG']
1363 with subprocess.Popen(args, stdout=subprocess.PIPE) as proc:
Simon Glassbeb825d2023-09-23 13:44:00 -06001364 out, _ = proc.communicate()
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001365 lines = out.splitlines()
1366 re_fname = re.compile('^([^:]*):(.*)')
1367 src_list = []
1368 mk_list = []
1369 for line in lines:
1370 linestr = line.decode('utf-8')
1371 m_fname = re_fname.search(linestr)
1372 if not m_fname:
1373 continue
1374 fname, rest = m_fname.groups()
1375 dirname, leaf = os.path.split(fname)
1376 root, ext = os.path.splitext(leaf)
1377 if ext == '.autoconf':
1378 pass
1379 elif ext in ['.c', '.h', '.S', '.lds', '.dts', '.dtsi', '.asl', '.cfg',
1380 '.env', '.tmpl']:
1381 src_list.append([fname, rest])
1382 elif 'Makefile' in root or ext == '.mk':
1383 mk_list.append([fname, rest])
1384 elif ext in ['.yml', '.sh', '.py', '.awk', '.pl', '.rst', '', '.sed']:
1385 pass
1386 elif 'Kconfig' in root or 'Kbuild' in root:
1387 pass
1388 elif 'README' in root:
1389 pass
1390 elif dirname in ['configs']:
1391 pass
1392 elif dirname.startswith('doc') or dirname.startswith('scripts/kconfig'):
1393 pass
1394 else:
1395 print(f'Not sure how to handle file {fname}')
1396
1397 # Scan the Makefiles
Simon Glassbeb825d2023-09-23 13:44:00 -06001398 all_uses, _ = scan_makefiles(mk_list)
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001399
1400 spl_not_found = set()
1401 proper_not_found = set()
1402
1403 # Make sure we know about all the options
1404 print('\nCONFIG options present in Makefiles but not Kconfig:')
1405 not_found = check_not_found(all_uses, MODE_NORMAL)
1406 show_uses(not_found)
1407
1408 print('\nCONFIG options present in Makefiles but not Kconfig (SPL):')
1409 not_found = check_not_found(all_uses, MODE_SPL)
1410 show_uses(not_found)
Simon Glassd708cf72023-09-23 13:44:03 -06001411 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001412
1413 print('\nCONFIG options used as Proper in Makefiles but without a non-SPL_ variant:')
1414 not_found = check_not_found(all_uses, MODE_PROPER)
1415 show_uses(not_found)
Simon Glassd708cf72023-09-23 13:44:03 -06001416 proper_not_found |= {not_found.keys()}
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001417
1418 # Scan the source code
Simon Glassbeb825d2023-09-23 13:44:00 -06001419 all_uses, _ = scan_src_files(src_list)
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001420
1421 # Make sure we know about all the options
1422 print('\nCONFIG options present in source but not Kconfig:')
1423 not_found = check_not_found(all_uses, MODE_NORMAL)
1424 show_uses(not_found)
1425
1426 print('\nCONFIG options present in source but not Kconfig (SPL):')
1427 not_found = check_not_found(all_uses, MODE_SPL)
1428 show_uses(not_found)
Simon Glassd708cf72023-09-23 13:44:03 -06001429 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001430
1431 print('\nCONFIG options used as Proper in source but without a non-SPL_ variant:')
1432 not_found = check_not_found(all_uses, MODE_PROPER)
1433 show_uses(not_found)
Simon Glassd708cf72023-09-23 13:44:03 -06001434 proper_not_found |= {not_found.keys()}
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001435
1436 print('\nCONFIG options used as SPL but without an SPL_ variant:')
1437 for item in sorted(spl_not_found):
1438 print(f' {item}')
1439
1440 print('\nCONFIG options used as Proper but without a non-SPL_ variant:')
1441 for item in sorted(proper_not_found):
1442 print(f' {item}')
1443
1444 # Write out the updated information
1445 if do_update:
Simon Glass47f22892023-09-23 13:44:04 -06001446 with open(os.path.join(path, 'scripts', 'conf_nospl'), 'w',
1447 encoding='utf-8') as out:
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001448 print('# These options should not be enabled in SPL builds\n',
1449 file=out)
1450 for item in sorted(spl_not_found):
1451 print(item, file=out)
Simon Glass47f22892023-09-23 13:44:04 -06001452 with open(os.path.join(path, 'scripts', 'conf_noproper'), 'w',
1453 encoding='utf-8') as out:
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001454 print('# These options should not be enabled in Proper builds\n',
1455 file=out)
1456 for item in sorted(proper_not_found):
1457 print(item, file=out)
Simon Glass6901a642024-07-17 16:57:02 +01001458 return 0
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001459
Simon Glass0082b2e2021-12-18 08:09:46 -07001460
Simon Glassfe11dcf2024-07-17 16:56:55 +01001461def parse_args():
1462 """Parse the program arguments
1463
1464 Returns:
1465 tuple:
1466 argparse.ArgumentParser: parser
1467 argparse.Namespace: Parsed arguments
1468 """
Masahiro Yamadab6160812015-05-20 11:36:07 +09001469 try:
1470 cpu_count = multiprocessing.cpu_count()
1471 except NotImplementedError:
1472 cpu_count = 1
1473
Simon Glassd9c1da22021-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 Glass44116332017-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 Glassd9c1da22021-12-18 14:54:31 -07001482 parser.add_argument('-A', '--skip-added', action='store_true', default=False,
Simon Glass44116332017-06-15 21:39:33 -06001483 help="don't show options which are already marked as "
1484 'implying others')
Simon Glassd9c1da22021-12-18 14:54:31 -07001485 parser.add_argument('-b', '--build-db', action='store_true', default=False,
Simon Glass43cf08f2017-06-01 19:39:02 -06001486 help='build a CONFIG database')
Simon Glassd9c1da22021-12-18 14:54:31 -07001487 parser.add_argument('-C', '--commit', action='store_true', default=False,
Simon Glass8bf41c22016-09-12 23:18:21 -06001488 help='Create a git commit for the operation')
Simon Glass9b191102023-09-23 13:44:09 -06001489 parser.add_argument('--nocolour', action='store_true', default=False,
1490 help="don't display the log in colour")
Simon Glassd9c1da22021-12-18 14:54:31 -07001491 parser.add_argument('-d', '--defconfigs', type=str,
Simon Glass8f3cf312017-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 Glassd9c1da22021-12-18 14:54:31 -07001495 parser.add_argument('-e', '--exit-on-error', action='store_true',
Masahiro Yamadab6160812015-05-20 11:36:07 +09001496 default=False,
1497 help='exit immediately on any error')
Simon Glassd9c1da22021-12-18 14:54:31 -07001498 parser.add_argument('-f', '--find', action='store_true', default=False,
Simon Glass0082b2e2021-12-18 08:09:46 -07001499 help='Find boards with a given config combination')
Simon Glassd9c1da22021-12-18 14:54:31 -07001500 parser.add_argument('-i', '--imply', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001501 help='find options which imply others')
Simon Glassd9c1da22021-12-18 14:54:31 -07001502 parser.add_argument('-I', '--imply-flags', type=str, default='',
Simon Glass0559a742021-12-18 08:09:44 -07001503 help="control the -i option ('help' for help")
Simon Glassd9c1da22021-12-18 14:54:31 -07001504 parser.add_argument('-j', '--jobs', type=int, default=cpu_count,
Masahiro Yamadab6160812015-05-20 11:36:07 +09001505 help='the number of jobs to run simultaneously')
Simon Glassd9c1da22021-12-18 14:54:31 -07001506 parser.add_argument('-n', '--dry-run', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001507 help='perform a trial run (show log with no changes)')
Simon Glassd9c1da22021-12-18 14:54:31 -07001508 parser.add_argument('-r', '--git-ref', type=str,
Joe Hershbergerb1a570f2016-06-10 14:53:32 -05001509 help='the git ref to clone for building the autoconf.mk')
Simon Glassd9c1da22021-12-18 14:54:31 -07001510 parser.add_argument('-s', '--force-sync', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001511 help='force sync by savedefconfig')
Simon Glassd9c1da22021-12-18 14:54:31 -07001512 parser.add_argument('-S', '--spl', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001513 help='parse config options defined for SPL build')
Simon Glass4c4eb7c2023-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 Glassd9c1da22021-12-18 14:54:31 -07001516 parser.add_argument('-t', '--test', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001517 help='run unit tests')
Simon Glassd9c1da22021-12-18 14:54:31 -07001518 parser.add_argument('-y', '--yes', action='store_true', default=False,
Simon Glass13e05a02016-09-12 23:18:20 -06001519 help="respond 'yes' to any prompts")
Simon Glass4c4eb7c2023-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 Glassd9c1da22021-12-18 14:54:31 -07001522 parser.add_argument('-v', '--verbose', action='store_true', default=False,
Joe Hershberger808b63f2015-05-19 13:21:24 -05001523 help='show any build errors as boards are built')
Simon Glassd9c1da22021-12-18 14:54:31 -07001524 parser.add_argument('configs', nargs='*')
Masahiro Yamadab6160812015-05-20 11:36:07 +09001525
Simon Glassfe11dcf2024-07-17 16:56:55 +01001526 return parser, parser.parse_args()
Masahiro Yamadab6160812015-05-20 11:36:07 +09001527
Simon Glassfe11dcf2024-07-17 16:56:55 +01001528
Simon Glass18352112024-07-17 16:57:03 +01001529def imply(args):
1530 """Handle checking for flags which imply others
1531
1532 Args:
1533 args (argparse.Namespace): Program arguments
1534
1535 Returns:
1536 int: exit code (0 for success)
1537 """
1538 imply_flags = 0
1539 if args.imply_flags == 'all':
1540 imply_flags = -1
1541
1542 elif args.imply_flags:
1543 for flag in args.imply_flags.split(','):
1544 bad = flag not in IMPLY_FLAGS
1545 if bad:
1546 print(f"Invalid flag '{flag}'")
1547 if flag == 'help' or bad:
1548 print("Imply flags: (separate with ',')")
1549 for name, info in IMPLY_FLAGS.items():
1550 print(f' {name:-15s}: {info[1]}')
1551 return 1
1552 imply_flags |= IMPLY_FLAGS[flag][0]
1553
1554 do_imply_config(args.configs, args.add_imply, imply_flags, args.skip_added)
1555 return 0
1556
1557
Simon Glassac261cc2024-07-17 16:57:01 +01001558def do_tests():
1559 """Run doctests and unit tests (so far there are no unit tests)"""
1560 sys.argv = [sys.argv[0]]
1561 fail, _ = doctest.testmod()
1562 if fail:
1563 return 1
1564 unittest.main()
1565 return 0
1566
1567
Simon Glassfe11dcf2024-07-17 16:56:55 +01001568def main():
1569 """Main program"""
1570 parser, args = parse_args()
Simon Glass855a0ce2024-07-17 16:56:57 +01001571 if not any((args.force_sync, args.build_db, args.imply, args.find,
1572 args.scan_source, args.test)):
1573 parser.print_usage()
1574 sys.exit(1)
Simon Glass9a042c52024-07-17 16:56:59 +01001575
1576 check_top_directory()
1577
Simon Glass84f541a2024-07-17 16:57:00 +01001578 # prefix the option name with CONFIG_ if missing
1579 args.configs = [prefix_config(cfg) for cfg in args.configs]
1580
Simon Glassd9c1da22021-12-18 14:54:31 -07001581 if args.test:
Simon Glassac261cc2024-07-17 16:57:01 +01001582 return do_tests()
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001583 if args.scan_source:
Simon Glass6901a642024-07-17 16:57:02 +01001584 return do_scan_source(os.getcwd(), args.update)
Simon Glassd9c1da22021-12-18 14:54:31 -07001585 if args.imply:
Simon Glass18352112024-07-17 16:57:03 +01001586 if imply(args):
1587 parser.print_usage()
1588 sys.exit(1)
Simon Glasse19a9cd2023-09-23 13:44:05 -06001589 return 0
Simon Glassd9c1da22021-12-18 14:54:31 -07001590 if args.find:
Simon Glass3c4cb202024-07-17 16:57:04 +01001591 return do_find_config(args.configs)
Simon Glass0082b2e2021-12-18 08:09:46 -07001592
Simon Glass35688b62023-09-23 13:43:53 -06001593 # We are either building the database or forcing a sync of defconfigs
Simon Glass43cf08f2017-06-01 19:39:02 -06001594 config_db = {}
Simon Glass1f701862019-10-31 07:42:57 -06001595 db_queue = queue.Queue()
Simon Glass4f6725c2023-09-23 13:44:01 -06001596 dbt = DatabaseThread(config_db, db_queue)
1597 dbt.daemon = True
1598 dbt.start()
Simon Glass43cf08f2017-06-01 19:39:02 -06001599
Simon Glass1572b3f2023-09-23 13:43:50 -06001600 check_clean_directory()
1601 bsettings.setup('')
1602 toolchains = toolchain.Toolchains()
1603 toolchains.GetSettings()
1604 toolchains.Scan(verbose=False)
Simon Glass704dd892024-07-17 16:56:58 +01001605
1606 col = terminal.Color(terminal.COLOR_NEVER if args.nocolour
1607 else terminal.COLOR_IF_TERMINAL)
Simon Glass65709242023-09-23 13:44:13 -06001608 progress = move_config(toolchains, args, db_queue, col)
Simon Glass1572b3f2023-09-23 13:43:50 -06001609 db_queue.join()
Joe Hershberger23475932015-05-19 13:21:20 -05001610
Simon Glass84f541a2024-07-17 16:57:00 +01001611 configs = args.configs
Simon Glassd9c1da22021-12-18 14:54:31 -07001612 if args.commit:
Simon Glass8bf41c22016-09-12 23:18:21 -06001613 subprocess.call(['git', 'add', '-u'])
1614 if configs:
Simon Glass28155572024-07-17 16:56:52 +01001615 part = 'et al ' if len(configs) > 1 else ''
1616 msg = f'Convert {configs[0]} {part}to Kconfig'
Simon Glass8bf41c22016-09-12 23:18:21 -06001617 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' %
1618 '\n '.join(configs))
1619 else:
1620 msg = 'configs: Resync with savedefconfig'
1621 msg += '\n\nRsync all defconfig files using moveconfig.py'
1622 subprocess.call(['git', 'commit', '-s', '-m', msg])
1623
Simon Glass65709242023-09-23 13:44:13 -06001624 failed = progress.total - progress.good
1625 failure = f'{failed} failed, ' if failed else ''
Simon Glassd9c1da22021-12-18 14:54:31 -07001626 if args.build_db:
Simon Glass4f6725c2023-09-23 13:44:01 -06001627 with open(CONFIG_DATABASE, 'w', encoding='utf-8') as outf:
Simon Glass1f701862019-10-31 07:42:57 -06001628 for defconfig, configs in config_db.items():
Simon Glass4f6725c2023-09-23 13:44:01 -06001629 outf.write(f'{defconfig}\n')
Simon Glass43cf08f2017-06-01 19:39:02 -06001630 for config in sorted(configs.keys()):
Simon Glass4f6725c2023-09-23 13:44:01 -06001631 outf.write(f' {config}={configs[config]}\n')
1632 outf.write('\n')
Simon Glass65709242023-09-23 13:44:13 -06001633 print(col.build(
1634 col.RED if failed else col.GREEN,
1635 f'{failure}{len(config_db)} boards written to {CONFIG_DATABASE}'))
1636 else:
1637 if failed:
1638 print(col.build(col.RED, f'{failure}see {FAILED_LIST}', True))
1639 else:
1640 # Add enough spaces to overwrite the progress indicator
1641 print(col.build(
1642 col.GREEN, f'{progress.total} processed ', bright=True))
1643
Simon Glasse19a9cd2023-09-23 13:44:05 -06001644 return 0
1645
Simon Glass43cf08f2017-06-01 19:39:02 -06001646
Masahiro Yamadab6160812015-05-20 11:36:07 +09001647if __name__ == '__main__':
Simon Glass0082b2e2021-12-18 08:09:46 -07001648 sys.exit(main())