blob: 405981938e575df15380220b979bf1b6a365d1ff [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:
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900220 """Progress Indicator"""
221
Simon Glass6b25d212023-09-23 13:44:10 -0600222 def __init__(self, col, total):
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900223 """Create a new progress indicator.
224
Simon Glass91197aa2021-12-18 14:54:35 -0700225 Args:
Simon Glass0229eee2024-07-17 16:57:07 +0100226 col (terminal.Color): Colour-output class
Simon Glass6b25d212023-09-23 13:44:10 -0600227 total (int): A number of defconfig files to process.
Simon Glass0229eee2024-07-17 16:57:07 +0100228
229 current (int): Number of boards processed so far
230 failed (int): Number of failed boards
231 failure_msg (str): Message indicating number of failures, '' if none
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900232 """
Simon Glass6b25d212023-09-23 13:44:10 -0600233 self.col = col
Simon Glass0229eee2024-07-17 16:57:07 +0100234 self.total = total
235
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900236 self.current = 0
Simon Glass6b25d212023-09-23 13:44:10 -0600237 self.good = 0
Simon Glass0229eee2024-07-17 16:57:07 +0100238 self.failed = None
239 self.failure_msg = None
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900240
Simon Glass6b25d212023-09-23 13:44:10 -0600241 def inc(self, success):
242 """Increment the number of processed defconfig files.
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900243
Simon Glass6b25d212023-09-23 13:44:10 -0600244 Args:
245 success (bool): True if processing succeeded
246 """
247 self.good += success
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900248 self.current += 1
249
250 def show(self):
251 """Display the progress."""
Simon Glass95f09142023-09-23 13:44:08 -0600252 if self.current != self.total:
Simon Glass6b25d212023-09-23 13:44:10 -0600253 line = self.col.build(self.col.GREEN, f'{self.good:5d}')
254 line += self.col.build(self.col.RED,
255 f'{self.current - self.good:5d}')
256 line += self.col.build(self.col.MAGENTA,
257 f'/{self.total - self.current}')
258 print(f'{line} \r', end='')
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900259 sys.stdout.flush()
260
Simon Glass0229eee2024-07-17 16:57:07 +0100261 def completed(self):
262 """Set up extra properties when completed"""
263 self.failed = self.total - self.good
264 self.failure_msg = f'{self.failed} failed, ' if self.failed else ''
265
Simon Glasscb008832017-06-15 21:39:33 -0600266
Simon Glass0a0c1242024-07-17 16:56:51 +0100267def scan_kconfig():
268 """Scan all the Kconfig files and create a Config object
Simon Glasscb008832017-06-15 21:39:33 -0600269
Simon Glass0a0c1242024-07-17 16:56:51 +0100270 Returns:
271 Kconfig object
272 """
273 # Define environment variables referenced from Kconfig
274 os.environ['srctree'] = os.getcwd()
275 os.environ['UBOOTVERSION'] = 'dummy'
276 os.environ['KCONFIG_OBJDIR'] = ''
277 os.environ['CC'] = 'gcc'
278 return kconfiglib.Kconfig()
Simon Glasscb008832017-06-15 21:39:33 -0600279
280
Simon Glassf876e962024-07-17 16:56:52 +0100281# pylint: disable=R0903
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900282class KconfigParser:
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900283 """A parser of .config and include/autoconf.mk."""
284
285 re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
286 re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
287
Simon Glass882c8e42023-09-23 13:43:54 -0600288 def __init__(self, args, build_dir):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900289 """Create a new parser.
290
Simon Glass91197aa2021-12-18 14:54:35 -0700291 Args:
Simon Glass91197aa2021-12-18 14:54:35 -0700292 args (Namespace): program arguments
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900293 build_dir: Build directory.
294 """
Simon Glassb2e83c62021-12-18 14:54:31 -0700295 self.args = args
Masahiro Yamada1f169922016-05-19 15:52:00 +0900296 self.dotconfig = os.path.join(build_dir, '.config')
297 self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
Masahiro Yamada07913d12016-08-22 22:18:22 +0900298 self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
299 'autoconf.mk')
Simon Glassf3b8e642017-06-01 19:39:01 -0600300 self.config_autoconf = os.path.join(build_dir, AUTO_CONF_PATH)
Masahiro Yamada5da4f852016-05-19 15:52:06 +0900301 self.defconfig = os.path.join(build_dir, 'defconfig')
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900302
Simon Glass6821a742017-07-10 14:47:47 -0600303 def get_arch(self):
304 """Parse .config file and return the architecture.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900305
306 Returns:
Simon Glass6821a742017-07-10 14:47:47 -0600307 Architecture name (e.g. 'arm').
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900308 """
309 arch = ''
310 cpu = ''
Simon Glass37f815c2021-12-18 14:54:34 -0700311 for line in read_file(self.dotconfig):
Simon Glassa4c9d172023-09-23 13:44:01 -0600312 m_arch = self.re_arch.match(line)
313 if m_arch:
314 arch = m_arch.group(1)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900315 continue
Simon Glassa4c9d172023-09-23 13:44:01 -0600316 m_cpu = self.re_cpu.match(line)
317 if m_cpu:
318 cpu = m_cpu.group(1)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900319
Masahiro Yamada90ed6cb2016-05-19 15:51:53 +0900320 if not arch:
321 return None
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900322
323 # fix-up for aarch64
324 if arch == 'arm' and cpu == 'armv8':
325 arch = 'aarch64'
326
Simon Glass6821a742017-07-10 14:47:47 -0600327 return arch
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900328
Simon Glassd73fcb12017-06-01 19:39:02 -0600329
330class DatabaseThread(threading.Thread):
331 """This thread processes results from Slot threads.
332
333 It collects the data in the master config directary. There is only one
334 result thread, and this helps to serialise the build output.
335 """
336 def __init__(self, config_db, db_queue):
337 """Set up a new result thread
338
339 Args:
340 builder: Builder which will be sent each result
341 """
342 threading.Thread.__init__(self)
343 self.config_db = config_db
344 self.db_queue= db_queue
345
346 def run(self):
347 """Called to start up the result thread.
348
349 We collect the next result job and pass it on to the build.
350 """
351 while True:
352 defconfig, configs = self.db_queue.get()
353 self.config_db[defconfig] = configs
354 self.db_queue.task_done()
355
356
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900357class Slot:
358
359 """A slot to store a subprocess.
360
361 Each instance of this class handles one subprocess.
362 This class is useful to control multiple threads
363 for faster processing.
364 """
365
Simon Glass15f19ab2023-09-23 13:44:09 -0600366 def __init__(self, toolchains, args, progress, devnull, make_cmd,
367 reference_src_dir, db_queue, col):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900368 """Create a new process slot.
369
Simon Glass91197aa2021-12-18 14:54:35 -0700370 Args:
Simon Glass6821a742017-07-10 14:47:47 -0600371 toolchains: Toolchains object containing toolchains.
Simon Glassb2e83c62021-12-18 14:54:31 -0700372 args: Program arguments
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900373 progress: A progress indicator.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900374 devnull: A file object of '/dev/null'.
375 make_cmd: command name of GNU Make.
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500376 reference_src_dir: Determine the true starting config state from this
377 source tree.
Simon Glassd73fcb12017-06-01 19:39:02 -0600378 db_queue: output queue to write config info for the database
Simon Glass15f19ab2023-09-23 13:44:09 -0600379 col (terminal.Color): Colour object
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900380 """
Simon Glass6821a742017-07-10 14:47:47 -0600381 self.toolchains = toolchains
Simon Glassb2e83c62021-12-18 14:54:31 -0700382 self.args = args
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900383 self.progress = progress
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900384 self.build_dir = tempfile.mkdtemp()
385 self.devnull = devnull
386 self.make_cmd = (make_cmd, 'O=' + self.build_dir)
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500387 self.reference_src_dir = reference_src_dir
Simon Glassd73fcb12017-06-01 19:39:02 -0600388 self.db_queue = db_queue
Simon Glass15f19ab2023-09-23 13:44:09 -0600389 self.col = col
Simon Glass882c8e42023-09-23 13:43:54 -0600390 self.parser = KconfigParser(args, self.build_dir)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900391 self.state = STATE_IDLE
Masahiro Yamada09c6c062016-08-22 22:18:20 +0900392 self.failed_boards = set()
Simon Glassa6ab4db2023-09-23 13:44:02 -0600393 self.defconfig = None
Simon Glass9461bf02023-09-23 13:44:07 -0600394 self.log = []
Simon Glassa6ab4db2023-09-23 13:44:02 -0600395 self.current_src_dir = None
396 self.proc = None
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900397
398 def __del__(self):
399 """Delete the working directory
400
401 This function makes sure the temporary directory is cleaned away
402 even if Python suddenly dies due to error. It should be done in here
Joe Hershbergerf2dae752016-06-10 14:53:29 -0500403 because it is guaranteed the destructor is always invoked when the
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900404 instance of the class gets unreferenced.
405
406 If the subprocess is still running, wait until it finishes.
407 """
408 if self.state != STATE_IDLE:
Simon Glassf297ba32023-09-23 13:44:05 -0600409 while self.proc.poll() is None:
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900410 pass
411 shutil.rmtree(self.build_dir)
412
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900413 def add(self, defconfig):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900414 """Assign a new subprocess for defconfig and add it to the slot.
415
416 If the slot is vacant, create a new subprocess for processing the
417 given defconfig and add it to the slot. Just returns False if
418 the slot is occupied (i.e. the current subprocess is still running).
419
Simon Glass91197aa2021-12-18 14:54:35 -0700420 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600421 defconfig (str): defconfig name.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900422
423 Returns:
424 Return True on success or False on failure
425 """
426 if self.state != STATE_IDLE:
427 return False
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900428
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900429 self.defconfig = defconfig
Simon Glass9461bf02023-09-23 13:44:07 -0600430 self.log = []
Masahiro Yamadaf432c332016-06-15 14:33:52 +0900431 self.current_src_dir = self.reference_src_dir
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900432 self.do_defconfig()
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900433 return True
434
435 def poll(self):
436 """Check the status of the subprocess and handle it as needed.
437
438 Returns True if the slot is vacant (i.e. in idle state).
439 If the configuration is successfully finished, assign a new
440 subprocess to build include/autoconf.mk.
441 If include/autoconf.mk is generated, invoke the parser to
Masahiro Yamada7fb0bac2016-05-19 15:52:04 +0900442 parse the .config and the include/autoconf.mk, moving
443 config options to the .config as needed.
444 If the .config was updated, run "make savedefconfig" to sync
445 it, update the original defconfig, and then set the slot back
446 to the idle state.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900447
448 Returns:
449 Return True if the subprocess is terminated, False otherwise
450 """
451 if self.state == STATE_IDLE:
452 return True
453
Simon Glassf297ba32023-09-23 13:44:05 -0600454 if self.proc.poll() is None:
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900455 return False
456
Simon Glassa4c9d172023-09-23 13:44:01 -0600457 if self.proc.poll() != 0:
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900458 self.handle_error()
459 elif self.state == STATE_DEFCONFIG:
Masahiro Yamadaf432c332016-06-15 14:33:52 +0900460 if self.reference_src_dir and not self.current_src_dir:
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500461 self.do_savedefconfig()
462 else:
463 self.do_autoconf()
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900464 elif self.state == STATE_AUTOCONF:
Masahiro Yamadaf432c332016-06-15 14:33:52 +0900465 if self.current_src_dir:
466 self.current_src_dir = None
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500467 self.do_defconfig()
Simon Glassb2e83c62021-12-18 14:54:31 -0700468 elif self.args.build_db:
Simon Glassd73fcb12017-06-01 19:39:02 -0600469 self.do_build_db()
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500470 else:
471 self.do_savedefconfig()
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900472 elif self.state == STATE_SAVEDEFCONFIG:
473 self.update_defconfig()
474 else:
Simon Glassdaa694d2021-12-18 14:54:30 -0700475 sys.exit('Internal Error. This should not happen.')
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900476
Simon Glassf297ba32023-09-23 13:44:05 -0600477 return self.state == STATE_IDLE
Joe Hershberger96464ba2015-05-19 13:21:17 -0500478
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900479 def handle_error(self):
480 """Handle error cases."""
Masahiro Yamada8513dc02016-05-19 15:52:08 +0900481
Simon Glass15f19ab2023-09-23 13:44:09 -0600482 self.log.append(self.col.build(self.col.RED, 'Failed to process',
483 bright=True))
Simon Glassb2e83c62021-12-18 14:54:31 -0700484 if self.args.verbose:
Simon Glass9461bf02023-09-23 13:44:07 -0600485 for line in self.proc.stderr.read().decode().splitlines():
Simon Glass15f19ab2023-09-23 13:44:09 -0600486 self.log.append(self.col.build(self.col.CYAN, line, True))
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900487 self.finish(False)
Joe Hershberger96464ba2015-05-19 13:21:17 -0500488
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900489 def do_defconfig(self):
490 """Run 'make <board>_defconfig' to create the .config file."""
Masahiro Yamadac8e1b102016-05-19 15:52:07 +0900491
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900492 cmd = list(self.make_cmd)
493 cmd.append(self.defconfig)
Simon Glassf876e962024-07-17 16:56:52 +0100494 # pylint: disable=R1732
Simon Glassa4c9d172023-09-23 13:44:01 -0600495 self.proc = subprocess.Popen(cmd, stdout=self.devnull,
496 stderr=subprocess.PIPE,
497 cwd=self.current_src_dir)
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900498 self.state = STATE_DEFCONFIG
Masahiro Yamadac8e1b102016-05-19 15:52:07 +0900499
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900500 def do_autoconf(self):
Simon Glassf3b8e642017-06-01 19:39:01 -0600501 """Run 'make AUTO_CONF_PATH'."""
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900502
Simon Glass6821a742017-07-10 14:47:47 -0600503 arch = self.parser.get_arch()
504 try:
Simon Glassf297ba32023-09-23 13:44:05 -0600505 tchain = self.toolchains.Select(arch)
Simon Glass6821a742017-07-10 14:47:47 -0600506 except ValueError:
Simon Glass15f19ab2023-09-23 13:44:09 -0600507 self.log.append(self.col.build(
508 self.col.YELLOW,
509 f"Tool chain for '{arch}' is missing: do nothing"))
Masahiro Yamada4efef992016-05-19 15:52:03 +0900510 self.finish(False)
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900511 return
Simon Glassf297ba32023-09-23 13:44:05 -0600512 env = tchain.MakeEnvironment(False)
Masahiro Yamada90ed6cb2016-05-19 15:51:53 +0900513
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900514 cmd = list(self.make_cmd)
Joe Hershberger7740f652015-05-19 13:21:18 -0500515 cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
Simon Glassf3b8e642017-06-01 19:39:01 -0600516 cmd.append(AUTO_CONF_PATH)
Simon Glassf876e962024-07-17 16:56:52 +0100517 # pylint: disable=R1732
Simon Glassa4c9d172023-09-23 13:44:01 -0600518 self.proc = subprocess.Popen(cmd, stdout=self.devnull, env=env,
519 stderr=subprocess.PIPE,
520 cwd=self.current_src_dir)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900521 self.state = STATE_AUTOCONF
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900522
Simon Glassd73fcb12017-06-01 19:39:02 -0600523 def do_build_db(self):
524 """Add the board to the database"""
525 configs = {}
Simon Glass37f815c2021-12-18 14:54:34 -0700526 for line in read_file(os.path.join(self.build_dir, AUTO_CONF_PATH)):
527 if line.startswith('CONFIG'):
528 config, value = line.split('=', 1)
529 configs[config] = value.rstrip()
Simon Glassd73fcb12017-06-01 19:39:02 -0600530 self.db_queue.put([self.defconfig, configs])
531 self.finish(True)
532
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900533 def do_savedefconfig(self):
534 """Update the .config and run 'make savedefconfig'."""
Simon Glassc7345612023-09-23 13:43:55 -0600535 if not self.args.force_sync:
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900536 self.finish(True)
537 return
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900538
539 cmd = list(self.make_cmd)
540 cmd.append('savedefconfig')
Simon Glassf876e962024-07-17 16:56:52 +0100541 # pylint: disable=R1732
Simon Glassa4c9d172023-09-23 13:44:01 -0600542 self.proc = subprocess.Popen(cmd, stdout=self.devnull,
543 stderr=subprocess.PIPE)
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900544 self.state = STATE_SAVEDEFCONFIG
545
546 def update_defconfig(self):
547 """Update the input defconfig and go back to the idle state."""
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900548 orig_defconfig = os.path.join('configs', self.defconfig)
549 new_defconfig = os.path.join(self.build_dir, 'defconfig')
550 updated = not filecmp.cmp(orig_defconfig, new_defconfig)
551
552 if updated:
Simon Glass15f19ab2023-09-23 13:44:09 -0600553 self.log.append(
554 self.col.build(self.col.BLUE, 'defconfig updated', bright=True))
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900555
Simon Glassb2e83c62021-12-18 14:54:31 -0700556 if not self.args.dry_run and updated:
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900557 shutil.move(new_defconfig, orig_defconfig)
558 self.finish(True)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900559
Masahiro Yamada4efef992016-05-19 15:52:03 +0900560 def finish(self, success):
561 """Display log along with progress and go to the idle state.
Masahiro Yamada1d085562016-05-19 15:52:02 +0900562
Simon Glass91197aa2021-12-18 14:54:35 -0700563 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600564 success (bool): Should be True when the defconfig was processed
Masahiro Yamada4efef992016-05-19 15:52:03 +0900565 successfully, or False when it fails.
Masahiro Yamada1d085562016-05-19 15:52:02 +0900566 """
567 # output at least 30 characters to hide the "* defconfigs out of *".
Simon Glassdc1d2e62023-09-23 13:44:11 -0600568 name = self.defconfig[:-len('_defconfig')]
Simon Glass5aba58c2023-09-23 13:44:06 -0600569 if self.log:
Masahiro Yamada1d085562016-05-19 15:52:02 +0900570
Simon Glass9461bf02023-09-23 13:44:07 -0600571 # Put the first log line on the first line
572 log = name.ljust(20) + ' ' + self.log[0]
573
574 if len(self.log) > 1:
575 log += '\n' + '\n'.join([' ' + s for s in self.log[1:]])
Simon Glass5aba58c2023-09-23 13:44:06 -0600576 # Some threads are running in parallel.
577 # Print log atomically to not mix up logs from different threads.
578 print(log, file=(sys.stdout if success else sys.stderr))
Masahiro Yamada4efef992016-05-19 15:52:03 +0900579
580 if not success:
Simon Glassb2e83c62021-12-18 14:54:31 -0700581 if self.args.exit_on_error:
Simon Glassdaa694d2021-12-18 14:54:30 -0700582 sys.exit('Exit on error.')
Masahiro Yamada4efef992016-05-19 15:52:03 +0900583 # If --exit-on-error flag is not set, skip this board and continue.
584 # Record the failed board.
Simon Glassdc1d2e62023-09-23 13:44:11 -0600585 self.failed_boards.add(name)
Masahiro Yamada4efef992016-05-19 15:52:03 +0900586
Simon Glass6b25d212023-09-23 13:44:10 -0600587 self.progress.inc(success)
Masahiro Yamada1d085562016-05-19 15:52:02 +0900588 self.progress.show()
Masahiro Yamada4efef992016-05-19 15:52:03 +0900589 self.state = STATE_IDLE
Masahiro Yamada1d085562016-05-19 15:52:02 +0900590
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900591 def get_failed_boards(self):
Masahiro Yamada09c6c062016-08-22 22:18:20 +0900592 """Returns a set of failed boards (defconfigs) in this slot.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900593 """
594 return self.failed_boards
595
596class Slots:
597
598 """Controller of the array of subprocess slots."""
599
Simon Glass15f19ab2023-09-23 13:44:09 -0600600 def __init__(self, toolchains, args, progress, reference_src_dir, db_queue,
601 col):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900602 """Create a new slots controller.
603
Simon Glass91197aa2021-12-18 14:54:35 -0700604 Args:
Simon Glass15f19ab2023-09-23 13:44:09 -0600605 toolchains (Toolchains): Toolchains object containing toolchains
606 args (Namespace): Program arguments
607 progress (Progress): A progress indicator.
608 reference_src_dir (str): Determine the true starting config state
609 from this source tree (None for none)
610 db_queue (Queue): output queue to write config info for the database
611 col (terminal.Color): Colour object
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900612 """
Simon Glassb2e83c62021-12-18 14:54:31 -0700613 self.args = args
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900614 self.slots = []
Simon Glassdc1d2e62023-09-23 13:44:11 -0600615 self.progress = progress
Simon Glass15f19ab2023-09-23 13:44:09 -0600616 self.col = col
Simon Glass478920d2021-12-18 14:54:32 -0700617 devnull = subprocess.DEVNULL
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900618 make_cmd = get_make_cmd()
Simon Glass62fae4b2023-09-23 13:44:00 -0600619 for _ in range(args.jobs):
Simon Glass882c8e42023-09-23 13:43:54 -0600620 self.slots.append(Slot(toolchains, args, progress, devnull,
Simon Glass15f19ab2023-09-23 13:44:09 -0600621 make_cmd, reference_src_dir, db_queue, col))
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900622
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900623 def add(self, defconfig):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900624 """Add a new subprocess if a vacant slot is found.
625
Simon Glass91197aa2021-12-18 14:54:35 -0700626 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600627 defconfig (str): defconfig name to be put into.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900628
629 Returns:
630 Return True on success or False on failure
631 """
632 for slot in self.slots:
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900633 if slot.add(defconfig):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900634 return True
635 return False
636
637 def available(self):
638 """Check if there is a vacant slot.
639
640 Returns:
641 Return True if at lease one vacant slot is found, False otherwise.
642 """
643 for slot in self.slots:
644 if slot.poll():
645 return True
646 return False
647
648 def empty(self):
649 """Check if all slots are vacant.
650
651 Returns:
652 Return True if all the slots are vacant, False otherwise.
653 """
654 ret = True
655 for slot in self.slots:
656 if not slot.poll():
657 ret = False
658 return ret
659
Simon Glass94e2ed72023-09-23 13:44:13 -0600660 def write_failed_boards(self):
Simon Glassdc1d2e62023-09-23 13:44:11 -0600661 """Show the results of processing"""
Masahiro Yamada09c6c062016-08-22 22:18:20 +0900662 boards = set()
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900663
664 for slot in self.slots:
Masahiro Yamada09c6c062016-08-22 22:18:20 +0900665 boards |= slot.get_failed_boards()
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900666
Masahiro Yamada96dccd92016-06-15 14:33:53 +0900667 if boards:
Simon Glassdc1d2e62023-09-23 13:44:11 -0600668 boards = '\n'.join(sorted(boards)) + '\n'
Simon Glass94e2ed72023-09-23 13:44:13 -0600669 write_file(FAILED_LIST, boards)
670
Joe Hershberger2559cd82015-05-19 13:21:22 -0500671
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900672class ReferenceSource:
673
674 """Reference source against which original configs should be parsed."""
675
676 def __init__(self, commit):
677 """Create a reference source directory based on a specified commit.
678
Simon Glass91197aa2021-12-18 14:54:35 -0700679 Args:
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900680 commit: commit to git-clone
681 """
682 self.src_dir = tempfile.mkdtemp()
Simon Glassdaa694d2021-12-18 14:54:30 -0700683 print('Cloning git repo to a separate work directory...')
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900684 subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
685 cwd=self.src_dir)
Simon Glass1bd43062023-09-23 13:43:59 -0600686 rev = subprocess.check_output(['git', 'rev-parse', '--short',
687 commit]).strip()
688 print(f"Checkout '{rev}' to build the original autoconf.mk.")
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900689 subprocess.check_output(['git', 'checkout', commit],
690 stderr=subprocess.STDOUT, cwd=self.src_dir)
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500691
692 def __del__(self):
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900693 """Delete the reference source directory
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500694
695 This function makes sure the temporary directory is cleaned away
696 even if Python suddenly dies due to error. It should be done in here
697 because it is guaranteed the destructor is always invoked when the
698 instance of the class gets unreferenced.
699 """
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900700 shutil.rmtree(self.src_dir)
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500701
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900702 def get_dir(self):
703 """Return the absolute path to the reference source directory."""
704
705 return self.src_dir
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500706
Simon Glasseb1df3f2024-07-17 16:57:05 +0100707def move_config(args, col):
Simon Glass882c8e42023-09-23 13:43:54 -0600708 """Build database or sync config options to defconfig files.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900709
Simon Glass91197aa2021-12-18 14:54:35 -0700710 Args:
Simon Glass15f19ab2023-09-23 13:44:09 -0600711 args (Namespace): Program arguments
Simon Glass15f19ab2023-09-23 13:44:09 -0600712 col (terminal.Color): Colour object
Simon Glass94e2ed72023-09-23 13:44:13 -0600713
714 Returns:
Simon Glasseb1df3f2024-07-17 16:57:05 +0100715 tuple:
716 config_db (dict of configs for each defconfig):
717 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
718 value: dict:
719 key: CONFIG option
720 value: Value of option
721 Progress: Progress indicator
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900722 """
Simon Glasseb1df3f2024-07-17 16:57:05 +0100723 config_db = {}
724 db_queue = queue.Queue()
725 dbt = DatabaseThread(config_db, db_queue)
726 dbt.daemon = True
727 dbt.start()
728
729 check_clean_directory()
730 bsettings.setup('')
731
732 # Get toolchains to use
733 toolchains = toolchain.Toolchains()
734 toolchains.GetSettings()
735 toolchains.Scan(verbose=False)
736
Simon Glassb2e83c62021-12-18 14:54:31 -0700737 if args.git_ref:
738 reference_src = ReferenceSource(args.git_ref)
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900739 reference_src_dir = reference_src.get_dir()
740 else:
Masahiro Yamadaf432c332016-06-15 14:33:52 +0900741 reference_src_dir = None
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500742
Simon Glassb2e83c62021-12-18 14:54:31 -0700743 if args.defconfigs:
744 defconfigs = get_matched_defconfigs(args.defconfigs)
Joe Hershberger91040e82015-05-19 13:21:19 -0500745 else:
Masahiro Yamada684c3062016-07-25 19:15:28 +0900746 defconfigs = get_all_defconfigs()
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900747
Simon Glass6b25d212023-09-23 13:44:10 -0600748 progress = Progress(col, len(defconfigs))
Simon Glass15f19ab2023-09-23 13:44:09 -0600749 slots = Slots(toolchains, args, progress, reference_src_dir, db_queue, col)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900750
751 # Main loop to process defconfig files:
752 # Add a new subprocess into a vacant slot.
753 # Sleep if there is no available slot.
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900754 for defconfig in defconfigs:
755 while not slots.add(defconfig):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900756 while not slots.available():
757 # No available slot: sleep for a while
758 time.sleep(SLEEP_TIME)
759
760 # wait until all the subprocesses finish
761 while not slots.empty():
762 time.sleep(SLEEP_TIME)
763
Simon Glass94e2ed72023-09-23 13:44:13 -0600764 slots.write_failed_boards()
Simon Glasseb1df3f2024-07-17 16:57:05 +0100765 db_queue.join()
Simon Glass0229eee2024-07-17 16:57:07 +0100766 progress.completed()
Simon Glasseb1df3f2024-07-17 16:57:05 +0100767 return config_db, progress
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900768
Simon Glasscb008832017-06-15 21:39:33 -0600769def find_kconfig_rules(kconf, config, imply_config):
770 """Check whether a config has a 'select' or 'imply' keyword
771
772 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600773 kconf (Kconfiglib.Kconfig): Kconfig object
774 config (str): Name of config to check (without CONFIG_ prefix)
775 imply_config (str): Implying config (without CONFIG_ prefix) which may
776 or may not have an 'imply' for 'config')
Simon Glasscb008832017-06-15 21:39:33 -0600777
778 Returns:
779 Symbol object for 'config' if found, else None
780 """
Tom Rini65e05dd2019-09-20 17:42:09 -0400781 sym = kconf.syms.get(imply_config)
Simon Glasscb008832017-06-15 21:39:33 -0600782 if sym:
Simon Glass62fae4b2023-09-23 13:44:00 -0600783 for sel, _ in (sym.selects + sym.implies):
Simon Glassa3627082021-12-18 08:09:42 -0700784 if sel.name == config:
Simon Glasscb008832017-06-15 21:39:33 -0600785 return sym
786 return None
787
Simon Glassf876e962024-07-17 16:56:52 +0100788def check_imply_rule(kconf, imply_config):
Simon Glasscb008832017-06-15 21:39:33 -0600789 """Check if we can add an 'imply' option
790
791 This finds imply_config in the Kconfig and looks to see if it is possible
792 to add an 'imply' for 'config' to that part of the Kconfig.
793
794 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600795 kconf (Kconfiglib.Kconfig): Kconfig object
Simon Glass549d4222023-09-23 13:43:58 -0600796 imply_config (str): Implying config (without CONFIG_ prefix) which may
797 or may not have an 'imply' for 'config')
Simon Glasscb008832017-06-15 21:39:33 -0600798
799 Returns:
800 tuple:
Simon Glass549d4222023-09-23 13:43:58 -0600801 str: filename of Kconfig file containing imply_config, or None if
802 none
803 int: line number within the Kconfig file, or 0 if none
804 str: message indicating the result
Simon Glasscb008832017-06-15 21:39:33 -0600805 """
Tom Rini65e05dd2019-09-20 17:42:09 -0400806 sym = kconf.syms.get(imply_config)
Simon Glasscb008832017-06-15 21:39:33 -0600807 if not sym:
808 return 'cannot find sym'
Simon Glassea40b202021-07-21 21:35:53 -0600809 nodes = sym.nodes
810 if len(nodes) != 1:
Simon Glass1bd43062023-09-23 13:43:59 -0600811 return f'{len(nodes)} locations'
Simon Glassa3627082021-12-18 08:09:42 -0700812 node = nodes[0]
813 fname, linenum = node.filename, node.linenr
Simon Glasscb008832017-06-15 21:39:33 -0600814 cwd = os.getcwd()
815 if cwd and fname.startswith(cwd):
816 fname = fname[len(cwd) + 1:]
Simon Glass1bd43062023-09-23 13:43:59 -0600817 file_line = f' at {fname}:{linenum}'
Simon Glass37f815c2021-12-18 14:54:34 -0700818 data = read_file(fname)
Simon Glass1bd43062023-09-23 13:43:59 -0600819 if data[linenum - 1] != f'config {imply_config}':
820 return None, 0, f'bad sym format {data[linenum]}{file_line})'
821 return fname, linenum, f'adding{file_line}'
Simon Glasscb008832017-06-15 21:39:33 -0600822
823def add_imply_rule(config, fname, linenum):
824 """Add a new 'imply' option to a Kconfig
825
826 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600827 config (str): config option to add an imply for (without CONFIG_ prefix)
828 fname (str): Kconfig filename to update
829 linenum (int): Line number to place the 'imply' before
Simon Glasscb008832017-06-15 21:39:33 -0600830
831 Returns:
832 Message indicating the result
833 """
Simon Glass1bd43062023-09-23 13:43:59 -0600834 file_line = f' at {fname}:{linenum}'
Simon Glass37f815c2021-12-18 14:54:34 -0700835 data = read_file(fname)
Simon Glasscb008832017-06-15 21:39:33 -0600836 linenum -= 1
837
838 for offset, line in enumerate(data[linenum:]):
839 if line.strip().startswith('help') or not line:
Simon Glass1bd43062023-09-23 13:43:59 -0600840 data.insert(linenum + offset, f'\timply {config}')
Simon Glass2fd85bd2021-12-18 14:54:33 -0700841 write_file(fname, data)
Simon Glass1bd43062023-09-23 13:43:59 -0600842 return f'added{file_line}'
Simon Glasscb008832017-06-15 21:39:33 -0600843
844 return 'could not insert%s'
845
846(IMPLY_MIN_2, IMPLY_TARGET, IMPLY_CMD, IMPLY_NON_ARCH_BOARD) = (
847 1, 2, 4, 8)
Simon Glass9b2a2e82017-06-15 21:39:32 -0600848
849IMPLY_FLAGS = {
850 'min2': [IMPLY_MIN_2, 'Show options which imply >2 boards (normally >5)'],
851 'target': [IMPLY_TARGET, 'Allow CONFIG_TARGET_... options to imply'],
852 'cmd': [IMPLY_CMD, 'Allow CONFIG_CMD_... to imply'],
Simon Glasscb008832017-06-15 21:39:33 -0600853 'non-arch-board': [
854 IMPLY_NON_ARCH_BOARD,
855 'Allow Kconfig options outside arch/ and /board/ to imply'],
Simon Glass91197aa2021-12-18 14:54:35 -0700856}
Simon Glass9b2a2e82017-06-15 21:39:32 -0600857
Simon Glass9d603392021-12-18 08:09:43 -0700858
859def read_database():
860 """Read in the config database
861
862 Returns:
863 tuple:
864 set of all config options seen (each a str)
865 set of all defconfigs seen (each a str)
866 dict of configs for each defconfig:
867 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
868 value: dict:
869 key: CONFIG option
870 value: Value of option
871 dict of defconfigs for each config:
872 key: CONFIG option
873 value: set of boards using that option
874
875 """
876 configs = {}
877
878 # key is defconfig name, value is dict of (CONFIG_xxx, value)
879 config_db = {}
880
881 # Set of all config options we have seen
882 all_configs = set()
883
884 # Set of all defconfigs we have seen
885 all_defconfigs = set()
886
887 defconfig_db = collections.defaultdict(set)
Simon Glass61d555d2024-07-17 16:56:49 +0100888 defconfig = None
Simon Glass37f815c2021-12-18 14:54:34 -0700889 for line in read_file(CONFIG_DATABASE):
890 line = line.rstrip()
891 if not line: # Separator between defconfigs
892 config_db[defconfig] = configs
893 all_defconfigs.add(defconfig)
894 configs = {}
895 elif line[0] == ' ': # CONFIG line
896 config, value = line.strip().split('=', 1)
897 configs[config] = value
898 defconfig_db[config].add(defconfig)
899 all_configs.add(config)
900 else: # New defconfig
901 defconfig = line
Simon Glass9d603392021-12-18 08:09:43 -0700902
903 return all_configs, all_defconfigs, config_db, defconfig_db
904
905
Simon Glasscb008832017-06-15 21:39:33 -0600906def do_imply_config(config_list, add_imply, imply_flags, skip_added,
907 check_kconfig=True, find_superset=False):
Simon Glass99b66602017-06-01 19:39:03 -0600908 """Find CONFIG options which imply those in the list
909
910 Some CONFIG options can be implied by others and this can help to reduce
911 the size of the defconfig files. For example, CONFIG_X86 implies
912 CONFIG_CMD_IRQ, so we can put 'imply CMD_IRQ' under 'config X86' and
913 all x86 boards will have that option, avoiding adding CONFIG_CMD_IRQ to
914 each of the x86 defconfig files.
915
Simon Glassea4d6de2023-09-23 13:44:14 -0600916 This function uses the qconfig database to find such options. It
Simon Glass99b66602017-06-01 19:39:03 -0600917 displays a list of things that could possibly imply those in the list.
918 The algorithm ignores any that start with CONFIG_TARGET since these
919 typically refer to only a few defconfigs (often one). It also does not
920 display a config with less than 5 defconfigs.
921
922 The algorithm works using sets. For each target config in config_list:
923 - Get the set 'defconfigs' which use that target config
924 - For each config (from a list of all configs):
925 - Get the set 'imply_defconfig' of defconfigs which use that config
926 -
927 - If imply_defconfigs contains anything not in defconfigs then
928 this config does not imply the target config
929
Simon Glasscc628f52024-07-17 16:57:03 +0100930 Args:
931 config_list (list of str): List of CONFIG options to check
932 add_imply (bool): Automatically add an 'imply' for each config.
933 imply_flags (int): Flags which control which implying configs are allowed
Simon Glass9b2a2e82017-06-15 21:39:32 -0600934 (IMPLY_...)
Simon Glasscc628f52024-07-17 16:57:03 +0100935 skip_added (bool): Don't show options which already have an imply added.
936 check_kconfig (bool): Check if implied symbols already have an 'imply' or
Simon Glasscb008832017-06-15 21:39:33 -0600937 'select' for the target config, and show this information if so.
Simon Glasscc628f52024-07-17 16:57:03 +0100938 find_superset (bool): True to look for configs which are a superset of those
Simon Glass99b66602017-06-01 19:39:03 -0600939 already found. So for example if CONFIG_EXYNOS5 implies an option,
940 but CONFIG_EXYNOS covers a larger set of defconfigs and also
941 implies that option, this will drop the former in favour of the
942 latter. In practice this option has not proved very used.
943
944 Note the terminoloy:
945 config - a CONFIG_XXX options (a string, e.g. 'CONFIG_CMD_EEPROM')
946 defconfig - a defconfig file (a string, e.g. 'configs/snow_defconfig')
947 """
Simon Glass0a0c1242024-07-17 16:56:51 +0100948 kconf = scan_kconfig() if check_kconfig else None
Simon Glasscb008832017-06-15 21:39:33 -0600949 if add_imply and add_imply != 'all':
Simon Glassa3627082021-12-18 08:09:42 -0700950 add_imply = add_imply.split(',')
Simon Glasscb008832017-06-15 21:39:33 -0600951
Simon Glass62fae4b2023-09-23 13:44:00 -0600952 all_configs, all_defconfigs, _, defconfig_db = read_database()
Simon Glass99b66602017-06-01 19:39:03 -0600953
Simon Glassa3627082021-12-18 08:09:42 -0700954 # Work through each target config option in turn, independently
Simon Glass99b66602017-06-01 19:39:03 -0600955 for config in config_list:
956 defconfigs = defconfig_db.get(config)
957 if not defconfigs:
Simon Glass1bd43062023-09-23 13:43:59 -0600958 print(f'{config} not found in any defconfig')
Simon Glass99b66602017-06-01 19:39:03 -0600959 continue
960
961 # Get the set of defconfigs without this one (since a config cannot
962 # imply itself)
963 non_defconfigs = all_defconfigs - defconfigs
964 num_defconfigs = len(defconfigs)
Simon Glass1bd43062023-09-23 13:43:59 -0600965 print(f'{config} found in {num_defconfigs}/{len(all_configs)} defconfigs')
Simon Glass99b66602017-06-01 19:39:03 -0600966
967 # This will hold the results: key=config, value=defconfigs containing it
968 imply_configs = {}
969 rest_configs = all_configs - set([config])
970
971 # Look at every possible config, except the target one
972 for imply_config in rest_configs:
Simon Glass9b2a2e82017-06-15 21:39:32 -0600973 if 'ERRATUM' in imply_config:
Simon Glass99b66602017-06-01 19:39:03 -0600974 continue
Simon Glass91197aa2021-12-18 14:54:35 -0700975 if not imply_flags & IMPLY_CMD:
Simon Glass9b2a2e82017-06-15 21:39:32 -0600976 if 'CONFIG_CMD' in imply_config:
977 continue
Simon Glass91197aa2021-12-18 14:54:35 -0700978 if not imply_flags & IMPLY_TARGET:
Simon Glass9b2a2e82017-06-15 21:39:32 -0600979 if 'CONFIG_TARGET' in imply_config:
980 continue
Simon Glass99b66602017-06-01 19:39:03 -0600981
982 # Find set of defconfigs that have this config
983 imply_defconfig = defconfig_db[imply_config]
984
985 # Get the intersection of this with defconfigs containing the
986 # target config
987 common_defconfigs = imply_defconfig & defconfigs
988
989 # Get the set of defconfigs containing this config which DO NOT
990 # also contain the taret config. If this set is non-empty it means
991 # that this config affects other defconfigs as well as (possibly)
992 # the ones affected by the target config. This means it implies
993 # things we don't want to imply.
994 not_common_defconfigs = imply_defconfig & non_defconfigs
995 if not_common_defconfigs:
996 continue
997
998 # If there are common defconfigs, imply_config may be useful
999 if common_defconfigs:
1000 skip = False
1001 if find_superset:
Simon Glass793dca32019-10-31 07:42:57 -06001002 for prev in list(imply_configs.keys()):
Simon Glass99b66602017-06-01 19:39:03 -06001003 prev_count = len(imply_configs[prev])
1004 count = len(common_defconfigs)
1005 if (prev_count > count and
1006 (imply_configs[prev] & common_defconfigs ==
1007 common_defconfigs)):
1008 # skip imply_config because prev is a superset
1009 skip = True
1010 break
Simon Glassf297ba32023-09-23 13:44:05 -06001011 if count > prev_count:
Simon Glass99b66602017-06-01 19:39:03 -06001012 # delete prev because imply_config is a superset
1013 del imply_configs[prev]
1014 if not skip:
1015 imply_configs[imply_config] = common_defconfigs
1016
1017 # Now we have a dict imply_configs of configs which imply each config
1018 # The value of each dict item is the set of defconfigs containing that
1019 # config. Rank them so that we print the configs that imply the largest
1020 # number of defconfigs first.
Simon Glasscb008832017-06-15 21:39:33 -06001021 ranked_iconfigs = sorted(imply_configs,
Simon Glass99b66602017-06-01 19:39:03 -06001022 key=lambda k: len(imply_configs[k]), reverse=True)
Simon Glasscb008832017-06-15 21:39:33 -06001023 kconfig_info = ''
1024 cwd = os.getcwd()
1025 add_list = collections.defaultdict(list)
1026 for iconfig in ranked_iconfigs:
1027 num_common = len(imply_configs[iconfig])
Simon Glass99b66602017-06-01 19:39:03 -06001028
1029 # Don't bother if there are less than 5 defconfigs affected.
Simon Glass9b2a2e82017-06-15 21:39:32 -06001030 if num_common < (2 if imply_flags & IMPLY_MIN_2 else 5):
Simon Glass99b66602017-06-01 19:39:03 -06001031 continue
Simon Glasscb008832017-06-15 21:39:33 -06001032 missing = defconfigs - imply_configs[iconfig]
Simon Glass99b66602017-06-01 19:39:03 -06001033 missing_str = ', '.join(missing) if missing else 'all'
1034 missing_str = ''
Simon Glasscb008832017-06-15 21:39:33 -06001035 show = True
1036 if kconf:
1037 sym = find_kconfig_rules(kconf, config[CONFIG_LEN:],
1038 iconfig[CONFIG_LEN:])
1039 kconfig_info = ''
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:]
Simon Glass1bd43062023-09-23 13:43:59 -06001046 kconfig_info = f'{fname}:{linenum}'
Simon Glasscb008832017-06-15 21:39:33 -06001047 if skip_added:
1048 show = False
1049 else:
Tom Rini65e05dd2019-09-20 17:42:09 -04001050 sym = kconf.syms.get(iconfig[CONFIG_LEN:])
Simon Glasscb008832017-06-15 21:39:33 -06001051 fname = ''
1052 if sym:
Simon Glassea40b202021-07-21 21:35:53 -06001053 nodes = sym.nodes
1054 if len(nodes) == 1:
1055 fname, linenum = nodes[0].filename, nodes[0].linenr
Simon Glasscb008832017-06-15 21:39:33 -06001056 if cwd and fname.startswith(cwd):
1057 fname = fname[len(cwd) + 1:]
1058 in_arch_board = not sym or (fname.startswith('arch') or
1059 fname.startswith('board'))
1060 if (not in_arch_board and
Simon Glass91197aa2021-12-18 14:54:35 -07001061 not imply_flags & IMPLY_NON_ARCH_BOARD):
Simon Glasscb008832017-06-15 21:39:33 -06001062 continue
1063
1064 if add_imply and (add_imply == 'all' or
1065 iconfig in add_imply):
1066 fname, linenum, kconfig_info = (check_imply_rule(kconf,
Simon Glassf876e962024-07-17 16:56:52 +01001067 iconfig[CONFIG_LEN:]))
Simon Glasscb008832017-06-15 21:39:33 -06001068 if fname:
1069 add_list[fname].append(linenum)
1070
1071 if show and kconfig_info != 'skip':
Simon Glass0e03fb12024-07-17 16:56:53 +01001072 print(f'{num_common:5} : '
1073 f'{iconfig.ljust(30)}{kconfig_info.ljust(25)} {missing_str}')
Simon Glasscb008832017-06-15 21:39:33 -06001074
1075 # Having collected a list of things to add, now we add them. We process
1076 # each file from the largest line number to the smallest so that
1077 # earlier additions do not affect our line numbers. E.g. if we added an
1078 # imply at line 20 it would change the position of each line after
1079 # that.
Simon Glass793dca32019-10-31 07:42:57 -06001080 for fname, linenums in add_list.items():
Simon Glasscb008832017-06-15 21:39:33 -06001081 for linenum in sorted(linenums, reverse=True):
1082 add_imply_rule(config[CONFIG_LEN:], fname, linenum)
Simon Glass99b66602017-06-01 19:39:03 -06001083
Simon Glass941671a2022-02-08 11:49:46 -07001084def defconfig_matches(configs, re_match):
1085 """Check if any CONFIG option matches a regex
1086
1087 The match must be complete, i.e. from the start to end of the CONFIG option.
1088
1089 Args:
1090 configs (dict): Dict of CONFIG options:
1091 key: CONFIG option
1092 value: Value of option
1093 re_match (re.Pattern): Match to check
1094
1095 Returns:
1096 bool: True if any CONFIG matches the regex
1097 """
1098 for cfg in configs:
Simon Glassd9c958f2022-03-05 20:18:54 -07001099 if re_match.fullmatch(cfg):
Simon Glass941671a2022-02-08 11:49:46 -07001100 return True
1101 return False
Simon Glass99b66602017-06-01 19:39:03 -06001102
Simon Glass65d7fce2021-12-18 08:09:46 -07001103def do_find_config(config_list):
1104 """Find boards with a given combination of CONFIGs
1105
Simon Glass630a9c92024-07-17 16:57:04 +01001106 Args:
1107 config_list (list of str): List of CONFIG options to check (each a regex
1108 consisting of a config option, with or without a CONFIG_ prefix. If
1109 an option is preceded by a tilde (~) then it must be false,
1110 otherwise it must be true)
1111
1112 Returns:
1113 int: exit code (0 for success)
Simon Glass65d7fce2021-12-18 08:09:46 -07001114 """
Simon Glass62fae4b2023-09-23 13:44:00 -06001115 _, all_defconfigs, config_db, _ = read_database()
Simon Glass65d7fce2021-12-18 08:09:46 -07001116
Simon Glass65d7fce2021-12-18 08:09:46 -07001117 # Start with all defconfigs
1118 out = all_defconfigs
1119
1120 # Work through each config in turn
Simon Glass65d7fce2021-12-18 08:09:46 -07001121 for item in config_list:
1122 # Get the real config name and whether we want this config or not
1123 cfg = item
1124 want = True
1125 if cfg[0] == '~':
1126 want = False
1127 cfg = cfg[1:]
1128
Simon Glass65d7fce2021-12-18 08:09:46 -07001129 # Search everything that is still in the running. If it has a config
1130 # that we want, or doesn't have one that we don't, add it into the
1131 # running for the next stage
1132 in_list = out
1133 out = set()
Simon Glass941671a2022-02-08 11:49:46 -07001134 re_match = re.compile(cfg)
Simon Glass65d7fce2021-12-18 08:09:46 -07001135 for defc in in_list:
Simon Glass941671a2022-02-08 11:49:46 -07001136 has_cfg = defconfig_matches(config_db[defc], re_match)
Simon Glass65d7fce2021-12-18 08:09:46 -07001137 if has_cfg == want:
1138 out.add(defc)
Tom Rini9ef3ba82022-12-04 10:14:16 -05001139 print(f'{len(out)} matches')
1140 print(' '.join(item.split('_defconfig')[0] for item in out))
Simon Glass630a9c92024-07-17 16:57:04 +01001141 return 0
Simon Glass65d7fce2021-12-18 08:09:46 -07001142
1143
1144def prefix_config(cfg):
1145 """Prefix a config with CONFIG_ if needed
1146
1147 This handles ~ operator, which indicates that the CONFIG should be disabled
1148
1149 >>> prefix_config('FRED')
1150 'CONFIG_FRED'
1151 >>> prefix_config('CONFIG_FRED')
1152 'CONFIG_FRED'
1153 >>> prefix_config('~FRED')
1154 '~CONFIG_FRED'
1155 >>> prefix_config('~CONFIG_FRED')
1156 '~CONFIG_FRED'
1157 >>> prefix_config('A123')
1158 'CONFIG_A123'
1159 """
Simon Glassa4c9d172023-09-23 13:44:01 -06001160 oper = ''
Simon Glass65d7fce2021-12-18 08:09:46 -07001161 if cfg[0] == '~':
Simon Glassa4c9d172023-09-23 13:44:01 -06001162 oper = cfg[0]
Simon Glass65d7fce2021-12-18 08:09:46 -07001163 cfg = cfg[1:]
1164 if not cfg.startswith('CONFIG_'):
1165 cfg = 'CONFIG_' + cfg
Simon Glassa4c9d172023-09-23 13:44:01 -06001166 return oper + cfg
Simon Glass65d7fce2021-12-18 08:09:46 -07001167
1168
Simon Glass98275712023-09-23 13:43:57 -06001169RE_MK_CONFIGS = re.compile(r'CONFIG_(\$\(SPL_(?:TPL_)?\))?([A-Za-z0-9_]*)')
1170RE_IFDEF = re.compile(r'(ifdef|ifndef)')
1171RE_C_CONFIGS = re.compile(r'CONFIG_([A-Za-z0-9_]*)')
1172RE_CONFIG_IS = re.compile(r'CONFIG_IS_ENABLED\(([A-Za-z0-9_]*)\)')
Simon Glass65e62032023-02-01 13:19:12 -07001173
1174class ConfigUse:
Simon Glassf876e962024-07-17 16:56:52 +01001175 """Tracks whether a config relates to SPL or not"""
Simon Glass65e62032023-02-01 13:19:12 -07001176 def __init__(self, cfg, is_spl, fname, rest):
Simon Glassf876e962024-07-17 16:56:52 +01001177 """Set up a new ConfigUse
1178
1179 Args:
1180 cfg (str): CONFIG option, without any CONFIG_ or SPL_ prefix
1181 is_spl (bool): True if this option relates to SPL
1182 fname (str): Makefile filename where the CONFIG option was found
1183 rest (str): Line of the Makefile
1184 """
Simon Glass65e62032023-02-01 13:19:12 -07001185 self.cfg = cfg
1186 self.is_spl = is_spl
1187 self.fname = fname
1188 self.rest = rest
1189
1190 def __hash__(self):
1191 return hash((self.cfg, self.is_spl))
1192
1193def scan_makefiles(fnames):
1194 """Scan Makefiles looking for Kconfig options
1195
1196 Looks for uses of CONFIG options in Makefiles
1197
1198 Args:
1199 fnames (list of tuple):
1200 str: Makefile filename where the option was found
1201 str: Line of the Makefile
1202
1203 Returns:
1204 tuple:
1205 dict: all_uses
1206 key (ConfigUse): object
1207 value (list of str): matching lines
1208 dict: Uses by filename
1209 key (str): filename
1210 value (set of ConfigUse): uses in that filename
1211
1212 >>> RE_MK_CONFIGS.search('CONFIG_FRED').groups()
1213 (None, 'FRED')
1214 >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_)MARY').groups()
1215 ('$(SPL_)', 'MARY')
1216 >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_TPL_)MARY').groups()
1217 ('$(SPL_TPL_)', 'MARY')
1218 """
1219 all_uses = collections.defaultdict(list)
1220 fname_uses = {}
1221 for fname, rest in fnames:
1222 m_iter = RE_MK_CONFIGS.finditer(rest)
Simon Glassa4c9d172023-09-23 13:44:01 -06001223 for mat in m_iter:
1224 real_opt = mat.group(2)
Simon Glass65e62032023-02-01 13:19:12 -07001225 if real_opt == '':
1226 continue
1227 is_spl = False
Simon Glassa4c9d172023-09-23 13:44:01 -06001228 if mat.group(1):
Simon Glass65e62032023-02-01 13:19:12 -07001229 is_spl = True
1230 use = ConfigUse(real_opt, is_spl, fname, rest)
1231 if fname not in fname_uses:
1232 fname_uses[fname] = set()
1233 fname_uses[fname].add(use)
1234 all_uses[use].append(rest)
1235 return all_uses, fname_uses
1236
1237
1238def scan_src_files(fnames):
1239 """Scan source files (other than Makefiles) looking for Kconfig options
1240
1241 Looks for uses of CONFIG options
1242
1243 Args:
1244 fnames (list of tuple):
1245 str: Makefile filename where the option was found
1246 str: Line of the Makefile
1247
1248 Returns:
1249 tuple:
1250 dict: all_uses
1251 key (ConfigUse): object
1252 value (list of str): matching lines
1253 dict: Uses by filename
1254 key (str): filename
1255 value (set of ConfigUse): uses in that filename
1256
1257 >>> RE_C_CONFIGS.search('CONFIG_FRED').groups()
1258 ('FRED',)
1259 >>> RE_CONFIG_IS.search('CONFIG_IS_ENABLED(MARY)').groups()
1260 ('MARY',)
1261 >>> RE_CONFIG_IS.search('#if CONFIG_IS_ENABLED(OF_PLATDATA)').groups()
1262 ('OF_PLATDATA',)
1263 """
Simon Glass62fae4b2023-09-23 13:44:00 -06001264 fname = None
1265 rest = None
1266
Simon Glass65e62032023-02-01 13:19:12 -07001267 def add_uses(m_iter, is_spl):
Simon Glassa4c9d172023-09-23 13:44:01 -06001268 for mat in m_iter:
1269 real_opt = mat.group(1)
Simon Glass65e62032023-02-01 13:19:12 -07001270 if real_opt == '':
1271 continue
1272 use = ConfigUse(real_opt, is_spl, fname, rest)
1273 if fname not in fname_uses:
1274 fname_uses[fname] = set()
1275 fname_uses[fname].add(use)
1276 all_uses[use].append(rest)
1277
1278 all_uses = collections.defaultdict(list)
1279 fname_uses = {}
1280 for fname, rest in fnames:
1281 m_iter = RE_C_CONFIGS.finditer(rest)
1282 add_uses(m_iter, False)
1283
1284 m_iter2 = RE_CONFIG_IS.finditer(rest)
1285 add_uses(m_iter2, True)
1286
1287 return all_uses, fname_uses
1288
1289
1290MODE_NORMAL, MODE_SPL, MODE_PROPER = range(3)
1291
1292def do_scan_source(path, do_update):
1293 """Scan the source tree for Kconfig inconsistencies
1294
1295 Args:
1296 path (str): Path to source tree
1297 do_update (bool) : True to write to scripts/kconf_... files
1298 """
1299 def is_not_proper(name):
1300 for prefix in SPL_PREFIXES:
1301 if name.startswith(prefix):
1302 return name[len(prefix):]
1303 return False
1304
1305 def check_not_found(all_uses, spl_mode):
1306 """Check for Kconfig options mentioned in the source but not in Kconfig
1307
1308 Args:
1309 all_uses (dict):
1310 key (ConfigUse): object
1311 value (list of str): matching lines
1312 spl_mode (int): If MODE_SPL, look at source code which implies
1313 an SPL_ option, but for which there is none;
1314 for MOD_PROPER, look at source code which implies a Proper
1315 option (i.e. use of CONFIG_IS_ENABLED() or $(SPL_) or
1316 $(SPL_TPL_) but for which there none;
1317 if MODE_NORMAL, ignore SPL
1318
1319 Returns:
1320 dict:
1321 key (str): CONFIG name (without 'CONFIG_' prefix
1322 value (list of ConfigUse): List of uses of this CONFIG
1323 """
1324 # Make sure we know about all the options
1325 not_found = collections.defaultdict(list)
Simon Glass62fae4b2023-09-23 13:44:00 -06001326 for use, _ in all_uses.items():
Simon Glass65e62032023-02-01 13:19:12 -07001327 name = use.cfg
1328 if name in IGNORE_SYMS:
1329 continue
1330 check = True
1331
1332 if spl_mode == MODE_SPL:
1333 check = use.is_spl
1334
1335 # If it is an SPL symbol, try prepending all SPL_ prefixes to
1336 # find at least one SPL symbol
1337 if use.is_spl:
Simon Glass65e62032023-02-01 13:19:12 -07001338 for prefix in SPL_PREFIXES:
1339 try_name = prefix + name
1340 sym = kconf.syms.get(try_name)
1341 if sym:
1342 break
1343 if not sym:
1344 not_found[f'SPL_{name}'].append(use)
1345 continue
1346 elif spl_mode == MODE_PROPER:
1347 # Try to find the Proper version of this symbol, i.e. without
1348 # the SPL_ prefix
1349 proper_name = is_not_proper(name)
1350 if proper_name:
1351 name = proper_name
1352 elif not use.is_spl:
1353 check = False
1354 else: # MODE_NORMAL
Simon Glass65e62032023-02-01 13:19:12 -07001355 sym = kconf.syms.get(name)
1356 if not sym:
1357 proper_name = is_not_proper(name)
1358 if proper_name:
1359 name = proper_name
1360 sym = kconf.syms.get(name)
1361 if not sym:
1362 for prefix in SPL_PREFIXES:
1363 try_name = prefix + name
1364 sym = kconf.syms.get(try_name)
1365 if sym:
1366 break
1367 if not sym:
1368 not_found[name].append(use)
1369 continue
1370
1371 sym = kconf.syms.get(name)
1372 if not sym and check:
1373 not_found[name].append(use)
1374 return not_found
1375
1376 def show_uses(uses):
1377 """Show a list of uses along with their filename and code snippet
1378
1379 Args:
1380 uses (dict):
1381 key (str): CONFIG name (without 'CONFIG_' prefix
1382 value (list of ConfigUse): List of uses of this CONFIG
1383 """
1384 for name in sorted(uses):
1385 print(f'{name}: ', end='')
1386 for i, use in enumerate(uses[name]):
1387 print(f'{" " if i else ""}{use.fname}: {use.rest.strip()}')
1388
1389
1390 print('Scanning Kconfig')
Simon Glass0a0c1242024-07-17 16:56:51 +01001391 kconf = scan_kconfig()
Simon Glass65e62032023-02-01 13:19:12 -07001392 print(f'Scanning source in {path}')
1393 args = ['git', 'grep', '-E', r'IS_ENABLED|\bCONFIG']
1394 with subprocess.Popen(args, stdout=subprocess.PIPE) as proc:
Simon Glass62fae4b2023-09-23 13:44:00 -06001395 out, _ = proc.communicate()
Simon Glass65e62032023-02-01 13:19:12 -07001396 lines = out.splitlines()
1397 re_fname = re.compile('^([^:]*):(.*)')
1398 src_list = []
1399 mk_list = []
1400 for line in lines:
1401 linestr = line.decode('utf-8')
1402 m_fname = re_fname.search(linestr)
1403 if not m_fname:
1404 continue
1405 fname, rest = m_fname.groups()
1406 dirname, leaf = os.path.split(fname)
1407 root, ext = os.path.splitext(leaf)
1408 if ext == '.autoconf':
1409 pass
1410 elif ext in ['.c', '.h', '.S', '.lds', '.dts', '.dtsi', '.asl', '.cfg',
1411 '.env', '.tmpl']:
1412 src_list.append([fname, rest])
1413 elif 'Makefile' in root or ext == '.mk':
1414 mk_list.append([fname, rest])
1415 elif ext in ['.yml', '.sh', '.py', '.awk', '.pl', '.rst', '', '.sed']:
1416 pass
1417 elif 'Kconfig' in root or 'Kbuild' in root:
1418 pass
1419 elif 'README' in root:
1420 pass
1421 elif dirname in ['configs']:
1422 pass
1423 elif dirname.startswith('doc') or dirname.startswith('scripts/kconfig'):
1424 pass
1425 else:
1426 print(f'Not sure how to handle file {fname}')
1427
1428 # Scan the Makefiles
Simon Glass62fae4b2023-09-23 13:44:00 -06001429 all_uses, _ = scan_makefiles(mk_list)
Simon Glass65e62032023-02-01 13:19:12 -07001430
1431 spl_not_found = set()
1432 proper_not_found = set()
1433
1434 # Make sure we know about all the options
1435 print('\nCONFIG options present in Makefiles but not Kconfig:')
1436 not_found = check_not_found(all_uses, MODE_NORMAL)
1437 show_uses(not_found)
1438
1439 print('\nCONFIG options present in Makefiles but not Kconfig (SPL):')
1440 not_found = check_not_found(all_uses, MODE_SPL)
1441 show_uses(not_found)
Simon Glassb774ba52023-09-23 13:44:03 -06001442 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
Simon Glass65e62032023-02-01 13:19:12 -07001443
1444 print('\nCONFIG options used as Proper in Makefiles but without a non-SPL_ variant:')
1445 not_found = check_not_found(all_uses, MODE_PROPER)
1446 show_uses(not_found)
Simon Glassb774ba52023-09-23 13:44:03 -06001447 proper_not_found |= {not_found.keys()}
Simon Glass65e62032023-02-01 13:19:12 -07001448
1449 # Scan the source code
Simon Glass62fae4b2023-09-23 13:44:00 -06001450 all_uses, _ = scan_src_files(src_list)
Simon Glass65e62032023-02-01 13:19:12 -07001451
1452 # Make sure we know about all the options
1453 print('\nCONFIG options present in source but not Kconfig:')
1454 not_found = check_not_found(all_uses, MODE_NORMAL)
1455 show_uses(not_found)
1456
1457 print('\nCONFIG options present in source but not Kconfig (SPL):')
1458 not_found = check_not_found(all_uses, MODE_SPL)
1459 show_uses(not_found)
Simon Glassb774ba52023-09-23 13:44:03 -06001460 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
Simon Glass65e62032023-02-01 13:19:12 -07001461
1462 print('\nCONFIG options used as Proper in source but without a non-SPL_ variant:')
1463 not_found = check_not_found(all_uses, MODE_PROPER)
1464 show_uses(not_found)
Simon Glassb774ba52023-09-23 13:44:03 -06001465 proper_not_found |= {not_found.keys()}
Simon Glass65e62032023-02-01 13:19:12 -07001466
1467 print('\nCONFIG options used as SPL but without an SPL_ variant:')
1468 for item in sorted(spl_not_found):
1469 print(f' {item}')
1470
1471 print('\nCONFIG options used as Proper but without a non-SPL_ variant:')
1472 for item in sorted(proper_not_found):
1473 print(f' {item}')
1474
1475 # Write out the updated information
1476 if do_update:
Simon Glasse6c686f2023-09-23 13:44:04 -06001477 with open(os.path.join(path, 'scripts', 'conf_nospl'), 'w',
1478 encoding='utf-8') as out:
Simon Glass65e62032023-02-01 13:19:12 -07001479 print('# These options should not be enabled in SPL builds\n',
1480 file=out)
1481 for item in sorted(spl_not_found):
1482 print(item, file=out)
Simon Glasse6c686f2023-09-23 13:44:04 -06001483 with open(os.path.join(path, 'scripts', 'conf_noproper'), 'w',
1484 encoding='utf-8') as out:
Simon Glass65e62032023-02-01 13:19:12 -07001485 print('# These options should not be enabled in Proper builds\n',
1486 file=out)
1487 for item in sorted(proper_not_found):
1488 print(item, file=out)
Simon Glassd63357e2024-07-17 16:57:02 +01001489 return 0
Simon Glass65e62032023-02-01 13:19:12 -07001490
1491
Simon Glassa056c422024-07-17 16:56:55 +01001492def parse_args():
1493 """Parse the program arguments
1494
1495 Returns:
1496 tuple:
1497 argparse.ArgumentParser: parser
1498 argparse.Namespace: Parsed arguments
1499 """
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001500 try:
1501 cpu_count = multiprocessing.cpu_count()
1502 except NotImplementedError:
1503 cpu_count = 1
1504
Simon Glassb2e83c62021-12-18 14:54:31 -07001505 epilog = '''Move config options from headers to defconfig files. See
1506doc/develop/moveconfig.rst for documentation.'''
1507
1508 parser = ArgumentParser(epilog=epilog)
1509 # Add arguments here
1510 parser.add_argument('-a', '--add-imply', type=str, default='',
Simon Glasscb008832017-06-15 21:39:33 -06001511 help='comma-separated list of CONFIG options to add '
1512 "an 'imply' statement to for the CONFIG in -i")
Simon Glassb2e83c62021-12-18 14:54:31 -07001513 parser.add_argument('-A', '--skip-added', action='store_true', default=False,
Simon Glasscb008832017-06-15 21:39:33 -06001514 help="don't show options which are already marked as "
1515 'implying others')
Simon Glassb2e83c62021-12-18 14:54:31 -07001516 parser.add_argument('-b', '--build-db', action='store_true', default=False,
Simon Glassd73fcb12017-06-01 19:39:02 -06001517 help='build a CONFIG database')
Simon Glassb2e83c62021-12-18 14:54:31 -07001518 parser.add_argument('-C', '--commit', action='store_true', default=False,
Simon Glass9ede2122016-09-12 23:18:21 -06001519 help='Create a git commit for the operation')
Simon Glass15f19ab2023-09-23 13:44:09 -06001520 parser.add_argument('--nocolour', action='store_true', default=False,
1521 help="don't display the log in colour")
Simon Glassb2e83c62021-12-18 14:54:31 -07001522 parser.add_argument('-d', '--defconfigs', type=str,
Simon Glassee4e61b2017-06-01 19:38:59 -06001523 help='a file containing a list of defconfigs to move, '
1524 "one per line (for example 'snow_defconfig') "
1525 "or '-' to read from stdin")
Simon Glassb2e83c62021-12-18 14:54:31 -07001526 parser.add_argument('-e', '--exit-on-error', action='store_true',
Simon Glasse1ae5632021-12-18 08:09:44 -07001527 default=False,
1528 help='exit immediately on any error')
Simon Glassb2e83c62021-12-18 14:54:31 -07001529 parser.add_argument('-f', '--find', action='store_true', default=False,
Simon Glass65d7fce2021-12-18 08:09:46 -07001530 help='Find boards with a given config combination')
Simon Glassb2e83c62021-12-18 14:54:31 -07001531 parser.add_argument('-i', '--imply', action='store_true', default=False,
Simon Glass99b66602017-06-01 19:39:03 -06001532 help='find options which imply others')
Simon Glassb2e83c62021-12-18 14:54:31 -07001533 parser.add_argument('-I', '--imply-flags', type=str, default='',
Simon Glass9b2a2e82017-06-15 21:39:32 -06001534 help="control the -i option ('help' for help")
Simon Glassb2e83c62021-12-18 14:54:31 -07001535 parser.add_argument('-j', '--jobs', type=int, default=cpu_count,
Simon Glasse1ae5632021-12-18 08:09:44 -07001536 help='the number of jobs to run simultaneously')
Simon Glassb2e83c62021-12-18 14:54:31 -07001537 parser.add_argument('-n', '--dry-run', action='store_true', default=False,
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001538 help='perform a trial run (show log with no changes)')
Simon Glassb2e83c62021-12-18 14:54:31 -07001539 parser.add_argument('-r', '--git-ref', type=str,
Simon Glasse1ae5632021-12-18 08:09:44 -07001540 help='the git ref to clone for building the autoconf.mk')
Simon Glassb2e83c62021-12-18 14:54:31 -07001541 parser.add_argument('-s', '--force-sync', action='store_true', default=False,
Masahiro Yamada8513dc02016-05-19 15:52:08 +09001542 help='force sync by savedefconfig')
Simon Glassb2e83c62021-12-18 14:54:31 -07001543 parser.add_argument('-S', '--spl', action='store_true', default=False,
Masahiro Yamada07913d12016-08-22 22:18:22 +09001544 help='parse config options defined for SPL build')
Simon Glass65e62032023-02-01 13:19:12 -07001545 parser.add_argument('--scan-source', action='store_true', default=False,
1546 help='scan source for uses of CONFIG options')
Simon Glassb2e83c62021-12-18 14:54:31 -07001547 parser.add_argument('-t', '--test', action='store_true', default=False,
Simon Glasse1ae5632021-12-18 08:09:44 -07001548 help='run unit tests')
Simon Glassb2e83c62021-12-18 14:54:31 -07001549 parser.add_argument('-y', '--yes', action='store_true', default=False,
Simon Glass6b403df2016-09-12 23:18:20 -06001550 help="respond 'yes' to any prompts")
Simon Glass65e62032023-02-01 13:19:12 -07001551 parser.add_argument('-u', '--update', action='store_true', default=False,
1552 help="update scripts/ files (use with --scan-source)")
Simon Glassb2e83c62021-12-18 14:54:31 -07001553 parser.add_argument('-v', '--verbose', action='store_true', default=False,
Joe Hershberger95bf9c72015-05-19 13:21:24 -05001554 help='show any build errors as boards are built')
Simon Glassb2e83c62021-12-18 14:54:31 -07001555 parser.add_argument('configs', nargs='*')
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001556
Simon Glassa056c422024-07-17 16:56:55 +01001557 return parser, parser.parse_args()
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001558
Simon Glassa056c422024-07-17 16:56:55 +01001559
Simon Glasscc628f52024-07-17 16:57:03 +01001560def imply(args):
1561 """Handle checking for flags which imply others
1562
1563 Args:
1564 args (argparse.Namespace): Program arguments
1565
1566 Returns:
1567 int: exit code (0 for success)
1568 """
1569 imply_flags = 0
1570 if args.imply_flags == 'all':
1571 imply_flags = -1
1572
1573 elif args.imply_flags:
1574 for flag in args.imply_flags.split(','):
1575 bad = flag not in IMPLY_FLAGS
1576 if bad:
1577 print(f"Invalid flag '{flag}'")
1578 if flag == 'help' or bad:
1579 print("Imply flags: (separate with ',')")
1580 for name, info in IMPLY_FLAGS.items():
1581 print(f' {name:-15s}: {info[1]}')
1582 return 1
1583 imply_flags |= IMPLY_FLAGS[flag][0]
1584
1585 do_imply_config(args.configs, args.add_imply, imply_flags, args.skip_added)
1586 return 0
1587
1588
Simon Glass6c2a4382024-07-17 16:57:06 +01001589def add_commit(configs):
1590 """Add a commit indicating which CONFIG options were converted
1591
1592 Args:
1593 configs (list of str) List of CONFIG_... options to process
1594 """
1595 subprocess.call(['git', 'add', '-u'])
1596 if configs:
1597 part = 'et al ' if len(configs) > 1 else ''
1598 msg = f'Convert {configs[0]} {part}to Kconfig'
1599 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' %
1600 '\n '.join(configs))
1601 else:
1602 msg = 'configs: Resync with savedefconfig'
1603 msg += '\n\nRsync all defconfig files using moveconfig.py'
1604 subprocess.call(['git', 'commit', '-s', '-m', msg])
1605
1606
Simon Glass7e688042024-07-17 16:57:01 +01001607def do_tests():
1608 """Run doctests and unit tests (so far there are no unit tests)"""
1609 sys.argv = [sys.argv[0]]
1610 fail, _ = doctest.testmod()
1611 if fail:
1612 return 1
1613 unittest.main()
1614 return 0
1615
1616
Simon Glassa056c422024-07-17 16:56:55 +01001617def main():
1618 """Main program"""
1619 parser, args = parse_args()
Simon Glass382c6622024-07-17 16:56:57 +01001620 if not any((args.force_sync, args.build_db, args.imply, args.find,
1621 args.scan_source, args.test)):
1622 parser.print_usage()
1623 sys.exit(1)
Simon Glassc50b6f12024-07-17 16:56:59 +01001624
1625 check_top_directory()
1626
Simon Glassfd35fbe2024-07-17 16:57:00 +01001627 # prefix the option name with CONFIG_ if missing
1628 args.configs = [prefix_config(cfg) for cfg in args.configs]
1629
Simon Glassb2e83c62021-12-18 14:54:31 -07001630 if args.test:
Simon Glass7e688042024-07-17 16:57:01 +01001631 return do_tests()
Simon Glass65e62032023-02-01 13:19:12 -07001632 if args.scan_source:
Simon Glassd63357e2024-07-17 16:57:02 +01001633 return do_scan_source(os.getcwd(), args.update)
Simon Glassb2e83c62021-12-18 14:54:31 -07001634 if args.imply:
Simon Glasscc628f52024-07-17 16:57:03 +01001635 if imply(args):
1636 parser.print_usage()
1637 sys.exit(1)
Simon Glassf297ba32023-09-23 13:44:05 -06001638 return 0
Simon Glassb2e83c62021-12-18 14:54:31 -07001639 if args.find:
Simon Glass630a9c92024-07-17 16:57:04 +01001640 return do_find_config(args.configs)
Simon Glass65d7fce2021-12-18 08:09:46 -07001641
Simon Glass035ecb42024-07-17 16:56:58 +01001642 col = terminal.Color(terminal.COLOR_NEVER if args.nocolour
1643 else terminal.COLOR_IF_TERMINAL)
Simon Glasseb1df3f2024-07-17 16:57:05 +01001644 config_db, progress = move_config(args, col)
Joe Hershberger2144f882015-05-19 13:21:20 -05001645
Simon Glassb2e83c62021-12-18 14:54:31 -07001646 if args.commit:
Simon Glass6c2a4382024-07-17 16:57:06 +01001647 add_commit(args.configs)
Simon Glass9ede2122016-09-12 23:18:21 -06001648
Simon Glassb2e83c62021-12-18 14:54:31 -07001649 if args.build_db:
Simon Glass6c2a4382024-07-17 16:57:06 +01001650 configs = args.configs
Simon Glassa4c9d172023-09-23 13:44:01 -06001651 with open(CONFIG_DATABASE, 'w', encoding='utf-8') as outf:
Simon Glass793dca32019-10-31 07:42:57 -06001652 for defconfig, configs in config_db.items():
Simon Glassa4c9d172023-09-23 13:44:01 -06001653 outf.write(f'{defconfig}\n')
Simon Glassd73fcb12017-06-01 19:39:02 -06001654 for config in sorted(configs.keys()):
Simon Glassa4c9d172023-09-23 13:44:01 -06001655 outf.write(f' {config}={configs[config]}\n')
1656 outf.write('\n')
Simon Glass94e2ed72023-09-23 13:44:13 -06001657 print(col.build(
Simon Glass0229eee2024-07-17 16:57:07 +01001658 col.RED if progress.failed else col.GREEN,
1659 f'{progress.failure_msg}{len(config_db)} boards written to {CONFIG_DATABASE}'))
Simon Glass94e2ed72023-09-23 13:44:13 -06001660 else:
Simon Glass0229eee2024-07-17 16:57:07 +01001661 if progress.failed:
1662 print(col.build(col.RED, f'{progress.failure_msg}see {FAILED_LIST}', True))
Simon Glass94e2ed72023-09-23 13:44:13 -06001663 else:
1664 # Add enough spaces to overwrite the progress indicator
1665 print(col.build(
1666 col.GREEN, f'{progress.total} processed ', bright=True))
1667
Simon Glassf297ba32023-09-23 13:44:05 -06001668 return 0
1669
Simon Glassd73fcb12017-06-01 19:39:02 -06001670
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001671if __name__ == '__main__':
Simon Glass65d7fce2021-12-18 08:09:46 -07001672 sys.exit(main())