blob: ec59ef4ec71cb3565e4d5de8de98515889f34593 [file] [log] [blame]
Simon Glass1f701862019-10-31 07:42:57 -06001#!/usr/bin/env python3
Tom Rini10e47792018-05-06 17:58:06 -04002# SPDX-License-Identifier: GPL-2.0+
Masahiro Yamadab6160812015-05-20 11:36:07 +09003
Simon Glass28155572024-07-17 16:56:52 +01004"""Build and query a Kconfig database for boards.
Masahiro Yamadab6160812015-05-20 11:36:07 +09005
Simon Glass46960942024-07-17 16:56:54 +01006See doc/develop/qconfig.rst for documentation.
Simon Glass28155572024-07-17 16:56:52 +01007
8Author: Masahiro Yamada <yamada.masahiro@socionext.com>
9Author: Simon Glass <sjg@chromium.org>
Masahiro Yamadab6160812015-05-20 11:36:07 +090010"""
11
Simon Glassd9c1da22021-12-18 14:54:31 -070012from argparse import ArgumentParser
Simon Glassc6e73cf2017-06-01 19:39:03 -060013import collections
Simon Glassb3464eb2021-12-18 14:54:35 -070014from contextlib import ExitStack
Simon Glassbb57be72021-12-18 08:09:45 -070015import doctest
Masahiro Yamada0f6beda2016-05-19 15:52:07 +090016import filecmp
Masahiro Yamadab6160812015-05-20 11:36:07 +090017import fnmatch
Masahiro Yamada3984d6e2016-10-19 14:39:54 +090018import glob
Masahiro Yamadab6160812015-05-20 11:36:07 +090019import multiprocessing
Masahiro Yamadab6160812015-05-20 11:36:07 +090020import os
Simon Glass1f701862019-10-31 07:42:57 -060021import queue
Masahiro Yamadab6160812015-05-20 11:36:07 +090022import re
23import shutil
24import subprocess
25import sys
26import tempfile
Simon Glass43cf08f2017-06-01 19:39:02 -060027import threading
Masahiro Yamadab6160812015-05-20 11:36:07 +090028import time
Simon Glassbb57be72021-12-18 08:09:45 -070029import unittest
Masahiro Yamadab6160812015-05-20 11:36:07 +090030
Simon Glassf0d9c102020-04-17 18:09:02 -060031from buildman import bsettings
32from buildman import kconfiglib
33from buildman import toolchain
Simon Glass9b191102023-09-23 13:44:09 -060034from u_boot_pylib import terminal
Simon Glass44116332017-06-15 21:39:33 -060035
Masahiro Yamadab6160812015-05-20 11:36:07 +090036SHOW_GNU_MAKE = 'scripts/show-gnu-make'
37SLEEP_TIME=0.03
38
Masahiro Yamadab6160812015-05-20 11:36:07 +090039STATE_IDLE = 0
40STATE_DEFCONFIG = 1
41STATE_AUTOCONF = 2
Joe Hershberger166edec2015-05-19 13:21:17 -050042STATE_SAVEDEFCONFIG = 3
Masahiro Yamadab6160812015-05-20 11:36:07 +090043
Simon Glass8fb5bd02017-06-01 19:39:01 -060044AUTO_CONF_PATH = 'include/config/auto.conf'
Simon Glass8a372032023-09-23 13:44:15 -060045CONFIG_DATABASE = 'qconfig.db'
46FAILED_LIST = 'qconfig.failed'
Simon Glass8fb5bd02017-06-01 19:39:01 -060047
Simon Glass44116332017-06-15 21:39:33 -060048CONFIG_LEN = len('CONFIG_')
Simon Glass8fb5bd02017-06-01 19:39:01 -060049
Markus Klotzbuecher3d5d4182019-05-15 15:15:52 +020050SIZES = {
Simon Glassdc634d92021-12-18 14:54:30 -070051 'SZ_1': 0x00000001, 'SZ_2': 0x00000002,
52 'SZ_4': 0x00000004, 'SZ_8': 0x00000008,
53 'SZ_16': 0x00000010, 'SZ_32': 0x00000020,
54 'SZ_64': 0x00000040, 'SZ_128': 0x00000080,
55 'SZ_256': 0x00000100, 'SZ_512': 0x00000200,
56 'SZ_1K': 0x00000400, 'SZ_2K': 0x00000800,
57 'SZ_4K': 0x00001000, 'SZ_8K': 0x00002000,
58 'SZ_16K': 0x00004000, 'SZ_32K': 0x00008000,
59 'SZ_64K': 0x00010000, 'SZ_128K': 0x00020000,
60 'SZ_256K': 0x00040000, 'SZ_512K': 0x00080000,
61 'SZ_1M': 0x00100000, 'SZ_2M': 0x00200000,
62 'SZ_4M': 0x00400000, 'SZ_8M': 0x00800000,
63 'SZ_16M': 0x01000000, 'SZ_32M': 0x02000000,
64 'SZ_64M': 0x04000000, 'SZ_128M': 0x08000000,
65 'SZ_256M': 0x10000000, 'SZ_512M': 0x20000000,
66 'SZ_1G': 0x40000000, 'SZ_2G': 0x80000000,
67 'SZ_4G': 0x100000000
Markus Klotzbuecher3d5d4182019-05-15 15:15:52 +020068}
69
Simon Glasse8037552022-02-08 11:49:45 -070070RE_REMOVE_DEFCONFIG = re.compile(r'(.*)_defconfig')
71
Simon Glass4c4eb7c2023-02-01 13:19:12 -070072# CONFIG symbols present in the build system (from Linux) but not actually used
73# in U-Boot; KCONFIG symbols
74IGNORE_SYMS = ['DEBUG_SECTION_MISMATCH', 'FTRACE_MCOUNT_RECORD', 'GCOV_KERNEL',
75 'GCOV_PROFILE_ALL', 'KALLSYMS', 'KASAN', 'MODVERSIONS', 'SHELL',
76 'TPL_BUILD', 'VPL_BUILD', 'IS_ENABLED', 'FOO', 'IF_ENABLED_INT',
77 'IS_ENABLED_', 'IS_ENABLED_1', 'IS_ENABLED_2', 'IS_ENABLED_3',
78 'SPL_', 'TPL_', 'SPL_FOO', 'TPL_FOO', 'TOOLS_FOO',
79 'ACME', 'SPL_ACME', 'TPL_ACME', 'TRACE_BRANCH_PROFILING',
80 'VAL', '_UNDEFINED', 'SPL_BUILD', ]
81
82SPL_PREFIXES = ['SPL_', 'TPL_', 'VPL_', 'TOOLS_']
83
Masahiro Yamadab6160812015-05-20 11:36:07 +090084### helper functions ###
Masahiro Yamadab6160812015-05-20 11:36:07 +090085def check_top_directory():
86 """Exit if we are not at the top of source directory."""
Simon Glassb3464eb2021-12-18 14:54:35 -070087 for fname in 'README', 'Licenses':
88 if not os.path.exists(fname):
Masahiro Yamadab6160812015-05-20 11:36:07 +090089 sys.exit('Please run at the top of source directory.')
90
Masahiro Yamada990e6772016-05-19 15:51:54 +090091def check_clean_directory():
92 """Exit if the source tree is not clean."""
Simon Glassb3464eb2021-12-18 14:54:35 -070093 for fname in '.config', 'include/config':
94 if os.path.exists(fname):
Masahiro Yamada990e6772016-05-19 15:51:54 +090095 sys.exit("source tree is not clean, please run 'make mrproper'")
96
Masahiro Yamadab6160812015-05-20 11:36:07 +090097def get_make_cmd():
98 """Get the command name of GNU Make.
99
100 U-Boot needs GNU Make for building, but the command name is not
101 necessarily "make". (for example, "gmake" on FreeBSD).
102 Returns the most appropriate command name on your system.
103 """
Simon Glassb3464eb2021-12-18 14:54:35 -0700104 with subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE) as proc:
105 ret = proc.communicate()
106 if proc.returncode:
107 sys.exit('GNU Make not found')
Masahiro Yamadab6160812015-05-20 11:36:07 +0900108 return ret[0].rstrip()
109
Simon Glass18774bc2017-06-01 19:38:58 -0600110def get_matched_defconfig(line):
111 """Get the defconfig files that match a pattern
112
113 Args:
Simon Glassb3464eb2021-12-18 14:54:35 -0700114 line (str): Path or filename to match, e.g. 'configs/snow_defconfig' or
Simon Glass18774bc2017-06-01 19:38:58 -0600115 'k2*_defconfig'. If no directory is provided, 'configs/' is
116 prepended
117
118 Returns:
Simon Glassb3464eb2021-12-18 14:54:35 -0700119 list of str: a list of matching defconfig files
Simon Glass18774bc2017-06-01 19:38:58 -0600120 """
121 dirname = os.path.dirname(line)
122 if dirname:
123 pattern = line
124 else:
125 pattern = os.path.join('configs', line)
126 return glob.glob(pattern) + glob.glob(pattern + '_defconfig')
127
Masahiro Yamada3984d6e2016-10-19 14:39:54 +0900128def get_matched_defconfigs(defconfigs_file):
Simon Glass8f3cf312017-06-01 19:38:59 -0600129 """Get all the defconfig files that match the patterns in a file.
130
131 Args:
Simon Glassb3464eb2021-12-18 14:54:35 -0700132 defconfigs_file (str): File containing a list of defconfigs to process,
133 or '-' to read the list from stdin
Simon Glass8f3cf312017-06-01 19:38:59 -0600134
135 Returns:
Simon Glassb3464eb2021-12-18 14:54:35 -0700136 list of str: A list of paths to defconfig files, with no duplicates
Simon Glass8f3cf312017-06-01 19:38:59 -0600137 """
Masahiro Yamada3984d6e2016-10-19 14:39:54 +0900138 defconfigs = []
Simon Glassb3464eb2021-12-18 14:54:35 -0700139 with ExitStack() as stack:
140 if defconfigs_file == '-':
141 inf = sys.stdin
142 defconfigs_file = 'stdin'
143 else:
144 inf = stack.enter_context(open(defconfigs_file, encoding='utf-8'))
145 for i, line in enumerate(inf):
146 line = line.strip()
147 if not line:
148 continue # skip blank lines silently
149 if ' ' in line:
150 line = line.split(' ')[0] # handle 'git log' input
151 matched = get_matched_defconfig(line)
152 if not matched:
153 print(f"warning: {defconfigs_file}:{i + 1}: no defconfig matched '{line}'",
154 file=sys.stderr)
Masahiro Yamada3984d6e2016-10-19 14:39:54 +0900155
Simon Glassb3464eb2021-12-18 14:54:35 -0700156 defconfigs += matched
Masahiro Yamada3984d6e2016-10-19 14:39:54 +0900157
158 # use set() to drop multiple matching
Simon Glassb3464eb2021-12-18 14:54:35 -0700159 return [defconfig[len('configs') + 1:] for defconfig in set(defconfigs)]
Masahiro Yamada3984d6e2016-10-19 14:39:54 +0900160
Masahiro Yamada58175e32016-07-25 19:15:28 +0900161def get_all_defconfigs():
Simon Glassb3464eb2021-12-18 14:54:35 -0700162 """Get all the defconfig files under the configs/ directory.
163
164 Returns:
165 list of str: List of paths to defconfig files
166 """
Masahiro Yamada58175e32016-07-25 19:15:28 +0900167 defconfigs = []
Simon Glassb3464eb2021-12-18 14:54:35 -0700168 for (dirpath, _, filenames) in os.walk('configs'):
Masahiro Yamada58175e32016-07-25 19:15:28 +0900169 dirpath = dirpath[len('configs') + 1:]
170 for filename in fnmatch.filter(filenames, '*_defconfig'):
171 defconfigs.append(os.path.join(dirpath, filename))
172
173 return defconfigs
174
Simon Glassb09ae452021-12-18 14:54:33 -0700175def write_file(fname, data):
176 """Write data to a file
177
178 Args:
179 fname (str): Filename to write to
180 data (list of str): Lines to write (with or without trailing newline);
181 or str to write
182 """
183 with open(fname, 'w', encoding='utf-8') as out:
184 if isinstance(data, list):
185 for line in data:
186 print(line.rstrip('\n'), file=out)
187 else:
188 out.write(data)
189
Simon Glassaba238f2021-12-18 14:54:34 -0700190def read_file(fname, as_lines=True, skip_unicode=False):
191 """Read a file and return the contents
192
193 Args:
194 fname (str): Filename to read from
Simon Glassc89a2962023-09-23 13:43:58 -0600195 as_lines (bool): Return file contents as a list of lines
Simon Glassaba238f2021-12-18 14:54:34 -0700196 skip_unicode (bool): True to report unicode errors and continue
197
198 Returns:
199 iter of str: List of ;ines from the file with newline removed; str if
200 as_lines is False with newlines intact; or None if a unicode error
201 occurred
202
203 Raises:
204 UnicodeDecodeError: Unicode error occurred when reading
205 """
206 with open(fname, encoding='utf-8') as inf:
207 try:
208 if as_lines:
209 return [line.rstrip('\n') for line in inf.readlines()]
Simon Glasse19a9cd2023-09-23 13:44:05 -0600210 return inf.read()
Simon Glass4f6725c2023-09-23 13:44:01 -0600211 except UnicodeDecodeError as exc:
Simon Glassaba238f2021-12-18 14:54:34 -0700212 if not skip_unicode:
Simon Glassafaddc72022-02-11 13:23:22 -0700213 raise
Simon Glass4f6725c2023-09-23 13:44:01 -0600214 print(f"Failed on file '{fname}: {exc}")
Simon Glassaba238f2021-12-18 14:54:34 -0700215 return None
216
Chris Packham9d5274f2017-05-02 21:30:47 +1200217
Masahiro Yamadab6160812015-05-20 11:36:07 +0900218### classes ###
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900219class Progress:
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900220 """Progress Indicator"""
221
Simon Glassbef67362023-09-23 13:44:10 -0600222 def __init__(self, col, total):
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900223 """Create a new progress indicator.
224
Simon Glassb3464eb2021-12-18 14:54:35 -0700225 Args:
Simon Glass19d91da2024-07-17 16:57:07 +0100226 col (terminal.Color): Colour-output class
Simon Glassbef67362023-09-23 13:44:10 -0600227 total (int): A number of defconfig files to process.
Simon Glass19d91da2024-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 Yamadacefaa582016-05-19 15:51:55 +0900232 """
Simon Glassbef67362023-09-23 13:44:10 -0600233 self.col = col
Simon Glass19d91da2024-07-17 16:57:07 +0100234 self.total = total
235
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900236 self.current = 0
Simon Glassbef67362023-09-23 13:44:10 -0600237 self.good = 0
Simon Glass19d91da2024-07-17 16:57:07 +0100238 self.failed = None
239 self.failure_msg = None
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900240
Simon Glassbef67362023-09-23 13:44:10 -0600241 def inc(self, success):
242 """Increment the number of processed defconfig files.
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900243
Simon Glassbef67362023-09-23 13:44:10 -0600244 Args:
245 success (bool): True if processing succeeded
246 """
247 self.good += success
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900248 self.current += 1
249
250 def show(self):
251 """Display the progress."""
Simon Glass99c28cd2023-09-23 13:44:08 -0600252 if self.current != self.total:
Simon Glassbef67362023-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 Yamadacefaa582016-05-19 15:51:55 +0900259 sys.stdout.flush()
260
Simon Glass19d91da2024-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 Glass44116332017-06-15 21:39:33 -0600266
Simon Glassa0a61602024-07-17 16:56:51 +0100267def scan_kconfig():
268 """Scan all the Kconfig files and create a Config object
Simon Glass44116332017-06-15 21:39:33 -0600269
Simon Glassa0a61602024-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 Glass44116332017-06-15 21:39:33 -0600279
280
Simon Glass28155572024-07-17 16:56:52 +0100281# pylint: disable=R0903
Masahiro Yamadab6160812015-05-20 11:36:07 +0900282class KconfigParser:
Masahiro Yamadab6160812015-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 Glass08d148a2023-09-23 13:43:54 -0600288 def __init__(self, args, build_dir):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900289 """Create a new parser.
290
Simon Glassb3464eb2021-12-18 14:54:35 -0700291 Args:
Simon Glassb3464eb2021-12-18 14:54:35 -0700292 args (Namespace): program arguments
Masahiro Yamadab6160812015-05-20 11:36:07 +0900293 build_dir: Build directory.
294 """
Simon Glassd9c1da22021-12-18 14:54:31 -0700295 self.args = args
Masahiro Yamada5393b612016-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 Yamada6d139172016-08-22 22:18:22 +0900298 self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
299 'autoconf.mk')
Simon Glass8fb5bd02017-06-01 19:39:01 -0600300 self.config_autoconf = os.path.join(build_dir, AUTO_CONF_PATH)
Masahiro Yamada07f98522016-05-19 15:52:06 +0900301 self.defconfig = os.path.join(build_dir, 'defconfig')
Masahiro Yamadab6160812015-05-20 11:36:07 +0900302
Simon Glass257f5232017-07-10 14:47:47 -0600303 def get_arch(self):
304 """Parse .config file and return the architecture.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900305
306 Returns:
Simon Glass257f5232017-07-10 14:47:47 -0600307 Architecture name (e.g. 'arm').
Masahiro Yamadab6160812015-05-20 11:36:07 +0900308 """
309 arch = ''
310 cpu = ''
Simon Glassaba238f2021-12-18 14:54:34 -0700311 for line in read_file(self.dotconfig):
Simon Glass4f6725c2023-09-23 13:44:01 -0600312 m_arch = self.re_arch.match(line)
313 if m_arch:
314 arch = m_arch.group(1)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900315 continue
Simon Glass4f6725c2023-09-23 13:44:01 -0600316 m_cpu = self.re_cpu.match(line)
317 if m_cpu:
318 cpu = m_cpu.group(1)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900319
Masahiro Yamadac4d76eb2016-05-19 15:51:53 +0900320 if not arch:
321 return None
Masahiro Yamadab6160812015-05-20 11:36:07 +0900322
323 # fix-up for aarch64
324 if arch == 'arm' and cpu == 'armv8':
325 arch = 'aarch64'
326
Simon Glass257f5232017-07-10 14:47:47 -0600327 return arch
Masahiro Yamadab6160812015-05-20 11:36:07 +0900328
Simon Glass43cf08f2017-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 Yamadab6160812015-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 Glass9b191102023-09-23 13:44:09 -0600366 def __init__(self, toolchains, args, progress, devnull, make_cmd,
367 reference_src_dir, db_queue, col):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900368 """Create a new process slot.
369
Simon Glassb3464eb2021-12-18 14:54:35 -0700370 Args:
Simon Glass257f5232017-07-10 14:47:47 -0600371 toolchains: Toolchains object containing toolchains.
Simon Glassd9c1da22021-12-18 14:54:31 -0700372 args: Program arguments
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900373 progress: A progress indicator.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900374 devnull: A file object of '/dev/null'.
375 make_cmd: command name of GNU Make.
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500376 reference_src_dir: Determine the true starting config state from this
377 source tree.
Simon Glass43cf08f2017-06-01 19:39:02 -0600378 db_queue: output queue to write config info for the database
Simon Glass9b191102023-09-23 13:44:09 -0600379 col (terminal.Color): Colour object
Masahiro Yamadab6160812015-05-20 11:36:07 +0900380 """
Simon Glass257f5232017-07-10 14:47:47 -0600381 self.toolchains = toolchains
Simon Glassd9c1da22021-12-18 14:54:31 -0700382 self.args = args
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900383 self.progress = progress
Masahiro Yamadab6160812015-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 Hershbergerb1a570f2016-06-10 14:53:32 -0500387 self.reference_src_dir = reference_src_dir
Simon Glass43cf08f2017-06-01 19:39:02 -0600388 self.db_queue = db_queue
Simon Glass9b191102023-09-23 13:44:09 -0600389 self.col = col
Simon Glass08d148a2023-09-23 13:43:54 -0600390 self.parser = KconfigParser(args, self.build_dir)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900391 self.state = STATE_IDLE
Masahiro Yamada1271b672016-08-22 22:18:20 +0900392 self.failed_boards = set()
Simon Glass67ee0112023-09-23 13:44:02 -0600393 self.defconfig = None
Simon Glasse24ac992023-09-23 13:44:07 -0600394 self.log = []
Simon Glass67ee0112023-09-23 13:44:02 -0600395 self.current_src_dir = None
396 self.proc = None
Masahiro Yamadab6160812015-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 Hershberger640de872016-06-10 14:53:29 -0500403 because it is guaranteed the destructor is always invoked when the
Masahiro Yamadab6160812015-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 Glasse19a9cd2023-09-23 13:44:05 -0600409 while self.proc.poll() is None:
Masahiro Yamadab6160812015-05-20 11:36:07 +0900410 pass
411 shutil.rmtree(self.build_dir)
412
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900413 def add(self, defconfig):
Masahiro Yamadab6160812015-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 Glassb3464eb2021-12-18 14:54:35 -0700420 Args:
Simon Glassc89a2962023-09-23 13:43:58 -0600421 defconfig (str): defconfig name.
Masahiro Yamadab6160812015-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 Yamadacb256cb2016-06-08 11:47:37 +0900428
Masahiro Yamadab6160812015-05-20 11:36:07 +0900429 self.defconfig = defconfig
Simon Glasse24ac992023-09-23 13:44:07 -0600430 self.log = []
Masahiro Yamada8f5256a2016-06-15 14:33:52 +0900431 self.current_src_dir = self.reference_src_dir
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900432 self.do_defconfig()
Masahiro Yamadab6160812015-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 Yamada263d1372016-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 Yamadab6160812015-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 Glasse19a9cd2023-09-23 13:44:05 -0600454 if self.proc.poll() is None:
Masahiro Yamadab6160812015-05-20 11:36:07 +0900455 return False
456
Simon Glass4f6725c2023-09-23 13:44:01 -0600457 if self.proc.poll() != 0:
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900458 self.handle_error()
459 elif self.state == STATE_DEFCONFIG:
Masahiro Yamada8f5256a2016-06-15 14:33:52 +0900460 if self.reference_src_dir and not self.current_src_dir:
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500461 self.do_savedefconfig()
462 else:
463 self.do_autoconf()
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900464 elif self.state == STATE_AUTOCONF:
Masahiro Yamada8f5256a2016-06-15 14:33:52 +0900465 if self.current_src_dir:
466 self.current_src_dir = None
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500467 self.do_defconfig()
Simon Glassd9c1da22021-12-18 14:54:31 -0700468 elif self.args.build_db:
Simon Glass43cf08f2017-06-01 19:39:02 -0600469 self.do_build_db()
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500470 else:
471 self.do_savedefconfig()
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900472 elif self.state == STATE_SAVEDEFCONFIG:
473 self.update_defconfig()
474 else:
Simon Glassdc634d92021-12-18 14:54:30 -0700475 sys.exit('Internal Error. This should not happen.')
Masahiro Yamadab6160812015-05-20 11:36:07 +0900476
Simon Glasse19a9cd2023-09-23 13:44:05 -0600477 return self.state == STATE_IDLE
Joe Hershberger166edec2015-05-19 13:21:17 -0500478
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900479 def handle_error(self):
480 """Handle error cases."""
Masahiro Yamada83c17672016-05-19 15:52:08 +0900481
Simon Glass9b191102023-09-23 13:44:09 -0600482 self.log.append(self.col.build(self.col.RED, 'Failed to process',
483 bright=True))
Simon Glassd9c1da22021-12-18 14:54:31 -0700484 if self.args.verbose:
Simon Glasse24ac992023-09-23 13:44:07 -0600485 for line in self.proc.stderr.read().decode().splitlines():
Simon Glass9b191102023-09-23 13:44:09 -0600486 self.log.append(self.col.build(self.col.CYAN, line, True))
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900487 self.finish(False)
Joe Hershberger166edec2015-05-19 13:21:17 -0500488
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900489 def do_defconfig(self):
490 """Run 'make <board>_defconfig' to create the .config file."""
Masahiro Yamada0f6beda2016-05-19 15:52:07 +0900491
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900492 cmd = list(self.make_cmd)
493 cmd.append(self.defconfig)
Simon Glass28155572024-07-17 16:56:52 +0100494 # pylint: disable=R1732
Simon Glass4f6725c2023-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 Yamadacb256cb2016-06-08 11:47:37 +0900498 self.state = STATE_DEFCONFIG
Masahiro Yamada0f6beda2016-05-19 15:52:07 +0900499
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900500 def do_autoconf(self):
Simon Glass8fb5bd02017-06-01 19:39:01 -0600501 """Run 'make AUTO_CONF_PATH'."""
Masahiro Yamadab6160812015-05-20 11:36:07 +0900502
Simon Glass257f5232017-07-10 14:47:47 -0600503 arch = self.parser.get_arch()
504 try:
Simon Glasse19a9cd2023-09-23 13:44:05 -0600505 tchain = self.toolchains.Select(arch)
Simon Glass257f5232017-07-10 14:47:47 -0600506 except ValueError:
Simon Glass9b191102023-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 Yamada274a5ee2016-05-19 15:52:03 +0900510 self.finish(False)
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900511 return
Simon Glasse19a9cd2023-09-23 13:44:05 -0600512 env = tchain.MakeEnvironment(False)
Masahiro Yamadac4d76eb2016-05-19 15:51:53 +0900513
Masahiro Yamadab6160812015-05-20 11:36:07 +0900514 cmd = list(self.make_cmd)
Joe Hershberger765442b2015-05-19 13:21:18 -0500515 cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
Simon Glass8fb5bd02017-06-01 19:39:01 -0600516 cmd.append(AUTO_CONF_PATH)
Simon Glass28155572024-07-17 16:56:52 +0100517 # pylint: disable=R1732
Simon Glass4f6725c2023-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 Yamadab6160812015-05-20 11:36:07 +0900521 self.state = STATE_AUTOCONF
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900522
Simon Glass43cf08f2017-06-01 19:39:02 -0600523 def do_build_db(self):
524 """Add the board to the database"""
525 configs = {}
Simon Glassaba238f2021-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 Glass43cf08f2017-06-01 19:39:02 -0600530 self.db_queue.put([self.defconfig, configs])
531 self.finish(True)
532
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900533 def do_savedefconfig(self):
534 """Update the .config and run 'make savedefconfig'."""
Simon Glassc1c10c22023-09-23 13:43:55 -0600535 if not self.args.force_sync:
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900536 self.finish(True)
537 return
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900538
539 cmd = list(self.make_cmd)
540 cmd.append('savedefconfig')
Simon Glass28155572024-07-17 16:56:52 +0100541 # pylint: disable=R1732
Simon Glass4f6725c2023-09-23 13:44:01 -0600542 self.proc = subprocess.Popen(cmd, stdout=self.devnull,
543 stderr=subprocess.PIPE)
Masahiro Yamadacb256cb2016-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 Yamadacb256cb2016-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 Glass9b191102023-09-23 13:44:09 -0600553 self.log.append(
554 self.col.build(self.col.BLUE, 'defconfig updated', bright=True))
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900555
Simon Glassd9c1da22021-12-18 14:54:31 -0700556 if not self.args.dry_run and updated:
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900557 shutil.move(new_defconfig, orig_defconfig)
558 self.finish(True)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900559
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900560 def finish(self, success):
561 """Display log along with progress and go to the idle state.
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900562
Simon Glassb3464eb2021-12-18 14:54:35 -0700563 Args:
Simon Glassc89a2962023-09-23 13:43:58 -0600564 success (bool): Should be True when the defconfig was processed
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900565 successfully, or False when it fails.
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900566 """
567 # output at least 30 characters to hide the "* defconfigs out of *".
Simon Glass29790432023-09-23 13:44:11 -0600568 name = self.defconfig[:-len('_defconfig')]
Simon Glass4fd16e62023-09-23 13:44:06 -0600569 if self.log:
Simon Glasse24ac992023-09-23 13:44:07 -0600570
571 # Put the first log line on the first line
572 log = name.ljust(20) + ' ' + self.log[0]
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900573
Simon Glasse24ac992023-09-23 13:44:07 -0600574 if len(self.log) > 1:
575 log += '\n' + '\n'.join([' ' + s for s in self.log[1:]])
Simon Glass4fd16e62023-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 Yamada274a5ee2016-05-19 15:52:03 +0900579
580 if not success:
Simon Glassd9c1da22021-12-18 14:54:31 -0700581 if self.args.exit_on_error:
Simon Glassdc634d92021-12-18 14:54:30 -0700582 sys.exit('Exit on error.')
Masahiro Yamada274a5ee2016-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 Glass29790432023-09-23 13:44:11 -0600585 self.failed_boards.add(name)
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900586
Simon Glassbef67362023-09-23 13:44:10 -0600587 self.progress.inc(success)
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900588 self.progress.show()
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900589 self.state = STATE_IDLE
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900590
Masahiro Yamadab6160812015-05-20 11:36:07 +0900591 def get_failed_boards(self):
Masahiro Yamada1271b672016-08-22 22:18:20 +0900592 """Returns a set of failed boards (defconfigs) in this slot.
Masahiro Yamadab6160812015-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 Glass9b191102023-09-23 13:44:09 -0600600 def __init__(self, toolchains, args, progress, reference_src_dir, db_queue,
601 col):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900602 """Create a new slots controller.
603
Simon Glassb3464eb2021-12-18 14:54:35 -0700604 Args:
Simon Glass9b191102023-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 Yamadab6160812015-05-20 11:36:07 +0900612 """
Simon Glassd9c1da22021-12-18 14:54:31 -0700613 self.args = args
Masahiro Yamadab6160812015-05-20 11:36:07 +0900614 self.slots = []
Simon Glass29790432023-09-23 13:44:11 -0600615 self.progress = progress
Simon Glass9b191102023-09-23 13:44:09 -0600616 self.col = col
Simon Glass34c505f2021-12-18 14:54:32 -0700617 devnull = subprocess.DEVNULL
Masahiro Yamadab6160812015-05-20 11:36:07 +0900618 make_cmd = get_make_cmd()
Simon Glassbeb825d2023-09-23 13:44:00 -0600619 for _ in range(args.jobs):
Simon Glass08d148a2023-09-23 13:43:54 -0600620 self.slots.append(Slot(toolchains, args, progress, devnull,
Simon Glass9b191102023-09-23 13:44:09 -0600621 make_cmd, reference_src_dir, db_queue, col))
Masahiro Yamadab6160812015-05-20 11:36:07 +0900622
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900623 def add(self, defconfig):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900624 """Add a new subprocess if a vacant slot is found.
625
Simon Glassb3464eb2021-12-18 14:54:35 -0700626 Args:
Simon Glassc89a2962023-09-23 13:43:58 -0600627 defconfig (str): defconfig name to be put into.
Masahiro Yamadab6160812015-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 Yamadacefaa582016-05-19 15:51:55 +0900633 if slot.add(defconfig):
Masahiro Yamadab6160812015-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 Glass65709242023-09-23 13:44:13 -0600660 def write_failed_boards(self):
Simon Glass29790432023-09-23 13:44:11 -0600661 """Show the results of processing"""
Masahiro Yamada1271b672016-08-22 22:18:20 +0900662 boards = set()
Masahiro Yamadab6160812015-05-20 11:36:07 +0900663
664 for slot in self.slots:
Masahiro Yamada1271b672016-08-22 22:18:20 +0900665 boards |= slot.get_failed_boards()
Masahiro Yamadab6160812015-05-20 11:36:07 +0900666
Masahiro Yamada0153f032016-06-15 14:33:53 +0900667 if boards:
Simon Glass29790432023-09-23 13:44:11 -0600668 boards = '\n'.join(sorted(boards)) + '\n'
Simon Glass65709242023-09-23 13:44:13 -0600669 write_file(FAILED_LIST, boards)
670
Joe Hershbergerdade12e2015-05-19 13:21:22 -0500671
Masahiro Yamada2e74fee2016-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 Glassb3464eb2021-12-18 14:54:35 -0700679 Args:
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900680 commit: commit to git-clone
681 """
682 self.src_dir = tempfile.mkdtemp()
Simon Glassdc634d92021-12-18 14:54:30 -0700683 print('Cloning git repo to a separate work directory...')
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900684 subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
685 cwd=self.src_dir)
Simon Glass96f8f312023-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 Yamada2e74fee2016-06-15 14:33:51 +0900689 subprocess.check_output(['git', 'checkout', commit],
690 stderr=subprocess.STDOUT, cwd=self.src_dir)
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500691
692 def __del__(self):
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900693 """Delete the reference source directory
Joe Hershbergerb1a570f2016-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 Yamada2e74fee2016-06-15 14:33:51 +0900700 shutil.rmtree(self.src_dir)
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500701
Masahiro Yamada2e74fee2016-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 Hershbergerb1a570f2016-06-10 14:53:32 -0500706
Simon Glass4c059032024-07-17 16:57:05 +0100707def move_config(args, col):
Simon Glass08d148a2023-09-23 13:43:54 -0600708 """Build database or sync config options to defconfig files.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900709
Simon Glassb3464eb2021-12-18 14:54:35 -0700710 Args:
Simon Glass9b191102023-09-23 13:44:09 -0600711 args (Namespace): Program arguments
Simon Glass9b191102023-09-23 13:44:09 -0600712 col (terminal.Color): Colour object
Simon Glass65709242023-09-23 13:44:13 -0600713
714 Returns:
Simon Glass4c059032024-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 Yamadab6160812015-05-20 11:36:07 +0900722 """
Simon Glass4c059032024-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 Glassd9c1da22021-12-18 14:54:31 -0700737 if args.git_ref:
738 reference_src = ReferenceSource(args.git_ref)
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900739 reference_src_dir = reference_src.get_dir()
740 else:
Masahiro Yamada8f5256a2016-06-15 14:33:52 +0900741 reference_src_dir = None
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500742
Simon Glassd9c1da22021-12-18 14:54:31 -0700743 if args.defconfigs:
744 defconfigs = get_matched_defconfigs(args.defconfigs)
Joe Hershbergerc6e043a2015-05-19 13:21:19 -0500745 else:
Masahiro Yamada58175e32016-07-25 19:15:28 +0900746 defconfigs = get_all_defconfigs()
Masahiro Yamadab6160812015-05-20 11:36:07 +0900747
Simon Glassbef67362023-09-23 13:44:10 -0600748 progress = Progress(col, len(defconfigs))
Simon Glass9b191102023-09-23 13:44:09 -0600749 slots = Slots(toolchains, args, progress, reference_src_dir, db_queue, col)
Masahiro Yamadab6160812015-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 Yamadacefaa582016-05-19 15:51:55 +0900754 for defconfig in defconfigs:
755 while not slots.add(defconfig):
Masahiro Yamadab6160812015-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 Glass65709242023-09-23 13:44:13 -0600764 slots.write_failed_boards()
Simon Glass4c059032024-07-17 16:57:05 +0100765 db_queue.join()
Simon Glass19d91da2024-07-17 16:57:07 +0100766 progress.completed()
Simon Glass4c059032024-07-17 16:57:05 +0100767 return config_db, progress
Masahiro Yamadab6160812015-05-20 11:36:07 +0900768
Simon Glass44116332017-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 Glassc89a2962023-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 Glass44116332017-06-15 21:39:33 -0600777
778 Returns:
779 Symbol object for 'config' if found, else None
780 """
Tom Rini3c5f4152019-09-20 17:42:09 -0400781 sym = kconf.syms.get(imply_config)
Simon Glass44116332017-06-15 21:39:33 -0600782 if sym:
Simon Glassbeb825d2023-09-23 13:44:00 -0600783 for sel, _ in (sym.selects + sym.implies):
Simon Glass93c0a9e2021-12-18 08:09:42 -0700784 if sel.name == config:
Simon Glass44116332017-06-15 21:39:33 -0600785 return sym
786 return None
787
Simon Glass28155572024-07-17 16:56:52 +0100788def check_imply_rule(kconf, imply_config):
Simon Glass44116332017-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 Glassc89a2962023-09-23 13:43:58 -0600795 kconf (Kconfiglib.Kconfig): Kconfig object
Simon Glassc89a2962023-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 Glass44116332017-06-15 21:39:33 -0600798
799 Returns:
800 tuple:
Simon Glassc89a2962023-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 Glass44116332017-06-15 21:39:33 -0600805 """
Tom Rini3c5f4152019-09-20 17:42:09 -0400806 sym = kconf.syms.get(imply_config)
Simon Glass44116332017-06-15 21:39:33 -0600807 if not sym:
808 return 'cannot find sym'
Simon Glass520b47a2021-07-21 21:35:53 -0600809 nodes = sym.nodes
810 if len(nodes) != 1:
Simon Glass96f8f312023-09-23 13:43:59 -0600811 return f'{len(nodes)} locations'
Simon Glass93c0a9e2021-12-18 08:09:42 -0700812 node = nodes[0]
813 fname, linenum = node.filename, node.linenr
Simon Glass44116332017-06-15 21:39:33 -0600814 cwd = os.getcwd()
815 if cwd and fname.startswith(cwd):
816 fname = fname[len(cwd) + 1:]
Simon Glass96f8f312023-09-23 13:43:59 -0600817 file_line = f' at {fname}:{linenum}'
Simon Glassaba238f2021-12-18 14:54:34 -0700818 data = read_file(fname)
Simon Glass96f8f312023-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 Glass44116332017-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 Glassc89a2962023-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 Glass44116332017-06-15 21:39:33 -0600830
831 Returns:
832 Message indicating the result
833 """
Simon Glass96f8f312023-09-23 13:43:59 -0600834 file_line = f' at {fname}:{linenum}'
Simon Glassaba238f2021-12-18 14:54:34 -0700835 data = read_file(fname)
Simon Glass44116332017-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 Glass96f8f312023-09-23 13:43:59 -0600840 data.insert(linenum + offset, f'\timply {config}')
Simon Glassb09ae452021-12-18 14:54:33 -0700841 write_file(fname, data)
Simon Glass96f8f312023-09-23 13:43:59 -0600842 return f'added{file_line}'
Simon Glass44116332017-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 Glass92e55582017-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 Glass44116332017-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 Glassb3464eb2021-12-18 14:54:35 -0700856}
Simon Glass92e55582017-06-15 21:39:32 -0600857
Simon Glassf931c2f2021-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 Glass3a315fa2024-07-17 16:56:49 +0100888 defconfig = None
Simon Glassaba238f2021-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 Glassf931c2f2021-12-18 08:09:43 -0700902
903 return all_configs, all_defconfigs, config_db, defconfig_db
904
905
Simon Glass44116332017-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 Glassc6e73cf2017-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 Glassbf0e11a2023-09-23 13:44:14 -0600916 This function uses the qconfig database to find such options. It
Simon Glassc6e73cf2017-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 Glass18352112024-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 Glass92e55582017-06-15 21:39:32 -0600934 (IMPLY_...)
Simon Glass18352112024-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 Glass44116332017-06-15 21:39:33 -0600937 'select' for the target config, and show this information if so.
Simon Glass18352112024-07-17 16:57:03 +0100938 find_superset (bool): True to look for configs which are a superset of those
Simon Glassc6e73cf2017-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 Glassa0a61602024-07-17 16:56:51 +0100948 kconf = scan_kconfig() if check_kconfig else None
Simon Glass44116332017-06-15 21:39:33 -0600949 if add_imply and add_imply != 'all':
Simon Glass93c0a9e2021-12-18 08:09:42 -0700950 add_imply = add_imply.split(',')
Simon Glass44116332017-06-15 21:39:33 -0600951
Simon Glassbeb825d2023-09-23 13:44:00 -0600952 all_configs, all_defconfigs, _, defconfig_db = read_database()
Simon Glassc6e73cf2017-06-01 19:39:03 -0600953
Simon Glass93c0a9e2021-12-18 08:09:42 -0700954 # Work through each target config option in turn, independently
Simon Glassc6e73cf2017-06-01 19:39:03 -0600955 for config in config_list:
956 defconfigs = defconfig_db.get(config)
957 if not defconfigs:
Simon Glass96f8f312023-09-23 13:43:59 -0600958 print(f'{config} not found in any defconfig')
Simon Glassc6e73cf2017-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 Glass96f8f312023-09-23 13:43:59 -0600965 print(f'{config} found in {num_defconfigs}/{len(all_configs)} defconfigs')
Simon Glassc6e73cf2017-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 Glass92e55582017-06-15 21:39:32 -0600973 if 'ERRATUM' in imply_config:
Simon Glassc6e73cf2017-06-01 19:39:03 -0600974 continue
Simon Glassb3464eb2021-12-18 14:54:35 -0700975 if not imply_flags & IMPLY_CMD:
Simon Glass92e55582017-06-15 21:39:32 -0600976 if 'CONFIG_CMD' in imply_config:
977 continue
Simon Glassb3464eb2021-12-18 14:54:35 -0700978 if not imply_flags & IMPLY_TARGET:
Simon Glass92e55582017-06-15 21:39:32 -0600979 if 'CONFIG_TARGET' in imply_config:
980 continue
Simon Glassc6e73cf2017-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 Glass1f701862019-10-31 07:42:57 -06001002 for prev in list(imply_configs.keys()):
Simon Glassc6e73cf2017-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 Glasse19a9cd2023-09-23 13:44:05 -06001011 if count > prev_count:
Simon Glassc6e73cf2017-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 Glass44116332017-06-15 21:39:33 -06001021 ranked_iconfigs = sorted(imply_configs,
Simon Glassc6e73cf2017-06-01 19:39:03 -06001022 key=lambda k: len(imply_configs[k]), reverse=True)
Simon Glass44116332017-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 Glassc6e73cf2017-06-01 19:39:03 -06001028
1029 # Don't bother if there are less than 5 defconfigs affected.
Simon Glass92e55582017-06-15 21:39:32 -06001030 if num_common < (2 if imply_flags & IMPLY_MIN_2 else 5):
Simon Glassc6e73cf2017-06-01 19:39:03 -06001031 continue
Simon Glass44116332017-06-15 21:39:33 -06001032 missing = defconfigs - imply_configs[iconfig]
Simon Glassc6e73cf2017-06-01 19:39:03 -06001033 missing_str = ', '.join(missing) if missing else 'all'
1034 missing_str = ''
Simon Glass44116332017-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 Glass520b47a2021-07-21 21:35:53 -06001041 nodes = sym.nodes
1042 if len(nodes) == 1:
1043 fname, linenum = nodes[0].filename, nodes[0].linenr
Simon Glass44116332017-06-15 21:39:33 -06001044 if cwd and fname.startswith(cwd):
1045 fname = fname[len(cwd) + 1:]
Simon Glass96f8f312023-09-23 13:43:59 -06001046 kconfig_info = f'{fname}:{linenum}'
Simon Glass44116332017-06-15 21:39:33 -06001047 if skip_added:
1048 show = False
1049 else:
Tom Rini3c5f4152019-09-20 17:42:09 -04001050 sym = kconf.syms.get(iconfig[CONFIG_LEN:])
Simon Glass44116332017-06-15 21:39:33 -06001051 fname = ''
1052 if sym:
Simon Glass520b47a2021-07-21 21:35:53 -06001053 nodes = sym.nodes
1054 if len(nodes) == 1:
1055 fname, linenum = nodes[0].filename, nodes[0].linenr
Simon Glass44116332017-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 Glassb3464eb2021-12-18 14:54:35 -07001061 not imply_flags & IMPLY_NON_ARCH_BOARD):
Simon Glass44116332017-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 Glass28155572024-07-17 16:56:52 +01001067 iconfig[CONFIG_LEN:]))
Simon Glass44116332017-06-15 21:39:33 -06001068 if fname:
1069 add_list[fname].append(linenum)
Simon Glassc6e73cf2017-06-01 19:39:03 -06001070
Simon Glass44116332017-06-15 21:39:33 -06001071 if show and kconfig_info != 'skip':
Simon Glass0f439202024-07-17 16:56:53 +01001072 print(f'{num_common:5} : '
1073 f'{iconfig.ljust(30)}{kconfig_info.ljust(25)} {missing_str}')
Simon Glass44116332017-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 Glass1f701862019-10-31 07:42:57 -06001080 for fname, linenums in add_list.items():
Simon Glass44116332017-06-15 21:39:33 -06001081 for linenum in sorted(linenums, reverse=True):
1082 add_imply_rule(config[CONFIG_LEN:], fname, linenum)
1083
Simon Glass99f79422022-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 Glassfea71c92022-03-05 20:18:54 -07001099 if re_match.fullmatch(cfg):
Simon Glass99f79422022-02-08 11:49:46 -07001100 return True
1101 return False
Simon Glassc6e73cf2017-06-01 19:39:03 -06001102
Simon Glass0082b2e2021-12-18 08:09:46 -07001103def do_find_config(config_list):
1104 """Find boards with a given combination of CONFIGs
1105
Simon Glass3c4cb202024-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 Glass0082b2e2021-12-18 08:09:46 -07001114 """
Simon Glassbeb825d2023-09-23 13:44:00 -06001115 _, all_defconfigs, config_db, _ = read_database()
Simon Glass0082b2e2021-12-18 08:09:46 -07001116
Simon Glass0082b2e2021-12-18 08:09:46 -07001117 # Start with all defconfigs
1118 out = all_defconfigs
1119
1120 # Work through each config in turn
Simon Glass0082b2e2021-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 Glass0082b2e2021-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 Glass99f79422022-02-08 11:49:46 -07001134 re_match = re.compile(cfg)
Simon Glass0082b2e2021-12-18 08:09:46 -07001135 for defc in in_list:
Simon Glass99f79422022-02-08 11:49:46 -07001136 has_cfg = defconfig_matches(config_db[defc], re_match)
Simon Glass0082b2e2021-12-18 08:09:46 -07001137 if has_cfg == want:
1138 out.add(defc)
Tom Rinic1b64aa2022-12-04 10:14:16 -05001139 print(f'{len(out)} matches')
1140 print(' '.join(item.split('_defconfig')[0] for item in out))
Simon Glass3c4cb202024-07-17 16:57:04 +01001141 return 0
Simon Glass0082b2e2021-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 Glass4f6725c2023-09-23 13:44:01 -06001160 oper = ''
Simon Glass0082b2e2021-12-18 08:09:46 -07001161 if cfg[0] == '~':
Simon Glass4f6725c2023-09-23 13:44:01 -06001162 oper = cfg[0]
Simon Glass0082b2e2021-12-18 08:09:46 -07001163 cfg = cfg[1:]
1164 if not cfg.startswith('CONFIG_'):
1165 cfg = 'CONFIG_' + cfg
Simon Glass4f6725c2023-09-23 13:44:01 -06001166 return oper + cfg
Simon Glass0082b2e2021-12-18 08:09:46 -07001167
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001168
Simon Glass2deee262023-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 Glass4c4eb7c2023-02-01 13:19:12 -07001173
1174class ConfigUse:
Simon Glass28155572024-07-17 16:56:52 +01001175 """Tracks whether a config relates to SPL or not"""
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001176 def __init__(self, cfg, is_spl, fname, rest):
Simon Glass28155572024-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 Glass4c4eb7c2023-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 Glass4f6725c2023-09-23 13:44:01 -06001223 for mat in m_iter:
1224 real_opt = mat.group(2)
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001225 if real_opt == '':
1226 continue
1227 is_spl = False
Simon Glass4f6725c2023-09-23 13:44:01 -06001228 if mat.group(1):
Simon Glass4c4eb7c2023-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 Glassbeb825d2023-09-23 13:44:00 -06001264 fname = None
1265 rest = None
1266
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001267 def add_uses(m_iter, is_spl):
Simon Glass4f6725c2023-09-23 13:44:01 -06001268 for mat in m_iter:
1269 real_opt = mat.group(1)
Simon Glass4c4eb7c2023-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 Glassbeb825d2023-09-23 13:44:00 -06001326 for use, _ in all_uses.items():
Simon Glass4c4eb7c2023-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 Glass4c4eb7c2023-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 Glass4c4eb7c2023-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 Glassa0a61602024-07-17 16:56:51 +01001391 kconf = scan_kconfig()
Simon Glass4c4eb7c2023-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 Glassbeb825d2023-09-23 13:44:00 -06001395 out, _ = proc.communicate()
Simon Glass4c4eb7c2023-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 Glassbeb825d2023-09-23 13:44:00 -06001429 all_uses, _ = scan_makefiles(mk_list)
Simon Glass4c4eb7c2023-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 Glassd708cf72023-09-23 13:44:03 -06001442 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
Simon Glass4c4eb7c2023-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 Glassd708cf72023-09-23 13:44:03 -06001447 proper_not_found |= {not_found.keys()}
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001448
1449 # Scan the source code
Simon Glassbeb825d2023-09-23 13:44:00 -06001450 all_uses, _ = scan_src_files(src_list)
Simon Glass4c4eb7c2023-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 Glassd708cf72023-09-23 13:44:03 -06001460 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
Simon Glass4c4eb7c2023-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 Glassd708cf72023-09-23 13:44:03 -06001465 proper_not_found |= {not_found.keys()}
Simon Glass4c4eb7c2023-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 Glass47f22892023-09-23 13:44:04 -06001477 with open(os.path.join(path, 'scripts', 'conf_nospl'), 'w',
1478 encoding='utf-8') as out:
Simon Glass4c4eb7c2023-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 Glass47f22892023-09-23 13:44:04 -06001483 with open(os.path.join(path, 'scripts', 'conf_noproper'), 'w',
1484 encoding='utf-8') as out:
Simon Glass4c4eb7c2023-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 Glass6901a642024-07-17 16:57:02 +01001489 return 0
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001490
Simon Glass0082b2e2021-12-18 08:09:46 -07001491
Simon Glassfe11dcf2024-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 Yamadab6160812015-05-20 11:36:07 +09001500 try:
1501 cpu_count = multiprocessing.cpu_count()
1502 except NotImplementedError:
1503 cpu_count = 1
1504
Simon Glassd9c1da22021-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 Glass44116332017-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 Glassd9c1da22021-12-18 14:54:31 -07001513 parser.add_argument('-A', '--skip-added', action='store_true', default=False,
Simon Glass44116332017-06-15 21:39:33 -06001514 help="don't show options which are already marked as "
1515 'implying others')
Simon Glassd9c1da22021-12-18 14:54:31 -07001516 parser.add_argument('-b', '--build-db', action='store_true', default=False,
Simon Glass43cf08f2017-06-01 19:39:02 -06001517 help='build a CONFIG database')
Simon Glassd9c1da22021-12-18 14:54:31 -07001518 parser.add_argument('-C', '--commit', action='store_true', default=False,
Simon Glass8bf41c22016-09-12 23:18:21 -06001519 help='Create a git commit for the operation')
Simon Glass9b191102023-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 Glassd9c1da22021-12-18 14:54:31 -07001522 parser.add_argument('-d', '--defconfigs', type=str,
Simon Glass8f3cf312017-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 Glassd9c1da22021-12-18 14:54:31 -07001526 parser.add_argument('-e', '--exit-on-error', action='store_true',
Masahiro Yamadab6160812015-05-20 11:36:07 +09001527 default=False,
1528 help='exit immediately on any error')
Simon Glassd9c1da22021-12-18 14:54:31 -07001529 parser.add_argument('-f', '--find', action='store_true', default=False,
Simon Glass0082b2e2021-12-18 08:09:46 -07001530 help='Find boards with a given config combination')
Simon Glassd9c1da22021-12-18 14:54:31 -07001531 parser.add_argument('-i', '--imply', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001532 help='find options which imply others')
Simon Glassd9c1da22021-12-18 14:54:31 -07001533 parser.add_argument('-I', '--imply-flags', type=str, default='',
Simon Glass0559a742021-12-18 08:09:44 -07001534 help="control the -i option ('help' for help")
Simon Glassd9c1da22021-12-18 14:54:31 -07001535 parser.add_argument('-j', '--jobs', type=int, default=cpu_count,
Masahiro Yamadab6160812015-05-20 11:36:07 +09001536 help='the number of jobs to run simultaneously')
Simon Glassd9c1da22021-12-18 14:54:31 -07001537 parser.add_argument('-n', '--dry-run', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001538 help='perform a trial run (show log with no changes)')
Simon Glassd9c1da22021-12-18 14:54:31 -07001539 parser.add_argument('-r', '--git-ref', type=str,
Joe Hershbergerb1a570f2016-06-10 14:53:32 -05001540 help='the git ref to clone for building the autoconf.mk')
Simon Glassd9c1da22021-12-18 14:54:31 -07001541 parser.add_argument('-s', '--force-sync', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001542 help='force sync by savedefconfig')
Simon Glassd9c1da22021-12-18 14:54:31 -07001543 parser.add_argument('-S', '--spl', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001544 help='parse config options defined for SPL build')
Simon Glass4c4eb7c2023-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 Glassd9c1da22021-12-18 14:54:31 -07001547 parser.add_argument('-t', '--test', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001548 help='run unit tests')
Simon Glassd9c1da22021-12-18 14:54:31 -07001549 parser.add_argument('-y', '--yes', action='store_true', default=False,
Simon Glass13e05a02016-09-12 23:18:20 -06001550 help="respond 'yes' to any prompts")
Simon Glass4c4eb7c2023-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 Glassd9c1da22021-12-18 14:54:31 -07001553 parser.add_argument('-v', '--verbose', action='store_true', default=False,
Joe Hershberger808b63f2015-05-19 13:21:24 -05001554 help='show any build errors as boards are built')
Simon Glassd9c1da22021-12-18 14:54:31 -07001555 parser.add_argument('configs', nargs='*')
Masahiro Yamadab6160812015-05-20 11:36:07 +09001556
Simon Glassfe11dcf2024-07-17 16:56:55 +01001557 return parser, parser.parse_args()
Masahiro Yamadab6160812015-05-20 11:36:07 +09001558
Simon Glassfe11dcf2024-07-17 16:56:55 +01001559
Simon Glass18352112024-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 Glasse054a752024-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 Glassc5ca9dc2024-07-17 16:57:08 +01001607def write_db(config_db, col, progress):
1608 """Write the database to a file
1609
1610 Args:
1611 config_db (dict of dict): configs for each defconfig
1612 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
1613 value: dict:
1614 key: CONFIG option
1615 value: Value of option
1616 col (terminal.Color): Colour-output class
1617 progress (Progress): Progress indicator.
1618
1619 Returns:
1620 int: exit code (0 for success)
1621 """
1622 with open(CONFIG_DATABASE, 'w', encoding='utf-8') as outf:
1623 for defconfig, configs in config_db.items():
1624 outf.write(f'{defconfig}\n')
1625 for config in sorted(configs.keys()):
1626 outf.write(f' {config}={configs[config]}\n')
1627 outf.write('\n')
1628 print(col.build(
1629 col.RED if progress.failed else col.GREEN,
1630 f'{progress.failure_msg}{len(config_db)} boards written to {CONFIG_DATABASE}'))
1631 return 0
1632
1633
1634def move_done(col, progress):
1635 """Write a message indicating that the move is done
1636
1637 Args:
1638 col (terminal.Color): Colour-output class
1639 progress (Progress): Progress indicator.
1640
1641 Returns:
1642 int: exit code (0 for success)
1643 """
1644 if progress.failed:
1645 print(col.build(col.RED, f'{progress.failure_msg}see {FAILED_LIST}', True))
1646 else:
1647 # Add enough spaces to overwrite the progress indicator
1648 print(col.build(
1649 col.GREEN, f'{progress.total} processed ', bright=True))
1650 return 0
1651
Simon Glassac261cc2024-07-17 16:57:01 +01001652def do_tests():
1653 """Run doctests and unit tests (so far there are no unit tests)"""
1654 sys.argv = [sys.argv[0]]
1655 fail, _ = doctest.testmod()
1656 if fail:
1657 return 1
1658 unittest.main()
1659 return 0
1660
1661
Simon Glassfe11dcf2024-07-17 16:56:55 +01001662def main():
1663 """Main program"""
1664 parser, args = parse_args()
Simon Glass855a0ce2024-07-17 16:56:57 +01001665 if not any((args.force_sync, args.build_db, args.imply, args.find,
1666 args.scan_source, args.test)):
1667 parser.print_usage()
1668 sys.exit(1)
Simon Glass9a042c52024-07-17 16:56:59 +01001669
1670 check_top_directory()
1671
Simon Glass84f541a2024-07-17 16:57:00 +01001672 # prefix the option name with CONFIG_ if missing
1673 args.configs = [prefix_config(cfg) for cfg in args.configs]
1674
Simon Glassd9c1da22021-12-18 14:54:31 -07001675 if args.test:
Simon Glassac261cc2024-07-17 16:57:01 +01001676 return do_tests()
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001677 if args.scan_source:
Simon Glass6901a642024-07-17 16:57:02 +01001678 return do_scan_source(os.getcwd(), args.update)
Simon Glassd9c1da22021-12-18 14:54:31 -07001679 if args.imply:
Simon Glass18352112024-07-17 16:57:03 +01001680 if imply(args):
1681 parser.print_usage()
1682 sys.exit(1)
Simon Glasse19a9cd2023-09-23 13:44:05 -06001683 return 0
Simon Glassd9c1da22021-12-18 14:54:31 -07001684 if args.find:
Simon Glass3c4cb202024-07-17 16:57:04 +01001685 return do_find_config(args.configs)
Simon Glass0082b2e2021-12-18 08:09:46 -07001686
Simon Glass704dd892024-07-17 16:56:58 +01001687 col = terminal.Color(terminal.COLOR_NEVER if args.nocolour
1688 else terminal.COLOR_IF_TERMINAL)
Simon Glass4c059032024-07-17 16:57:05 +01001689 config_db, progress = move_config(args, col)
Joe Hershberger23475932015-05-19 13:21:20 -05001690
Simon Glassd9c1da22021-12-18 14:54:31 -07001691 if args.commit:
Simon Glasse054a752024-07-17 16:57:06 +01001692 add_commit(args.configs)
Simon Glass8bf41c22016-09-12 23:18:21 -06001693
Simon Glassd9c1da22021-12-18 14:54:31 -07001694 if args.build_db:
Simon Glassc5ca9dc2024-07-17 16:57:08 +01001695 return write_db(config_db, col, progress)
1696 return move_done(col, progress)
Simon Glasse19a9cd2023-09-23 13:44:05 -06001697
Simon Glass43cf08f2017-06-01 19:39:02 -06001698
Masahiro Yamadab6160812015-05-20 11:36:07 +09001699if __name__ == '__main__':
Simon Glass0082b2e2021-12-18 08:09:46 -07001700 sys.exit(main())