blob: 74d7d1bd175ecb031eace03ca0eace48139019cd [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 Glass495e58c2024-07-17 16:56:54 +01006See doc/develop/qconfig.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 Glasseb1df3f2024-07-17 16:57:05 +0100696def move_config(args, 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 args (Namespace): Program arguments
Simon Glass15f19ab2023-09-23 13:44:09 -0600701 col (terminal.Color): Colour object
Simon Glass94e2ed72023-09-23 13:44:13 -0600702
703 Returns:
Simon Glasseb1df3f2024-07-17 16:57:05 +0100704 tuple:
705 config_db (dict of configs for each defconfig):
706 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
707 value: dict:
708 key: CONFIG option
709 value: Value of option
710 Progress: Progress indicator
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900711 """
Simon Glasseb1df3f2024-07-17 16:57:05 +0100712 config_db = {}
713 db_queue = queue.Queue()
714 dbt = DatabaseThread(config_db, db_queue)
715 dbt.daemon = True
716 dbt.start()
717
718 check_clean_directory()
719 bsettings.setup('')
720
721 # Get toolchains to use
722 toolchains = toolchain.Toolchains()
723 toolchains.GetSettings()
724 toolchains.Scan(verbose=False)
725
Simon Glassb2e83c62021-12-18 14:54:31 -0700726 if args.git_ref:
727 reference_src = ReferenceSource(args.git_ref)
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900728 reference_src_dir = reference_src.get_dir()
729 else:
Masahiro Yamadaf432c332016-06-15 14:33:52 +0900730 reference_src_dir = None
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500731
Simon Glassb2e83c62021-12-18 14:54:31 -0700732 if args.defconfigs:
733 defconfigs = get_matched_defconfigs(args.defconfigs)
Joe Hershberger91040e82015-05-19 13:21:19 -0500734 else:
Masahiro Yamada684c3062016-07-25 19:15:28 +0900735 defconfigs = get_all_defconfigs()
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900736
Simon Glass6b25d212023-09-23 13:44:10 -0600737 progress = Progress(col, len(defconfigs))
Simon Glass15f19ab2023-09-23 13:44:09 -0600738 slots = Slots(toolchains, args, progress, reference_src_dir, db_queue, col)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900739
740 # Main loop to process defconfig files:
741 # Add a new subprocess into a vacant slot.
742 # Sleep if there is no available slot.
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900743 for defconfig in defconfigs:
744 while not slots.add(defconfig):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900745 while not slots.available():
746 # No available slot: sleep for a while
747 time.sleep(SLEEP_TIME)
748
749 # wait until all the subprocesses finish
750 while not slots.empty():
751 time.sleep(SLEEP_TIME)
752
Simon Glass94e2ed72023-09-23 13:44:13 -0600753 slots.write_failed_boards()
Simon Glasseb1df3f2024-07-17 16:57:05 +0100754 db_queue.join()
755 return config_db, progress
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900756
Simon Glasscb008832017-06-15 21:39:33 -0600757def find_kconfig_rules(kconf, config, imply_config):
758 """Check whether a config has a 'select' or 'imply' keyword
759
760 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600761 kconf (Kconfiglib.Kconfig): Kconfig object
762 config (str): Name of config to check (without CONFIG_ prefix)
763 imply_config (str): Implying config (without CONFIG_ prefix) which may
764 or may not have an 'imply' for 'config')
Simon Glasscb008832017-06-15 21:39:33 -0600765
766 Returns:
767 Symbol object for 'config' if found, else None
768 """
Tom Rini65e05dd2019-09-20 17:42:09 -0400769 sym = kconf.syms.get(imply_config)
Simon Glasscb008832017-06-15 21:39:33 -0600770 if sym:
Simon Glass62fae4b2023-09-23 13:44:00 -0600771 for sel, _ in (sym.selects + sym.implies):
Simon Glassa3627082021-12-18 08:09:42 -0700772 if sel.name == config:
Simon Glasscb008832017-06-15 21:39:33 -0600773 return sym
774 return None
775
Simon Glassf876e962024-07-17 16:56:52 +0100776def check_imply_rule(kconf, imply_config):
Simon Glasscb008832017-06-15 21:39:33 -0600777 """Check if we can add an 'imply' option
778
779 This finds imply_config in the Kconfig and looks to see if it is possible
780 to add an 'imply' for 'config' to that part of the Kconfig.
781
782 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600783 kconf (Kconfiglib.Kconfig): Kconfig object
Simon Glass549d4222023-09-23 13:43:58 -0600784 imply_config (str): Implying config (without CONFIG_ prefix) which may
785 or may not have an 'imply' for 'config')
Simon Glasscb008832017-06-15 21:39:33 -0600786
787 Returns:
788 tuple:
Simon Glass549d4222023-09-23 13:43:58 -0600789 str: filename of Kconfig file containing imply_config, or None if
790 none
791 int: line number within the Kconfig file, or 0 if none
792 str: message indicating the result
Simon Glasscb008832017-06-15 21:39:33 -0600793 """
Tom Rini65e05dd2019-09-20 17:42:09 -0400794 sym = kconf.syms.get(imply_config)
Simon Glasscb008832017-06-15 21:39:33 -0600795 if not sym:
796 return 'cannot find sym'
Simon Glassea40b202021-07-21 21:35:53 -0600797 nodes = sym.nodes
798 if len(nodes) != 1:
Simon Glass1bd43062023-09-23 13:43:59 -0600799 return f'{len(nodes)} locations'
Simon Glassa3627082021-12-18 08:09:42 -0700800 node = nodes[0]
801 fname, linenum = node.filename, node.linenr
Simon Glasscb008832017-06-15 21:39:33 -0600802 cwd = os.getcwd()
803 if cwd and fname.startswith(cwd):
804 fname = fname[len(cwd) + 1:]
Simon Glass1bd43062023-09-23 13:43:59 -0600805 file_line = f' at {fname}:{linenum}'
Simon Glass37f815c2021-12-18 14:54:34 -0700806 data = read_file(fname)
Simon Glass1bd43062023-09-23 13:43:59 -0600807 if data[linenum - 1] != f'config {imply_config}':
808 return None, 0, f'bad sym format {data[linenum]}{file_line})'
809 return fname, linenum, f'adding{file_line}'
Simon Glasscb008832017-06-15 21:39:33 -0600810
811def add_imply_rule(config, fname, linenum):
812 """Add a new 'imply' option to a Kconfig
813
814 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600815 config (str): config option to add an imply for (without CONFIG_ prefix)
816 fname (str): Kconfig filename to update
817 linenum (int): Line number to place the 'imply' before
Simon Glasscb008832017-06-15 21:39:33 -0600818
819 Returns:
820 Message indicating the result
821 """
Simon Glass1bd43062023-09-23 13:43:59 -0600822 file_line = f' at {fname}:{linenum}'
Simon Glass37f815c2021-12-18 14:54:34 -0700823 data = read_file(fname)
Simon Glasscb008832017-06-15 21:39:33 -0600824 linenum -= 1
825
826 for offset, line in enumerate(data[linenum:]):
827 if line.strip().startswith('help') or not line:
Simon Glass1bd43062023-09-23 13:43:59 -0600828 data.insert(linenum + offset, f'\timply {config}')
Simon Glass2fd85bd2021-12-18 14:54:33 -0700829 write_file(fname, data)
Simon Glass1bd43062023-09-23 13:43:59 -0600830 return f'added{file_line}'
Simon Glasscb008832017-06-15 21:39:33 -0600831
832 return 'could not insert%s'
833
834(IMPLY_MIN_2, IMPLY_TARGET, IMPLY_CMD, IMPLY_NON_ARCH_BOARD) = (
835 1, 2, 4, 8)
Simon Glass9b2a2e82017-06-15 21:39:32 -0600836
837IMPLY_FLAGS = {
838 'min2': [IMPLY_MIN_2, 'Show options which imply >2 boards (normally >5)'],
839 'target': [IMPLY_TARGET, 'Allow CONFIG_TARGET_... options to imply'],
840 'cmd': [IMPLY_CMD, 'Allow CONFIG_CMD_... to imply'],
Simon Glasscb008832017-06-15 21:39:33 -0600841 'non-arch-board': [
842 IMPLY_NON_ARCH_BOARD,
843 'Allow Kconfig options outside arch/ and /board/ to imply'],
Simon Glass91197aa2021-12-18 14:54:35 -0700844}
Simon Glass9b2a2e82017-06-15 21:39:32 -0600845
Simon Glass9d603392021-12-18 08:09:43 -0700846
847def read_database():
848 """Read in the config database
849
850 Returns:
851 tuple:
852 set of all config options seen (each a str)
853 set of all defconfigs seen (each a str)
854 dict of configs for each defconfig:
855 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
856 value: dict:
857 key: CONFIG option
858 value: Value of option
859 dict of defconfigs for each config:
860 key: CONFIG option
861 value: set of boards using that option
862
863 """
864 configs = {}
865
866 # key is defconfig name, value is dict of (CONFIG_xxx, value)
867 config_db = {}
868
869 # Set of all config options we have seen
870 all_configs = set()
871
872 # Set of all defconfigs we have seen
873 all_defconfigs = set()
874
875 defconfig_db = collections.defaultdict(set)
Simon Glass61d555d2024-07-17 16:56:49 +0100876 defconfig = None
Simon Glass37f815c2021-12-18 14:54:34 -0700877 for line in read_file(CONFIG_DATABASE):
878 line = line.rstrip()
879 if not line: # Separator between defconfigs
880 config_db[defconfig] = configs
881 all_defconfigs.add(defconfig)
882 configs = {}
883 elif line[0] == ' ': # CONFIG line
884 config, value = line.strip().split('=', 1)
885 configs[config] = value
886 defconfig_db[config].add(defconfig)
887 all_configs.add(config)
888 else: # New defconfig
889 defconfig = line
Simon Glass9d603392021-12-18 08:09:43 -0700890
891 return all_configs, all_defconfigs, config_db, defconfig_db
892
893
Simon Glasscb008832017-06-15 21:39:33 -0600894def do_imply_config(config_list, add_imply, imply_flags, skip_added,
895 check_kconfig=True, find_superset=False):
Simon Glass99b66602017-06-01 19:39:03 -0600896 """Find CONFIG options which imply those in the list
897
898 Some CONFIG options can be implied by others and this can help to reduce
899 the size of the defconfig files. For example, CONFIG_X86 implies
900 CONFIG_CMD_IRQ, so we can put 'imply CMD_IRQ' under 'config X86' and
901 all x86 boards will have that option, avoiding adding CONFIG_CMD_IRQ to
902 each of the x86 defconfig files.
903
Simon Glassea4d6de2023-09-23 13:44:14 -0600904 This function uses the qconfig database to find such options. It
Simon Glass99b66602017-06-01 19:39:03 -0600905 displays a list of things that could possibly imply those in the list.
906 The algorithm ignores any that start with CONFIG_TARGET since these
907 typically refer to only a few defconfigs (often one). It also does not
908 display a config with less than 5 defconfigs.
909
910 The algorithm works using sets. For each target config in config_list:
911 - Get the set 'defconfigs' which use that target config
912 - For each config (from a list of all configs):
913 - Get the set 'imply_defconfig' of defconfigs which use that config
914 -
915 - If imply_defconfigs contains anything not in defconfigs then
916 this config does not imply the target config
917
Simon Glasscc628f52024-07-17 16:57:03 +0100918 Args:
919 config_list (list of str): List of CONFIG options to check
920 add_imply (bool): Automatically add an 'imply' for each config.
921 imply_flags (int): Flags which control which implying configs are allowed
Simon Glass9b2a2e82017-06-15 21:39:32 -0600922 (IMPLY_...)
Simon Glasscc628f52024-07-17 16:57:03 +0100923 skip_added (bool): Don't show options which already have an imply added.
924 check_kconfig (bool): Check if implied symbols already have an 'imply' or
Simon Glasscb008832017-06-15 21:39:33 -0600925 'select' for the target config, and show this information if so.
Simon Glasscc628f52024-07-17 16:57:03 +0100926 find_superset (bool): True to look for configs which are a superset of those
Simon Glass99b66602017-06-01 19:39:03 -0600927 already found. So for example if CONFIG_EXYNOS5 implies an option,
928 but CONFIG_EXYNOS covers a larger set of defconfigs and also
929 implies that option, this will drop the former in favour of the
930 latter. In practice this option has not proved very used.
931
932 Note the terminoloy:
933 config - a CONFIG_XXX options (a string, e.g. 'CONFIG_CMD_EEPROM')
934 defconfig - a defconfig file (a string, e.g. 'configs/snow_defconfig')
935 """
Simon Glass0a0c1242024-07-17 16:56:51 +0100936 kconf = scan_kconfig() if check_kconfig else None
Simon Glasscb008832017-06-15 21:39:33 -0600937 if add_imply and add_imply != 'all':
Simon Glassa3627082021-12-18 08:09:42 -0700938 add_imply = add_imply.split(',')
Simon Glasscb008832017-06-15 21:39:33 -0600939
Simon Glass62fae4b2023-09-23 13:44:00 -0600940 all_configs, all_defconfigs, _, defconfig_db = read_database()
Simon Glass99b66602017-06-01 19:39:03 -0600941
Simon Glassa3627082021-12-18 08:09:42 -0700942 # Work through each target config option in turn, independently
Simon Glass99b66602017-06-01 19:39:03 -0600943 for config in config_list:
944 defconfigs = defconfig_db.get(config)
945 if not defconfigs:
Simon Glass1bd43062023-09-23 13:43:59 -0600946 print(f'{config} not found in any defconfig')
Simon Glass99b66602017-06-01 19:39:03 -0600947 continue
948
949 # Get the set of defconfigs without this one (since a config cannot
950 # imply itself)
951 non_defconfigs = all_defconfigs - defconfigs
952 num_defconfigs = len(defconfigs)
Simon Glass1bd43062023-09-23 13:43:59 -0600953 print(f'{config} found in {num_defconfigs}/{len(all_configs)} defconfigs')
Simon Glass99b66602017-06-01 19:39:03 -0600954
955 # This will hold the results: key=config, value=defconfigs containing it
956 imply_configs = {}
957 rest_configs = all_configs - set([config])
958
959 # Look at every possible config, except the target one
960 for imply_config in rest_configs:
Simon Glass9b2a2e82017-06-15 21:39:32 -0600961 if 'ERRATUM' in imply_config:
Simon Glass99b66602017-06-01 19:39:03 -0600962 continue
Simon Glass91197aa2021-12-18 14:54:35 -0700963 if not imply_flags & IMPLY_CMD:
Simon Glass9b2a2e82017-06-15 21:39:32 -0600964 if 'CONFIG_CMD' in imply_config:
965 continue
Simon Glass91197aa2021-12-18 14:54:35 -0700966 if not imply_flags & IMPLY_TARGET:
Simon Glass9b2a2e82017-06-15 21:39:32 -0600967 if 'CONFIG_TARGET' in imply_config:
968 continue
Simon Glass99b66602017-06-01 19:39:03 -0600969
970 # Find set of defconfigs that have this config
971 imply_defconfig = defconfig_db[imply_config]
972
973 # Get the intersection of this with defconfigs containing the
974 # target config
975 common_defconfigs = imply_defconfig & defconfigs
976
977 # Get the set of defconfigs containing this config which DO NOT
978 # also contain the taret config. If this set is non-empty it means
979 # that this config affects other defconfigs as well as (possibly)
980 # the ones affected by the target config. This means it implies
981 # things we don't want to imply.
982 not_common_defconfigs = imply_defconfig & non_defconfigs
983 if not_common_defconfigs:
984 continue
985
986 # If there are common defconfigs, imply_config may be useful
987 if common_defconfigs:
988 skip = False
989 if find_superset:
Simon Glass793dca32019-10-31 07:42:57 -0600990 for prev in list(imply_configs.keys()):
Simon Glass99b66602017-06-01 19:39:03 -0600991 prev_count = len(imply_configs[prev])
992 count = len(common_defconfigs)
993 if (prev_count > count and
994 (imply_configs[prev] & common_defconfigs ==
995 common_defconfigs)):
996 # skip imply_config because prev is a superset
997 skip = True
998 break
Simon Glassf297ba32023-09-23 13:44:05 -0600999 if count > prev_count:
Simon Glass99b66602017-06-01 19:39:03 -06001000 # delete prev because imply_config is a superset
1001 del imply_configs[prev]
1002 if not skip:
1003 imply_configs[imply_config] = common_defconfigs
1004
1005 # Now we have a dict imply_configs of configs which imply each config
1006 # The value of each dict item is the set of defconfigs containing that
1007 # config. Rank them so that we print the configs that imply the largest
1008 # number of defconfigs first.
Simon Glasscb008832017-06-15 21:39:33 -06001009 ranked_iconfigs = sorted(imply_configs,
Simon Glass99b66602017-06-01 19:39:03 -06001010 key=lambda k: len(imply_configs[k]), reverse=True)
Simon Glasscb008832017-06-15 21:39:33 -06001011 kconfig_info = ''
1012 cwd = os.getcwd()
1013 add_list = collections.defaultdict(list)
1014 for iconfig in ranked_iconfigs:
1015 num_common = len(imply_configs[iconfig])
Simon Glass99b66602017-06-01 19:39:03 -06001016
1017 # Don't bother if there are less than 5 defconfigs affected.
Simon Glass9b2a2e82017-06-15 21:39:32 -06001018 if num_common < (2 if imply_flags & IMPLY_MIN_2 else 5):
Simon Glass99b66602017-06-01 19:39:03 -06001019 continue
Simon Glasscb008832017-06-15 21:39:33 -06001020 missing = defconfigs - imply_configs[iconfig]
Simon Glass99b66602017-06-01 19:39:03 -06001021 missing_str = ', '.join(missing) if missing else 'all'
1022 missing_str = ''
Simon Glasscb008832017-06-15 21:39:33 -06001023 show = True
1024 if kconf:
1025 sym = find_kconfig_rules(kconf, config[CONFIG_LEN:],
1026 iconfig[CONFIG_LEN:])
1027 kconfig_info = ''
1028 if sym:
Simon Glassea40b202021-07-21 21:35:53 -06001029 nodes = sym.nodes
1030 if len(nodes) == 1:
1031 fname, linenum = nodes[0].filename, nodes[0].linenr
Simon Glasscb008832017-06-15 21:39:33 -06001032 if cwd and fname.startswith(cwd):
1033 fname = fname[len(cwd) + 1:]
Simon Glass1bd43062023-09-23 13:43:59 -06001034 kconfig_info = f'{fname}:{linenum}'
Simon Glasscb008832017-06-15 21:39:33 -06001035 if skip_added:
1036 show = False
1037 else:
Tom Rini65e05dd2019-09-20 17:42:09 -04001038 sym = kconf.syms.get(iconfig[CONFIG_LEN:])
Simon Glasscb008832017-06-15 21:39:33 -06001039 fname = ''
1040 if sym:
Simon Glassea40b202021-07-21 21:35:53 -06001041 nodes = sym.nodes
1042 if len(nodes) == 1:
1043 fname, linenum = nodes[0].filename, nodes[0].linenr
Simon Glasscb008832017-06-15 21:39:33 -06001044 if cwd and fname.startswith(cwd):
1045 fname = fname[len(cwd) + 1:]
1046 in_arch_board = not sym or (fname.startswith('arch') or
1047 fname.startswith('board'))
1048 if (not in_arch_board and
Simon Glass91197aa2021-12-18 14:54:35 -07001049 not imply_flags & IMPLY_NON_ARCH_BOARD):
Simon Glasscb008832017-06-15 21:39:33 -06001050 continue
1051
1052 if add_imply and (add_imply == 'all' or
1053 iconfig in add_imply):
1054 fname, linenum, kconfig_info = (check_imply_rule(kconf,
Simon Glassf876e962024-07-17 16:56:52 +01001055 iconfig[CONFIG_LEN:]))
Simon Glasscb008832017-06-15 21:39:33 -06001056 if fname:
1057 add_list[fname].append(linenum)
1058
1059 if show and kconfig_info != 'skip':
Simon Glass0e03fb12024-07-17 16:56:53 +01001060 print(f'{num_common:5} : '
1061 f'{iconfig.ljust(30)}{kconfig_info.ljust(25)} {missing_str}')
Simon Glasscb008832017-06-15 21:39:33 -06001062
1063 # Having collected a list of things to add, now we add them. We process
1064 # each file from the largest line number to the smallest so that
1065 # earlier additions do not affect our line numbers. E.g. if we added an
1066 # imply at line 20 it would change the position of each line after
1067 # that.
Simon Glass793dca32019-10-31 07:42:57 -06001068 for fname, linenums in add_list.items():
Simon Glasscb008832017-06-15 21:39:33 -06001069 for linenum in sorted(linenums, reverse=True):
1070 add_imply_rule(config[CONFIG_LEN:], fname, linenum)
Simon Glass99b66602017-06-01 19:39:03 -06001071
Simon Glass941671a2022-02-08 11:49:46 -07001072def defconfig_matches(configs, re_match):
1073 """Check if any CONFIG option matches a regex
1074
1075 The match must be complete, i.e. from the start to end of the CONFIG option.
1076
1077 Args:
1078 configs (dict): Dict of CONFIG options:
1079 key: CONFIG option
1080 value: Value of option
1081 re_match (re.Pattern): Match to check
1082
1083 Returns:
1084 bool: True if any CONFIG matches the regex
1085 """
1086 for cfg in configs:
Simon Glassd9c958f2022-03-05 20:18:54 -07001087 if re_match.fullmatch(cfg):
Simon Glass941671a2022-02-08 11:49:46 -07001088 return True
1089 return False
Simon Glass99b66602017-06-01 19:39:03 -06001090
Simon Glass65d7fce2021-12-18 08:09:46 -07001091def do_find_config(config_list):
1092 """Find boards with a given combination of CONFIGs
1093
Simon Glass630a9c92024-07-17 16:57:04 +01001094 Args:
1095 config_list (list of str): List of CONFIG options to check (each a regex
1096 consisting of a config option, with or without a CONFIG_ prefix. If
1097 an option is preceded by a tilde (~) then it must be false,
1098 otherwise it must be true)
1099
1100 Returns:
1101 int: exit code (0 for success)
Simon Glass65d7fce2021-12-18 08:09:46 -07001102 """
Simon Glass62fae4b2023-09-23 13:44:00 -06001103 _, all_defconfigs, config_db, _ = read_database()
Simon Glass65d7fce2021-12-18 08:09:46 -07001104
Simon Glass65d7fce2021-12-18 08:09:46 -07001105 # Start with all defconfigs
1106 out = all_defconfigs
1107
1108 # Work through each config in turn
Simon Glass65d7fce2021-12-18 08:09:46 -07001109 for item in config_list:
1110 # Get the real config name and whether we want this config or not
1111 cfg = item
1112 want = True
1113 if cfg[0] == '~':
1114 want = False
1115 cfg = cfg[1:]
1116
Simon Glass65d7fce2021-12-18 08:09:46 -07001117 # Search everything that is still in the running. If it has a config
1118 # that we want, or doesn't have one that we don't, add it into the
1119 # running for the next stage
1120 in_list = out
1121 out = set()
Simon Glass941671a2022-02-08 11:49:46 -07001122 re_match = re.compile(cfg)
Simon Glass65d7fce2021-12-18 08:09:46 -07001123 for defc in in_list:
Simon Glass941671a2022-02-08 11:49:46 -07001124 has_cfg = defconfig_matches(config_db[defc], re_match)
Simon Glass65d7fce2021-12-18 08:09:46 -07001125 if has_cfg == want:
1126 out.add(defc)
Tom Rini9ef3ba82022-12-04 10:14:16 -05001127 print(f'{len(out)} matches')
1128 print(' '.join(item.split('_defconfig')[0] for item in out))
Simon Glass630a9c92024-07-17 16:57:04 +01001129 return 0
Simon Glass65d7fce2021-12-18 08:09:46 -07001130
1131
1132def prefix_config(cfg):
1133 """Prefix a config with CONFIG_ if needed
1134
1135 This handles ~ operator, which indicates that the CONFIG should be disabled
1136
1137 >>> prefix_config('FRED')
1138 'CONFIG_FRED'
1139 >>> prefix_config('CONFIG_FRED')
1140 'CONFIG_FRED'
1141 >>> prefix_config('~FRED')
1142 '~CONFIG_FRED'
1143 >>> prefix_config('~CONFIG_FRED')
1144 '~CONFIG_FRED'
1145 >>> prefix_config('A123')
1146 'CONFIG_A123'
1147 """
Simon Glassa4c9d172023-09-23 13:44:01 -06001148 oper = ''
Simon Glass65d7fce2021-12-18 08:09:46 -07001149 if cfg[0] == '~':
Simon Glassa4c9d172023-09-23 13:44:01 -06001150 oper = cfg[0]
Simon Glass65d7fce2021-12-18 08:09:46 -07001151 cfg = cfg[1:]
1152 if not cfg.startswith('CONFIG_'):
1153 cfg = 'CONFIG_' + cfg
Simon Glassa4c9d172023-09-23 13:44:01 -06001154 return oper + cfg
Simon Glass65d7fce2021-12-18 08:09:46 -07001155
1156
Simon Glass98275712023-09-23 13:43:57 -06001157RE_MK_CONFIGS = re.compile(r'CONFIG_(\$\(SPL_(?:TPL_)?\))?([A-Za-z0-9_]*)')
1158RE_IFDEF = re.compile(r'(ifdef|ifndef)')
1159RE_C_CONFIGS = re.compile(r'CONFIG_([A-Za-z0-9_]*)')
1160RE_CONFIG_IS = re.compile(r'CONFIG_IS_ENABLED\(([A-Za-z0-9_]*)\)')
Simon Glass65e62032023-02-01 13:19:12 -07001161
1162class ConfigUse:
Simon Glassf876e962024-07-17 16:56:52 +01001163 """Tracks whether a config relates to SPL or not"""
Simon Glass65e62032023-02-01 13:19:12 -07001164 def __init__(self, cfg, is_spl, fname, rest):
Simon Glassf876e962024-07-17 16:56:52 +01001165 """Set up a new ConfigUse
1166
1167 Args:
1168 cfg (str): CONFIG option, without any CONFIG_ or SPL_ prefix
1169 is_spl (bool): True if this option relates to SPL
1170 fname (str): Makefile filename where the CONFIG option was found
1171 rest (str): Line of the Makefile
1172 """
Simon Glass65e62032023-02-01 13:19:12 -07001173 self.cfg = cfg
1174 self.is_spl = is_spl
1175 self.fname = fname
1176 self.rest = rest
1177
1178 def __hash__(self):
1179 return hash((self.cfg, self.is_spl))
1180
1181def scan_makefiles(fnames):
1182 """Scan Makefiles looking for Kconfig options
1183
1184 Looks for uses of CONFIG options in Makefiles
1185
1186 Args:
1187 fnames (list of tuple):
1188 str: Makefile filename where the option was found
1189 str: Line of the Makefile
1190
1191 Returns:
1192 tuple:
1193 dict: all_uses
1194 key (ConfigUse): object
1195 value (list of str): matching lines
1196 dict: Uses by filename
1197 key (str): filename
1198 value (set of ConfigUse): uses in that filename
1199
1200 >>> RE_MK_CONFIGS.search('CONFIG_FRED').groups()
1201 (None, 'FRED')
1202 >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_)MARY').groups()
1203 ('$(SPL_)', 'MARY')
1204 >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_TPL_)MARY').groups()
1205 ('$(SPL_TPL_)', 'MARY')
1206 """
1207 all_uses = collections.defaultdict(list)
1208 fname_uses = {}
1209 for fname, rest in fnames:
1210 m_iter = RE_MK_CONFIGS.finditer(rest)
Simon Glassa4c9d172023-09-23 13:44:01 -06001211 for mat in m_iter:
1212 real_opt = mat.group(2)
Simon Glass65e62032023-02-01 13:19:12 -07001213 if real_opt == '':
1214 continue
1215 is_spl = False
Simon Glassa4c9d172023-09-23 13:44:01 -06001216 if mat.group(1):
Simon Glass65e62032023-02-01 13:19:12 -07001217 is_spl = True
1218 use = ConfigUse(real_opt, is_spl, fname, rest)
1219 if fname not in fname_uses:
1220 fname_uses[fname] = set()
1221 fname_uses[fname].add(use)
1222 all_uses[use].append(rest)
1223 return all_uses, fname_uses
1224
1225
1226def scan_src_files(fnames):
1227 """Scan source files (other than Makefiles) looking for Kconfig options
1228
1229 Looks for uses of CONFIG options
1230
1231 Args:
1232 fnames (list of tuple):
1233 str: Makefile filename where the option was found
1234 str: Line of the Makefile
1235
1236 Returns:
1237 tuple:
1238 dict: all_uses
1239 key (ConfigUse): object
1240 value (list of str): matching lines
1241 dict: Uses by filename
1242 key (str): filename
1243 value (set of ConfigUse): uses in that filename
1244
1245 >>> RE_C_CONFIGS.search('CONFIG_FRED').groups()
1246 ('FRED',)
1247 >>> RE_CONFIG_IS.search('CONFIG_IS_ENABLED(MARY)').groups()
1248 ('MARY',)
1249 >>> RE_CONFIG_IS.search('#if CONFIG_IS_ENABLED(OF_PLATDATA)').groups()
1250 ('OF_PLATDATA',)
1251 """
Simon Glass62fae4b2023-09-23 13:44:00 -06001252 fname = None
1253 rest = None
1254
Simon Glass65e62032023-02-01 13:19:12 -07001255 def add_uses(m_iter, is_spl):
Simon Glassa4c9d172023-09-23 13:44:01 -06001256 for mat in m_iter:
1257 real_opt = mat.group(1)
Simon Glass65e62032023-02-01 13:19:12 -07001258 if real_opt == '':
1259 continue
1260 use = ConfigUse(real_opt, is_spl, fname, rest)
1261 if fname not in fname_uses:
1262 fname_uses[fname] = set()
1263 fname_uses[fname].add(use)
1264 all_uses[use].append(rest)
1265
1266 all_uses = collections.defaultdict(list)
1267 fname_uses = {}
1268 for fname, rest in fnames:
1269 m_iter = RE_C_CONFIGS.finditer(rest)
1270 add_uses(m_iter, False)
1271
1272 m_iter2 = RE_CONFIG_IS.finditer(rest)
1273 add_uses(m_iter2, True)
1274
1275 return all_uses, fname_uses
1276
1277
1278MODE_NORMAL, MODE_SPL, MODE_PROPER = range(3)
1279
1280def do_scan_source(path, do_update):
1281 """Scan the source tree for Kconfig inconsistencies
1282
1283 Args:
1284 path (str): Path to source tree
1285 do_update (bool) : True to write to scripts/kconf_... files
1286 """
1287 def is_not_proper(name):
1288 for prefix in SPL_PREFIXES:
1289 if name.startswith(prefix):
1290 return name[len(prefix):]
1291 return False
1292
1293 def check_not_found(all_uses, spl_mode):
1294 """Check for Kconfig options mentioned in the source but not in Kconfig
1295
1296 Args:
1297 all_uses (dict):
1298 key (ConfigUse): object
1299 value (list of str): matching lines
1300 spl_mode (int): If MODE_SPL, look at source code which implies
1301 an SPL_ option, but for which there is none;
1302 for MOD_PROPER, look at source code which implies a Proper
1303 option (i.e. use of CONFIG_IS_ENABLED() or $(SPL_) or
1304 $(SPL_TPL_) but for which there none;
1305 if MODE_NORMAL, ignore SPL
1306
1307 Returns:
1308 dict:
1309 key (str): CONFIG name (without 'CONFIG_' prefix
1310 value (list of ConfigUse): List of uses of this CONFIG
1311 """
1312 # Make sure we know about all the options
1313 not_found = collections.defaultdict(list)
Simon Glass62fae4b2023-09-23 13:44:00 -06001314 for use, _ in all_uses.items():
Simon Glass65e62032023-02-01 13:19:12 -07001315 name = use.cfg
1316 if name in IGNORE_SYMS:
1317 continue
1318 check = True
1319
1320 if spl_mode == MODE_SPL:
1321 check = use.is_spl
1322
1323 # If it is an SPL symbol, try prepending all SPL_ prefixes to
1324 # find at least one SPL symbol
1325 if use.is_spl:
Simon Glass65e62032023-02-01 13:19:12 -07001326 for prefix in SPL_PREFIXES:
1327 try_name = prefix + name
1328 sym = kconf.syms.get(try_name)
1329 if sym:
1330 break
1331 if not sym:
1332 not_found[f'SPL_{name}'].append(use)
1333 continue
1334 elif spl_mode == MODE_PROPER:
1335 # Try to find the Proper version of this symbol, i.e. without
1336 # the SPL_ prefix
1337 proper_name = is_not_proper(name)
1338 if proper_name:
1339 name = proper_name
1340 elif not use.is_spl:
1341 check = False
1342 else: # MODE_NORMAL
Simon Glass65e62032023-02-01 13:19:12 -07001343 sym = kconf.syms.get(name)
1344 if not sym:
1345 proper_name = is_not_proper(name)
1346 if proper_name:
1347 name = proper_name
1348 sym = kconf.syms.get(name)
1349 if not sym:
1350 for prefix in SPL_PREFIXES:
1351 try_name = prefix + name
1352 sym = kconf.syms.get(try_name)
1353 if sym:
1354 break
1355 if not sym:
1356 not_found[name].append(use)
1357 continue
1358
1359 sym = kconf.syms.get(name)
1360 if not sym and check:
1361 not_found[name].append(use)
1362 return not_found
1363
1364 def show_uses(uses):
1365 """Show a list of uses along with their filename and code snippet
1366
1367 Args:
1368 uses (dict):
1369 key (str): CONFIG name (without 'CONFIG_' prefix
1370 value (list of ConfigUse): List of uses of this CONFIG
1371 """
1372 for name in sorted(uses):
1373 print(f'{name}: ', end='')
1374 for i, use in enumerate(uses[name]):
1375 print(f'{" " if i else ""}{use.fname}: {use.rest.strip()}')
1376
1377
1378 print('Scanning Kconfig')
Simon Glass0a0c1242024-07-17 16:56:51 +01001379 kconf = scan_kconfig()
Simon Glass65e62032023-02-01 13:19:12 -07001380 print(f'Scanning source in {path}')
1381 args = ['git', 'grep', '-E', r'IS_ENABLED|\bCONFIG']
1382 with subprocess.Popen(args, stdout=subprocess.PIPE) as proc:
Simon Glass62fae4b2023-09-23 13:44:00 -06001383 out, _ = proc.communicate()
Simon Glass65e62032023-02-01 13:19:12 -07001384 lines = out.splitlines()
1385 re_fname = re.compile('^([^:]*):(.*)')
1386 src_list = []
1387 mk_list = []
1388 for line in lines:
1389 linestr = line.decode('utf-8')
1390 m_fname = re_fname.search(linestr)
1391 if not m_fname:
1392 continue
1393 fname, rest = m_fname.groups()
1394 dirname, leaf = os.path.split(fname)
1395 root, ext = os.path.splitext(leaf)
1396 if ext == '.autoconf':
1397 pass
1398 elif ext in ['.c', '.h', '.S', '.lds', '.dts', '.dtsi', '.asl', '.cfg',
1399 '.env', '.tmpl']:
1400 src_list.append([fname, rest])
1401 elif 'Makefile' in root or ext == '.mk':
1402 mk_list.append([fname, rest])
1403 elif ext in ['.yml', '.sh', '.py', '.awk', '.pl', '.rst', '', '.sed']:
1404 pass
1405 elif 'Kconfig' in root or 'Kbuild' in root:
1406 pass
1407 elif 'README' in root:
1408 pass
1409 elif dirname in ['configs']:
1410 pass
1411 elif dirname.startswith('doc') or dirname.startswith('scripts/kconfig'):
1412 pass
1413 else:
1414 print(f'Not sure how to handle file {fname}')
1415
1416 # Scan the Makefiles
Simon Glass62fae4b2023-09-23 13:44:00 -06001417 all_uses, _ = scan_makefiles(mk_list)
Simon Glass65e62032023-02-01 13:19:12 -07001418
1419 spl_not_found = set()
1420 proper_not_found = set()
1421
1422 # Make sure we know about all the options
1423 print('\nCONFIG options present in Makefiles but not Kconfig:')
1424 not_found = check_not_found(all_uses, MODE_NORMAL)
1425 show_uses(not_found)
1426
1427 print('\nCONFIG options present in Makefiles but not Kconfig (SPL):')
1428 not_found = check_not_found(all_uses, MODE_SPL)
1429 show_uses(not_found)
Simon Glassb774ba52023-09-23 13:44:03 -06001430 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
Simon Glass65e62032023-02-01 13:19:12 -07001431
1432 print('\nCONFIG options used as Proper in Makefiles but without a non-SPL_ variant:')
1433 not_found = check_not_found(all_uses, MODE_PROPER)
1434 show_uses(not_found)
Simon Glassb774ba52023-09-23 13:44:03 -06001435 proper_not_found |= {not_found.keys()}
Simon Glass65e62032023-02-01 13:19:12 -07001436
1437 # Scan the source code
Simon Glass62fae4b2023-09-23 13:44:00 -06001438 all_uses, _ = scan_src_files(src_list)
Simon Glass65e62032023-02-01 13:19:12 -07001439
1440 # Make sure we know about all the options
1441 print('\nCONFIG options present in source but not Kconfig:')
1442 not_found = check_not_found(all_uses, MODE_NORMAL)
1443 show_uses(not_found)
1444
1445 print('\nCONFIG options present in source but not Kconfig (SPL):')
1446 not_found = check_not_found(all_uses, MODE_SPL)
1447 show_uses(not_found)
Simon Glassb774ba52023-09-23 13:44:03 -06001448 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
Simon Glass65e62032023-02-01 13:19:12 -07001449
1450 print('\nCONFIG options used as Proper in source but without a non-SPL_ variant:')
1451 not_found = check_not_found(all_uses, MODE_PROPER)
1452 show_uses(not_found)
Simon Glassb774ba52023-09-23 13:44:03 -06001453 proper_not_found |= {not_found.keys()}
Simon Glass65e62032023-02-01 13:19:12 -07001454
1455 print('\nCONFIG options used as SPL but without an SPL_ variant:')
1456 for item in sorted(spl_not_found):
1457 print(f' {item}')
1458
1459 print('\nCONFIG options used as Proper but without a non-SPL_ variant:')
1460 for item in sorted(proper_not_found):
1461 print(f' {item}')
1462
1463 # Write out the updated information
1464 if do_update:
Simon Glasse6c686f2023-09-23 13:44:04 -06001465 with open(os.path.join(path, 'scripts', 'conf_nospl'), 'w',
1466 encoding='utf-8') as out:
Simon Glass65e62032023-02-01 13:19:12 -07001467 print('# These options should not be enabled in SPL builds\n',
1468 file=out)
1469 for item in sorted(spl_not_found):
1470 print(item, file=out)
Simon Glasse6c686f2023-09-23 13:44:04 -06001471 with open(os.path.join(path, 'scripts', 'conf_noproper'), 'w',
1472 encoding='utf-8') as out:
Simon Glass65e62032023-02-01 13:19:12 -07001473 print('# These options should not be enabled in Proper builds\n',
1474 file=out)
1475 for item in sorted(proper_not_found):
1476 print(item, file=out)
Simon Glassd63357e2024-07-17 16:57:02 +01001477 return 0
Simon Glass65e62032023-02-01 13:19:12 -07001478
1479
Simon Glassa056c422024-07-17 16:56:55 +01001480def parse_args():
1481 """Parse the program arguments
1482
1483 Returns:
1484 tuple:
1485 argparse.ArgumentParser: parser
1486 argparse.Namespace: Parsed arguments
1487 """
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001488 try:
1489 cpu_count = multiprocessing.cpu_count()
1490 except NotImplementedError:
1491 cpu_count = 1
1492
Simon Glassb2e83c62021-12-18 14:54:31 -07001493 epilog = '''Move config options from headers to defconfig files. See
1494doc/develop/moveconfig.rst for documentation.'''
1495
1496 parser = ArgumentParser(epilog=epilog)
1497 # Add arguments here
1498 parser.add_argument('-a', '--add-imply', type=str, default='',
Simon Glasscb008832017-06-15 21:39:33 -06001499 help='comma-separated list of CONFIG options to add '
1500 "an 'imply' statement to for the CONFIG in -i")
Simon Glassb2e83c62021-12-18 14:54:31 -07001501 parser.add_argument('-A', '--skip-added', action='store_true', default=False,
Simon Glasscb008832017-06-15 21:39:33 -06001502 help="don't show options which are already marked as "
1503 'implying others')
Simon Glassb2e83c62021-12-18 14:54:31 -07001504 parser.add_argument('-b', '--build-db', action='store_true', default=False,
Simon Glassd73fcb12017-06-01 19:39:02 -06001505 help='build a CONFIG database')
Simon Glassb2e83c62021-12-18 14:54:31 -07001506 parser.add_argument('-C', '--commit', action='store_true', default=False,
Simon Glass9ede2122016-09-12 23:18:21 -06001507 help='Create a git commit for the operation')
Simon Glass15f19ab2023-09-23 13:44:09 -06001508 parser.add_argument('--nocolour', action='store_true', default=False,
1509 help="don't display the log in colour")
Simon Glassb2e83c62021-12-18 14:54:31 -07001510 parser.add_argument('-d', '--defconfigs', type=str,
Simon Glassee4e61b2017-06-01 19:38:59 -06001511 help='a file containing a list of defconfigs to move, '
1512 "one per line (for example 'snow_defconfig') "
1513 "or '-' to read from stdin")
Simon Glassb2e83c62021-12-18 14:54:31 -07001514 parser.add_argument('-e', '--exit-on-error', action='store_true',
Simon Glasse1ae5632021-12-18 08:09:44 -07001515 default=False,
1516 help='exit immediately on any error')
Simon Glassb2e83c62021-12-18 14:54:31 -07001517 parser.add_argument('-f', '--find', action='store_true', default=False,
Simon Glass65d7fce2021-12-18 08:09:46 -07001518 help='Find boards with a given config combination')
Simon Glassb2e83c62021-12-18 14:54:31 -07001519 parser.add_argument('-i', '--imply', action='store_true', default=False,
Simon Glass99b66602017-06-01 19:39:03 -06001520 help='find options which imply others')
Simon Glassb2e83c62021-12-18 14:54:31 -07001521 parser.add_argument('-I', '--imply-flags', type=str, default='',
Simon Glass9b2a2e82017-06-15 21:39:32 -06001522 help="control the -i option ('help' for help")
Simon Glassb2e83c62021-12-18 14:54:31 -07001523 parser.add_argument('-j', '--jobs', type=int, default=cpu_count,
Simon Glasse1ae5632021-12-18 08:09:44 -07001524 help='the number of jobs to run simultaneously')
Simon Glassb2e83c62021-12-18 14:54:31 -07001525 parser.add_argument('-n', '--dry-run', action='store_true', default=False,
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001526 help='perform a trial run (show log with no changes)')
Simon Glassb2e83c62021-12-18 14:54:31 -07001527 parser.add_argument('-r', '--git-ref', type=str,
Simon Glasse1ae5632021-12-18 08:09:44 -07001528 help='the git ref to clone for building the autoconf.mk')
Simon Glassb2e83c62021-12-18 14:54:31 -07001529 parser.add_argument('-s', '--force-sync', action='store_true', default=False,
Masahiro Yamada8513dc02016-05-19 15:52:08 +09001530 help='force sync by savedefconfig')
Simon Glassb2e83c62021-12-18 14:54:31 -07001531 parser.add_argument('-S', '--spl', action='store_true', default=False,
Masahiro Yamada07913d12016-08-22 22:18:22 +09001532 help='parse config options defined for SPL build')
Simon Glass65e62032023-02-01 13:19:12 -07001533 parser.add_argument('--scan-source', action='store_true', default=False,
1534 help='scan source for uses of CONFIG options')
Simon Glassb2e83c62021-12-18 14:54:31 -07001535 parser.add_argument('-t', '--test', action='store_true', default=False,
Simon Glasse1ae5632021-12-18 08:09:44 -07001536 help='run unit tests')
Simon Glassb2e83c62021-12-18 14:54:31 -07001537 parser.add_argument('-y', '--yes', action='store_true', default=False,
Simon Glass6b403df2016-09-12 23:18:20 -06001538 help="respond 'yes' to any prompts")
Simon Glass65e62032023-02-01 13:19:12 -07001539 parser.add_argument('-u', '--update', action='store_true', default=False,
1540 help="update scripts/ files (use with --scan-source)")
Simon Glassb2e83c62021-12-18 14:54:31 -07001541 parser.add_argument('-v', '--verbose', action='store_true', default=False,
Joe Hershberger95bf9c72015-05-19 13:21:24 -05001542 help='show any build errors as boards are built')
Simon Glassb2e83c62021-12-18 14:54:31 -07001543 parser.add_argument('configs', nargs='*')
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001544
Simon Glassa056c422024-07-17 16:56:55 +01001545 return parser, parser.parse_args()
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001546
Simon Glassa056c422024-07-17 16:56:55 +01001547
Simon Glasscc628f52024-07-17 16:57:03 +01001548def imply(args):
1549 """Handle checking for flags which imply others
1550
1551 Args:
1552 args (argparse.Namespace): Program arguments
1553
1554 Returns:
1555 int: exit code (0 for success)
1556 """
1557 imply_flags = 0
1558 if args.imply_flags == 'all':
1559 imply_flags = -1
1560
1561 elif args.imply_flags:
1562 for flag in args.imply_flags.split(','):
1563 bad = flag not in IMPLY_FLAGS
1564 if bad:
1565 print(f"Invalid flag '{flag}'")
1566 if flag == 'help' or bad:
1567 print("Imply flags: (separate with ',')")
1568 for name, info in IMPLY_FLAGS.items():
1569 print(f' {name:-15s}: {info[1]}')
1570 return 1
1571 imply_flags |= IMPLY_FLAGS[flag][0]
1572
1573 do_imply_config(args.configs, args.add_imply, imply_flags, args.skip_added)
1574 return 0
1575
1576
Simon Glass7e688042024-07-17 16:57:01 +01001577def do_tests():
1578 """Run doctests and unit tests (so far there are no unit tests)"""
1579 sys.argv = [sys.argv[0]]
1580 fail, _ = doctest.testmod()
1581 if fail:
1582 return 1
1583 unittest.main()
1584 return 0
1585
1586
Simon Glassa056c422024-07-17 16:56:55 +01001587def main():
1588 """Main program"""
1589 parser, args = parse_args()
Simon Glass382c6622024-07-17 16:56:57 +01001590 if not any((args.force_sync, args.build_db, args.imply, args.find,
1591 args.scan_source, args.test)):
1592 parser.print_usage()
1593 sys.exit(1)
Simon Glassc50b6f12024-07-17 16:56:59 +01001594
1595 check_top_directory()
1596
Simon Glassfd35fbe2024-07-17 16:57:00 +01001597 # prefix the option name with CONFIG_ if missing
1598 args.configs = [prefix_config(cfg) for cfg in args.configs]
1599
Simon Glassb2e83c62021-12-18 14:54:31 -07001600 if args.test:
Simon Glass7e688042024-07-17 16:57:01 +01001601 return do_tests()
Simon Glass65e62032023-02-01 13:19:12 -07001602 if args.scan_source:
Simon Glassd63357e2024-07-17 16:57:02 +01001603 return do_scan_source(os.getcwd(), args.update)
Simon Glassb2e83c62021-12-18 14:54:31 -07001604 if args.imply:
Simon Glasscc628f52024-07-17 16:57:03 +01001605 if imply(args):
1606 parser.print_usage()
1607 sys.exit(1)
Simon Glassf297ba32023-09-23 13:44:05 -06001608 return 0
Simon Glassb2e83c62021-12-18 14:54:31 -07001609 if args.find:
Simon Glass630a9c92024-07-17 16:57:04 +01001610 return do_find_config(args.configs)
Simon Glass65d7fce2021-12-18 08:09:46 -07001611
Simon Glass035ecb42024-07-17 16:56:58 +01001612 col = terminal.Color(terminal.COLOR_NEVER if args.nocolour
1613 else terminal.COLOR_IF_TERMINAL)
Simon Glasseb1df3f2024-07-17 16:57:05 +01001614 config_db, progress = move_config(args, col)
Joe Hershberger2144f882015-05-19 13:21:20 -05001615
Simon Glassfd35fbe2024-07-17 16:57:00 +01001616 configs = args.configs
Simon Glassb2e83c62021-12-18 14:54:31 -07001617 if args.commit:
Simon Glass9ede2122016-09-12 23:18:21 -06001618 subprocess.call(['git', 'add', '-u'])
1619 if configs:
Simon Glassf876e962024-07-17 16:56:52 +01001620 part = 'et al ' if len(configs) > 1 else ''
1621 msg = f'Convert {configs[0]} {part}to Kconfig'
Simon Glass9ede2122016-09-12 23:18:21 -06001622 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' %
1623 '\n '.join(configs))
1624 else:
1625 msg = 'configs: Resync with savedefconfig'
1626 msg += '\n\nRsync all defconfig files using moveconfig.py'
1627 subprocess.call(['git', 'commit', '-s', '-m', msg])
1628
Simon Glass94e2ed72023-09-23 13:44:13 -06001629 failed = progress.total - progress.good
1630 failure = f'{failed} failed, ' if failed else ''
Simon Glassb2e83c62021-12-18 14:54:31 -07001631 if args.build_db:
Simon Glassa4c9d172023-09-23 13:44:01 -06001632 with open(CONFIG_DATABASE, 'w', encoding='utf-8') as outf:
Simon Glass793dca32019-10-31 07:42:57 -06001633 for defconfig, configs in config_db.items():
Simon Glassa4c9d172023-09-23 13:44:01 -06001634 outf.write(f'{defconfig}\n')
Simon Glassd73fcb12017-06-01 19:39:02 -06001635 for config in sorted(configs.keys()):
Simon Glassa4c9d172023-09-23 13:44:01 -06001636 outf.write(f' {config}={configs[config]}\n')
1637 outf.write('\n')
Simon Glass94e2ed72023-09-23 13:44:13 -06001638 print(col.build(
1639 col.RED if failed else col.GREEN,
1640 f'{failure}{len(config_db)} boards written to {CONFIG_DATABASE}'))
1641 else:
1642 if failed:
1643 print(col.build(col.RED, f'{failure}see {FAILED_LIST}', True))
1644 else:
1645 # Add enough spaces to overwrite the progress indicator
1646 print(col.build(
1647 col.GREEN, f'{progress.total} processed ', bright=True))
1648
Simon Glassf297ba32023-09-23 13:44:05 -06001649 return 0
1650
Simon Glassd73fcb12017-06-01 19:39:02 -06001651
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001652if __name__ == '__main__':
Simon Glass65d7fce2021-12-18 08:09:46 -07001653 sys.exit(main())