blob: 8a2384d895b88bf8c683417beee7ca3afba82240 [file] [log] [blame]
Simon Glass793dca32019-10-31 07:42:57 -06001#!/usr/bin/env python3
Tom Rini83d290c2018-05-06 17:58:06 -04002# SPDX-License-Identifier: GPL-2.0+
Masahiro Yamada5a27c732015-05-20 11:36:07 +09003
Simon Glassf876e962024-07-17 16:56:52 +01004"""Build and query a Kconfig database for boards.
Masahiro Yamada5a27c732015-05-20 11:36:07 +09005
Simon Glass5c72c0e2021-07-21 21:35:51 -06006See doc/develop/moveconfig.rst for documentation.
Simon Glassf876e962024-07-17 16:56:52 +01007
8Author: Masahiro Yamada <yamada.masahiro@socionext.com>
9Author: Simon Glass <sjg@chromium.org>
Masahiro Yamada5a27c732015-05-20 11:36:07 +090010"""
11
Simon Glassb2e83c62021-12-18 14:54:31 -070012from argparse import ArgumentParser
Simon Glass99b66602017-06-01 19:39:03 -060013import collections
Simon Glass91197aa2021-12-18 14:54:35 -070014from contextlib import ExitStack
Simon Glass84067a52021-12-18 08:09:45 -070015import doctest
Masahiro Yamadac8e1b102016-05-19 15:52:07 +090016import filecmp
Masahiro Yamada5a27c732015-05-20 11:36:07 +090017import fnmatch
Masahiro Yamada0dbc9b52016-10-19 14:39:54 +090018import glob
Masahiro Yamada5a27c732015-05-20 11:36:07 +090019import multiprocessing
Masahiro Yamada5a27c732015-05-20 11:36:07 +090020import os
Simon Glass793dca32019-10-31 07:42:57 -060021import queue
Masahiro Yamada5a27c732015-05-20 11:36:07 +090022import re
23import shutil
24import subprocess
25import sys
26import tempfile
Simon Glassd73fcb12017-06-01 19:39:02 -060027import threading
Masahiro Yamada5a27c732015-05-20 11:36:07 +090028import time
Simon Glass84067a52021-12-18 08:09:45 -070029import unittest
Masahiro Yamada5a27c732015-05-20 11:36:07 +090030
Simon Glass0ede00f2020-04-17 18:09:02 -060031from buildman import bsettings
32from buildman import kconfiglib
33from buildman import toolchain
Simon Glass15f19ab2023-09-23 13:44:09 -060034from u_boot_pylib import terminal
Simon Glasscb008832017-06-15 21:39:33 -060035
Masahiro Yamada5a27c732015-05-20 11:36:07 +090036SHOW_GNU_MAKE = 'scripts/show-gnu-make'
37SLEEP_TIME=0.03
38
Masahiro Yamada5a27c732015-05-20 11:36:07 +090039STATE_IDLE = 0
40STATE_DEFCONFIG = 1
41STATE_AUTOCONF = 2
Joe Hershberger96464ba2015-05-19 13:21:17 -050042STATE_SAVEDEFCONFIG = 3
Masahiro Yamada5a27c732015-05-20 11:36:07 +090043
Simon Glassf3b8e642017-06-01 19:39:01 -060044AUTO_CONF_PATH = 'include/config/auto.conf'
Simon Glass51963792023-09-23 13:44:15 -060045CONFIG_DATABASE = 'qconfig.db'
46FAILED_LIST = 'qconfig.failed'
Simon Glassf3b8e642017-06-01 19:39:01 -060047
Simon Glasscb008832017-06-15 21:39:33 -060048CONFIG_LEN = len('CONFIG_')
Simon Glassf3b8e642017-06-01 19:39:01 -060049
Markus Klotzbuecherb237d352019-05-15 15:15:52 +020050SIZES = {
Simon Glassdaa694d2021-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 Klotzbuecherb237d352019-05-15 15:15:52 +020068}
69
Simon Glassb8d11da2022-02-08 11:49:45 -070070RE_REMOVE_DEFCONFIG = re.compile(r'(.*)_defconfig')
71
Simon Glass65e62032023-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 Yamada5a27c732015-05-20 11:36:07 +090084### helper functions ###
Masahiro Yamada5a27c732015-05-20 11:36:07 +090085def check_top_directory():
86 """Exit if we are not at the top of source directory."""
Simon Glass91197aa2021-12-18 14:54:35 -070087 for fname in 'README', 'Licenses':
88 if not os.path.exists(fname):
Masahiro Yamada5a27c732015-05-20 11:36:07 +090089 sys.exit('Please run at the top of source directory.')
90
Masahiro Yamadabd63e5b2016-05-19 15:51:54 +090091def check_clean_directory():
92 """Exit if the source tree is not clean."""
Simon Glass91197aa2021-12-18 14:54:35 -070093 for fname in '.config', 'include/config':
94 if os.path.exists(fname):
Masahiro Yamadabd63e5b2016-05-19 15:51:54 +090095 sys.exit("source tree is not clean, please run 'make mrproper'")
96
Masahiro Yamada5a27c732015-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 Glass91197aa2021-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 Yamada5a27c732015-05-20 11:36:07 +0900108 return ret[0].rstrip()
109
Simon Glass25f978c2017-06-01 19:38:58 -0600110def get_matched_defconfig(line):
111 """Get the defconfig files that match a pattern
112
113 Args:
Simon Glass91197aa2021-12-18 14:54:35 -0700114 line (str): Path or filename to match, e.g. 'configs/snow_defconfig' or
Simon Glass25f978c2017-06-01 19:38:58 -0600115 'k2*_defconfig'. If no directory is provided, 'configs/' is
116 prepended
117
118 Returns:
Simon Glass91197aa2021-12-18 14:54:35 -0700119 list of str: a list of matching defconfig files
Simon Glass25f978c2017-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 Yamada0dbc9b52016-10-19 14:39:54 +0900128def get_matched_defconfigs(defconfigs_file):
Simon Glassee4e61b2017-06-01 19:38:59 -0600129 """Get all the defconfig files that match the patterns in a file.
130
131 Args:
Simon Glass91197aa2021-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 Glassee4e61b2017-06-01 19:38:59 -0600134
135 Returns:
Simon Glass91197aa2021-12-18 14:54:35 -0700136 list of str: A list of paths to defconfig files, with no duplicates
Simon Glassee4e61b2017-06-01 19:38:59 -0600137 """
Masahiro Yamada0dbc9b52016-10-19 14:39:54 +0900138 defconfigs = []
Simon Glass91197aa2021-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 Yamada0dbc9b52016-10-19 14:39:54 +0900155
Simon Glass91197aa2021-12-18 14:54:35 -0700156 defconfigs += matched
Masahiro Yamada0dbc9b52016-10-19 14:39:54 +0900157
158 # use set() to drop multiple matching
Simon Glass91197aa2021-12-18 14:54:35 -0700159 return [defconfig[len('configs') + 1:] for defconfig in set(defconfigs)]
Masahiro Yamada0dbc9b52016-10-19 14:39:54 +0900160
Masahiro Yamada684c3062016-07-25 19:15:28 +0900161def get_all_defconfigs():
Simon Glass91197aa2021-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 Yamada684c3062016-07-25 19:15:28 +0900167 defconfigs = []
Simon Glass91197aa2021-12-18 14:54:35 -0700168 for (dirpath, _, filenames) in os.walk('configs'):
Masahiro Yamada684c3062016-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 Glass2fd85bd2021-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 Glass37f815c2021-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 Glass549d4222023-09-23 13:43:58 -0600195 as_lines (bool): Return file contents as a list of lines
Simon Glass37f815c2021-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 Glassf297ba32023-09-23 13:44:05 -0600210 return inf.read()
Simon Glassa4c9d172023-09-23 13:44:01 -0600211 except UnicodeDecodeError as exc:
Simon Glass37f815c2021-12-18 14:54:34 -0700212 if not skip_unicode:
Simon Glass68a0b712022-02-11 13:23:22 -0700213 raise
Simon Glassa4c9d172023-09-23 13:44:01 -0600214 print(f"Failed on file '{fname}: {exc}")
Simon Glass37f815c2021-12-18 14:54:34 -0700215 return None
216
Chris Packhamca438342017-05-02 21:30:47 +1200217
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900218### classes ###
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900219class Progress:
220
221 """Progress Indicator"""
222
Simon Glass6b25d212023-09-23 13:44:10 -0600223 def __init__(self, col, total):
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900224 """Create a new progress indicator.
225
Simon Glass91197aa2021-12-18 14:54:35 -0700226 Args:
Simon Glass6b25d212023-09-23 13:44:10 -0600227 color_enabled (bool): True for colour output
228 total (int): A number of defconfig files to process.
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900229 """
Simon Glass6b25d212023-09-23 13:44:10 -0600230 self.col = col
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900231 self.current = 0
Simon Glass6b25d212023-09-23 13:44:10 -0600232 self.good = 0
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900233 self.total = total
234
Simon Glass6b25d212023-09-23 13:44:10 -0600235 def inc(self, success):
236 """Increment the number of processed defconfig files.
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900237
Simon Glass6b25d212023-09-23 13:44:10 -0600238 Args:
239 success (bool): True if processing succeeded
240 """
241 self.good += success
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900242 self.current += 1
243
244 def show(self):
245 """Display the progress."""
Simon Glass95f09142023-09-23 13:44:08 -0600246 if self.current != self.total:
Simon Glass6b25d212023-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 Yamadac5e60fd2016-05-19 15:51:55 +0900253 sys.stdout.flush()
254
Simon Glasscb008832017-06-15 21:39:33 -0600255
Simon Glass0a0c1242024-07-17 16:56:51 +0100256def scan_kconfig():
257 """Scan all the Kconfig files and create a Config object
Simon Glasscb008832017-06-15 21:39:33 -0600258
Simon Glass0a0c1242024-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 Glasscb008832017-06-15 21:39:33 -0600268
269
Simon Glassf876e962024-07-17 16:56:52 +0100270# pylint: disable=R0903
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900271class KconfigParser:
Masahiro Yamada5a27c732015-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 Glass882c8e42023-09-23 13:43:54 -0600277 def __init__(self, args, build_dir):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900278 """Create a new parser.
279
Simon Glass91197aa2021-12-18 14:54:35 -0700280 Args:
Simon Glass91197aa2021-12-18 14:54:35 -0700281 args (Namespace): program arguments
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900282 build_dir: Build directory.
283 """
Simon Glassb2e83c62021-12-18 14:54:31 -0700284 self.args = args
Masahiro Yamada1f169922016-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 Yamada07913d12016-08-22 22:18:22 +0900287 self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
288 'autoconf.mk')
Simon Glassf3b8e642017-06-01 19:39:01 -0600289 self.config_autoconf = os.path.join(build_dir, AUTO_CONF_PATH)
Masahiro Yamada5da4f852016-05-19 15:52:06 +0900290 self.defconfig = os.path.join(build_dir, 'defconfig')
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900291
Simon Glass6821a742017-07-10 14:47:47 -0600292 def get_arch(self):
293 """Parse .config file and return the architecture.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900294
295 Returns:
Simon Glass6821a742017-07-10 14:47:47 -0600296 Architecture name (e.g. 'arm').
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900297 """
298 arch = ''
299 cpu = ''
Simon Glass37f815c2021-12-18 14:54:34 -0700300 for line in read_file(self.dotconfig):
Simon Glassa4c9d172023-09-23 13:44:01 -0600301 m_arch = self.re_arch.match(line)
302 if m_arch:
303 arch = m_arch.group(1)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900304 continue
Simon Glassa4c9d172023-09-23 13:44:01 -0600305 m_cpu = self.re_cpu.match(line)
306 if m_cpu:
307 cpu = m_cpu.group(1)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900308
Masahiro Yamada90ed6cb2016-05-19 15:51:53 +0900309 if not arch:
310 return None
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900311
312 # fix-up for aarch64
313 if arch == 'arm' and cpu == 'armv8':
314 arch = 'aarch64'
315
Simon Glass6821a742017-07-10 14:47:47 -0600316 return arch
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900317
Simon Glassd73fcb12017-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 Yamada5a27c732015-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 Glass15f19ab2023-09-23 13:44:09 -0600355 def __init__(self, toolchains, args, progress, devnull, make_cmd,
356 reference_src_dir, db_queue, col):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900357 """Create a new process slot.
358
Simon Glass91197aa2021-12-18 14:54:35 -0700359 Args:
Simon Glass6821a742017-07-10 14:47:47 -0600360 toolchains: Toolchains object containing toolchains.
Simon Glassb2e83c62021-12-18 14:54:31 -0700361 args: Program arguments
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900362 progress: A progress indicator.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900363 devnull: A file object of '/dev/null'.
364 make_cmd: command name of GNU Make.
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500365 reference_src_dir: Determine the true starting config state from this
366 source tree.
Simon Glassd73fcb12017-06-01 19:39:02 -0600367 db_queue: output queue to write config info for the database
Simon Glass15f19ab2023-09-23 13:44:09 -0600368 col (terminal.Color): Colour object
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900369 """
Simon Glass6821a742017-07-10 14:47:47 -0600370 self.toolchains = toolchains
Simon Glassb2e83c62021-12-18 14:54:31 -0700371 self.args = args
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900372 self.progress = progress
Masahiro Yamada5a27c732015-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 Hershberger6b96c1a2016-06-10 14:53:32 -0500376 self.reference_src_dir = reference_src_dir
Simon Glassd73fcb12017-06-01 19:39:02 -0600377 self.db_queue = db_queue
Simon Glass15f19ab2023-09-23 13:44:09 -0600378 self.col = col
Simon Glass882c8e42023-09-23 13:43:54 -0600379 self.parser = KconfigParser(args, self.build_dir)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900380 self.state = STATE_IDLE
Masahiro Yamada09c6c062016-08-22 22:18:20 +0900381 self.failed_boards = set()
Simon Glassa6ab4db2023-09-23 13:44:02 -0600382 self.defconfig = None
Simon Glass9461bf02023-09-23 13:44:07 -0600383 self.log = []
Simon Glassa6ab4db2023-09-23 13:44:02 -0600384 self.current_src_dir = None
385 self.proc = None
Masahiro Yamada5a27c732015-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 Hershbergerf2dae752016-06-10 14:53:29 -0500392 because it is guaranteed the destructor is always invoked when the
Masahiro Yamada5a27c732015-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 Glassf297ba32023-09-23 13:44:05 -0600398 while self.proc.poll() is None:
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900399 pass
400 shutil.rmtree(self.build_dir)
401
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900402 def add(self, defconfig):
Masahiro Yamada5a27c732015-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 Glass91197aa2021-12-18 14:54:35 -0700409 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600410 defconfig (str): defconfig name.
Masahiro Yamada5a27c732015-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 Yamadae307fa92016-06-08 11:47:37 +0900417
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900418 self.defconfig = defconfig
Simon Glass9461bf02023-09-23 13:44:07 -0600419 self.log = []
Masahiro Yamadaf432c332016-06-15 14:33:52 +0900420 self.current_src_dir = self.reference_src_dir
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900421 self.do_defconfig()
Masahiro Yamada5a27c732015-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 Yamada7fb0bac2016-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 Yamada5a27c732015-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 Glassf297ba32023-09-23 13:44:05 -0600443 if self.proc.poll() is None:
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900444 return False
445
Simon Glassa4c9d172023-09-23 13:44:01 -0600446 if self.proc.poll() != 0:
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900447 self.handle_error()
448 elif self.state == STATE_DEFCONFIG:
Masahiro Yamadaf432c332016-06-15 14:33:52 +0900449 if self.reference_src_dir and not self.current_src_dir:
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500450 self.do_savedefconfig()
451 else:
452 self.do_autoconf()
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900453 elif self.state == STATE_AUTOCONF:
Masahiro Yamadaf432c332016-06-15 14:33:52 +0900454 if self.current_src_dir:
455 self.current_src_dir = None
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500456 self.do_defconfig()
Simon Glassb2e83c62021-12-18 14:54:31 -0700457 elif self.args.build_db:
Simon Glassd73fcb12017-06-01 19:39:02 -0600458 self.do_build_db()
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500459 else:
460 self.do_savedefconfig()
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900461 elif self.state == STATE_SAVEDEFCONFIG:
462 self.update_defconfig()
463 else:
Simon Glassdaa694d2021-12-18 14:54:30 -0700464 sys.exit('Internal Error. This should not happen.')
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900465
Simon Glassf297ba32023-09-23 13:44:05 -0600466 return self.state == STATE_IDLE
Joe Hershberger96464ba2015-05-19 13:21:17 -0500467
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900468 def handle_error(self):
469 """Handle error cases."""
Masahiro Yamada8513dc02016-05-19 15:52:08 +0900470
Simon Glass15f19ab2023-09-23 13:44:09 -0600471 self.log.append(self.col.build(self.col.RED, 'Failed to process',
472 bright=True))
Simon Glassb2e83c62021-12-18 14:54:31 -0700473 if self.args.verbose:
Simon Glass9461bf02023-09-23 13:44:07 -0600474 for line in self.proc.stderr.read().decode().splitlines():
Simon Glass15f19ab2023-09-23 13:44:09 -0600475 self.log.append(self.col.build(self.col.CYAN, line, True))
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900476 self.finish(False)
Joe Hershberger96464ba2015-05-19 13:21:17 -0500477
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900478 def do_defconfig(self):
479 """Run 'make <board>_defconfig' to create the .config file."""
Masahiro Yamadac8e1b102016-05-19 15:52:07 +0900480
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900481 cmd = list(self.make_cmd)
482 cmd.append(self.defconfig)
Simon Glassf876e962024-07-17 16:56:52 +0100483 # pylint: disable=R1732
Simon Glassa4c9d172023-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 Yamadae307fa92016-06-08 11:47:37 +0900487 self.state = STATE_DEFCONFIG
Masahiro Yamadac8e1b102016-05-19 15:52:07 +0900488
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900489 def do_autoconf(self):
Simon Glassf3b8e642017-06-01 19:39:01 -0600490 """Run 'make AUTO_CONF_PATH'."""
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900491
Simon Glass6821a742017-07-10 14:47:47 -0600492 arch = self.parser.get_arch()
493 try:
Simon Glassf297ba32023-09-23 13:44:05 -0600494 tchain = self.toolchains.Select(arch)
Simon Glass6821a742017-07-10 14:47:47 -0600495 except ValueError:
Simon Glass15f19ab2023-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 Yamada4efef992016-05-19 15:52:03 +0900499 self.finish(False)
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900500 return
Simon Glassf297ba32023-09-23 13:44:05 -0600501 env = tchain.MakeEnvironment(False)
Masahiro Yamada90ed6cb2016-05-19 15:51:53 +0900502
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900503 cmd = list(self.make_cmd)
Joe Hershberger7740f652015-05-19 13:21:18 -0500504 cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
Simon Glassf3b8e642017-06-01 19:39:01 -0600505 cmd.append(AUTO_CONF_PATH)
Simon Glassf876e962024-07-17 16:56:52 +0100506 # pylint: disable=R1732
Simon Glassa4c9d172023-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 Yamada5a27c732015-05-20 11:36:07 +0900510 self.state = STATE_AUTOCONF
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900511
Simon Glassd73fcb12017-06-01 19:39:02 -0600512 def do_build_db(self):
513 """Add the board to the database"""
514 configs = {}
Simon Glass37f815c2021-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 Glassd73fcb12017-06-01 19:39:02 -0600519 self.db_queue.put([self.defconfig, configs])
520 self.finish(True)
521
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900522 def do_savedefconfig(self):
523 """Update the .config and run 'make savedefconfig'."""
Simon Glassc7345612023-09-23 13:43:55 -0600524 if not self.args.force_sync:
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900525 self.finish(True)
526 return
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900527
528 cmd = list(self.make_cmd)
529 cmd.append('savedefconfig')
Simon Glassf876e962024-07-17 16:56:52 +0100530 # pylint: disable=R1732
Simon Glassa4c9d172023-09-23 13:44:01 -0600531 self.proc = subprocess.Popen(cmd, stdout=self.devnull,
532 stderr=subprocess.PIPE)
Masahiro Yamadae307fa92016-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 Yamadae307fa92016-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 Glass15f19ab2023-09-23 13:44:09 -0600542 self.log.append(
543 self.col.build(self.col.BLUE, 'defconfig updated', bright=True))
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900544
Simon Glassb2e83c62021-12-18 14:54:31 -0700545 if not self.args.dry_run and updated:
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900546 shutil.move(new_defconfig, orig_defconfig)
547 self.finish(True)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900548
Masahiro Yamada4efef992016-05-19 15:52:03 +0900549 def finish(self, success):
550 """Display log along with progress and go to the idle state.
Masahiro Yamada1d085562016-05-19 15:52:02 +0900551
Simon Glass91197aa2021-12-18 14:54:35 -0700552 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600553 success (bool): Should be True when the defconfig was processed
Masahiro Yamada4efef992016-05-19 15:52:03 +0900554 successfully, or False when it fails.
Masahiro Yamada1d085562016-05-19 15:52:02 +0900555 """
556 # output at least 30 characters to hide the "* defconfigs out of *".
Simon Glassdc1d2e62023-09-23 13:44:11 -0600557 name = self.defconfig[:-len('_defconfig')]
Simon Glass5aba58c2023-09-23 13:44:06 -0600558 if self.log:
Masahiro Yamada1d085562016-05-19 15:52:02 +0900559
Simon Glass9461bf02023-09-23 13:44:07 -0600560 # Put the first log line on the first line
561 log = name.ljust(20) + ' ' + self.log[0]
562
563 if len(self.log) > 1:
564 log += '\n' + '\n'.join([' ' + s for s in self.log[1:]])
Simon Glass5aba58c2023-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 Yamada4efef992016-05-19 15:52:03 +0900568
569 if not success:
Simon Glassb2e83c62021-12-18 14:54:31 -0700570 if self.args.exit_on_error:
Simon Glassdaa694d2021-12-18 14:54:30 -0700571 sys.exit('Exit on error.')
Masahiro Yamada4efef992016-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 Glassdc1d2e62023-09-23 13:44:11 -0600574 self.failed_boards.add(name)
Masahiro Yamada4efef992016-05-19 15:52:03 +0900575
Simon Glass6b25d212023-09-23 13:44:10 -0600576 self.progress.inc(success)
Masahiro Yamada1d085562016-05-19 15:52:02 +0900577 self.progress.show()
Masahiro Yamada4efef992016-05-19 15:52:03 +0900578 self.state = STATE_IDLE
Masahiro Yamada1d085562016-05-19 15:52:02 +0900579
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900580 def get_failed_boards(self):
Masahiro Yamada09c6c062016-08-22 22:18:20 +0900581 """Returns a set of failed boards (defconfigs) in this slot.
Masahiro Yamada5a27c732015-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 Glass15f19ab2023-09-23 13:44:09 -0600589 def __init__(self, toolchains, args, progress, reference_src_dir, db_queue,
590 col):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900591 """Create a new slots controller.
592
Simon Glass91197aa2021-12-18 14:54:35 -0700593 Args:
Simon Glass15f19ab2023-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 Yamada5a27c732015-05-20 11:36:07 +0900601 """
Simon Glassb2e83c62021-12-18 14:54:31 -0700602 self.args = args
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900603 self.slots = []
Simon Glassdc1d2e62023-09-23 13:44:11 -0600604 self.progress = progress
Simon Glass15f19ab2023-09-23 13:44:09 -0600605 self.col = col
Simon Glass478920d2021-12-18 14:54:32 -0700606 devnull = subprocess.DEVNULL
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900607 make_cmd = get_make_cmd()
Simon Glass62fae4b2023-09-23 13:44:00 -0600608 for _ in range(args.jobs):
Simon Glass882c8e42023-09-23 13:43:54 -0600609 self.slots.append(Slot(toolchains, args, progress, devnull,
Simon Glass15f19ab2023-09-23 13:44:09 -0600610 make_cmd, reference_src_dir, db_queue, col))
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900611
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900612 def add(self, defconfig):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900613 """Add a new subprocess if a vacant slot is found.
614
Simon Glass91197aa2021-12-18 14:54:35 -0700615 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600616 defconfig (str): defconfig name to be put into.
Masahiro Yamada5a27c732015-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 Yamadac5e60fd2016-05-19 15:51:55 +0900622 if slot.add(defconfig):
Masahiro Yamada5a27c732015-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 Glass94e2ed72023-09-23 13:44:13 -0600649 def write_failed_boards(self):
Simon Glassdc1d2e62023-09-23 13:44:11 -0600650 """Show the results of processing"""
Masahiro Yamada09c6c062016-08-22 22:18:20 +0900651 boards = set()
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900652
653 for slot in self.slots:
Masahiro Yamada09c6c062016-08-22 22:18:20 +0900654 boards |= slot.get_failed_boards()
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900655
Masahiro Yamada96dccd92016-06-15 14:33:53 +0900656 if boards:
Simon Glassdc1d2e62023-09-23 13:44:11 -0600657 boards = '\n'.join(sorted(boards)) + '\n'
Simon Glass94e2ed72023-09-23 13:44:13 -0600658 write_file(FAILED_LIST, boards)
659
Joe Hershberger2559cd82015-05-19 13:21:22 -0500660
Masahiro Yamada5cc42a52016-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 Glass91197aa2021-12-18 14:54:35 -0700668 Args:
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900669 commit: commit to git-clone
670 """
671 self.src_dir = tempfile.mkdtemp()
Simon Glassdaa694d2021-12-18 14:54:30 -0700672 print('Cloning git repo to a separate work directory...')
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900673 subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
674 cwd=self.src_dir)
Simon Glass1bd43062023-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 Yamada5cc42a52016-06-15 14:33:51 +0900678 subprocess.check_output(['git', 'checkout', commit],
679 stderr=subprocess.STDOUT, cwd=self.src_dir)
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500680
681 def __del__(self):
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900682 """Delete the reference source directory
Joe Hershberger6b96c1a2016-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 Yamada5cc42a52016-06-15 14:33:51 +0900689 shutil.rmtree(self.src_dir)
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500690
Masahiro Yamada5cc42a52016-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 Hershberger6b96c1a2016-06-10 14:53:32 -0500695
Simon Glass15f19ab2023-09-23 13:44:09 -0600696def move_config(toolchains, args, db_queue, col):
Simon Glass882c8e42023-09-23 13:43:54 -0600697 """Build database or sync config options to defconfig files.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900698
Simon Glass91197aa2021-12-18 14:54:35 -0700699 Args:
Simon Glass15f19ab2023-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 Glass94e2ed72023-09-23 13:44:13 -0600704
705 Returns:
706 Progress: Progress indicator
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900707 """
Simon Glassb2e83c62021-12-18 14:54:31 -0700708 if args.git_ref:
709 reference_src = ReferenceSource(args.git_ref)
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900710 reference_src_dir = reference_src.get_dir()
711 else:
Masahiro Yamadaf432c332016-06-15 14:33:52 +0900712 reference_src_dir = None
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500713
Simon Glassb2e83c62021-12-18 14:54:31 -0700714 if args.defconfigs:
715 defconfigs = get_matched_defconfigs(args.defconfigs)
Joe Hershberger91040e82015-05-19 13:21:19 -0500716 else:
Masahiro Yamada684c3062016-07-25 19:15:28 +0900717 defconfigs = get_all_defconfigs()
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900718
Simon Glass6b25d212023-09-23 13:44:10 -0600719 progress = Progress(col, len(defconfigs))
Simon Glass15f19ab2023-09-23 13:44:09 -0600720 slots = Slots(toolchains, args, progress, reference_src_dir, db_queue, col)
Masahiro Yamada5a27c732015-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 Yamadac5e60fd2016-05-19 15:51:55 +0900725 for defconfig in defconfigs:
726 while not slots.add(defconfig):
Masahiro Yamada5a27c732015-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 Glass94e2ed72023-09-23 13:44:13 -0600735 slots.write_failed_boards()
736 return progress
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900737
Simon Glasscb008832017-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 Glass549d4222023-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 Glasscb008832017-06-15 21:39:33 -0600746
747 Returns:
748 Symbol object for 'config' if found, else None
749 """
Tom Rini65e05dd2019-09-20 17:42:09 -0400750 sym = kconf.syms.get(imply_config)
Simon Glasscb008832017-06-15 21:39:33 -0600751 if sym:
Simon Glass62fae4b2023-09-23 13:44:00 -0600752 for sel, _ in (sym.selects + sym.implies):
Simon Glassa3627082021-12-18 08:09:42 -0700753 if sel.name == config:
Simon Glasscb008832017-06-15 21:39:33 -0600754 return sym
755 return None
756
Simon Glassf876e962024-07-17 16:56:52 +0100757def check_imply_rule(kconf, imply_config):
Simon Glasscb008832017-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 Glass549d4222023-09-23 13:43:58 -0600764 kconf (Kconfiglib.Kconfig): Kconfig object
Simon Glass549d4222023-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 Glasscb008832017-06-15 21:39:33 -0600767
768 Returns:
769 tuple:
Simon Glass549d4222023-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 Glasscb008832017-06-15 21:39:33 -0600774 """
Tom Rini65e05dd2019-09-20 17:42:09 -0400775 sym = kconf.syms.get(imply_config)
Simon Glasscb008832017-06-15 21:39:33 -0600776 if not sym:
777 return 'cannot find sym'
Simon Glassea40b202021-07-21 21:35:53 -0600778 nodes = sym.nodes
779 if len(nodes) != 1:
Simon Glass1bd43062023-09-23 13:43:59 -0600780 return f'{len(nodes)} locations'
Simon Glassa3627082021-12-18 08:09:42 -0700781 node = nodes[0]
782 fname, linenum = node.filename, node.linenr
Simon Glasscb008832017-06-15 21:39:33 -0600783 cwd = os.getcwd()
784 if cwd and fname.startswith(cwd):
785 fname = fname[len(cwd) + 1:]
Simon Glass1bd43062023-09-23 13:43:59 -0600786 file_line = f' at {fname}:{linenum}'
Simon Glass37f815c2021-12-18 14:54:34 -0700787 data = read_file(fname)
Simon Glass1bd43062023-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 Glasscb008832017-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 Glass549d4222023-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 Glasscb008832017-06-15 21:39:33 -0600799
800 Returns:
801 Message indicating the result
802 """
Simon Glass1bd43062023-09-23 13:43:59 -0600803 file_line = f' at {fname}:{linenum}'
Simon Glass37f815c2021-12-18 14:54:34 -0700804 data = read_file(fname)
Simon Glasscb008832017-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 Glass1bd43062023-09-23 13:43:59 -0600809 data.insert(linenum + offset, f'\timply {config}')
Simon Glass2fd85bd2021-12-18 14:54:33 -0700810 write_file(fname, data)
Simon Glass1bd43062023-09-23 13:43:59 -0600811 return f'added{file_line}'
Simon Glasscb008832017-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 Glass9b2a2e82017-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 Glasscb008832017-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 Glass91197aa2021-12-18 14:54:35 -0700825}
Simon Glass9b2a2e82017-06-15 21:39:32 -0600826
Simon Glass9d603392021-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 Glass61d555d2024-07-17 16:56:49 +0100857 defconfig = None
Simon Glass37f815c2021-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 Glass9d603392021-12-18 08:09:43 -0700871
872 return all_configs, all_defconfigs, config_db, defconfig_db
873
874
Simon Glasscb008832017-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 Glass99b66602017-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 Glassea4d6de2023-09-23 13:44:14 -0600885 This function uses the qconfig database to find such options. It
Simon Glass99b66602017-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 Glasscb008832017-06-15 21:39:33 -0600901 add_imply: Automatically add an 'imply' for each config.
Simon Glass9b2a2e82017-06-15 21:39:32 -0600902 imply_flags: Flags which control which implying configs are allowed
903 (IMPLY_...)
Simon Glasscb008832017-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 Glass99b66602017-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 Glass0a0c1242024-07-17 16:56:51 +0100917 kconf = scan_kconfig() if check_kconfig else None
Simon Glasscb008832017-06-15 21:39:33 -0600918 if add_imply and add_imply != 'all':
Simon Glassa3627082021-12-18 08:09:42 -0700919 add_imply = add_imply.split(',')
Simon Glasscb008832017-06-15 21:39:33 -0600920
Simon Glass62fae4b2023-09-23 13:44:00 -0600921 all_configs, all_defconfigs, _, defconfig_db = read_database()
Simon Glass99b66602017-06-01 19:39:03 -0600922
Simon Glassa3627082021-12-18 08:09:42 -0700923 # Work through each target config option in turn, independently
Simon Glass99b66602017-06-01 19:39:03 -0600924 for config in config_list:
925 defconfigs = defconfig_db.get(config)
926 if not defconfigs:
Simon Glass1bd43062023-09-23 13:43:59 -0600927 print(f'{config} not found in any defconfig')
Simon Glass99b66602017-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 Glass1bd43062023-09-23 13:43:59 -0600934 print(f'{config} found in {num_defconfigs}/{len(all_configs)} defconfigs')
Simon Glass99b66602017-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 Glass9b2a2e82017-06-15 21:39:32 -0600942 if 'ERRATUM' in imply_config:
Simon Glass99b66602017-06-01 19:39:03 -0600943 continue
Simon Glass91197aa2021-12-18 14:54:35 -0700944 if not imply_flags & IMPLY_CMD:
Simon Glass9b2a2e82017-06-15 21:39:32 -0600945 if 'CONFIG_CMD' in imply_config:
946 continue
Simon Glass91197aa2021-12-18 14:54:35 -0700947 if not imply_flags & IMPLY_TARGET:
Simon Glass9b2a2e82017-06-15 21:39:32 -0600948 if 'CONFIG_TARGET' in imply_config:
949 continue
Simon Glass99b66602017-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 Glass793dca32019-10-31 07:42:57 -0600971 for prev in list(imply_configs.keys()):
Simon Glass99b66602017-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 Glassf297ba32023-09-23 13:44:05 -0600980 if count > prev_count:
Simon Glass99b66602017-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 Glasscb008832017-06-15 21:39:33 -0600990 ranked_iconfigs = sorted(imply_configs,
Simon Glass99b66602017-06-01 19:39:03 -0600991 key=lambda k: len(imply_configs[k]), reverse=True)
Simon Glasscb008832017-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 Glass99b66602017-06-01 19:39:03 -0600997
998 # Don't bother if there are less than 5 defconfigs affected.
Simon Glass9b2a2e82017-06-15 21:39:32 -0600999 if num_common < (2 if imply_flags & IMPLY_MIN_2 else 5):
Simon Glass99b66602017-06-01 19:39:03 -06001000 continue
Simon Glasscb008832017-06-15 21:39:33 -06001001 missing = defconfigs - imply_configs[iconfig]
Simon Glass99b66602017-06-01 19:39:03 -06001002 missing_str = ', '.join(missing) if missing else 'all'
1003 missing_str = ''
Simon Glasscb008832017-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 Glassea40b202021-07-21 21:35:53 -06001010 nodes = sym.nodes
1011 if len(nodes) == 1:
1012 fname, linenum = nodes[0].filename, nodes[0].linenr
Simon Glasscb008832017-06-15 21:39:33 -06001013 if cwd and fname.startswith(cwd):
1014 fname = fname[len(cwd) + 1:]
Simon Glass1bd43062023-09-23 13:43:59 -06001015 kconfig_info = f'{fname}:{linenum}'
Simon Glasscb008832017-06-15 21:39:33 -06001016 if skip_added:
1017 show = False
1018 else:
Tom Rini65e05dd2019-09-20 17:42:09 -04001019 sym = kconf.syms.get(iconfig[CONFIG_LEN:])
Simon Glasscb008832017-06-15 21:39:33 -06001020 fname = ''
1021 if sym:
Simon Glassea40b202021-07-21 21:35:53 -06001022 nodes = sym.nodes
1023 if len(nodes) == 1:
1024 fname, linenum = nodes[0].filename, nodes[0].linenr
Simon Glasscb008832017-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 Glass91197aa2021-12-18 14:54:35 -07001030 not imply_flags & IMPLY_NON_ARCH_BOARD):
Simon Glasscb008832017-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 Glassf876e962024-07-17 16:56:52 +01001036 iconfig[CONFIG_LEN:]))
Simon Glasscb008832017-06-15 21:39:33 -06001037 if fname:
1038 add_list[fname].append(linenum)
1039
1040 if show and kconfig_info != 'skip':
Simon Glass0e03fb12024-07-17 16:56:53 +01001041 print(f'{num_common:5} : '
1042 f'{iconfig.ljust(30)}{kconfig_info.ljust(25)} {missing_str}')
Simon Glasscb008832017-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 Glass793dca32019-10-31 07:42:57 -06001049 for fname, linenums in add_list.items():
Simon Glasscb008832017-06-15 21:39:33 -06001050 for linenum in sorted(linenums, reverse=True):
1051 add_imply_rule(config[CONFIG_LEN:], fname, linenum)
Simon Glass99b66602017-06-01 19:39:03 -06001052
Simon Glass941671a2022-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 Glassd9c958f2022-03-05 20:18:54 -07001068 if re_match.fullmatch(cfg):
Simon Glass941671a2022-02-08 11:49:46 -07001069 return True
1070 return False
Simon Glass99b66602017-06-01 19:39:03 -06001071
Simon Glass65d7fce2021-12-18 08:09:46 -07001072def do_find_config(config_list):
1073 """Find boards with a given combination of CONFIGs
1074
1075 Params:
Simon Glass941671a2022-02-08 11:49:46 -07001076 config_list: List of CONFIG options to check (each a regex consisting
Simon Glass65d7fce2021-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 Glass62fae4b2023-09-23 13:44:00 -06001081 _, all_defconfigs, config_db, _ = read_database()
Simon Glass65d7fce2021-12-18 08:09:46 -07001082
Simon Glass65d7fce2021-12-18 08:09:46 -07001083 # Start with all defconfigs
1084 out = all_defconfigs
1085
1086 # Work through each config in turn
Simon Glass65d7fce2021-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 Glass65d7fce2021-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 Glass941671a2022-02-08 11:49:46 -07001100 re_match = re.compile(cfg)
Simon Glass65d7fce2021-12-18 08:09:46 -07001101 for defc in in_list:
Simon Glass941671a2022-02-08 11:49:46 -07001102 has_cfg = defconfig_matches(config_db[defc], re_match)
Simon Glass65d7fce2021-12-18 08:09:46 -07001103 if has_cfg == want:
1104 out.add(defc)
Tom Rini9ef3ba82022-12-04 10:14:16 -05001105 print(f'{len(out)} matches')
1106 print(' '.join(item.split('_defconfig')[0] for item in out))
Simon Glass65d7fce2021-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 Glassa4c9d172023-09-23 13:44:01 -06001125 oper = ''
Simon Glass65d7fce2021-12-18 08:09:46 -07001126 if cfg[0] == '~':
Simon Glassa4c9d172023-09-23 13:44:01 -06001127 oper = cfg[0]
Simon Glass65d7fce2021-12-18 08:09:46 -07001128 cfg = cfg[1:]
1129 if not cfg.startswith('CONFIG_'):
1130 cfg = 'CONFIG_' + cfg
Simon Glassa4c9d172023-09-23 13:44:01 -06001131 return oper + cfg
Simon Glass65d7fce2021-12-18 08:09:46 -07001132
1133
Simon Glass98275712023-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 Glass65e62032023-02-01 13:19:12 -07001138
1139class ConfigUse:
Simon Glassf876e962024-07-17 16:56:52 +01001140 """Tracks whether a config relates to SPL or not"""
Simon Glass65e62032023-02-01 13:19:12 -07001141 def __init__(self, cfg, is_spl, fname, rest):
Simon Glassf876e962024-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 Glass65e62032023-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 Glassa4c9d172023-09-23 13:44:01 -06001188 for mat in m_iter:
1189 real_opt = mat.group(2)
Simon Glass65e62032023-02-01 13:19:12 -07001190 if real_opt == '':
1191 continue
1192 is_spl = False
Simon Glassa4c9d172023-09-23 13:44:01 -06001193 if mat.group(1):
Simon Glass65e62032023-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 Glass62fae4b2023-09-23 13:44:00 -06001229 fname = None
1230 rest = None
1231
Simon Glass65e62032023-02-01 13:19:12 -07001232 def add_uses(m_iter, is_spl):
Simon Glassa4c9d172023-09-23 13:44:01 -06001233 for mat in m_iter:
1234 real_opt = mat.group(1)
Simon Glass65e62032023-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 Glass62fae4b2023-09-23 13:44:00 -06001291 for use, _ in all_uses.items():
Simon Glass65e62032023-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 Glass65e62032023-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 Glass65e62032023-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 Glass0a0c1242024-07-17 16:56:51 +01001356 kconf = scan_kconfig()
Simon Glass65e62032023-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 Glass62fae4b2023-09-23 13:44:00 -06001360 out, _ = proc.communicate()
Simon Glass65e62032023-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 Glass62fae4b2023-09-23 13:44:00 -06001394 all_uses, _ = scan_makefiles(mk_list)
Simon Glass65e62032023-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 Glassb774ba52023-09-23 13:44:03 -06001407 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
Simon Glass65e62032023-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 Glassb774ba52023-09-23 13:44:03 -06001412 proper_not_found |= {not_found.keys()}
Simon Glass65e62032023-02-01 13:19:12 -07001413
1414 # Scan the source code
Simon Glass62fae4b2023-09-23 13:44:00 -06001415 all_uses, _ = scan_src_files(src_list)
Simon Glass65e62032023-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 Glassb774ba52023-09-23 13:44:03 -06001425 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
Simon Glass65e62032023-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 Glassb774ba52023-09-23 13:44:03 -06001430 proper_not_found |= {not_found.keys()}
Simon Glass65e62032023-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 Glasse6c686f2023-09-23 13:44:04 -06001442 with open(os.path.join(path, 'scripts', 'conf_nospl'), 'w',
1443 encoding='utf-8') as out:
Simon Glass65e62032023-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 Glasse6c686f2023-09-23 13:44:04 -06001448 with open(os.path.join(path, 'scripts', 'conf_noproper'), 'w',
1449 encoding='utf-8') as out:
Simon Glass65e62032023-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
1455
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001456def main():
Simon Glassf876e962024-07-17 16:56:52 +01001457 """Main program"""
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001458 try:
1459 cpu_count = multiprocessing.cpu_count()
1460 except NotImplementedError:
1461 cpu_count = 1
1462
Simon Glassb2e83c62021-12-18 14:54:31 -07001463 epilog = '''Move config options from headers to defconfig files. See
1464doc/develop/moveconfig.rst for documentation.'''
1465
1466 parser = ArgumentParser(epilog=epilog)
1467 # Add arguments here
1468 parser.add_argument('-a', '--add-imply', type=str, default='',
Simon Glasscb008832017-06-15 21:39:33 -06001469 help='comma-separated list of CONFIG options to add '
1470 "an 'imply' statement to for the CONFIG in -i")
Simon Glassb2e83c62021-12-18 14:54:31 -07001471 parser.add_argument('-A', '--skip-added', action='store_true', default=False,
Simon Glasscb008832017-06-15 21:39:33 -06001472 help="don't show options which are already marked as "
1473 'implying others')
Simon Glassb2e83c62021-12-18 14:54:31 -07001474 parser.add_argument('-b', '--build-db', action='store_true', default=False,
Simon Glassd73fcb12017-06-01 19:39:02 -06001475 help='build a CONFIG database')
Simon Glassb2e83c62021-12-18 14:54:31 -07001476 parser.add_argument('-C', '--commit', action='store_true', default=False,
Simon Glass9ede2122016-09-12 23:18:21 -06001477 help='Create a git commit for the operation')
Simon Glass15f19ab2023-09-23 13:44:09 -06001478 parser.add_argument('--nocolour', action='store_true', default=False,
1479 help="don't display the log in colour")
Simon Glassb2e83c62021-12-18 14:54:31 -07001480 parser.add_argument('-d', '--defconfigs', type=str,
Simon Glassee4e61b2017-06-01 19:38:59 -06001481 help='a file containing a list of defconfigs to move, '
1482 "one per line (for example 'snow_defconfig') "
1483 "or '-' to read from stdin")
Simon Glassb2e83c62021-12-18 14:54:31 -07001484 parser.add_argument('-e', '--exit-on-error', action='store_true',
Simon Glasse1ae5632021-12-18 08:09:44 -07001485 default=False,
1486 help='exit immediately on any error')
Simon Glassb2e83c62021-12-18 14:54:31 -07001487 parser.add_argument('-f', '--find', action='store_true', default=False,
Simon Glass65d7fce2021-12-18 08:09:46 -07001488 help='Find boards with a given config combination')
Simon Glassb2e83c62021-12-18 14:54:31 -07001489 parser.add_argument('-i', '--imply', action='store_true', default=False,
Simon Glass99b66602017-06-01 19:39:03 -06001490 help='find options which imply others')
Simon Glassb2e83c62021-12-18 14:54:31 -07001491 parser.add_argument('-I', '--imply-flags', type=str, default='',
Simon Glass9b2a2e82017-06-15 21:39:32 -06001492 help="control the -i option ('help' for help")
Simon Glassb2e83c62021-12-18 14:54:31 -07001493 parser.add_argument('-j', '--jobs', type=int, default=cpu_count,
Simon Glasse1ae5632021-12-18 08:09:44 -07001494 help='the number of jobs to run simultaneously')
Simon Glassb2e83c62021-12-18 14:54:31 -07001495 parser.add_argument('-n', '--dry-run', action='store_true', default=False,
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001496 help='perform a trial run (show log with no changes)')
Simon Glassb2e83c62021-12-18 14:54:31 -07001497 parser.add_argument('-r', '--git-ref', type=str,
Simon Glasse1ae5632021-12-18 08:09:44 -07001498 help='the git ref to clone for building the autoconf.mk')
Simon Glassb2e83c62021-12-18 14:54:31 -07001499 parser.add_argument('-s', '--force-sync', action='store_true', default=False,
Masahiro Yamada8513dc02016-05-19 15:52:08 +09001500 help='force sync by savedefconfig')
Simon Glassb2e83c62021-12-18 14:54:31 -07001501 parser.add_argument('-S', '--spl', action='store_true', default=False,
Masahiro Yamada07913d12016-08-22 22:18:22 +09001502 help='parse config options defined for SPL build')
Simon Glass65e62032023-02-01 13:19:12 -07001503 parser.add_argument('--scan-source', action='store_true', default=False,
1504 help='scan source for uses of CONFIG options')
Simon Glassb2e83c62021-12-18 14:54:31 -07001505 parser.add_argument('-t', '--test', action='store_true', default=False,
Simon Glasse1ae5632021-12-18 08:09:44 -07001506 help='run unit tests')
Simon Glassb2e83c62021-12-18 14:54:31 -07001507 parser.add_argument('-y', '--yes', action='store_true', default=False,
Simon Glass6b403df2016-09-12 23:18:20 -06001508 help="respond 'yes' to any prompts")
Simon Glass65e62032023-02-01 13:19:12 -07001509 parser.add_argument('-u', '--update', action='store_true', default=False,
1510 help="update scripts/ files (use with --scan-source)")
Simon Glassb2e83c62021-12-18 14:54:31 -07001511 parser.add_argument('-v', '--verbose', action='store_true', default=False,
Joe Hershberger95bf9c72015-05-19 13:21:24 -05001512 help='show any build errors as boards are built')
Simon Glassb2e83c62021-12-18 14:54:31 -07001513 parser.add_argument('configs', nargs='*')
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001514
Simon Glassb2e83c62021-12-18 14:54:31 -07001515 args = parser.parse_args()
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001516
Simon Glassb2e83c62021-12-18 14:54:31 -07001517 if args.test:
Simon Glass84067a52021-12-18 08:09:45 -07001518 sys.argv = [sys.argv[0]]
Simon Glass62fae4b2023-09-23 13:44:00 -06001519 fail, _ = doctest.testmod()
Simon Glass84067a52021-12-18 08:09:45 -07001520 if fail:
1521 return 1
1522 unittest.main()
1523
Simon Glass15f19ab2023-09-23 13:44:09 -06001524 col = terminal.Color(terminal.COLOR_NEVER if args.nocolour
1525 else terminal.COLOR_IF_TERMINAL)
1526
Simon Glass65e62032023-02-01 13:19:12 -07001527 if args.scan_source:
1528 do_scan_source(os.getcwd(), args.update)
Simon Glassf297ba32023-09-23 13:44:05 -06001529 return 0
Simon Glass65e62032023-02-01 13:19:12 -07001530
Simon Glass882c8e42023-09-23 13:43:54 -06001531 if not any((args.force_sync, args.build_db, args.imply, args.find)):
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001532 parser.print_usage()
1533 sys.exit(1)
1534
Masahiro Yamadab6ef3932016-05-19 15:51:58 +09001535 # prefix the option name with CONFIG_ if missing
Simon Glass882c8e42023-09-23 13:43:54 -06001536 configs = [prefix_config(cfg) for cfg in args.configs]
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001537
Joe Hershberger2144f882015-05-19 13:21:20 -05001538 check_top_directory()
1539
Simon Glassb2e83c62021-12-18 14:54:31 -07001540 if args.imply:
Simon Glass9b2a2e82017-06-15 21:39:32 -06001541 imply_flags = 0
Simon Glassb2e83c62021-12-18 14:54:31 -07001542 if args.imply_flags == 'all':
Simon Glassdee36c72017-07-10 14:47:46 -06001543 imply_flags = -1
1544
Simon Glassb2e83c62021-12-18 14:54:31 -07001545 elif args.imply_flags:
1546 for flag in args.imply_flags.split(','):
Simon Glassdee36c72017-07-10 14:47:46 -06001547 bad = flag not in IMPLY_FLAGS
1548 if bad:
Simon Glass1bd43062023-09-23 13:43:59 -06001549 print(f"Invalid flag '{flag}'")
Simon Glassdee36c72017-07-10 14:47:46 -06001550 if flag == 'help' or bad:
Simon Glass793dca32019-10-31 07:42:57 -06001551 print("Imply flags: (separate with ',')")
1552 for name, info in IMPLY_FLAGS.items():
Simon Glass1bd43062023-09-23 13:43:59 -06001553 print(f' {name:-15s}: {info[1]}')
Simon Glassdee36c72017-07-10 14:47:46 -06001554 parser.print_usage()
1555 sys.exit(1)
1556 imply_flags |= IMPLY_FLAGS[flag][0]
Simon Glass9b2a2e82017-06-15 21:39:32 -06001557
Simon Glassb2e83c62021-12-18 14:54:31 -07001558 do_imply_config(configs, args.add_imply, imply_flags, args.skip_added)
Simon Glassf297ba32023-09-23 13:44:05 -06001559 return 0
Simon Glass99b66602017-06-01 19:39:03 -06001560
Simon Glassb2e83c62021-12-18 14:54:31 -07001561 if args.find:
Simon Glass65d7fce2021-12-18 08:09:46 -07001562 do_find_config(configs)
Simon Glassf297ba32023-09-23 13:44:05 -06001563 return 0
Simon Glass65d7fce2021-12-18 08:09:46 -07001564
Simon Glass3481e892023-09-23 13:43:53 -06001565 # We are either building the database or forcing a sync of defconfigs
Simon Glassd73fcb12017-06-01 19:39:02 -06001566 config_db = {}
Simon Glass793dca32019-10-31 07:42:57 -06001567 db_queue = queue.Queue()
Simon Glassa4c9d172023-09-23 13:44:01 -06001568 dbt = DatabaseThread(config_db, db_queue)
1569 dbt.daemon = True
1570 dbt.start()
Simon Glassd73fcb12017-06-01 19:39:02 -06001571
Simon Glass63df2022023-09-23 13:43:50 -06001572 check_clean_directory()
1573 bsettings.setup('')
1574 toolchains = toolchain.Toolchains()
1575 toolchains.GetSettings()
1576 toolchains.Scan(verbose=False)
Simon Glass94e2ed72023-09-23 13:44:13 -06001577 progress = move_config(toolchains, args, db_queue, col)
Simon Glass63df2022023-09-23 13:43:50 -06001578 db_queue.join()
Joe Hershberger2144f882015-05-19 13:21:20 -05001579
Simon Glassb2e83c62021-12-18 14:54:31 -07001580 if args.commit:
Simon Glass9ede2122016-09-12 23:18:21 -06001581 subprocess.call(['git', 'add', '-u'])
1582 if configs:
Simon Glassf876e962024-07-17 16:56:52 +01001583 part = 'et al ' if len(configs) > 1 else ''
1584 msg = f'Convert {configs[0]} {part}to Kconfig'
Simon Glass9ede2122016-09-12 23:18:21 -06001585 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' %
1586 '\n '.join(configs))
1587 else:
1588 msg = 'configs: Resync with savedefconfig'
1589 msg += '\n\nRsync all defconfig files using moveconfig.py'
1590 subprocess.call(['git', 'commit', '-s', '-m', msg])
1591
Simon Glass94e2ed72023-09-23 13:44:13 -06001592 failed = progress.total - progress.good
1593 failure = f'{failed} failed, ' if failed else ''
Simon Glassb2e83c62021-12-18 14:54:31 -07001594 if args.build_db:
Simon Glassa4c9d172023-09-23 13:44:01 -06001595 with open(CONFIG_DATABASE, 'w', encoding='utf-8') as outf:
Simon Glass793dca32019-10-31 07:42:57 -06001596 for defconfig, configs in config_db.items():
Simon Glassa4c9d172023-09-23 13:44:01 -06001597 outf.write(f'{defconfig}\n')
Simon Glassd73fcb12017-06-01 19:39:02 -06001598 for config in sorted(configs.keys()):
Simon Glassa4c9d172023-09-23 13:44:01 -06001599 outf.write(f' {config}={configs[config]}\n')
1600 outf.write('\n')
Simon Glass94e2ed72023-09-23 13:44:13 -06001601 print(col.build(
1602 col.RED if failed else col.GREEN,
1603 f'{failure}{len(config_db)} boards written to {CONFIG_DATABASE}'))
1604 else:
1605 if failed:
1606 print(col.build(col.RED, f'{failure}see {FAILED_LIST}', True))
1607 else:
1608 # Add enough spaces to overwrite the progress indicator
1609 print(col.build(
1610 col.GREEN, f'{progress.total} processed ', bright=True))
1611
Simon Glassf297ba32023-09-23 13:44:05 -06001612 return 0
1613
Simon Glassd73fcb12017-06-01 19:39:02 -06001614
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001615if __name__ == '__main__':
Simon Glass65d7fce2021-12-18 08:09:46 -07001616 sys.exit(main())