blob: c4171cf9076b6b6cdbb6e0488bb966f9d1729051 [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 Glass948d0b42024-07-17 16:57:09 +0100707def move_config(args):
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 Glass94e2ed72023-09-23 13:44:13 -0600712
713 Returns:
Simon Glasseb1df3f2024-07-17 16:57:05 +0100714 tuple:
715 config_db (dict of configs for each defconfig):
716 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
717 value: dict:
718 key: CONFIG option
719 value: Value of option
720 Progress: Progress indicator
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900721 """
Simon Glasseb1df3f2024-07-17 16:57:05 +0100722 config_db = {}
723 db_queue = queue.Queue()
724 dbt = DatabaseThread(config_db, db_queue)
725 dbt.daemon = True
726 dbt.start()
727
728 check_clean_directory()
729 bsettings.setup('')
730
731 # Get toolchains to use
732 toolchains = toolchain.Toolchains()
733 toolchains.GetSettings()
734 toolchains.Scan(verbose=False)
735
Simon Glassb2e83c62021-12-18 14:54:31 -0700736 if args.git_ref:
737 reference_src = ReferenceSource(args.git_ref)
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900738 reference_src_dir = reference_src.get_dir()
739 else:
Masahiro Yamadaf432c332016-06-15 14:33:52 +0900740 reference_src_dir = None
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500741
Simon Glassb2e83c62021-12-18 14:54:31 -0700742 if args.defconfigs:
743 defconfigs = get_matched_defconfigs(args.defconfigs)
Joe Hershberger91040e82015-05-19 13:21:19 -0500744 else:
Masahiro Yamada684c3062016-07-25 19:15:28 +0900745 defconfigs = get_all_defconfigs()
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900746
Simon Glass948d0b42024-07-17 16:57:09 +0100747 col = terminal.Color(terminal.COLOR_NEVER if args.nocolour
748 else terminal.COLOR_IF_TERMINAL)
Simon Glass6b25d212023-09-23 13:44:10 -0600749 progress = Progress(col, len(defconfigs))
Simon Glass15f19ab2023-09-23 13:44:09 -0600750 slots = Slots(toolchains, args, progress, reference_src_dir, db_queue, col)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900751
752 # Main loop to process defconfig files:
753 # Add a new subprocess into a vacant slot.
754 # Sleep if there is no available slot.
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900755 for defconfig in defconfigs:
756 while not slots.add(defconfig):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900757 while not slots.available():
758 # No available slot: sleep for a while
759 time.sleep(SLEEP_TIME)
760
761 # wait until all the subprocesses finish
762 while not slots.empty():
763 time.sleep(SLEEP_TIME)
764
Simon Glass94e2ed72023-09-23 13:44:13 -0600765 slots.write_failed_boards()
Simon Glasseb1df3f2024-07-17 16:57:05 +0100766 db_queue.join()
Simon Glass0229eee2024-07-17 16:57:07 +0100767 progress.completed()
Simon Glasseb1df3f2024-07-17 16:57:05 +0100768 return config_db, progress
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900769
Simon Glasscb008832017-06-15 21:39:33 -0600770def find_kconfig_rules(kconf, config, imply_config):
771 """Check whether a config has a 'select' or 'imply' keyword
772
773 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600774 kconf (Kconfiglib.Kconfig): Kconfig object
775 config (str): Name of config to check (without CONFIG_ prefix)
776 imply_config (str): Implying config (without CONFIG_ prefix) which may
777 or may not have an 'imply' for 'config')
Simon Glasscb008832017-06-15 21:39:33 -0600778
779 Returns:
780 Symbol object for 'config' if found, else None
781 """
Tom Rini65e05dd2019-09-20 17:42:09 -0400782 sym = kconf.syms.get(imply_config)
Simon Glasscb008832017-06-15 21:39:33 -0600783 if sym:
Simon Glass62fae4b2023-09-23 13:44:00 -0600784 for sel, _ in (sym.selects + sym.implies):
Simon Glassa3627082021-12-18 08:09:42 -0700785 if sel.name == config:
Simon Glasscb008832017-06-15 21:39:33 -0600786 return sym
787 return None
788
Simon Glassf876e962024-07-17 16:56:52 +0100789def check_imply_rule(kconf, imply_config):
Simon Glasscb008832017-06-15 21:39:33 -0600790 """Check if we can add an 'imply' option
791
792 This finds imply_config in the Kconfig and looks to see if it is possible
793 to add an 'imply' for 'config' to that part of the Kconfig.
794
795 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600796 kconf (Kconfiglib.Kconfig): Kconfig object
Simon Glass549d4222023-09-23 13:43:58 -0600797 imply_config (str): Implying config (without CONFIG_ prefix) which may
798 or may not have an 'imply' for 'config')
Simon Glasscb008832017-06-15 21:39:33 -0600799
800 Returns:
801 tuple:
Simon Glass549d4222023-09-23 13:43:58 -0600802 str: filename of Kconfig file containing imply_config, or None if
803 none
804 int: line number within the Kconfig file, or 0 if none
805 str: message indicating the result
Simon Glasscb008832017-06-15 21:39:33 -0600806 """
Tom Rini65e05dd2019-09-20 17:42:09 -0400807 sym = kconf.syms.get(imply_config)
Simon Glasscb008832017-06-15 21:39:33 -0600808 if not sym:
809 return 'cannot find sym'
Simon Glassea40b202021-07-21 21:35:53 -0600810 nodes = sym.nodes
811 if len(nodes) != 1:
Simon Glass1bd43062023-09-23 13:43:59 -0600812 return f'{len(nodes)} locations'
Simon Glassa3627082021-12-18 08:09:42 -0700813 node = nodes[0]
814 fname, linenum = node.filename, node.linenr
Simon Glasscb008832017-06-15 21:39:33 -0600815 cwd = os.getcwd()
816 if cwd and fname.startswith(cwd):
817 fname = fname[len(cwd) + 1:]
Simon Glass1bd43062023-09-23 13:43:59 -0600818 file_line = f' at {fname}:{linenum}'
Simon Glass37f815c2021-12-18 14:54:34 -0700819 data = read_file(fname)
Simon Glass1bd43062023-09-23 13:43:59 -0600820 if data[linenum - 1] != f'config {imply_config}':
821 return None, 0, f'bad sym format {data[linenum]}{file_line})'
822 return fname, linenum, f'adding{file_line}'
Simon Glasscb008832017-06-15 21:39:33 -0600823
824def add_imply_rule(config, fname, linenum):
825 """Add a new 'imply' option to a Kconfig
826
827 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600828 config (str): config option to add an imply for (without CONFIG_ prefix)
829 fname (str): Kconfig filename to update
830 linenum (int): Line number to place the 'imply' before
Simon Glasscb008832017-06-15 21:39:33 -0600831
832 Returns:
833 Message indicating the result
834 """
Simon Glass1bd43062023-09-23 13:43:59 -0600835 file_line = f' at {fname}:{linenum}'
Simon Glass37f815c2021-12-18 14:54:34 -0700836 data = read_file(fname)
Simon Glasscb008832017-06-15 21:39:33 -0600837 linenum -= 1
838
839 for offset, line in enumerate(data[linenum:]):
840 if line.strip().startswith('help') or not line:
Simon Glass1bd43062023-09-23 13:43:59 -0600841 data.insert(linenum + offset, f'\timply {config}')
Simon Glass2fd85bd2021-12-18 14:54:33 -0700842 write_file(fname, data)
Simon Glass1bd43062023-09-23 13:43:59 -0600843 return f'added{file_line}'
Simon Glasscb008832017-06-15 21:39:33 -0600844
845 return 'could not insert%s'
846
847(IMPLY_MIN_2, IMPLY_TARGET, IMPLY_CMD, IMPLY_NON_ARCH_BOARD) = (
848 1, 2, 4, 8)
Simon Glass9b2a2e82017-06-15 21:39:32 -0600849
850IMPLY_FLAGS = {
851 'min2': [IMPLY_MIN_2, 'Show options which imply >2 boards (normally >5)'],
852 'target': [IMPLY_TARGET, 'Allow CONFIG_TARGET_... options to imply'],
853 'cmd': [IMPLY_CMD, 'Allow CONFIG_CMD_... to imply'],
Simon Glasscb008832017-06-15 21:39:33 -0600854 'non-arch-board': [
855 IMPLY_NON_ARCH_BOARD,
856 'Allow Kconfig options outside arch/ and /board/ to imply'],
Simon Glass91197aa2021-12-18 14:54:35 -0700857}
Simon Glass9b2a2e82017-06-15 21:39:32 -0600858
Simon Glass9d603392021-12-18 08:09:43 -0700859
860def read_database():
861 """Read in the config database
862
863 Returns:
864 tuple:
865 set of all config options seen (each a str)
866 set of all defconfigs seen (each a str)
867 dict of configs for each defconfig:
868 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
869 value: dict:
870 key: CONFIG option
871 value: Value of option
872 dict of defconfigs for each config:
873 key: CONFIG option
874 value: set of boards using that option
875
876 """
877 configs = {}
878
879 # key is defconfig name, value is dict of (CONFIG_xxx, value)
880 config_db = {}
881
882 # Set of all config options we have seen
883 all_configs = set()
884
885 # Set of all defconfigs we have seen
886 all_defconfigs = set()
887
888 defconfig_db = collections.defaultdict(set)
Simon Glass61d555d2024-07-17 16:56:49 +0100889 defconfig = None
Simon Glass37f815c2021-12-18 14:54:34 -0700890 for line in read_file(CONFIG_DATABASE):
891 line = line.rstrip()
892 if not line: # Separator between defconfigs
893 config_db[defconfig] = configs
894 all_defconfigs.add(defconfig)
895 configs = {}
896 elif line[0] == ' ': # CONFIG line
897 config, value = line.strip().split('=', 1)
898 configs[config] = value
899 defconfig_db[config].add(defconfig)
900 all_configs.add(config)
901 else: # New defconfig
902 defconfig = line
Simon Glass9d603392021-12-18 08:09:43 -0700903
904 return all_configs, all_defconfigs, config_db, defconfig_db
905
906
Simon Glasscb008832017-06-15 21:39:33 -0600907def do_imply_config(config_list, add_imply, imply_flags, skip_added,
908 check_kconfig=True, find_superset=False):
Simon Glass99b66602017-06-01 19:39:03 -0600909 """Find CONFIG options which imply those in the list
910
911 Some CONFIG options can be implied by others and this can help to reduce
912 the size of the defconfig files. For example, CONFIG_X86 implies
913 CONFIG_CMD_IRQ, so we can put 'imply CMD_IRQ' under 'config X86' and
914 all x86 boards will have that option, avoiding adding CONFIG_CMD_IRQ to
915 each of the x86 defconfig files.
916
Simon Glassea4d6de2023-09-23 13:44:14 -0600917 This function uses the qconfig database to find such options. It
Simon Glass99b66602017-06-01 19:39:03 -0600918 displays a list of things that could possibly imply those in the list.
919 The algorithm ignores any that start with CONFIG_TARGET since these
920 typically refer to only a few defconfigs (often one). It also does not
921 display a config with less than 5 defconfigs.
922
923 The algorithm works using sets. For each target config in config_list:
924 - Get the set 'defconfigs' which use that target config
925 - For each config (from a list of all configs):
926 - Get the set 'imply_defconfig' of defconfigs which use that config
927 -
928 - If imply_defconfigs contains anything not in defconfigs then
929 this config does not imply the target config
930
Simon Glasscc628f52024-07-17 16:57:03 +0100931 Args:
932 config_list (list of str): List of CONFIG options to check
933 add_imply (bool): Automatically add an 'imply' for each config.
934 imply_flags (int): Flags which control which implying configs are allowed
Simon Glass9b2a2e82017-06-15 21:39:32 -0600935 (IMPLY_...)
Simon Glasscc628f52024-07-17 16:57:03 +0100936 skip_added (bool): Don't show options which already have an imply added.
937 check_kconfig (bool): Check if implied symbols already have an 'imply' or
Simon Glasscb008832017-06-15 21:39:33 -0600938 'select' for the target config, and show this information if so.
Simon Glasscc628f52024-07-17 16:57:03 +0100939 find_superset (bool): True to look for configs which are a superset of those
Simon Glass99b66602017-06-01 19:39:03 -0600940 already found. So for example if CONFIG_EXYNOS5 implies an option,
941 but CONFIG_EXYNOS covers a larger set of defconfigs and also
942 implies that option, this will drop the former in favour of the
943 latter. In practice this option has not proved very used.
944
945 Note the terminoloy:
946 config - a CONFIG_XXX options (a string, e.g. 'CONFIG_CMD_EEPROM')
947 defconfig - a defconfig file (a string, e.g. 'configs/snow_defconfig')
948 """
Simon Glass0a0c1242024-07-17 16:56:51 +0100949 kconf = scan_kconfig() if check_kconfig else None
Simon Glasscb008832017-06-15 21:39:33 -0600950 if add_imply and add_imply != 'all':
Simon Glassa3627082021-12-18 08:09:42 -0700951 add_imply = add_imply.split(',')
Simon Glasscb008832017-06-15 21:39:33 -0600952
Simon Glass62fae4b2023-09-23 13:44:00 -0600953 all_configs, all_defconfigs, _, defconfig_db = read_database()
Simon Glass99b66602017-06-01 19:39:03 -0600954
Simon Glassa3627082021-12-18 08:09:42 -0700955 # Work through each target config option in turn, independently
Simon Glass99b66602017-06-01 19:39:03 -0600956 for config in config_list:
957 defconfigs = defconfig_db.get(config)
958 if not defconfigs:
Simon Glass1bd43062023-09-23 13:43:59 -0600959 print(f'{config} not found in any defconfig')
Simon Glass99b66602017-06-01 19:39:03 -0600960 continue
961
962 # Get the set of defconfigs without this one (since a config cannot
963 # imply itself)
964 non_defconfigs = all_defconfigs - defconfigs
965 num_defconfigs = len(defconfigs)
Simon Glass1bd43062023-09-23 13:43:59 -0600966 print(f'{config} found in {num_defconfigs}/{len(all_configs)} defconfigs')
Simon Glass99b66602017-06-01 19:39:03 -0600967
968 # This will hold the results: key=config, value=defconfigs containing it
969 imply_configs = {}
970 rest_configs = all_configs - set([config])
971
972 # Look at every possible config, except the target one
973 for imply_config in rest_configs:
Simon Glass9b2a2e82017-06-15 21:39:32 -0600974 if 'ERRATUM' in imply_config:
Simon Glass99b66602017-06-01 19:39:03 -0600975 continue
Simon Glass91197aa2021-12-18 14:54:35 -0700976 if not imply_flags & IMPLY_CMD:
Simon Glass9b2a2e82017-06-15 21:39:32 -0600977 if 'CONFIG_CMD' in imply_config:
978 continue
Simon Glass91197aa2021-12-18 14:54:35 -0700979 if not imply_flags & IMPLY_TARGET:
Simon Glass9b2a2e82017-06-15 21:39:32 -0600980 if 'CONFIG_TARGET' in imply_config:
981 continue
Simon Glass99b66602017-06-01 19:39:03 -0600982
983 # Find set of defconfigs that have this config
984 imply_defconfig = defconfig_db[imply_config]
985
986 # Get the intersection of this with defconfigs containing the
987 # target config
988 common_defconfigs = imply_defconfig & defconfigs
989
990 # Get the set of defconfigs containing this config which DO NOT
991 # also contain the taret config. If this set is non-empty it means
992 # that this config affects other defconfigs as well as (possibly)
993 # the ones affected by the target config. This means it implies
994 # things we don't want to imply.
995 not_common_defconfigs = imply_defconfig & non_defconfigs
996 if not_common_defconfigs:
997 continue
998
999 # If there are common defconfigs, imply_config may be useful
1000 if common_defconfigs:
1001 skip = False
1002 if find_superset:
Simon Glass793dca32019-10-31 07:42:57 -06001003 for prev in list(imply_configs.keys()):
Simon Glass99b66602017-06-01 19:39:03 -06001004 prev_count = len(imply_configs[prev])
1005 count = len(common_defconfigs)
1006 if (prev_count > count and
1007 (imply_configs[prev] & common_defconfigs ==
1008 common_defconfigs)):
1009 # skip imply_config because prev is a superset
1010 skip = True
1011 break
Simon Glassf297ba32023-09-23 13:44:05 -06001012 if count > prev_count:
Simon Glass99b66602017-06-01 19:39:03 -06001013 # delete prev because imply_config is a superset
1014 del imply_configs[prev]
1015 if not skip:
1016 imply_configs[imply_config] = common_defconfigs
1017
1018 # Now we have a dict imply_configs of configs which imply each config
1019 # The value of each dict item is the set of defconfigs containing that
1020 # config. Rank them so that we print the configs that imply the largest
1021 # number of defconfigs first.
Simon Glasscb008832017-06-15 21:39:33 -06001022 ranked_iconfigs = sorted(imply_configs,
Simon Glass99b66602017-06-01 19:39:03 -06001023 key=lambda k: len(imply_configs[k]), reverse=True)
Simon Glasscb008832017-06-15 21:39:33 -06001024 kconfig_info = ''
1025 cwd = os.getcwd()
1026 add_list = collections.defaultdict(list)
1027 for iconfig in ranked_iconfigs:
1028 num_common = len(imply_configs[iconfig])
Simon Glass99b66602017-06-01 19:39:03 -06001029
1030 # Don't bother if there are less than 5 defconfigs affected.
Simon Glass9b2a2e82017-06-15 21:39:32 -06001031 if num_common < (2 if imply_flags & IMPLY_MIN_2 else 5):
Simon Glass99b66602017-06-01 19:39:03 -06001032 continue
Simon Glasscb008832017-06-15 21:39:33 -06001033 missing = defconfigs - imply_configs[iconfig]
Simon Glass99b66602017-06-01 19:39:03 -06001034 missing_str = ', '.join(missing) if missing else 'all'
1035 missing_str = ''
Simon Glasscb008832017-06-15 21:39:33 -06001036 show = True
1037 if kconf:
1038 sym = find_kconfig_rules(kconf, config[CONFIG_LEN:],
1039 iconfig[CONFIG_LEN:])
1040 kconfig_info = ''
1041 if sym:
Simon Glassea40b202021-07-21 21:35:53 -06001042 nodes = sym.nodes
1043 if len(nodes) == 1:
1044 fname, linenum = nodes[0].filename, nodes[0].linenr
Simon Glasscb008832017-06-15 21:39:33 -06001045 if cwd and fname.startswith(cwd):
1046 fname = fname[len(cwd) + 1:]
Simon Glass1bd43062023-09-23 13:43:59 -06001047 kconfig_info = f'{fname}:{linenum}'
Simon Glasscb008832017-06-15 21:39:33 -06001048 if skip_added:
1049 show = False
1050 else:
Tom Rini65e05dd2019-09-20 17:42:09 -04001051 sym = kconf.syms.get(iconfig[CONFIG_LEN:])
Simon Glasscb008832017-06-15 21:39:33 -06001052 fname = ''
1053 if sym:
Simon Glassea40b202021-07-21 21:35:53 -06001054 nodes = sym.nodes
1055 if len(nodes) == 1:
1056 fname, linenum = nodes[0].filename, nodes[0].linenr
Simon Glasscb008832017-06-15 21:39:33 -06001057 if cwd and fname.startswith(cwd):
1058 fname = fname[len(cwd) + 1:]
1059 in_arch_board = not sym or (fname.startswith('arch') or
1060 fname.startswith('board'))
1061 if (not in_arch_board and
Simon Glass91197aa2021-12-18 14:54:35 -07001062 not imply_flags & IMPLY_NON_ARCH_BOARD):
Simon Glasscb008832017-06-15 21:39:33 -06001063 continue
1064
1065 if add_imply and (add_imply == 'all' or
1066 iconfig in add_imply):
1067 fname, linenum, kconfig_info = (check_imply_rule(kconf,
Simon Glassf876e962024-07-17 16:56:52 +01001068 iconfig[CONFIG_LEN:]))
Simon Glasscb008832017-06-15 21:39:33 -06001069 if fname:
1070 add_list[fname].append(linenum)
1071
1072 if show and kconfig_info != 'skip':
Simon Glass0e03fb12024-07-17 16:56:53 +01001073 print(f'{num_common:5} : '
1074 f'{iconfig.ljust(30)}{kconfig_info.ljust(25)} {missing_str}')
Simon Glasscb008832017-06-15 21:39:33 -06001075
1076 # Having collected a list of things to add, now we add them. We process
1077 # each file from the largest line number to the smallest so that
1078 # earlier additions do not affect our line numbers. E.g. if we added an
1079 # imply at line 20 it would change the position of each line after
1080 # that.
Simon Glass793dca32019-10-31 07:42:57 -06001081 for fname, linenums in add_list.items():
Simon Glasscb008832017-06-15 21:39:33 -06001082 for linenum in sorted(linenums, reverse=True):
1083 add_imply_rule(config[CONFIG_LEN:], fname, linenum)
Simon Glass99b66602017-06-01 19:39:03 -06001084
Simon Glass941671a2022-02-08 11:49:46 -07001085def defconfig_matches(configs, re_match):
1086 """Check if any CONFIG option matches a regex
1087
1088 The match must be complete, i.e. from the start to end of the CONFIG option.
1089
1090 Args:
1091 configs (dict): Dict of CONFIG options:
1092 key: CONFIG option
1093 value: Value of option
1094 re_match (re.Pattern): Match to check
1095
1096 Returns:
1097 bool: True if any CONFIG matches the regex
1098 """
1099 for cfg in configs:
Simon Glassd9c958f2022-03-05 20:18:54 -07001100 if re_match.fullmatch(cfg):
Simon Glass941671a2022-02-08 11:49:46 -07001101 return True
1102 return False
Simon Glass99b66602017-06-01 19:39:03 -06001103
Simon Glass65d7fce2021-12-18 08:09:46 -07001104def do_find_config(config_list):
1105 """Find boards with a given combination of CONFIGs
1106
Simon Glass630a9c92024-07-17 16:57:04 +01001107 Args:
1108 config_list (list of str): List of CONFIG options to check (each a regex
1109 consisting of a config option, with or without a CONFIG_ prefix. If
1110 an option is preceded by a tilde (~) then it must be false,
1111 otherwise it must be true)
1112
1113 Returns:
1114 int: exit code (0 for success)
Simon Glass65d7fce2021-12-18 08:09:46 -07001115 """
Simon Glass62fae4b2023-09-23 13:44:00 -06001116 _, all_defconfigs, config_db, _ = read_database()
Simon Glass65d7fce2021-12-18 08:09:46 -07001117
Simon Glass65d7fce2021-12-18 08:09:46 -07001118 # Start with all defconfigs
1119 out = all_defconfigs
1120
1121 # Work through each config in turn
Simon Glass65d7fce2021-12-18 08:09:46 -07001122 for item in config_list:
1123 # Get the real config name and whether we want this config or not
1124 cfg = item
1125 want = True
1126 if cfg[0] == '~':
1127 want = False
1128 cfg = cfg[1:]
1129
Simon Glass65d7fce2021-12-18 08:09:46 -07001130 # Search everything that is still in the running. If it has a config
1131 # that we want, or doesn't have one that we don't, add it into the
1132 # running for the next stage
1133 in_list = out
1134 out = set()
Simon Glass941671a2022-02-08 11:49:46 -07001135 re_match = re.compile(cfg)
Simon Glass65d7fce2021-12-18 08:09:46 -07001136 for defc in in_list:
Simon Glass941671a2022-02-08 11:49:46 -07001137 has_cfg = defconfig_matches(config_db[defc], re_match)
Simon Glass65d7fce2021-12-18 08:09:46 -07001138 if has_cfg == want:
1139 out.add(defc)
Tom Rini9ef3ba82022-12-04 10:14:16 -05001140 print(f'{len(out)} matches')
1141 print(' '.join(item.split('_defconfig')[0] for item in out))
Simon Glass630a9c92024-07-17 16:57:04 +01001142 return 0
Simon Glass65d7fce2021-12-18 08:09:46 -07001143
1144
1145def prefix_config(cfg):
1146 """Prefix a config with CONFIG_ if needed
1147
1148 This handles ~ operator, which indicates that the CONFIG should be disabled
1149
1150 >>> prefix_config('FRED')
1151 'CONFIG_FRED'
1152 >>> prefix_config('CONFIG_FRED')
1153 'CONFIG_FRED'
1154 >>> prefix_config('~FRED')
1155 '~CONFIG_FRED'
1156 >>> prefix_config('~CONFIG_FRED')
1157 '~CONFIG_FRED'
1158 >>> prefix_config('A123')
1159 'CONFIG_A123'
1160 """
Simon Glassa4c9d172023-09-23 13:44:01 -06001161 oper = ''
Simon Glass65d7fce2021-12-18 08:09:46 -07001162 if cfg[0] == '~':
Simon Glassa4c9d172023-09-23 13:44:01 -06001163 oper = cfg[0]
Simon Glass65d7fce2021-12-18 08:09:46 -07001164 cfg = cfg[1:]
1165 if not cfg.startswith('CONFIG_'):
1166 cfg = 'CONFIG_' + cfg
Simon Glassa4c9d172023-09-23 13:44:01 -06001167 return oper + cfg
Simon Glass65d7fce2021-12-18 08:09:46 -07001168
1169
Simon Glass98275712023-09-23 13:43:57 -06001170RE_MK_CONFIGS = re.compile(r'CONFIG_(\$\(SPL_(?:TPL_)?\))?([A-Za-z0-9_]*)')
1171RE_IFDEF = re.compile(r'(ifdef|ifndef)')
1172RE_C_CONFIGS = re.compile(r'CONFIG_([A-Za-z0-9_]*)')
1173RE_CONFIG_IS = re.compile(r'CONFIG_IS_ENABLED\(([A-Za-z0-9_]*)\)')
Simon Glass65e62032023-02-01 13:19:12 -07001174
1175class ConfigUse:
Simon Glassf876e962024-07-17 16:56:52 +01001176 """Tracks whether a config relates to SPL or not"""
Simon Glass65e62032023-02-01 13:19:12 -07001177 def __init__(self, cfg, is_spl, fname, rest):
Simon Glassf876e962024-07-17 16:56:52 +01001178 """Set up a new ConfigUse
1179
1180 Args:
1181 cfg (str): CONFIG option, without any CONFIG_ or SPL_ prefix
1182 is_spl (bool): True if this option relates to SPL
1183 fname (str): Makefile filename where the CONFIG option was found
1184 rest (str): Line of the Makefile
1185 """
Simon Glass65e62032023-02-01 13:19:12 -07001186 self.cfg = cfg
1187 self.is_spl = is_spl
1188 self.fname = fname
1189 self.rest = rest
1190
1191 def __hash__(self):
1192 return hash((self.cfg, self.is_spl))
1193
1194def scan_makefiles(fnames):
1195 """Scan Makefiles looking for Kconfig options
1196
1197 Looks for uses of CONFIG options in Makefiles
1198
1199 Args:
1200 fnames (list of tuple):
1201 str: Makefile filename where the option was found
1202 str: Line of the Makefile
1203
1204 Returns:
1205 tuple:
1206 dict: all_uses
1207 key (ConfigUse): object
1208 value (list of str): matching lines
1209 dict: Uses by filename
1210 key (str): filename
1211 value (set of ConfigUse): uses in that filename
1212
1213 >>> RE_MK_CONFIGS.search('CONFIG_FRED').groups()
1214 (None, 'FRED')
1215 >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_)MARY').groups()
1216 ('$(SPL_)', 'MARY')
1217 >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_TPL_)MARY').groups()
1218 ('$(SPL_TPL_)', 'MARY')
1219 """
1220 all_uses = collections.defaultdict(list)
1221 fname_uses = {}
1222 for fname, rest in fnames:
1223 m_iter = RE_MK_CONFIGS.finditer(rest)
Simon Glassa4c9d172023-09-23 13:44:01 -06001224 for mat in m_iter:
1225 real_opt = mat.group(2)
Simon Glass65e62032023-02-01 13:19:12 -07001226 if real_opt == '':
1227 continue
1228 is_spl = False
Simon Glassa4c9d172023-09-23 13:44:01 -06001229 if mat.group(1):
Simon Glass65e62032023-02-01 13:19:12 -07001230 is_spl = True
1231 use = ConfigUse(real_opt, is_spl, fname, rest)
1232 if fname not in fname_uses:
1233 fname_uses[fname] = set()
1234 fname_uses[fname].add(use)
1235 all_uses[use].append(rest)
1236 return all_uses, fname_uses
1237
1238
1239def scan_src_files(fnames):
1240 """Scan source files (other than Makefiles) looking for Kconfig options
1241
1242 Looks for uses of CONFIG options
1243
1244 Args:
1245 fnames (list of tuple):
1246 str: Makefile filename where the option was found
1247 str: Line of the Makefile
1248
1249 Returns:
1250 tuple:
1251 dict: all_uses
1252 key (ConfigUse): object
1253 value (list of str): matching lines
1254 dict: Uses by filename
1255 key (str): filename
1256 value (set of ConfigUse): uses in that filename
1257
1258 >>> RE_C_CONFIGS.search('CONFIG_FRED').groups()
1259 ('FRED',)
1260 >>> RE_CONFIG_IS.search('CONFIG_IS_ENABLED(MARY)').groups()
1261 ('MARY',)
1262 >>> RE_CONFIG_IS.search('#if CONFIG_IS_ENABLED(OF_PLATDATA)').groups()
1263 ('OF_PLATDATA',)
1264 """
Simon Glass62fae4b2023-09-23 13:44:00 -06001265 fname = None
1266 rest = None
1267
Simon Glass65e62032023-02-01 13:19:12 -07001268 def add_uses(m_iter, is_spl):
Simon Glassa4c9d172023-09-23 13:44:01 -06001269 for mat in m_iter:
1270 real_opt = mat.group(1)
Simon Glass65e62032023-02-01 13:19:12 -07001271 if real_opt == '':
1272 continue
1273 use = ConfigUse(real_opt, is_spl, fname, rest)
1274 if fname not in fname_uses:
1275 fname_uses[fname] = set()
1276 fname_uses[fname].add(use)
1277 all_uses[use].append(rest)
1278
1279 all_uses = collections.defaultdict(list)
1280 fname_uses = {}
1281 for fname, rest in fnames:
1282 m_iter = RE_C_CONFIGS.finditer(rest)
1283 add_uses(m_iter, False)
1284
1285 m_iter2 = RE_CONFIG_IS.finditer(rest)
1286 add_uses(m_iter2, True)
1287
1288 return all_uses, fname_uses
1289
1290
1291MODE_NORMAL, MODE_SPL, MODE_PROPER = range(3)
1292
1293def do_scan_source(path, do_update):
1294 """Scan the source tree for Kconfig inconsistencies
1295
1296 Args:
1297 path (str): Path to source tree
1298 do_update (bool) : True to write to scripts/kconf_... files
1299 """
1300 def is_not_proper(name):
1301 for prefix in SPL_PREFIXES:
1302 if name.startswith(prefix):
1303 return name[len(prefix):]
1304 return False
1305
1306 def check_not_found(all_uses, spl_mode):
1307 """Check for Kconfig options mentioned in the source but not in Kconfig
1308
1309 Args:
1310 all_uses (dict):
1311 key (ConfigUse): object
1312 value (list of str): matching lines
1313 spl_mode (int): If MODE_SPL, look at source code which implies
1314 an SPL_ option, but for which there is none;
1315 for MOD_PROPER, look at source code which implies a Proper
1316 option (i.e. use of CONFIG_IS_ENABLED() or $(SPL_) or
1317 $(SPL_TPL_) but for which there none;
1318 if MODE_NORMAL, ignore SPL
1319
1320 Returns:
1321 dict:
1322 key (str): CONFIG name (without 'CONFIG_' prefix
1323 value (list of ConfigUse): List of uses of this CONFIG
1324 """
1325 # Make sure we know about all the options
1326 not_found = collections.defaultdict(list)
Simon Glass62fae4b2023-09-23 13:44:00 -06001327 for use, _ in all_uses.items():
Simon Glass65e62032023-02-01 13:19:12 -07001328 name = use.cfg
1329 if name in IGNORE_SYMS:
1330 continue
1331 check = True
1332
1333 if spl_mode == MODE_SPL:
1334 check = use.is_spl
1335
1336 # If it is an SPL symbol, try prepending all SPL_ prefixes to
1337 # find at least one SPL symbol
1338 if use.is_spl:
Simon Glass65e62032023-02-01 13:19:12 -07001339 for prefix in SPL_PREFIXES:
1340 try_name = prefix + name
1341 sym = kconf.syms.get(try_name)
1342 if sym:
1343 break
1344 if not sym:
1345 not_found[f'SPL_{name}'].append(use)
1346 continue
1347 elif spl_mode == MODE_PROPER:
1348 # Try to find the Proper version of this symbol, i.e. without
1349 # the SPL_ prefix
1350 proper_name = is_not_proper(name)
1351 if proper_name:
1352 name = proper_name
1353 elif not use.is_spl:
1354 check = False
1355 else: # MODE_NORMAL
Simon Glass65e62032023-02-01 13:19:12 -07001356 sym = kconf.syms.get(name)
1357 if not sym:
1358 proper_name = is_not_proper(name)
1359 if proper_name:
1360 name = proper_name
1361 sym = kconf.syms.get(name)
1362 if not sym:
1363 for prefix in SPL_PREFIXES:
1364 try_name = prefix + name
1365 sym = kconf.syms.get(try_name)
1366 if sym:
1367 break
1368 if not sym:
1369 not_found[name].append(use)
1370 continue
1371
1372 sym = kconf.syms.get(name)
1373 if not sym and check:
1374 not_found[name].append(use)
1375 return not_found
1376
1377 def show_uses(uses):
1378 """Show a list of uses along with their filename and code snippet
1379
1380 Args:
1381 uses (dict):
1382 key (str): CONFIG name (without 'CONFIG_' prefix
1383 value (list of ConfigUse): List of uses of this CONFIG
1384 """
1385 for name in sorted(uses):
1386 print(f'{name}: ', end='')
1387 for i, use in enumerate(uses[name]):
1388 print(f'{" " if i else ""}{use.fname}: {use.rest.strip()}')
1389
1390
1391 print('Scanning Kconfig')
Simon Glass0a0c1242024-07-17 16:56:51 +01001392 kconf = scan_kconfig()
Simon Glass65e62032023-02-01 13:19:12 -07001393 print(f'Scanning source in {path}')
1394 args = ['git', 'grep', '-E', r'IS_ENABLED|\bCONFIG']
1395 with subprocess.Popen(args, stdout=subprocess.PIPE) as proc:
Simon Glass62fae4b2023-09-23 13:44:00 -06001396 out, _ = proc.communicate()
Simon Glass65e62032023-02-01 13:19:12 -07001397 lines = out.splitlines()
1398 re_fname = re.compile('^([^:]*):(.*)')
1399 src_list = []
1400 mk_list = []
1401 for line in lines:
1402 linestr = line.decode('utf-8')
1403 m_fname = re_fname.search(linestr)
1404 if not m_fname:
1405 continue
1406 fname, rest = m_fname.groups()
1407 dirname, leaf = os.path.split(fname)
1408 root, ext = os.path.splitext(leaf)
1409 if ext == '.autoconf':
1410 pass
1411 elif ext in ['.c', '.h', '.S', '.lds', '.dts', '.dtsi', '.asl', '.cfg',
1412 '.env', '.tmpl']:
1413 src_list.append([fname, rest])
1414 elif 'Makefile' in root or ext == '.mk':
1415 mk_list.append([fname, rest])
1416 elif ext in ['.yml', '.sh', '.py', '.awk', '.pl', '.rst', '', '.sed']:
1417 pass
1418 elif 'Kconfig' in root or 'Kbuild' in root:
1419 pass
1420 elif 'README' in root:
1421 pass
1422 elif dirname in ['configs']:
1423 pass
1424 elif dirname.startswith('doc') or dirname.startswith('scripts/kconfig'):
1425 pass
1426 else:
1427 print(f'Not sure how to handle file {fname}')
1428
1429 # Scan the Makefiles
Simon Glass62fae4b2023-09-23 13:44:00 -06001430 all_uses, _ = scan_makefiles(mk_list)
Simon Glass65e62032023-02-01 13:19:12 -07001431
1432 spl_not_found = set()
1433 proper_not_found = set()
1434
1435 # Make sure we know about all the options
1436 print('\nCONFIG options present in Makefiles but not Kconfig:')
1437 not_found = check_not_found(all_uses, MODE_NORMAL)
1438 show_uses(not_found)
1439
1440 print('\nCONFIG options present in Makefiles but not Kconfig (SPL):')
1441 not_found = check_not_found(all_uses, MODE_SPL)
1442 show_uses(not_found)
Simon Glassb774ba52023-09-23 13:44:03 -06001443 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
Simon Glass65e62032023-02-01 13:19:12 -07001444
1445 print('\nCONFIG options used as Proper in Makefiles but without a non-SPL_ variant:')
1446 not_found = check_not_found(all_uses, MODE_PROPER)
1447 show_uses(not_found)
Simon Glassb774ba52023-09-23 13:44:03 -06001448 proper_not_found |= {not_found.keys()}
Simon Glass65e62032023-02-01 13:19:12 -07001449
1450 # Scan the source code
Simon Glass62fae4b2023-09-23 13:44:00 -06001451 all_uses, _ = scan_src_files(src_list)
Simon Glass65e62032023-02-01 13:19:12 -07001452
1453 # Make sure we know about all the options
1454 print('\nCONFIG options present in source but not Kconfig:')
1455 not_found = check_not_found(all_uses, MODE_NORMAL)
1456 show_uses(not_found)
1457
1458 print('\nCONFIG options present in source but not Kconfig (SPL):')
1459 not_found = check_not_found(all_uses, MODE_SPL)
1460 show_uses(not_found)
Simon Glassb774ba52023-09-23 13:44:03 -06001461 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
Simon Glass65e62032023-02-01 13:19:12 -07001462
1463 print('\nCONFIG options used as Proper in source but without a non-SPL_ variant:')
1464 not_found = check_not_found(all_uses, MODE_PROPER)
1465 show_uses(not_found)
Simon Glassb774ba52023-09-23 13:44:03 -06001466 proper_not_found |= {not_found.keys()}
Simon Glass65e62032023-02-01 13:19:12 -07001467
1468 print('\nCONFIG options used as SPL but without an SPL_ variant:')
1469 for item in sorted(spl_not_found):
1470 print(f' {item}')
1471
1472 print('\nCONFIG options used as Proper but without a non-SPL_ variant:')
1473 for item in sorted(proper_not_found):
1474 print(f' {item}')
1475
1476 # Write out the updated information
1477 if do_update:
Simon Glasse6c686f2023-09-23 13:44:04 -06001478 with open(os.path.join(path, 'scripts', 'conf_nospl'), 'w',
1479 encoding='utf-8') as out:
Simon Glass65e62032023-02-01 13:19:12 -07001480 print('# These options should not be enabled in SPL builds\n',
1481 file=out)
1482 for item in sorted(spl_not_found):
1483 print(item, file=out)
Simon Glasse6c686f2023-09-23 13:44:04 -06001484 with open(os.path.join(path, 'scripts', 'conf_noproper'), 'w',
1485 encoding='utf-8') as out:
Simon Glass65e62032023-02-01 13:19:12 -07001486 print('# These options should not be enabled in Proper builds\n',
1487 file=out)
1488 for item in sorted(proper_not_found):
1489 print(item, file=out)
Simon Glassd63357e2024-07-17 16:57:02 +01001490 return 0
Simon Glass65e62032023-02-01 13:19:12 -07001491
1492
Simon Glassa056c422024-07-17 16:56:55 +01001493def parse_args():
1494 """Parse the program arguments
1495
1496 Returns:
1497 tuple:
1498 argparse.ArgumentParser: parser
1499 argparse.Namespace: Parsed arguments
1500 """
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001501 try:
1502 cpu_count = multiprocessing.cpu_count()
1503 except NotImplementedError:
1504 cpu_count = 1
1505
Simon Glassb2e83c62021-12-18 14:54:31 -07001506 epilog = '''Move config options from headers to defconfig files. See
1507doc/develop/moveconfig.rst for documentation.'''
1508
1509 parser = ArgumentParser(epilog=epilog)
1510 # Add arguments here
1511 parser.add_argument('-a', '--add-imply', type=str, default='',
Simon Glasscb008832017-06-15 21:39:33 -06001512 help='comma-separated list of CONFIG options to add '
1513 "an 'imply' statement to for the CONFIG in -i")
Simon Glassb2e83c62021-12-18 14:54:31 -07001514 parser.add_argument('-A', '--skip-added', action='store_true', default=False,
Simon Glasscb008832017-06-15 21:39:33 -06001515 help="don't show options which are already marked as "
1516 'implying others')
Simon Glassb2e83c62021-12-18 14:54:31 -07001517 parser.add_argument('-b', '--build-db', action='store_true', default=False,
Simon Glassd73fcb12017-06-01 19:39:02 -06001518 help='build a CONFIG database')
Simon Glassb2e83c62021-12-18 14:54:31 -07001519 parser.add_argument('-C', '--commit', action='store_true', default=False,
Simon Glass9ede2122016-09-12 23:18:21 -06001520 help='Create a git commit for the operation')
Simon Glass15f19ab2023-09-23 13:44:09 -06001521 parser.add_argument('--nocolour', action='store_true', default=False,
1522 help="don't display the log in colour")
Simon Glassb2e83c62021-12-18 14:54:31 -07001523 parser.add_argument('-d', '--defconfigs', type=str,
Simon Glassee4e61b2017-06-01 19:38:59 -06001524 help='a file containing a list of defconfigs to move, '
1525 "one per line (for example 'snow_defconfig') "
1526 "or '-' to read from stdin")
Simon Glassb2e83c62021-12-18 14:54:31 -07001527 parser.add_argument('-e', '--exit-on-error', action='store_true',
Simon Glasse1ae5632021-12-18 08:09:44 -07001528 default=False,
1529 help='exit immediately on any error')
Simon Glassb2e83c62021-12-18 14:54:31 -07001530 parser.add_argument('-f', '--find', action='store_true', default=False,
Simon Glass65d7fce2021-12-18 08:09:46 -07001531 help='Find boards with a given config combination')
Simon Glassb2e83c62021-12-18 14:54:31 -07001532 parser.add_argument('-i', '--imply', action='store_true', default=False,
Simon Glass99b66602017-06-01 19:39:03 -06001533 help='find options which imply others')
Simon Glassb2e83c62021-12-18 14:54:31 -07001534 parser.add_argument('-I', '--imply-flags', type=str, default='',
Simon Glass9b2a2e82017-06-15 21:39:32 -06001535 help="control the -i option ('help' for help")
Simon Glassb2e83c62021-12-18 14:54:31 -07001536 parser.add_argument('-j', '--jobs', type=int, default=cpu_count,
Simon Glasse1ae5632021-12-18 08:09:44 -07001537 help='the number of jobs to run simultaneously')
Simon Glassb2e83c62021-12-18 14:54:31 -07001538 parser.add_argument('-n', '--dry-run', action='store_true', default=False,
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001539 help='perform a trial run (show log with no changes)')
Simon Glassb2e83c62021-12-18 14:54:31 -07001540 parser.add_argument('-r', '--git-ref', type=str,
Simon Glasse1ae5632021-12-18 08:09:44 -07001541 help='the git ref to clone for building the autoconf.mk')
Simon Glassb2e83c62021-12-18 14:54:31 -07001542 parser.add_argument('-s', '--force-sync', action='store_true', default=False,
Masahiro Yamada8513dc02016-05-19 15:52:08 +09001543 help='force sync by savedefconfig')
Simon Glassb2e83c62021-12-18 14:54:31 -07001544 parser.add_argument('-S', '--spl', action='store_true', default=False,
Masahiro Yamada07913d12016-08-22 22:18:22 +09001545 help='parse config options defined for SPL build')
Simon Glass65e62032023-02-01 13:19:12 -07001546 parser.add_argument('--scan-source', action='store_true', default=False,
1547 help='scan source for uses of CONFIG options')
Simon Glassb2e83c62021-12-18 14:54:31 -07001548 parser.add_argument('-t', '--test', action='store_true', default=False,
Simon Glasse1ae5632021-12-18 08:09:44 -07001549 help='run unit tests')
Simon Glassb2e83c62021-12-18 14:54:31 -07001550 parser.add_argument('-y', '--yes', action='store_true', default=False,
Simon Glass6b403df2016-09-12 23:18:20 -06001551 help="respond 'yes' to any prompts")
Simon Glass65e62032023-02-01 13:19:12 -07001552 parser.add_argument('-u', '--update', action='store_true', default=False,
1553 help="update scripts/ files (use with --scan-source)")
Simon Glassb2e83c62021-12-18 14:54:31 -07001554 parser.add_argument('-v', '--verbose', action='store_true', default=False,
Joe Hershberger95bf9c72015-05-19 13:21:24 -05001555 help='show any build errors as boards are built')
Simon Glassb2e83c62021-12-18 14:54:31 -07001556 parser.add_argument('configs', nargs='*')
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001557
Simon Glassa056c422024-07-17 16:56:55 +01001558 return parser, parser.parse_args()
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001559
Simon Glassa056c422024-07-17 16:56:55 +01001560
Simon Glasscc628f52024-07-17 16:57:03 +01001561def imply(args):
1562 """Handle checking for flags which imply others
1563
1564 Args:
1565 args (argparse.Namespace): Program arguments
1566
1567 Returns:
1568 int: exit code (0 for success)
1569 """
1570 imply_flags = 0
1571 if args.imply_flags == 'all':
1572 imply_flags = -1
1573
1574 elif args.imply_flags:
1575 for flag in args.imply_flags.split(','):
1576 bad = flag not in IMPLY_FLAGS
1577 if bad:
1578 print(f"Invalid flag '{flag}'")
1579 if flag == 'help' or bad:
1580 print("Imply flags: (separate with ',')")
1581 for name, info in IMPLY_FLAGS.items():
1582 print(f' {name:-15s}: {info[1]}')
1583 return 1
1584 imply_flags |= IMPLY_FLAGS[flag][0]
1585
1586 do_imply_config(args.configs, args.add_imply, imply_flags, args.skip_added)
1587 return 0
1588
1589
Simon Glass6c2a4382024-07-17 16:57:06 +01001590def add_commit(configs):
1591 """Add a commit indicating which CONFIG options were converted
1592
1593 Args:
1594 configs (list of str) List of CONFIG_... options to process
1595 """
1596 subprocess.call(['git', 'add', '-u'])
1597 if configs:
1598 part = 'et al ' if len(configs) > 1 else ''
1599 msg = f'Convert {configs[0]} {part}to Kconfig'
1600 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' %
1601 '\n '.join(configs))
1602 else:
1603 msg = 'configs: Resync with savedefconfig'
1604 msg += '\n\nRsync all defconfig files using moveconfig.py'
1605 subprocess.call(['git', 'commit', '-s', '-m', msg])
1606
1607
Simon Glass948d0b42024-07-17 16:57:09 +01001608def write_db(config_db, progress):
Simon Glassab1bfd42024-07-17 16:57:08 +01001609 """Write the database to a file
1610
1611 Args:
1612 config_db (dict of dict): configs for each defconfig
1613 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
1614 value: dict:
1615 key: CONFIG option
1616 value: Value of option
Simon Glassab1bfd42024-07-17 16:57:08 +01001617 progress (Progress): Progress indicator.
1618
1619 Returns:
1620 int: exit code (0 for success)
1621 """
Simon Glass948d0b42024-07-17 16:57:09 +01001622 col = progress.col
Simon Glassab1bfd42024-07-17 16:57:08 +01001623 with open(CONFIG_DATABASE, 'w', encoding='utf-8') as outf:
1624 for defconfig, configs in config_db.items():
1625 outf.write(f'{defconfig}\n')
1626 for config in sorted(configs.keys()):
1627 outf.write(f' {config}={configs[config]}\n')
1628 outf.write('\n')
1629 print(col.build(
1630 col.RED if progress.failed else col.GREEN,
1631 f'{progress.failure_msg}{len(config_db)} boards written to {CONFIG_DATABASE}'))
1632 return 0
1633
1634
Simon Glass948d0b42024-07-17 16:57:09 +01001635def move_done(progress):
Simon Glassab1bfd42024-07-17 16:57:08 +01001636 """Write a message indicating that the move is done
1637
1638 Args:
Simon Glassab1bfd42024-07-17 16:57:08 +01001639 progress (Progress): Progress indicator.
1640
1641 Returns:
1642 int: exit code (0 for success)
1643 """
Simon Glass948d0b42024-07-17 16:57:09 +01001644 col = progress.col
Simon Glassab1bfd42024-07-17 16:57:08 +01001645 if progress.failed:
1646 print(col.build(col.RED, f'{progress.failure_msg}see {FAILED_LIST}', True))
1647 else:
1648 # Add enough spaces to overwrite the progress indicator
1649 print(col.build(
1650 col.GREEN, f'{progress.total} processed ', bright=True))
1651 return 0
1652
Simon Glass7e688042024-07-17 16:57:01 +01001653def do_tests():
1654 """Run doctests and unit tests (so far there are no unit tests)"""
1655 sys.argv = [sys.argv[0]]
1656 fail, _ = doctest.testmod()
1657 if fail:
1658 return 1
1659 unittest.main()
1660 return 0
1661
1662
Simon Glassa056c422024-07-17 16:56:55 +01001663def main():
1664 """Main program"""
1665 parser, args = parse_args()
Simon Glass382c6622024-07-17 16:56:57 +01001666 if not any((args.force_sync, args.build_db, args.imply, args.find,
1667 args.scan_source, args.test)):
1668 parser.print_usage()
1669 sys.exit(1)
Simon Glassc50b6f12024-07-17 16:56:59 +01001670
1671 check_top_directory()
1672
Simon Glassfd35fbe2024-07-17 16:57:00 +01001673 # prefix the option name with CONFIG_ if missing
1674 args.configs = [prefix_config(cfg) for cfg in args.configs]
1675
Simon Glassb2e83c62021-12-18 14:54:31 -07001676 if args.test:
Simon Glass7e688042024-07-17 16:57:01 +01001677 return do_tests()
Simon Glass65e62032023-02-01 13:19:12 -07001678 if args.scan_source:
Simon Glassd63357e2024-07-17 16:57:02 +01001679 return do_scan_source(os.getcwd(), args.update)
Simon Glassb2e83c62021-12-18 14:54:31 -07001680 if args.imply:
Simon Glasscc628f52024-07-17 16:57:03 +01001681 if imply(args):
1682 parser.print_usage()
1683 sys.exit(1)
Simon Glassf297ba32023-09-23 13:44:05 -06001684 return 0
Simon Glassb2e83c62021-12-18 14:54:31 -07001685 if args.find:
Simon Glass630a9c92024-07-17 16:57:04 +01001686 return do_find_config(args.configs)
Simon Glass65d7fce2021-12-18 08:09:46 -07001687
Simon Glass948d0b42024-07-17 16:57:09 +01001688 config_db, progress = move_config(args)
Joe Hershberger2144f882015-05-19 13:21:20 -05001689
Simon Glassb2e83c62021-12-18 14:54:31 -07001690 if args.commit:
Simon Glass6c2a4382024-07-17 16:57:06 +01001691 add_commit(args.configs)
Simon Glass9ede2122016-09-12 23:18:21 -06001692
Simon Glassb2e83c62021-12-18 14:54:31 -07001693 if args.build_db:
Simon Glass948d0b42024-07-17 16:57:09 +01001694 return write_db(config_db, progress)
1695 return move_done(progress)
Simon Glassf297ba32023-09-23 13:44:05 -06001696
Simon Glassd73fcb12017-06-01 19:39:02 -06001697
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001698if __name__ == '__main__':
Simon Glass65d7fce2021-12-18 08:09:46 -07001699 sys.exit(main())