blob: 9052aedf57d04c0e331b88dda7095065e24ae4fb [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
899 Params:
900 config_list: List of CONFIG options to check (each a string)
Simon Glass44116332017-06-15 21:39:33 -0600901 add_imply: Automatically add an 'imply' for each config.
Simon Glass92e55582017-06-15 21:39:32 -0600902 imply_flags: Flags which control which implying configs are allowed
903 (IMPLY_...)
Simon Glass44116332017-06-15 21:39:33 -0600904 skip_added: Don't show options which already have an imply added.
905 check_kconfig: Check if implied symbols already have an 'imply' or
906 'select' for the target config, and show this information if so.
Simon Glassc6e73cf2017-06-01 19:39:03 -0600907 find_superset: True to look for configs which are a superset of those
908 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
1075 Params:
Simon Glass99f79422022-02-08 11:49:46 -07001076 config_list: List of CONFIG options to check (each a regex consisting
Simon Glass0082b2e2021-12-18 08:09:46 -07001077 of a config option, with or without a CONFIG_ prefix. If an option
1078 is preceded by a tilde (~) then it must be false, otherwise it must
1079 be true)
1080 """
Simon Glassbeb825d2023-09-23 13:44:00 -06001081 _, all_defconfigs, config_db, _ = read_database()
Simon Glass0082b2e2021-12-18 08:09:46 -07001082
Simon Glass0082b2e2021-12-18 08:09:46 -07001083 # Start with all defconfigs
1084 out = all_defconfigs
1085
1086 # Work through each config in turn
Simon Glass0082b2e2021-12-18 08:09:46 -07001087 for item in config_list:
1088 # Get the real config name and whether we want this config or not
1089 cfg = item
1090 want = True
1091 if cfg[0] == '~':
1092 want = False
1093 cfg = cfg[1:]
1094
Simon Glass0082b2e2021-12-18 08:09:46 -07001095 # Search everything that is still in the running. If it has a config
1096 # that we want, or doesn't have one that we don't, add it into the
1097 # running for the next stage
1098 in_list = out
1099 out = set()
Simon Glass99f79422022-02-08 11:49:46 -07001100 re_match = re.compile(cfg)
Simon Glass0082b2e2021-12-18 08:09:46 -07001101 for defc in in_list:
Simon Glass99f79422022-02-08 11:49:46 -07001102 has_cfg = defconfig_matches(config_db[defc], re_match)
Simon Glass0082b2e2021-12-18 08:09:46 -07001103 if has_cfg == want:
1104 out.add(defc)
Tom Rinic1b64aa2022-12-04 10:14:16 -05001105 print(f'{len(out)} matches')
1106 print(' '.join(item.split('_defconfig')[0] for item in out))
Simon Glass0082b2e2021-12-18 08:09:46 -07001107
1108
1109def prefix_config(cfg):
1110 """Prefix a config with CONFIG_ if needed
1111
1112 This handles ~ operator, which indicates that the CONFIG should be disabled
1113
1114 >>> prefix_config('FRED')
1115 'CONFIG_FRED'
1116 >>> prefix_config('CONFIG_FRED')
1117 'CONFIG_FRED'
1118 >>> prefix_config('~FRED')
1119 '~CONFIG_FRED'
1120 >>> prefix_config('~CONFIG_FRED')
1121 '~CONFIG_FRED'
1122 >>> prefix_config('A123')
1123 'CONFIG_A123'
1124 """
Simon Glass4f6725c2023-09-23 13:44:01 -06001125 oper = ''
Simon Glass0082b2e2021-12-18 08:09:46 -07001126 if cfg[0] == '~':
Simon Glass4f6725c2023-09-23 13:44:01 -06001127 oper = cfg[0]
Simon Glass0082b2e2021-12-18 08:09:46 -07001128 cfg = cfg[1:]
1129 if not cfg.startswith('CONFIG_'):
1130 cfg = 'CONFIG_' + cfg
Simon Glass4f6725c2023-09-23 13:44:01 -06001131 return oper + cfg
Simon Glass0082b2e2021-12-18 08:09:46 -07001132
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001133
Simon Glass2deee262023-09-23 13:43:57 -06001134RE_MK_CONFIGS = re.compile(r'CONFIG_(\$\(SPL_(?:TPL_)?\))?([A-Za-z0-9_]*)')
1135RE_IFDEF = re.compile(r'(ifdef|ifndef)')
1136RE_C_CONFIGS = re.compile(r'CONFIG_([A-Za-z0-9_]*)')
1137RE_CONFIG_IS = re.compile(r'CONFIG_IS_ENABLED\(([A-Za-z0-9_]*)\)')
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001138
1139class ConfigUse:
Simon Glass28155572024-07-17 16:56:52 +01001140 """Tracks whether a config relates to SPL or not"""
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001141 def __init__(self, cfg, is_spl, fname, rest):
Simon Glass28155572024-07-17 16:56:52 +01001142 """Set up a new ConfigUse
1143
1144 Args:
1145 cfg (str): CONFIG option, without any CONFIG_ or SPL_ prefix
1146 is_spl (bool): True if this option relates to SPL
1147 fname (str): Makefile filename where the CONFIG option was found
1148 rest (str): Line of the Makefile
1149 """
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001150 self.cfg = cfg
1151 self.is_spl = is_spl
1152 self.fname = fname
1153 self.rest = rest
1154
1155 def __hash__(self):
1156 return hash((self.cfg, self.is_spl))
1157
1158def scan_makefiles(fnames):
1159 """Scan Makefiles looking for Kconfig options
1160
1161 Looks for uses of CONFIG options in Makefiles
1162
1163 Args:
1164 fnames (list of tuple):
1165 str: Makefile filename where the option was found
1166 str: Line of the Makefile
1167
1168 Returns:
1169 tuple:
1170 dict: all_uses
1171 key (ConfigUse): object
1172 value (list of str): matching lines
1173 dict: Uses by filename
1174 key (str): filename
1175 value (set of ConfigUse): uses in that filename
1176
1177 >>> RE_MK_CONFIGS.search('CONFIG_FRED').groups()
1178 (None, 'FRED')
1179 >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_)MARY').groups()
1180 ('$(SPL_)', 'MARY')
1181 >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_TPL_)MARY').groups()
1182 ('$(SPL_TPL_)', 'MARY')
1183 """
1184 all_uses = collections.defaultdict(list)
1185 fname_uses = {}
1186 for fname, rest in fnames:
1187 m_iter = RE_MK_CONFIGS.finditer(rest)
Simon Glass4f6725c2023-09-23 13:44:01 -06001188 for mat in m_iter:
1189 real_opt = mat.group(2)
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001190 if real_opt == '':
1191 continue
1192 is_spl = False
Simon Glass4f6725c2023-09-23 13:44:01 -06001193 if mat.group(1):
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001194 is_spl = True
1195 use = ConfigUse(real_opt, is_spl, fname, rest)
1196 if fname not in fname_uses:
1197 fname_uses[fname] = set()
1198 fname_uses[fname].add(use)
1199 all_uses[use].append(rest)
1200 return all_uses, fname_uses
1201
1202
1203def scan_src_files(fnames):
1204 """Scan source files (other than Makefiles) looking for Kconfig options
1205
1206 Looks for uses of CONFIG options
1207
1208 Args:
1209 fnames (list of tuple):
1210 str: Makefile filename where the option was found
1211 str: Line of the Makefile
1212
1213 Returns:
1214 tuple:
1215 dict: all_uses
1216 key (ConfigUse): object
1217 value (list of str): matching lines
1218 dict: Uses by filename
1219 key (str): filename
1220 value (set of ConfigUse): uses in that filename
1221
1222 >>> RE_C_CONFIGS.search('CONFIG_FRED').groups()
1223 ('FRED',)
1224 >>> RE_CONFIG_IS.search('CONFIG_IS_ENABLED(MARY)').groups()
1225 ('MARY',)
1226 >>> RE_CONFIG_IS.search('#if CONFIG_IS_ENABLED(OF_PLATDATA)').groups()
1227 ('OF_PLATDATA',)
1228 """
Simon Glassbeb825d2023-09-23 13:44:00 -06001229 fname = None
1230 rest = None
1231
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001232 def add_uses(m_iter, is_spl):
Simon Glass4f6725c2023-09-23 13:44:01 -06001233 for mat in m_iter:
1234 real_opt = mat.group(1)
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001235 if real_opt == '':
1236 continue
1237 use = ConfigUse(real_opt, is_spl, fname, rest)
1238 if fname not in fname_uses:
1239 fname_uses[fname] = set()
1240 fname_uses[fname].add(use)
1241 all_uses[use].append(rest)
1242
1243 all_uses = collections.defaultdict(list)
1244 fname_uses = {}
1245 for fname, rest in fnames:
1246 m_iter = RE_C_CONFIGS.finditer(rest)
1247 add_uses(m_iter, False)
1248
1249 m_iter2 = RE_CONFIG_IS.finditer(rest)
1250 add_uses(m_iter2, True)
1251
1252 return all_uses, fname_uses
1253
1254
1255MODE_NORMAL, MODE_SPL, MODE_PROPER = range(3)
1256
1257def do_scan_source(path, do_update):
1258 """Scan the source tree for Kconfig inconsistencies
1259
1260 Args:
1261 path (str): Path to source tree
1262 do_update (bool) : True to write to scripts/kconf_... files
1263 """
1264 def is_not_proper(name):
1265 for prefix in SPL_PREFIXES:
1266 if name.startswith(prefix):
1267 return name[len(prefix):]
1268 return False
1269
1270 def check_not_found(all_uses, spl_mode):
1271 """Check for Kconfig options mentioned in the source but not in Kconfig
1272
1273 Args:
1274 all_uses (dict):
1275 key (ConfigUse): object
1276 value (list of str): matching lines
1277 spl_mode (int): If MODE_SPL, look at source code which implies
1278 an SPL_ option, but for which there is none;
1279 for MOD_PROPER, look at source code which implies a Proper
1280 option (i.e. use of CONFIG_IS_ENABLED() or $(SPL_) or
1281 $(SPL_TPL_) but for which there none;
1282 if MODE_NORMAL, ignore SPL
1283
1284 Returns:
1285 dict:
1286 key (str): CONFIG name (without 'CONFIG_' prefix
1287 value (list of ConfigUse): List of uses of this CONFIG
1288 """
1289 # Make sure we know about all the options
1290 not_found = collections.defaultdict(list)
Simon Glassbeb825d2023-09-23 13:44:00 -06001291 for use, _ in all_uses.items():
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001292 name = use.cfg
1293 if name in IGNORE_SYMS:
1294 continue
1295 check = True
1296
1297 if spl_mode == MODE_SPL:
1298 check = use.is_spl
1299
1300 # If it is an SPL symbol, try prepending all SPL_ prefixes to
1301 # find at least one SPL symbol
1302 if use.is_spl:
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001303 for prefix in SPL_PREFIXES:
1304 try_name = prefix + name
1305 sym = kconf.syms.get(try_name)
1306 if sym:
1307 break
1308 if not sym:
1309 not_found[f'SPL_{name}'].append(use)
1310 continue
1311 elif spl_mode == MODE_PROPER:
1312 # Try to find the Proper version of this symbol, i.e. without
1313 # the SPL_ prefix
1314 proper_name = is_not_proper(name)
1315 if proper_name:
1316 name = proper_name
1317 elif not use.is_spl:
1318 check = False
1319 else: # MODE_NORMAL
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001320 sym = kconf.syms.get(name)
1321 if not sym:
1322 proper_name = is_not_proper(name)
1323 if proper_name:
1324 name = proper_name
1325 sym = kconf.syms.get(name)
1326 if not sym:
1327 for prefix in SPL_PREFIXES:
1328 try_name = prefix + name
1329 sym = kconf.syms.get(try_name)
1330 if sym:
1331 break
1332 if not sym:
1333 not_found[name].append(use)
1334 continue
1335
1336 sym = kconf.syms.get(name)
1337 if not sym and check:
1338 not_found[name].append(use)
1339 return not_found
1340
1341 def show_uses(uses):
1342 """Show a list of uses along with their filename and code snippet
1343
1344 Args:
1345 uses (dict):
1346 key (str): CONFIG name (without 'CONFIG_' prefix
1347 value (list of ConfigUse): List of uses of this CONFIG
1348 """
1349 for name in sorted(uses):
1350 print(f'{name}: ', end='')
1351 for i, use in enumerate(uses[name]):
1352 print(f'{" " if i else ""}{use.fname}: {use.rest.strip()}')
1353
1354
1355 print('Scanning Kconfig')
Simon Glassa0a61602024-07-17 16:56:51 +01001356 kconf = scan_kconfig()
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001357 print(f'Scanning source in {path}')
1358 args = ['git', 'grep', '-E', r'IS_ENABLED|\bCONFIG']
1359 with subprocess.Popen(args, stdout=subprocess.PIPE) as proc:
Simon Glassbeb825d2023-09-23 13:44:00 -06001360 out, _ = proc.communicate()
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001361 lines = out.splitlines()
1362 re_fname = re.compile('^([^:]*):(.*)')
1363 src_list = []
1364 mk_list = []
1365 for line in lines:
1366 linestr = line.decode('utf-8')
1367 m_fname = re_fname.search(linestr)
1368 if not m_fname:
1369 continue
1370 fname, rest = m_fname.groups()
1371 dirname, leaf = os.path.split(fname)
1372 root, ext = os.path.splitext(leaf)
1373 if ext == '.autoconf':
1374 pass
1375 elif ext in ['.c', '.h', '.S', '.lds', '.dts', '.dtsi', '.asl', '.cfg',
1376 '.env', '.tmpl']:
1377 src_list.append([fname, rest])
1378 elif 'Makefile' in root or ext == '.mk':
1379 mk_list.append([fname, rest])
1380 elif ext in ['.yml', '.sh', '.py', '.awk', '.pl', '.rst', '', '.sed']:
1381 pass
1382 elif 'Kconfig' in root or 'Kbuild' in root:
1383 pass
1384 elif 'README' in root:
1385 pass
1386 elif dirname in ['configs']:
1387 pass
1388 elif dirname.startswith('doc') or dirname.startswith('scripts/kconfig'):
1389 pass
1390 else:
1391 print(f'Not sure how to handle file {fname}')
1392
1393 # Scan the Makefiles
Simon Glassbeb825d2023-09-23 13:44:00 -06001394 all_uses, _ = scan_makefiles(mk_list)
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001395
1396 spl_not_found = set()
1397 proper_not_found = set()
1398
1399 # Make sure we know about all the options
1400 print('\nCONFIG options present in Makefiles but not Kconfig:')
1401 not_found = check_not_found(all_uses, MODE_NORMAL)
1402 show_uses(not_found)
1403
1404 print('\nCONFIG options present in Makefiles but not Kconfig (SPL):')
1405 not_found = check_not_found(all_uses, MODE_SPL)
1406 show_uses(not_found)
Simon Glassd708cf72023-09-23 13:44:03 -06001407 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001408
1409 print('\nCONFIG options used as Proper in Makefiles but without a non-SPL_ variant:')
1410 not_found = check_not_found(all_uses, MODE_PROPER)
1411 show_uses(not_found)
Simon Glassd708cf72023-09-23 13:44:03 -06001412 proper_not_found |= {not_found.keys()}
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001413
1414 # Scan the source code
Simon Glassbeb825d2023-09-23 13:44:00 -06001415 all_uses, _ = scan_src_files(src_list)
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001416
1417 # Make sure we know about all the options
1418 print('\nCONFIG options present in source but not Kconfig:')
1419 not_found = check_not_found(all_uses, MODE_NORMAL)
1420 show_uses(not_found)
1421
1422 print('\nCONFIG options present in source but not Kconfig (SPL):')
1423 not_found = check_not_found(all_uses, MODE_SPL)
1424 show_uses(not_found)
Simon Glassd708cf72023-09-23 13:44:03 -06001425 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001426
1427 print('\nCONFIG options used as Proper in source but without a non-SPL_ variant:')
1428 not_found = check_not_found(all_uses, MODE_PROPER)
1429 show_uses(not_found)
Simon Glassd708cf72023-09-23 13:44:03 -06001430 proper_not_found |= {not_found.keys()}
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001431
1432 print('\nCONFIG options used as SPL but without an SPL_ variant:')
1433 for item in sorted(spl_not_found):
1434 print(f' {item}')
1435
1436 print('\nCONFIG options used as Proper but without a non-SPL_ variant:')
1437 for item in sorted(proper_not_found):
1438 print(f' {item}')
1439
1440 # Write out the updated information
1441 if do_update:
Simon Glass47f22892023-09-23 13:44:04 -06001442 with open(os.path.join(path, 'scripts', 'conf_nospl'), 'w',
1443 encoding='utf-8') as out:
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001444 print('# These options should not be enabled in SPL builds\n',
1445 file=out)
1446 for item in sorted(spl_not_found):
1447 print(item, file=out)
Simon Glass47f22892023-09-23 13:44:04 -06001448 with open(os.path.join(path, 'scripts', 'conf_noproper'), 'w',
1449 encoding='utf-8') as out:
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001450 print('# These options should not be enabled in Proper builds\n',
1451 file=out)
1452 for item in sorted(proper_not_found):
1453 print(item, file=out)
1454
Simon Glass0082b2e2021-12-18 08:09:46 -07001455
Simon Glassfe11dcf2024-07-17 16:56:55 +01001456def parse_args():
1457 """Parse the program arguments
1458
1459 Returns:
1460 tuple:
1461 argparse.ArgumentParser: parser
1462 argparse.Namespace: Parsed arguments
1463 """
Masahiro Yamadab6160812015-05-20 11:36:07 +09001464 try:
1465 cpu_count = multiprocessing.cpu_count()
1466 except NotImplementedError:
1467 cpu_count = 1
1468
Simon Glassd9c1da22021-12-18 14:54:31 -07001469 epilog = '''Move config options from headers to defconfig files. See
1470doc/develop/moveconfig.rst for documentation.'''
1471
1472 parser = ArgumentParser(epilog=epilog)
1473 # Add arguments here
1474 parser.add_argument('-a', '--add-imply', type=str, default='',
Simon Glass44116332017-06-15 21:39:33 -06001475 help='comma-separated list of CONFIG options to add '
1476 "an 'imply' statement to for the CONFIG in -i")
Simon Glassd9c1da22021-12-18 14:54:31 -07001477 parser.add_argument('-A', '--skip-added', action='store_true', default=False,
Simon Glass44116332017-06-15 21:39:33 -06001478 help="don't show options which are already marked as "
1479 'implying others')
Simon Glassd9c1da22021-12-18 14:54:31 -07001480 parser.add_argument('-b', '--build-db', action='store_true', default=False,
Simon Glass43cf08f2017-06-01 19:39:02 -06001481 help='build a CONFIG database')
Simon Glassd9c1da22021-12-18 14:54:31 -07001482 parser.add_argument('-C', '--commit', action='store_true', default=False,
Simon Glass8bf41c22016-09-12 23:18:21 -06001483 help='Create a git commit for the operation')
Simon Glass9b191102023-09-23 13:44:09 -06001484 parser.add_argument('--nocolour', action='store_true', default=False,
1485 help="don't display the log in colour")
Simon Glassd9c1da22021-12-18 14:54:31 -07001486 parser.add_argument('-d', '--defconfigs', type=str,
Simon Glass8f3cf312017-06-01 19:38:59 -06001487 help='a file containing a list of defconfigs to move, '
1488 "one per line (for example 'snow_defconfig') "
1489 "or '-' to read from stdin")
Simon Glassd9c1da22021-12-18 14:54:31 -07001490 parser.add_argument('-e', '--exit-on-error', action='store_true',
Masahiro Yamadab6160812015-05-20 11:36:07 +09001491 default=False,
1492 help='exit immediately on any error')
Simon Glassd9c1da22021-12-18 14:54:31 -07001493 parser.add_argument('-f', '--find', action='store_true', default=False,
Simon Glass0082b2e2021-12-18 08:09:46 -07001494 help='Find boards with a given config combination')
Simon Glassd9c1da22021-12-18 14:54:31 -07001495 parser.add_argument('-i', '--imply', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001496 help='find options which imply others')
Simon Glassd9c1da22021-12-18 14:54:31 -07001497 parser.add_argument('-I', '--imply-flags', type=str, default='',
Simon Glass0559a742021-12-18 08:09:44 -07001498 help="control the -i option ('help' for help")
Simon Glassd9c1da22021-12-18 14:54:31 -07001499 parser.add_argument('-j', '--jobs', type=int, default=cpu_count,
Masahiro Yamadab6160812015-05-20 11:36:07 +09001500 help='the number of jobs to run simultaneously')
Simon Glassd9c1da22021-12-18 14:54:31 -07001501 parser.add_argument('-n', '--dry-run', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001502 help='perform a trial run (show log with no changes)')
Simon Glassd9c1da22021-12-18 14:54:31 -07001503 parser.add_argument('-r', '--git-ref', type=str,
Joe Hershbergerb1a570f2016-06-10 14:53:32 -05001504 help='the git ref to clone for building the autoconf.mk')
Simon Glassd9c1da22021-12-18 14:54:31 -07001505 parser.add_argument('-s', '--force-sync', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001506 help='force sync by savedefconfig')
Simon Glassd9c1da22021-12-18 14:54:31 -07001507 parser.add_argument('-S', '--spl', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001508 help='parse config options defined for SPL build')
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001509 parser.add_argument('--scan-source', action='store_true', default=False,
1510 help='scan source for uses of CONFIG options')
Simon Glassd9c1da22021-12-18 14:54:31 -07001511 parser.add_argument('-t', '--test', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001512 help='run unit tests')
Simon Glassd9c1da22021-12-18 14:54:31 -07001513 parser.add_argument('-y', '--yes', action='store_true', default=False,
Simon Glass13e05a02016-09-12 23:18:20 -06001514 help="respond 'yes' to any prompts")
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001515 parser.add_argument('-u', '--update', action='store_true', default=False,
1516 help="update scripts/ files (use with --scan-source)")
Simon Glassd9c1da22021-12-18 14:54:31 -07001517 parser.add_argument('-v', '--verbose', action='store_true', default=False,
Joe Hershberger808b63f2015-05-19 13:21:24 -05001518 help='show any build errors as boards are built')
Simon Glassd9c1da22021-12-18 14:54:31 -07001519 parser.add_argument('configs', nargs='*')
Masahiro Yamadab6160812015-05-20 11:36:07 +09001520
Simon Glassfe11dcf2024-07-17 16:56:55 +01001521 return parser, parser.parse_args()
Masahiro Yamadab6160812015-05-20 11:36:07 +09001522
Simon Glassfe11dcf2024-07-17 16:56:55 +01001523
1524def main():
1525 """Main program"""
1526 parser, args = parse_args()
Simon Glass855a0ce2024-07-17 16:56:57 +01001527 if not any((args.force_sync, args.build_db, args.imply, args.find,
1528 args.scan_source, args.test)):
1529 parser.print_usage()
1530 sys.exit(1)
Simon Glassd9c1da22021-12-18 14:54:31 -07001531 if args.test:
Simon Glassbb57be72021-12-18 08:09:45 -07001532 sys.argv = [sys.argv[0]]
Simon Glassbeb825d2023-09-23 13:44:00 -06001533 fail, _ = doctest.testmod()
Simon Glassbb57be72021-12-18 08:09:45 -07001534 if fail:
1535 return 1
1536 unittest.main()
1537
Simon Glass9b191102023-09-23 13:44:09 -06001538 col = terminal.Color(terminal.COLOR_NEVER if args.nocolour
1539 else terminal.COLOR_IF_TERMINAL)
1540
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001541 if args.scan_source:
1542 do_scan_source(os.getcwd(), args.update)
Simon Glasse19a9cd2023-09-23 13:44:05 -06001543 return 0
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001544
Masahiro Yamadab903c4e2016-05-19 15:51:58 +09001545 # prefix the option name with CONFIG_ if missing
Simon Glass08d148a2023-09-23 13:43:54 -06001546 configs = [prefix_config(cfg) for cfg in args.configs]
Masahiro Yamadab6160812015-05-20 11:36:07 +09001547
Joe Hershberger23475932015-05-19 13:21:20 -05001548 check_top_directory()
1549
Simon Glassd9c1da22021-12-18 14:54:31 -07001550 if args.imply:
Simon Glass92e55582017-06-15 21:39:32 -06001551 imply_flags = 0
Simon Glassd9c1da22021-12-18 14:54:31 -07001552 if args.imply_flags == 'all':
Simon Glass5f096922017-07-10 14:47:46 -06001553 imply_flags = -1
1554
Simon Glassd9c1da22021-12-18 14:54:31 -07001555 elif args.imply_flags:
1556 for flag in args.imply_flags.split(','):
Simon Glass5f096922017-07-10 14:47:46 -06001557 bad = flag not in IMPLY_FLAGS
1558 if bad:
Simon Glass96f8f312023-09-23 13:43:59 -06001559 print(f"Invalid flag '{flag}'")
Simon Glass5f096922017-07-10 14:47:46 -06001560 if flag == 'help' or bad:
Simon Glass1f701862019-10-31 07:42:57 -06001561 print("Imply flags: (separate with ',')")
1562 for name, info in IMPLY_FLAGS.items():
Simon Glass96f8f312023-09-23 13:43:59 -06001563 print(f' {name:-15s}: {info[1]}')
Simon Glass5f096922017-07-10 14:47:46 -06001564 parser.print_usage()
1565 sys.exit(1)
1566 imply_flags |= IMPLY_FLAGS[flag][0]
Simon Glass92e55582017-06-15 21:39:32 -06001567
Simon Glassd9c1da22021-12-18 14:54:31 -07001568 do_imply_config(configs, args.add_imply, imply_flags, args.skip_added)
Simon Glasse19a9cd2023-09-23 13:44:05 -06001569 return 0
Simon Glassc6e73cf2017-06-01 19:39:03 -06001570
Simon Glassd9c1da22021-12-18 14:54:31 -07001571 if args.find:
Simon Glass0082b2e2021-12-18 08:09:46 -07001572 do_find_config(configs)
Simon Glasse19a9cd2023-09-23 13:44:05 -06001573 return 0
Simon Glass0082b2e2021-12-18 08:09:46 -07001574
Simon Glass35688b62023-09-23 13:43:53 -06001575 # We are either building the database or forcing a sync of defconfigs
Simon Glass43cf08f2017-06-01 19:39:02 -06001576 config_db = {}
Simon Glass1f701862019-10-31 07:42:57 -06001577 db_queue = queue.Queue()
Simon Glass4f6725c2023-09-23 13:44:01 -06001578 dbt = DatabaseThread(config_db, db_queue)
1579 dbt.daemon = True
1580 dbt.start()
Simon Glass43cf08f2017-06-01 19:39:02 -06001581
Simon Glass1572b3f2023-09-23 13:43:50 -06001582 check_clean_directory()
1583 bsettings.setup('')
1584 toolchains = toolchain.Toolchains()
1585 toolchains.GetSettings()
1586 toolchains.Scan(verbose=False)
Simon Glass65709242023-09-23 13:44:13 -06001587 progress = move_config(toolchains, args, db_queue, col)
Simon Glass1572b3f2023-09-23 13:43:50 -06001588 db_queue.join()
Joe Hershberger23475932015-05-19 13:21:20 -05001589
Simon Glassd9c1da22021-12-18 14:54:31 -07001590 if args.commit:
Simon Glass8bf41c22016-09-12 23:18:21 -06001591 subprocess.call(['git', 'add', '-u'])
1592 if configs:
Simon Glass28155572024-07-17 16:56:52 +01001593 part = 'et al ' if len(configs) > 1 else ''
1594 msg = f'Convert {configs[0]} {part}to Kconfig'
Simon Glass8bf41c22016-09-12 23:18:21 -06001595 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' %
1596 '\n '.join(configs))
1597 else:
1598 msg = 'configs: Resync with savedefconfig'
1599 msg += '\n\nRsync all defconfig files using moveconfig.py'
1600 subprocess.call(['git', 'commit', '-s', '-m', msg])
1601
Simon Glass65709242023-09-23 13:44:13 -06001602 failed = progress.total - progress.good
1603 failure = f'{failed} failed, ' if failed else ''
Simon Glassd9c1da22021-12-18 14:54:31 -07001604 if args.build_db:
Simon Glass4f6725c2023-09-23 13:44:01 -06001605 with open(CONFIG_DATABASE, 'w', encoding='utf-8') as outf:
Simon Glass1f701862019-10-31 07:42:57 -06001606 for defconfig, configs in config_db.items():
Simon Glass4f6725c2023-09-23 13:44:01 -06001607 outf.write(f'{defconfig}\n')
Simon Glass43cf08f2017-06-01 19:39:02 -06001608 for config in sorted(configs.keys()):
Simon Glass4f6725c2023-09-23 13:44:01 -06001609 outf.write(f' {config}={configs[config]}\n')
1610 outf.write('\n')
Simon Glass65709242023-09-23 13:44:13 -06001611 print(col.build(
1612 col.RED if failed else col.GREEN,
1613 f'{failure}{len(config_db)} boards written to {CONFIG_DATABASE}'))
1614 else:
1615 if failed:
1616 print(col.build(col.RED, f'{failure}see {FAILED_LIST}', True))
1617 else:
1618 # Add enough spaces to overwrite the progress indicator
1619 print(col.build(
1620 col.GREEN, f'{progress.total} processed ', bright=True))
1621
Simon Glasse19a9cd2023-09-23 13:44:05 -06001622 return 0
1623
Simon Glass43cf08f2017-06-01 19:39:02 -06001624
Masahiro Yamadab6160812015-05-20 11:36:07 +09001625if __name__ == '__main__':
Simon Glass0082b2e2021-12-18 08:09:46 -07001626 sys.exit(main())