blob: a11ed5303d085faeb733062d09713eb7742d1ace [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#
4# Author: Masahiro Yamada <yamada.masahiro@socionext.com>
5#
Masahiro Yamadab6160812015-05-20 11:36:07 +09006
7"""
Simon Glassbf0e11a2023-09-23 13:44:14 -06008Build and query a Kconfig database for boards.
Masahiro Yamadab6160812015-05-20 11:36:07 +09009
Simon Glass83cc72e2021-07-21 21:35:51 -060010See doc/develop/moveconfig.rst for documentation.
Masahiro Yamadab6160812015-05-20 11:36:07 +090011"""
12
Simon Glassd9c1da22021-12-18 14:54:31 -070013from argparse import ArgumentParser
Simon Glassc6e73cf2017-06-01 19:39:03 -060014import collections
Simon Glassb3464eb2021-12-18 14:54:35 -070015from contextlib import ExitStack
Simon Glassbb57be72021-12-18 08:09:45 -070016import doctest
Masahiro Yamada0f6beda2016-05-19 15:52:07 +090017import filecmp
Masahiro Yamadab6160812015-05-20 11:36:07 +090018import fnmatch
Masahiro Yamada3984d6e2016-10-19 14:39:54 +090019import glob
Masahiro Yamadab6160812015-05-20 11:36:07 +090020import multiprocessing
Masahiro Yamadab6160812015-05-20 11:36:07 +090021import os
Simon Glass1f701862019-10-31 07:42:57 -060022import queue
Masahiro Yamadab6160812015-05-20 11:36:07 +090023import re
24import shutil
25import subprocess
26import sys
27import tempfile
Simon Glass43cf08f2017-06-01 19:39:02 -060028import threading
Masahiro Yamadab6160812015-05-20 11:36:07 +090029import time
Simon Glassbb57be72021-12-18 08:09:45 -070030import unittest
Masahiro Yamadab6160812015-05-20 11:36:07 +090031
Simon Glassf0d9c102020-04-17 18:09:02 -060032from buildman import bsettings
33from buildman import kconfiglib
34from buildman import toolchain
Simon Glass9b191102023-09-23 13:44:09 -060035from u_boot_pylib import terminal
Simon Glass44116332017-06-15 21:39:33 -060036
Masahiro Yamadab6160812015-05-20 11:36:07 +090037SHOW_GNU_MAKE = 'scripts/show-gnu-make'
38SLEEP_TIME=0.03
39
Masahiro Yamadab6160812015-05-20 11:36:07 +090040STATE_IDLE = 0
41STATE_DEFCONFIG = 1
42STATE_AUTOCONF = 2
Joe Hershberger166edec2015-05-19 13:21:17 -050043STATE_SAVEDEFCONFIG = 3
Masahiro Yamadab6160812015-05-20 11:36:07 +090044
Simon Glass8fb5bd02017-06-01 19:39:01 -060045AUTO_CONF_PATH = 'include/config/auto.conf'
Simon Glass8a372032023-09-23 13:44:15 -060046CONFIG_DATABASE = 'qconfig.db'
47FAILED_LIST = 'qconfig.failed'
Simon Glass8fb5bd02017-06-01 19:39:01 -060048
Simon Glass44116332017-06-15 21:39:33 -060049CONFIG_LEN = len('CONFIG_')
Simon Glass8fb5bd02017-06-01 19:39:01 -060050
Markus Klotzbuecher3d5d4182019-05-15 15:15:52 +020051SIZES = {
Simon Glassdc634d92021-12-18 14:54:30 -070052 'SZ_1': 0x00000001, 'SZ_2': 0x00000002,
53 'SZ_4': 0x00000004, 'SZ_8': 0x00000008,
54 'SZ_16': 0x00000010, 'SZ_32': 0x00000020,
55 'SZ_64': 0x00000040, 'SZ_128': 0x00000080,
56 'SZ_256': 0x00000100, 'SZ_512': 0x00000200,
57 'SZ_1K': 0x00000400, 'SZ_2K': 0x00000800,
58 'SZ_4K': 0x00001000, 'SZ_8K': 0x00002000,
59 'SZ_16K': 0x00004000, 'SZ_32K': 0x00008000,
60 'SZ_64K': 0x00010000, 'SZ_128K': 0x00020000,
61 'SZ_256K': 0x00040000, 'SZ_512K': 0x00080000,
62 'SZ_1M': 0x00100000, 'SZ_2M': 0x00200000,
63 'SZ_4M': 0x00400000, 'SZ_8M': 0x00800000,
64 'SZ_16M': 0x01000000, 'SZ_32M': 0x02000000,
65 'SZ_64M': 0x04000000, 'SZ_128M': 0x08000000,
66 'SZ_256M': 0x10000000, 'SZ_512M': 0x20000000,
67 'SZ_1G': 0x40000000, 'SZ_2G': 0x80000000,
68 'SZ_4G': 0x100000000
Markus Klotzbuecher3d5d4182019-05-15 15:15:52 +020069}
70
Simon Glasse8037552022-02-08 11:49:45 -070071RE_REMOVE_DEFCONFIG = re.compile(r'(.*)_defconfig')
72
Simon Glass4c4eb7c2023-02-01 13:19:12 -070073# CONFIG symbols present in the build system (from Linux) but not actually used
74# in U-Boot; KCONFIG symbols
75IGNORE_SYMS = ['DEBUG_SECTION_MISMATCH', 'FTRACE_MCOUNT_RECORD', 'GCOV_KERNEL',
76 'GCOV_PROFILE_ALL', 'KALLSYMS', 'KASAN', 'MODVERSIONS', 'SHELL',
77 'TPL_BUILD', 'VPL_BUILD', 'IS_ENABLED', 'FOO', 'IF_ENABLED_INT',
78 'IS_ENABLED_', 'IS_ENABLED_1', 'IS_ENABLED_2', 'IS_ENABLED_3',
79 'SPL_', 'TPL_', 'SPL_FOO', 'TPL_FOO', 'TOOLS_FOO',
80 'ACME', 'SPL_ACME', 'TPL_ACME', 'TRACE_BRANCH_PROFILING',
81 'VAL', '_UNDEFINED', 'SPL_BUILD', ]
82
83SPL_PREFIXES = ['SPL_', 'TPL_', 'VPL_', 'TOOLS_']
84
Masahiro Yamadab6160812015-05-20 11:36:07 +090085### helper functions ###
Masahiro Yamadab6160812015-05-20 11:36:07 +090086def check_top_directory():
87 """Exit if we are not at the top of source directory."""
Simon Glassb3464eb2021-12-18 14:54:35 -070088 for fname in 'README', 'Licenses':
89 if not os.path.exists(fname):
Masahiro Yamadab6160812015-05-20 11:36:07 +090090 sys.exit('Please run at the top of source directory.')
91
Masahiro Yamada990e6772016-05-19 15:51:54 +090092def check_clean_directory():
93 """Exit if the source tree is not clean."""
Simon Glassb3464eb2021-12-18 14:54:35 -070094 for fname in '.config', 'include/config':
95 if os.path.exists(fname):
Masahiro Yamada990e6772016-05-19 15:51:54 +090096 sys.exit("source tree is not clean, please run 'make mrproper'")
97
Masahiro Yamadab6160812015-05-20 11:36:07 +090098def get_make_cmd():
99 """Get the command name of GNU Make.
100
101 U-Boot needs GNU Make for building, but the command name is not
102 necessarily "make". (for example, "gmake" on FreeBSD).
103 Returns the most appropriate command name on your system.
104 """
Simon Glassb3464eb2021-12-18 14:54:35 -0700105 with subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE) as proc:
106 ret = proc.communicate()
107 if proc.returncode:
108 sys.exit('GNU Make not found')
Masahiro Yamadab6160812015-05-20 11:36:07 +0900109 return ret[0].rstrip()
110
Simon Glass18774bc2017-06-01 19:38:58 -0600111def get_matched_defconfig(line):
112 """Get the defconfig files that match a pattern
113
114 Args:
Simon Glassb3464eb2021-12-18 14:54:35 -0700115 line (str): Path or filename to match, e.g. 'configs/snow_defconfig' or
Simon Glass18774bc2017-06-01 19:38:58 -0600116 'k2*_defconfig'. If no directory is provided, 'configs/' is
117 prepended
118
119 Returns:
Simon Glassb3464eb2021-12-18 14:54:35 -0700120 list of str: a list of matching defconfig files
Simon Glass18774bc2017-06-01 19:38:58 -0600121 """
122 dirname = os.path.dirname(line)
123 if dirname:
124 pattern = line
125 else:
126 pattern = os.path.join('configs', line)
127 return glob.glob(pattern) + glob.glob(pattern + '_defconfig')
128
Masahiro Yamada3984d6e2016-10-19 14:39:54 +0900129def get_matched_defconfigs(defconfigs_file):
Simon Glass8f3cf312017-06-01 19:38:59 -0600130 """Get all the defconfig files that match the patterns in a file.
131
132 Args:
Simon Glassb3464eb2021-12-18 14:54:35 -0700133 defconfigs_file (str): File containing a list of defconfigs to process,
134 or '-' to read the list from stdin
Simon Glass8f3cf312017-06-01 19:38:59 -0600135
136 Returns:
Simon Glassb3464eb2021-12-18 14:54:35 -0700137 list of str: A list of paths to defconfig files, with no duplicates
Simon Glass8f3cf312017-06-01 19:38:59 -0600138 """
Masahiro Yamada3984d6e2016-10-19 14:39:54 +0900139 defconfigs = []
Simon Glassb3464eb2021-12-18 14:54:35 -0700140 with ExitStack() as stack:
141 if defconfigs_file == '-':
142 inf = sys.stdin
143 defconfigs_file = 'stdin'
144 else:
145 inf = stack.enter_context(open(defconfigs_file, encoding='utf-8'))
146 for i, line in enumerate(inf):
147 line = line.strip()
148 if not line:
149 continue # skip blank lines silently
150 if ' ' in line:
151 line = line.split(' ')[0] # handle 'git log' input
152 matched = get_matched_defconfig(line)
153 if not matched:
154 print(f"warning: {defconfigs_file}:{i + 1}: no defconfig matched '{line}'",
155 file=sys.stderr)
Masahiro Yamada3984d6e2016-10-19 14:39:54 +0900156
Simon Glassb3464eb2021-12-18 14:54:35 -0700157 defconfigs += matched
Masahiro Yamada3984d6e2016-10-19 14:39:54 +0900158
159 # use set() to drop multiple matching
Simon Glassb3464eb2021-12-18 14:54:35 -0700160 return [defconfig[len('configs') + 1:] for defconfig in set(defconfigs)]
Masahiro Yamada3984d6e2016-10-19 14:39:54 +0900161
Masahiro Yamada58175e32016-07-25 19:15:28 +0900162def get_all_defconfigs():
Simon Glassb3464eb2021-12-18 14:54:35 -0700163 """Get all the defconfig files under the configs/ directory.
164
165 Returns:
166 list of str: List of paths to defconfig files
167 """
Masahiro Yamada58175e32016-07-25 19:15:28 +0900168 defconfigs = []
Simon Glassb3464eb2021-12-18 14:54:35 -0700169 for (dirpath, _, filenames) in os.walk('configs'):
Masahiro Yamada58175e32016-07-25 19:15:28 +0900170 dirpath = dirpath[len('configs') + 1:]
171 for filename in fnmatch.filter(filenames, '*_defconfig'):
172 defconfigs.append(os.path.join(dirpath, filename))
173
174 return defconfigs
175
Simon Glassb09ae452021-12-18 14:54:33 -0700176def write_file(fname, data):
177 """Write data to a file
178
179 Args:
180 fname (str): Filename to write to
181 data (list of str): Lines to write (with or without trailing newline);
182 or str to write
183 """
184 with open(fname, 'w', encoding='utf-8') as out:
185 if isinstance(data, list):
186 for line in data:
187 print(line.rstrip('\n'), file=out)
188 else:
189 out.write(data)
190
Simon Glassaba238f2021-12-18 14:54:34 -0700191def read_file(fname, as_lines=True, skip_unicode=False):
192 """Read a file and return the contents
193
194 Args:
195 fname (str): Filename to read from
Simon Glassc89a2962023-09-23 13:43:58 -0600196 as_lines (bool): Return file contents as a list of lines
Simon Glassaba238f2021-12-18 14:54:34 -0700197 skip_unicode (bool): True to report unicode errors and continue
198
199 Returns:
200 iter of str: List of ;ines from the file with newline removed; str if
201 as_lines is False with newlines intact; or None if a unicode error
202 occurred
203
204 Raises:
205 UnicodeDecodeError: Unicode error occurred when reading
206 """
207 with open(fname, encoding='utf-8') as inf:
208 try:
209 if as_lines:
210 return [line.rstrip('\n') for line in inf.readlines()]
Simon Glasse19a9cd2023-09-23 13:44:05 -0600211 return inf.read()
Simon Glass4f6725c2023-09-23 13:44:01 -0600212 except UnicodeDecodeError as exc:
Simon Glassaba238f2021-12-18 14:54:34 -0700213 if not skip_unicode:
Simon Glassafaddc72022-02-11 13:23:22 -0700214 raise
Simon Glass4f6725c2023-09-23 13:44:01 -0600215 print(f"Failed on file '{fname}: {exc}")
Simon Glassaba238f2021-12-18 14:54:34 -0700216 return None
217
Chris Packham9d5274f2017-05-02 21:30:47 +1200218
Masahiro Yamadab6160812015-05-20 11:36:07 +0900219### classes ###
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900220class Progress:
221
222 """Progress Indicator"""
223
Simon Glassbef67362023-09-23 13:44:10 -0600224 def __init__(self, col, total):
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900225 """Create a new progress indicator.
226
Simon Glassb3464eb2021-12-18 14:54:35 -0700227 Args:
Simon Glassbef67362023-09-23 13:44:10 -0600228 color_enabled (bool): True for colour output
229 total (int): A number of defconfig files to process.
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900230 """
Simon Glassbef67362023-09-23 13:44:10 -0600231 self.col = col
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900232 self.current = 0
Simon Glassbef67362023-09-23 13:44:10 -0600233 self.good = 0
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900234 self.total = total
235
Simon Glassbef67362023-09-23 13:44:10 -0600236 def inc(self, success):
237 """Increment the number of processed defconfig files.
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900238
Simon Glassbef67362023-09-23 13:44:10 -0600239 Args:
240 success (bool): True if processing succeeded
241 """
242 self.good += success
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900243 self.current += 1
244
245 def show(self):
246 """Display the progress."""
Simon Glass99c28cd2023-09-23 13:44:08 -0600247 if self.current != self.total:
Simon Glassbef67362023-09-23 13:44:10 -0600248 line = self.col.build(self.col.GREEN, f'{self.good:5d}')
249 line += self.col.build(self.col.RED,
250 f'{self.current - self.good:5d}')
251 line += self.col.build(self.col.MAGENTA,
252 f'/{self.total - self.current}')
253 print(f'{line} \r', end='')
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900254 sys.stdout.flush()
255
Simon Glass44116332017-06-15 21:39:33 -0600256
Simon Glassa0a61602024-07-17 16:56:51 +0100257def scan_kconfig():
258 """Scan all the Kconfig files and create a Config object
Simon Glass44116332017-06-15 21:39:33 -0600259
Simon Glassa0a61602024-07-17 16:56:51 +0100260 Returns:
261 Kconfig object
262 """
263 # Define environment variables referenced from Kconfig
264 os.environ['srctree'] = os.getcwd()
265 os.environ['UBOOTVERSION'] = 'dummy'
266 os.environ['KCONFIG_OBJDIR'] = ''
267 os.environ['CC'] = 'gcc'
268 return kconfiglib.Kconfig()
Simon Glass44116332017-06-15 21:39:33 -0600269
270
Masahiro Yamadab6160812015-05-20 11:36:07 +0900271class KconfigParser:
272
273 """A parser of .config and include/autoconf.mk."""
274
275 re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
276 re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
277
Simon Glass08d148a2023-09-23 13:43:54 -0600278 def __init__(self, args, build_dir):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900279 """Create a new parser.
280
Simon Glassb3464eb2021-12-18 14:54:35 -0700281 Args:
Simon Glassb3464eb2021-12-18 14:54:35 -0700282 args (Namespace): program arguments
Masahiro Yamadab6160812015-05-20 11:36:07 +0900283 build_dir: Build directory.
284 """
Simon Glassd9c1da22021-12-18 14:54:31 -0700285 self.args = args
Masahiro Yamada5393b612016-05-19 15:52:00 +0900286 self.dotconfig = os.path.join(build_dir, '.config')
287 self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
Masahiro Yamada6d139172016-08-22 22:18:22 +0900288 self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
289 'autoconf.mk')
Simon Glass8fb5bd02017-06-01 19:39:01 -0600290 self.config_autoconf = os.path.join(build_dir, AUTO_CONF_PATH)
Masahiro Yamada07f98522016-05-19 15:52:06 +0900291 self.defconfig = os.path.join(build_dir, 'defconfig')
Masahiro Yamadab6160812015-05-20 11:36:07 +0900292
Simon Glass257f5232017-07-10 14:47:47 -0600293 def get_arch(self):
294 """Parse .config file and return the architecture.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900295
296 Returns:
Simon Glass257f5232017-07-10 14:47:47 -0600297 Architecture name (e.g. 'arm').
Masahiro Yamadab6160812015-05-20 11:36:07 +0900298 """
299 arch = ''
300 cpu = ''
Simon Glassaba238f2021-12-18 14:54:34 -0700301 for line in read_file(self.dotconfig):
Simon Glass4f6725c2023-09-23 13:44:01 -0600302 m_arch = self.re_arch.match(line)
303 if m_arch:
304 arch = m_arch.group(1)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900305 continue
Simon Glass4f6725c2023-09-23 13:44:01 -0600306 m_cpu = self.re_cpu.match(line)
307 if m_cpu:
308 cpu = m_cpu.group(1)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900309
Masahiro Yamadac4d76eb2016-05-19 15:51:53 +0900310 if not arch:
311 return None
Masahiro Yamadab6160812015-05-20 11:36:07 +0900312
313 # fix-up for aarch64
314 if arch == 'arm' and cpu == 'armv8':
315 arch = 'aarch64'
316
Simon Glass257f5232017-07-10 14:47:47 -0600317 return arch
Masahiro Yamadab6160812015-05-20 11:36:07 +0900318
Simon Glass43cf08f2017-06-01 19:39:02 -0600319
320class DatabaseThread(threading.Thread):
321 """This thread processes results from Slot threads.
322
323 It collects the data in the master config directary. There is only one
324 result thread, and this helps to serialise the build output.
325 """
326 def __init__(self, config_db, db_queue):
327 """Set up a new result thread
328
329 Args:
330 builder: Builder which will be sent each result
331 """
332 threading.Thread.__init__(self)
333 self.config_db = config_db
334 self.db_queue= db_queue
335
336 def run(self):
337 """Called to start up the result thread.
338
339 We collect the next result job and pass it on to the build.
340 """
341 while True:
342 defconfig, configs = self.db_queue.get()
343 self.config_db[defconfig] = configs
344 self.db_queue.task_done()
345
346
Masahiro Yamadab6160812015-05-20 11:36:07 +0900347class Slot:
348
349 """A slot to store a subprocess.
350
351 Each instance of this class handles one subprocess.
352 This class is useful to control multiple threads
353 for faster processing.
354 """
355
Simon Glass9b191102023-09-23 13:44:09 -0600356 def __init__(self, toolchains, args, progress, devnull, make_cmd,
357 reference_src_dir, db_queue, col):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900358 """Create a new process slot.
359
Simon Glassb3464eb2021-12-18 14:54:35 -0700360 Args:
Simon Glass257f5232017-07-10 14:47:47 -0600361 toolchains: Toolchains object containing toolchains.
Simon Glassd9c1da22021-12-18 14:54:31 -0700362 args: Program arguments
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900363 progress: A progress indicator.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900364 devnull: A file object of '/dev/null'.
365 make_cmd: command name of GNU Make.
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500366 reference_src_dir: Determine the true starting config state from this
367 source tree.
Simon Glass43cf08f2017-06-01 19:39:02 -0600368 db_queue: output queue to write config info for the database
Simon Glass9b191102023-09-23 13:44:09 -0600369 col (terminal.Color): Colour object
Masahiro Yamadab6160812015-05-20 11:36:07 +0900370 """
Simon Glass257f5232017-07-10 14:47:47 -0600371 self.toolchains = toolchains
Simon Glassd9c1da22021-12-18 14:54:31 -0700372 self.args = args
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900373 self.progress = progress
Masahiro Yamadab6160812015-05-20 11:36:07 +0900374 self.build_dir = tempfile.mkdtemp()
375 self.devnull = devnull
376 self.make_cmd = (make_cmd, 'O=' + self.build_dir)
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500377 self.reference_src_dir = reference_src_dir
Simon Glass43cf08f2017-06-01 19:39:02 -0600378 self.db_queue = db_queue
Simon Glass9b191102023-09-23 13:44:09 -0600379 self.col = col
Simon Glass08d148a2023-09-23 13:43:54 -0600380 self.parser = KconfigParser(args, self.build_dir)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900381 self.state = STATE_IDLE
Masahiro Yamada1271b672016-08-22 22:18:20 +0900382 self.failed_boards = set()
Simon Glass67ee0112023-09-23 13:44:02 -0600383 self.defconfig = None
Simon Glasse24ac992023-09-23 13:44:07 -0600384 self.log = []
Simon Glass67ee0112023-09-23 13:44:02 -0600385 self.current_src_dir = None
386 self.proc = None
Masahiro Yamadab6160812015-05-20 11:36:07 +0900387
388 def __del__(self):
389 """Delete the working directory
390
391 This function makes sure the temporary directory is cleaned away
392 even if Python suddenly dies due to error. It should be done in here
Joe Hershberger640de872016-06-10 14:53:29 -0500393 because it is guaranteed the destructor is always invoked when the
Masahiro Yamadab6160812015-05-20 11:36:07 +0900394 instance of the class gets unreferenced.
395
396 If the subprocess is still running, wait until it finishes.
397 """
398 if self.state != STATE_IDLE:
Simon Glasse19a9cd2023-09-23 13:44:05 -0600399 while self.proc.poll() is None:
Masahiro Yamadab6160812015-05-20 11:36:07 +0900400 pass
401 shutil.rmtree(self.build_dir)
402
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900403 def add(self, defconfig):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900404 """Assign a new subprocess for defconfig and add it to the slot.
405
406 If the slot is vacant, create a new subprocess for processing the
407 given defconfig and add it to the slot. Just returns False if
408 the slot is occupied (i.e. the current subprocess is still running).
409
Simon Glassb3464eb2021-12-18 14:54:35 -0700410 Args:
Simon Glassc89a2962023-09-23 13:43:58 -0600411 defconfig (str): defconfig name.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900412
413 Returns:
414 Return True on success or False on failure
415 """
416 if self.state != STATE_IDLE:
417 return False
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900418
Masahiro Yamadab6160812015-05-20 11:36:07 +0900419 self.defconfig = defconfig
Simon Glasse24ac992023-09-23 13:44:07 -0600420 self.log = []
Masahiro Yamada8f5256a2016-06-15 14:33:52 +0900421 self.current_src_dir = self.reference_src_dir
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900422 self.do_defconfig()
Masahiro Yamadab6160812015-05-20 11:36:07 +0900423 return True
424
425 def poll(self):
426 """Check the status of the subprocess and handle it as needed.
427
428 Returns True if the slot is vacant (i.e. in idle state).
429 If the configuration is successfully finished, assign a new
430 subprocess to build include/autoconf.mk.
431 If include/autoconf.mk is generated, invoke the parser to
Masahiro Yamada263d1372016-05-19 15:52:04 +0900432 parse the .config and the include/autoconf.mk, moving
433 config options to the .config as needed.
434 If the .config was updated, run "make savedefconfig" to sync
435 it, update the original defconfig, and then set the slot back
436 to the idle state.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900437
438 Returns:
439 Return True if the subprocess is terminated, False otherwise
440 """
441 if self.state == STATE_IDLE:
442 return True
443
Simon Glasse19a9cd2023-09-23 13:44:05 -0600444 if self.proc.poll() is None:
Masahiro Yamadab6160812015-05-20 11:36:07 +0900445 return False
446
Simon Glass4f6725c2023-09-23 13:44:01 -0600447 if self.proc.poll() != 0:
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900448 self.handle_error()
449 elif self.state == STATE_DEFCONFIG:
Masahiro Yamada8f5256a2016-06-15 14:33:52 +0900450 if self.reference_src_dir and not self.current_src_dir:
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500451 self.do_savedefconfig()
452 else:
453 self.do_autoconf()
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900454 elif self.state == STATE_AUTOCONF:
Masahiro Yamada8f5256a2016-06-15 14:33:52 +0900455 if self.current_src_dir:
456 self.current_src_dir = None
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500457 self.do_defconfig()
Simon Glassd9c1da22021-12-18 14:54:31 -0700458 elif self.args.build_db:
Simon Glass43cf08f2017-06-01 19:39:02 -0600459 self.do_build_db()
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500460 else:
461 self.do_savedefconfig()
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900462 elif self.state == STATE_SAVEDEFCONFIG:
463 self.update_defconfig()
464 else:
Simon Glassdc634d92021-12-18 14:54:30 -0700465 sys.exit('Internal Error. This should not happen.')
Masahiro Yamadab6160812015-05-20 11:36:07 +0900466
Simon Glasse19a9cd2023-09-23 13:44:05 -0600467 return self.state == STATE_IDLE
Joe Hershberger166edec2015-05-19 13:21:17 -0500468
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900469 def handle_error(self):
470 """Handle error cases."""
Masahiro Yamada83c17672016-05-19 15:52:08 +0900471
Simon Glass9b191102023-09-23 13:44:09 -0600472 self.log.append(self.col.build(self.col.RED, 'Failed to process',
473 bright=True))
Simon Glassd9c1da22021-12-18 14:54:31 -0700474 if self.args.verbose:
Simon Glasse24ac992023-09-23 13:44:07 -0600475 for line in self.proc.stderr.read().decode().splitlines():
Simon Glass9b191102023-09-23 13:44:09 -0600476 self.log.append(self.col.build(self.col.CYAN, line, True))
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900477 self.finish(False)
Joe Hershberger166edec2015-05-19 13:21:17 -0500478
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900479 def do_defconfig(self):
480 """Run 'make <board>_defconfig' to create the .config file."""
Masahiro Yamada0f6beda2016-05-19 15:52:07 +0900481
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900482 cmd = list(self.make_cmd)
483 cmd.append(self.defconfig)
Simon Glass4f6725c2023-09-23 13:44:01 -0600484 self.proc = subprocess.Popen(cmd, stdout=self.devnull,
485 stderr=subprocess.PIPE,
486 cwd=self.current_src_dir)
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900487 self.state = STATE_DEFCONFIG
Masahiro Yamada0f6beda2016-05-19 15:52:07 +0900488
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900489 def do_autoconf(self):
Simon Glass8fb5bd02017-06-01 19:39:01 -0600490 """Run 'make AUTO_CONF_PATH'."""
Masahiro Yamadab6160812015-05-20 11:36:07 +0900491
Simon Glass257f5232017-07-10 14:47:47 -0600492 arch = self.parser.get_arch()
493 try:
Simon Glasse19a9cd2023-09-23 13:44:05 -0600494 tchain = self.toolchains.Select(arch)
Simon Glass257f5232017-07-10 14:47:47 -0600495 except ValueError:
Simon Glass9b191102023-09-23 13:44:09 -0600496 self.log.append(self.col.build(
497 self.col.YELLOW,
498 f"Tool chain for '{arch}' is missing: do nothing"))
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900499 self.finish(False)
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900500 return
Simon Glasse19a9cd2023-09-23 13:44:05 -0600501 env = tchain.MakeEnvironment(False)
Masahiro Yamadac4d76eb2016-05-19 15:51:53 +0900502
Masahiro Yamadab6160812015-05-20 11:36:07 +0900503 cmd = list(self.make_cmd)
Joe Hershberger765442b2015-05-19 13:21:18 -0500504 cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
Simon Glass8fb5bd02017-06-01 19:39:01 -0600505 cmd.append(AUTO_CONF_PATH)
Simon Glass4f6725c2023-09-23 13:44:01 -0600506 self.proc = subprocess.Popen(cmd, stdout=self.devnull, env=env,
507 stderr=subprocess.PIPE,
508 cwd=self.current_src_dir)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900509 self.state = STATE_AUTOCONF
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900510
Simon Glass43cf08f2017-06-01 19:39:02 -0600511 def do_build_db(self):
512 """Add the board to the database"""
513 configs = {}
Simon Glassaba238f2021-12-18 14:54:34 -0700514 for line in read_file(os.path.join(self.build_dir, AUTO_CONF_PATH)):
515 if line.startswith('CONFIG'):
516 config, value = line.split('=', 1)
517 configs[config] = value.rstrip()
Simon Glass43cf08f2017-06-01 19:39:02 -0600518 self.db_queue.put([self.defconfig, configs])
519 self.finish(True)
520
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900521 def do_savedefconfig(self):
522 """Update the .config and run 'make savedefconfig'."""
Simon Glassc1c10c22023-09-23 13:43:55 -0600523 if not self.args.force_sync:
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900524 self.finish(True)
525 return
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900526
527 cmd = list(self.make_cmd)
528 cmd.append('savedefconfig')
Simon Glass4f6725c2023-09-23 13:44:01 -0600529 self.proc = subprocess.Popen(cmd, stdout=self.devnull,
530 stderr=subprocess.PIPE)
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900531 self.state = STATE_SAVEDEFCONFIG
532
533 def update_defconfig(self):
534 """Update the input defconfig and go back to the idle state."""
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900535 orig_defconfig = os.path.join('configs', self.defconfig)
536 new_defconfig = os.path.join(self.build_dir, 'defconfig')
537 updated = not filecmp.cmp(orig_defconfig, new_defconfig)
538
539 if updated:
Simon Glass9b191102023-09-23 13:44:09 -0600540 self.log.append(
541 self.col.build(self.col.BLUE, 'defconfig updated', bright=True))
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900542
Simon Glassd9c1da22021-12-18 14:54:31 -0700543 if not self.args.dry_run and updated:
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900544 shutil.move(new_defconfig, orig_defconfig)
545 self.finish(True)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900546
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900547 def finish(self, success):
548 """Display log along with progress and go to the idle state.
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900549
Simon Glassb3464eb2021-12-18 14:54:35 -0700550 Args:
Simon Glassc89a2962023-09-23 13:43:58 -0600551 success (bool): Should be True when the defconfig was processed
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900552 successfully, or False when it fails.
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900553 """
554 # output at least 30 characters to hide the "* defconfigs out of *".
Simon Glass29790432023-09-23 13:44:11 -0600555 name = self.defconfig[:-len('_defconfig')]
Simon Glass4fd16e62023-09-23 13:44:06 -0600556 if self.log:
Simon Glasse24ac992023-09-23 13:44:07 -0600557
558 # Put the first log line on the first line
559 log = name.ljust(20) + ' ' + self.log[0]
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900560
Simon Glasse24ac992023-09-23 13:44:07 -0600561 if len(self.log) > 1:
562 log += '\n' + '\n'.join([' ' + s for s in self.log[1:]])
Simon Glass4fd16e62023-09-23 13:44:06 -0600563 # Some threads are running in parallel.
564 # Print log atomically to not mix up logs from different threads.
565 print(log, file=(sys.stdout if success else sys.stderr))
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900566
567 if not success:
Simon Glassd9c1da22021-12-18 14:54:31 -0700568 if self.args.exit_on_error:
Simon Glassdc634d92021-12-18 14:54:30 -0700569 sys.exit('Exit on error.')
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900570 # If --exit-on-error flag is not set, skip this board and continue.
571 # Record the failed board.
Simon Glass29790432023-09-23 13:44:11 -0600572 self.failed_boards.add(name)
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900573
Simon Glassbef67362023-09-23 13:44:10 -0600574 self.progress.inc(success)
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900575 self.progress.show()
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900576 self.state = STATE_IDLE
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900577
Masahiro Yamadab6160812015-05-20 11:36:07 +0900578 def get_failed_boards(self):
Masahiro Yamada1271b672016-08-22 22:18:20 +0900579 """Returns a set of failed boards (defconfigs) in this slot.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900580 """
581 return self.failed_boards
582
583class Slots:
584
585 """Controller of the array of subprocess slots."""
586
Simon Glass9b191102023-09-23 13:44:09 -0600587 def __init__(self, toolchains, args, progress, reference_src_dir, db_queue,
588 col):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900589 """Create a new slots controller.
590
Simon Glassb3464eb2021-12-18 14:54:35 -0700591 Args:
Simon Glass9b191102023-09-23 13:44:09 -0600592 toolchains (Toolchains): Toolchains object containing toolchains
593 args (Namespace): Program arguments
594 progress (Progress): A progress indicator.
595 reference_src_dir (str): Determine the true starting config state
596 from this source tree (None for none)
597 db_queue (Queue): output queue to write config info for the database
598 col (terminal.Color): Colour object
Masahiro Yamadab6160812015-05-20 11:36:07 +0900599 """
Simon Glassd9c1da22021-12-18 14:54:31 -0700600 self.args = args
Masahiro Yamadab6160812015-05-20 11:36:07 +0900601 self.slots = []
Simon Glass29790432023-09-23 13:44:11 -0600602 self.progress = progress
Simon Glass9b191102023-09-23 13:44:09 -0600603 self.col = col
Simon Glass34c505f2021-12-18 14:54:32 -0700604 devnull = subprocess.DEVNULL
Masahiro Yamadab6160812015-05-20 11:36:07 +0900605 make_cmd = get_make_cmd()
Simon Glassbeb825d2023-09-23 13:44:00 -0600606 for _ in range(args.jobs):
Simon Glass08d148a2023-09-23 13:43:54 -0600607 self.slots.append(Slot(toolchains, args, progress, devnull,
Simon Glass9b191102023-09-23 13:44:09 -0600608 make_cmd, reference_src_dir, db_queue, col))
Masahiro Yamadab6160812015-05-20 11:36:07 +0900609
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900610 def add(self, defconfig):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900611 """Add a new subprocess if a vacant slot is found.
612
Simon Glassb3464eb2021-12-18 14:54:35 -0700613 Args:
Simon Glassc89a2962023-09-23 13:43:58 -0600614 defconfig (str): defconfig name to be put into.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900615
616 Returns:
617 Return True on success or False on failure
618 """
619 for slot in self.slots:
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900620 if slot.add(defconfig):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900621 return True
622 return False
623
624 def available(self):
625 """Check if there is a vacant slot.
626
627 Returns:
628 Return True if at lease one vacant slot is found, False otherwise.
629 """
630 for slot in self.slots:
631 if slot.poll():
632 return True
633 return False
634
635 def empty(self):
636 """Check if all slots are vacant.
637
638 Returns:
639 Return True if all the slots are vacant, False otherwise.
640 """
641 ret = True
642 for slot in self.slots:
643 if not slot.poll():
644 ret = False
645 return ret
646
Simon Glass65709242023-09-23 13:44:13 -0600647 def write_failed_boards(self):
Simon Glass29790432023-09-23 13:44:11 -0600648 """Show the results of processing"""
Masahiro Yamada1271b672016-08-22 22:18:20 +0900649 boards = set()
Masahiro Yamadab6160812015-05-20 11:36:07 +0900650
651 for slot in self.slots:
Masahiro Yamada1271b672016-08-22 22:18:20 +0900652 boards |= slot.get_failed_boards()
Masahiro Yamadab6160812015-05-20 11:36:07 +0900653
Masahiro Yamada0153f032016-06-15 14:33:53 +0900654 if boards:
Simon Glass29790432023-09-23 13:44:11 -0600655 boards = '\n'.join(sorted(boards)) + '\n'
Simon Glass65709242023-09-23 13:44:13 -0600656 write_file(FAILED_LIST, boards)
657
Joe Hershbergerdade12e2015-05-19 13:21:22 -0500658
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900659class ReferenceSource:
660
661 """Reference source against which original configs should be parsed."""
662
663 def __init__(self, commit):
664 """Create a reference source directory based on a specified commit.
665
Simon Glassb3464eb2021-12-18 14:54:35 -0700666 Args:
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900667 commit: commit to git-clone
668 """
669 self.src_dir = tempfile.mkdtemp()
Simon Glassdc634d92021-12-18 14:54:30 -0700670 print('Cloning git repo to a separate work directory...')
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900671 subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
672 cwd=self.src_dir)
Simon Glass96f8f312023-09-23 13:43:59 -0600673 rev = subprocess.check_output(['git', 'rev-parse', '--short',
674 commit]).strip()
675 print(f"Checkout '{rev}' to build the original autoconf.mk.")
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900676 subprocess.check_output(['git', 'checkout', commit],
677 stderr=subprocess.STDOUT, cwd=self.src_dir)
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500678
679 def __del__(self):
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900680 """Delete the reference source directory
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500681
682 This function makes sure the temporary directory is cleaned away
683 even if Python suddenly dies due to error. It should be done in here
684 because it is guaranteed the destructor is always invoked when the
685 instance of the class gets unreferenced.
686 """
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900687 shutil.rmtree(self.src_dir)
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500688
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900689 def get_dir(self):
690 """Return the absolute path to the reference source directory."""
691
692 return self.src_dir
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500693
Simon Glass9b191102023-09-23 13:44:09 -0600694def move_config(toolchains, args, db_queue, col):
Simon Glass08d148a2023-09-23 13:43:54 -0600695 """Build database or sync config options to defconfig files.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900696
Simon Glassb3464eb2021-12-18 14:54:35 -0700697 Args:
Simon Glass9b191102023-09-23 13:44:09 -0600698 toolchains (Toolchains): Toolchains to use
699 args (Namespace): Program arguments
700 db_queue (Queue): Queue for database updates
701 col (terminal.Color): Colour object
Simon Glass65709242023-09-23 13:44:13 -0600702
703 Returns:
704 Progress: Progress indicator
Masahiro Yamadab6160812015-05-20 11:36:07 +0900705 """
Simon Glassd9c1da22021-12-18 14:54:31 -0700706 if args.git_ref:
707 reference_src = ReferenceSource(args.git_ref)
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900708 reference_src_dir = reference_src.get_dir()
709 else:
Masahiro Yamada8f5256a2016-06-15 14:33:52 +0900710 reference_src_dir = None
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500711
Simon Glassd9c1da22021-12-18 14:54:31 -0700712 if args.defconfigs:
713 defconfigs = get_matched_defconfigs(args.defconfigs)
Joe Hershbergerc6e043a2015-05-19 13:21:19 -0500714 else:
Masahiro Yamada58175e32016-07-25 19:15:28 +0900715 defconfigs = get_all_defconfigs()
Masahiro Yamadab6160812015-05-20 11:36:07 +0900716
Simon Glassbef67362023-09-23 13:44:10 -0600717 progress = Progress(col, len(defconfigs))
Simon Glass9b191102023-09-23 13:44:09 -0600718 slots = Slots(toolchains, args, progress, reference_src_dir, db_queue, col)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900719
720 # Main loop to process defconfig files:
721 # Add a new subprocess into a vacant slot.
722 # Sleep if there is no available slot.
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900723 for defconfig in defconfigs:
724 while not slots.add(defconfig):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900725 while not slots.available():
726 # No available slot: sleep for a while
727 time.sleep(SLEEP_TIME)
728
729 # wait until all the subprocesses finish
730 while not slots.empty():
731 time.sleep(SLEEP_TIME)
732
Simon Glass65709242023-09-23 13:44:13 -0600733 slots.write_failed_boards()
734 return progress
Masahiro Yamadab6160812015-05-20 11:36:07 +0900735
Simon Glass44116332017-06-15 21:39:33 -0600736def find_kconfig_rules(kconf, config, imply_config):
737 """Check whether a config has a 'select' or 'imply' keyword
738
739 Args:
Simon Glassc89a2962023-09-23 13:43:58 -0600740 kconf (Kconfiglib.Kconfig): Kconfig object
741 config (str): Name of config to check (without CONFIG_ prefix)
742 imply_config (str): Implying config (without CONFIG_ prefix) which may
743 or may not have an 'imply' for 'config')
Simon Glass44116332017-06-15 21:39:33 -0600744
745 Returns:
746 Symbol object for 'config' if found, else None
747 """
Tom Rini3c5f4152019-09-20 17:42:09 -0400748 sym = kconf.syms.get(imply_config)
Simon Glass44116332017-06-15 21:39:33 -0600749 if sym:
Simon Glassbeb825d2023-09-23 13:44:00 -0600750 for sel, _ in (sym.selects + sym.implies):
Simon Glass93c0a9e2021-12-18 08:09:42 -0700751 if sel.name == config:
Simon Glass44116332017-06-15 21:39:33 -0600752 return sym
753 return None
754
755def check_imply_rule(kconf, config, imply_config):
756 """Check if we can add an 'imply' option
757
758 This finds imply_config in the Kconfig and looks to see if it is possible
759 to add an 'imply' for 'config' to that part of the Kconfig.
760
761 Args:
Simon Glassc89a2962023-09-23 13:43:58 -0600762 kconf (Kconfiglib.Kconfig): Kconfig object
763 config (str): Name of config to check (without CONFIG_ prefix)
764 imply_config (str): Implying config (without CONFIG_ prefix) which may
765 or may not have an 'imply' for 'config')
Simon Glass44116332017-06-15 21:39:33 -0600766
767 Returns:
768 tuple:
Simon Glassc89a2962023-09-23 13:43:58 -0600769 str: filename of Kconfig file containing imply_config, or None if
770 none
771 int: line number within the Kconfig file, or 0 if none
772 str: message indicating the result
Simon Glass44116332017-06-15 21:39:33 -0600773 """
Tom Rini3c5f4152019-09-20 17:42:09 -0400774 sym = kconf.syms.get(imply_config)
Simon Glass44116332017-06-15 21:39:33 -0600775 if not sym:
776 return 'cannot find sym'
Simon Glass520b47a2021-07-21 21:35:53 -0600777 nodes = sym.nodes
778 if len(nodes) != 1:
Simon Glass96f8f312023-09-23 13:43:59 -0600779 return f'{len(nodes)} locations'
Simon Glass93c0a9e2021-12-18 08:09:42 -0700780 node = nodes[0]
781 fname, linenum = node.filename, node.linenr
Simon Glass44116332017-06-15 21:39:33 -0600782 cwd = os.getcwd()
783 if cwd and fname.startswith(cwd):
784 fname = fname[len(cwd) + 1:]
Simon Glass96f8f312023-09-23 13:43:59 -0600785 file_line = f' at {fname}:{linenum}'
Simon Glassaba238f2021-12-18 14:54:34 -0700786 data = read_file(fname)
Simon Glass96f8f312023-09-23 13:43:59 -0600787 if data[linenum - 1] != f'config {imply_config}':
788 return None, 0, f'bad sym format {data[linenum]}{file_line})'
789 return fname, linenum, f'adding{file_line}'
Simon Glass44116332017-06-15 21:39:33 -0600790
791def add_imply_rule(config, fname, linenum):
792 """Add a new 'imply' option to a Kconfig
793
794 Args:
Simon Glassc89a2962023-09-23 13:43:58 -0600795 config (str): config option to add an imply for (without CONFIG_ prefix)
796 fname (str): Kconfig filename to update
797 linenum (int): Line number to place the 'imply' before
Simon Glass44116332017-06-15 21:39:33 -0600798
799 Returns:
800 Message indicating the result
801 """
Simon Glass96f8f312023-09-23 13:43:59 -0600802 file_line = f' at {fname}:{linenum}'
Simon Glassaba238f2021-12-18 14:54:34 -0700803 data = read_file(fname)
Simon Glass44116332017-06-15 21:39:33 -0600804 linenum -= 1
805
806 for offset, line in enumerate(data[linenum:]):
807 if line.strip().startswith('help') or not line:
Simon Glass96f8f312023-09-23 13:43:59 -0600808 data.insert(linenum + offset, f'\timply {config}')
Simon Glassb09ae452021-12-18 14:54:33 -0700809 write_file(fname, data)
Simon Glass96f8f312023-09-23 13:43:59 -0600810 return f'added{file_line}'
Simon Glass44116332017-06-15 21:39:33 -0600811
812 return 'could not insert%s'
813
814(IMPLY_MIN_2, IMPLY_TARGET, IMPLY_CMD, IMPLY_NON_ARCH_BOARD) = (
815 1, 2, 4, 8)
Simon Glass92e55582017-06-15 21:39:32 -0600816
817IMPLY_FLAGS = {
818 'min2': [IMPLY_MIN_2, 'Show options which imply >2 boards (normally >5)'],
819 'target': [IMPLY_TARGET, 'Allow CONFIG_TARGET_... options to imply'],
820 'cmd': [IMPLY_CMD, 'Allow CONFIG_CMD_... to imply'],
Simon Glass44116332017-06-15 21:39:33 -0600821 'non-arch-board': [
822 IMPLY_NON_ARCH_BOARD,
823 'Allow Kconfig options outside arch/ and /board/ to imply'],
Simon Glassb3464eb2021-12-18 14:54:35 -0700824}
Simon Glass92e55582017-06-15 21:39:32 -0600825
Simon Glassf931c2f2021-12-18 08:09:43 -0700826
827def read_database():
828 """Read in the config database
829
830 Returns:
831 tuple:
832 set of all config options seen (each a str)
833 set of all defconfigs seen (each a str)
834 dict of configs for each defconfig:
835 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
836 value: dict:
837 key: CONFIG option
838 value: Value of option
839 dict of defconfigs for each config:
840 key: CONFIG option
841 value: set of boards using that option
842
843 """
844 configs = {}
845
846 # key is defconfig name, value is dict of (CONFIG_xxx, value)
847 config_db = {}
848
849 # Set of all config options we have seen
850 all_configs = set()
851
852 # Set of all defconfigs we have seen
853 all_defconfigs = set()
854
855 defconfig_db = collections.defaultdict(set)
Simon Glass3a315fa2024-07-17 16:56:49 +0100856 defconfig = None
Simon Glassaba238f2021-12-18 14:54:34 -0700857 for line in read_file(CONFIG_DATABASE):
858 line = line.rstrip()
859 if not line: # Separator between defconfigs
860 config_db[defconfig] = configs
861 all_defconfigs.add(defconfig)
862 configs = {}
863 elif line[0] == ' ': # CONFIG line
864 config, value = line.strip().split('=', 1)
865 configs[config] = value
866 defconfig_db[config].add(defconfig)
867 all_configs.add(config)
868 else: # New defconfig
869 defconfig = line
Simon Glassf931c2f2021-12-18 08:09:43 -0700870
871 return all_configs, all_defconfigs, config_db, defconfig_db
872
873
Simon Glass44116332017-06-15 21:39:33 -0600874def do_imply_config(config_list, add_imply, imply_flags, skip_added,
875 check_kconfig=True, find_superset=False):
Simon Glassc6e73cf2017-06-01 19:39:03 -0600876 """Find CONFIG options which imply those in the list
877
878 Some CONFIG options can be implied by others and this can help to reduce
879 the size of the defconfig files. For example, CONFIG_X86 implies
880 CONFIG_CMD_IRQ, so we can put 'imply CMD_IRQ' under 'config X86' and
881 all x86 boards will have that option, avoiding adding CONFIG_CMD_IRQ to
882 each of the x86 defconfig files.
883
Simon Glassbf0e11a2023-09-23 13:44:14 -0600884 This function uses the qconfig database to find such options. It
Simon Glassc6e73cf2017-06-01 19:39:03 -0600885 displays a list of things that could possibly imply those in the list.
886 The algorithm ignores any that start with CONFIG_TARGET since these
887 typically refer to only a few defconfigs (often one). It also does not
888 display a config with less than 5 defconfigs.
889
890 The algorithm works using sets. For each target config in config_list:
891 - Get the set 'defconfigs' which use that target config
892 - For each config (from a list of all configs):
893 - Get the set 'imply_defconfig' of defconfigs which use that config
894 -
895 - If imply_defconfigs contains anything not in defconfigs then
896 this config does not imply the target config
897
898 Params:
899 config_list: List of CONFIG options to check (each a string)
Simon Glass44116332017-06-15 21:39:33 -0600900 add_imply: Automatically add an 'imply' for each config.
Simon Glass92e55582017-06-15 21:39:32 -0600901 imply_flags: Flags which control which implying configs are allowed
902 (IMPLY_...)
Simon Glass44116332017-06-15 21:39:33 -0600903 skip_added: Don't show options which already have an imply added.
904 check_kconfig: Check if implied symbols already have an 'imply' or
905 'select' for the target config, and show this information if so.
Simon Glassc6e73cf2017-06-01 19:39:03 -0600906 find_superset: True to look for configs which are a superset of those
907 already found. So for example if CONFIG_EXYNOS5 implies an option,
908 but CONFIG_EXYNOS covers a larger set of defconfigs and also
909 implies that option, this will drop the former in favour of the
910 latter. In practice this option has not proved very used.
911
912 Note the terminoloy:
913 config - a CONFIG_XXX options (a string, e.g. 'CONFIG_CMD_EEPROM')
914 defconfig - a defconfig file (a string, e.g. 'configs/snow_defconfig')
915 """
Simon Glassa0a61602024-07-17 16:56:51 +0100916 kconf = scan_kconfig() if check_kconfig else None
Simon Glass44116332017-06-15 21:39:33 -0600917 if add_imply and add_imply != 'all':
Simon Glass93c0a9e2021-12-18 08:09:42 -0700918 add_imply = add_imply.split(',')
Simon Glass44116332017-06-15 21:39:33 -0600919
Simon Glassbeb825d2023-09-23 13:44:00 -0600920 all_configs, all_defconfigs, _, defconfig_db = read_database()
Simon Glassc6e73cf2017-06-01 19:39:03 -0600921
Simon Glass93c0a9e2021-12-18 08:09:42 -0700922 # Work through each target config option in turn, independently
Simon Glassc6e73cf2017-06-01 19:39:03 -0600923 for config in config_list:
924 defconfigs = defconfig_db.get(config)
925 if not defconfigs:
Simon Glass96f8f312023-09-23 13:43:59 -0600926 print(f'{config} not found in any defconfig')
Simon Glassc6e73cf2017-06-01 19:39:03 -0600927 continue
928
929 # Get the set of defconfigs without this one (since a config cannot
930 # imply itself)
931 non_defconfigs = all_defconfigs - defconfigs
932 num_defconfigs = len(defconfigs)
Simon Glass96f8f312023-09-23 13:43:59 -0600933 print(f'{config} found in {num_defconfigs}/{len(all_configs)} defconfigs')
Simon Glassc6e73cf2017-06-01 19:39:03 -0600934
935 # This will hold the results: key=config, value=defconfigs containing it
936 imply_configs = {}
937 rest_configs = all_configs - set([config])
938
939 # Look at every possible config, except the target one
940 for imply_config in rest_configs:
Simon Glass92e55582017-06-15 21:39:32 -0600941 if 'ERRATUM' in imply_config:
Simon Glassc6e73cf2017-06-01 19:39:03 -0600942 continue
Simon Glassb3464eb2021-12-18 14:54:35 -0700943 if not imply_flags & IMPLY_CMD:
Simon Glass92e55582017-06-15 21:39:32 -0600944 if 'CONFIG_CMD' in imply_config:
945 continue
Simon Glassb3464eb2021-12-18 14:54:35 -0700946 if not imply_flags & IMPLY_TARGET:
Simon Glass92e55582017-06-15 21:39:32 -0600947 if 'CONFIG_TARGET' in imply_config:
948 continue
Simon Glassc6e73cf2017-06-01 19:39:03 -0600949
950 # Find set of defconfigs that have this config
951 imply_defconfig = defconfig_db[imply_config]
952
953 # Get the intersection of this with defconfigs containing the
954 # target config
955 common_defconfigs = imply_defconfig & defconfigs
956
957 # Get the set of defconfigs containing this config which DO NOT
958 # also contain the taret config. If this set is non-empty it means
959 # that this config affects other defconfigs as well as (possibly)
960 # the ones affected by the target config. This means it implies
961 # things we don't want to imply.
962 not_common_defconfigs = imply_defconfig & non_defconfigs
963 if not_common_defconfigs:
964 continue
965
966 # If there are common defconfigs, imply_config may be useful
967 if common_defconfigs:
968 skip = False
969 if find_superset:
Simon Glass1f701862019-10-31 07:42:57 -0600970 for prev in list(imply_configs.keys()):
Simon Glassc6e73cf2017-06-01 19:39:03 -0600971 prev_count = len(imply_configs[prev])
972 count = len(common_defconfigs)
973 if (prev_count > count and
974 (imply_configs[prev] & common_defconfigs ==
975 common_defconfigs)):
976 # skip imply_config because prev is a superset
977 skip = True
978 break
Simon Glasse19a9cd2023-09-23 13:44:05 -0600979 if count > prev_count:
Simon Glassc6e73cf2017-06-01 19:39:03 -0600980 # delete prev because imply_config is a superset
981 del imply_configs[prev]
982 if not skip:
983 imply_configs[imply_config] = common_defconfigs
984
985 # Now we have a dict imply_configs of configs which imply each config
986 # The value of each dict item is the set of defconfigs containing that
987 # config. Rank them so that we print the configs that imply the largest
988 # number of defconfigs first.
Simon Glass44116332017-06-15 21:39:33 -0600989 ranked_iconfigs = sorted(imply_configs,
Simon Glassc6e73cf2017-06-01 19:39:03 -0600990 key=lambda k: len(imply_configs[k]), reverse=True)
Simon Glass44116332017-06-15 21:39:33 -0600991 kconfig_info = ''
992 cwd = os.getcwd()
993 add_list = collections.defaultdict(list)
994 for iconfig in ranked_iconfigs:
995 num_common = len(imply_configs[iconfig])
Simon Glassc6e73cf2017-06-01 19:39:03 -0600996
997 # Don't bother if there are less than 5 defconfigs affected.
Simon Glass92e55582017-06-15 21:39:32 -0600998 if num_common < (2 if imply_flags & IMPLY_MIN_2 else 5):
Simon Glassc6e73cf2017-06-01 19:39:03 -0600999 continue
Simon Glass44116332017-06-15 21:39:33 -06001000 missing = defconfigs - imply_configs[iconfig]
Simon Glassc6e73cf2017-06-01 19:39:03 -06001001 missing_str = ', '.join(missing) if missing else 'all'
1002 missing_str = ''
Simon Glass44116332017-06-15 21:39:33 -06001003 show = True
1004 if kconf:
1005 sym = find_kconfig_rules(kconf, config[CONFIG_LEN:],
1006 iconfig[CONFIG_LEN:])
1007 kconfig_info = ''
1008 if sym:
Simon Glass520b47a2021-07-21 21:35:53 -06001009 nodes = sym.nodes
1010 if len(nodes) == 1:
1011 fname, linenum = nodes[0].filename, nodes[0].linenr
Simon Glass44116332017-06-15 21:39:33 -06001012 if cwd and fname.startswith(cwd):
1013 fname = fname[len(cwd) + 1:]
Simon Glass96f8f312023-09-23 13:43:59 -06001014 kconfig_info = f'{fname}:{linenum}'
Simon Glass44116332017-06-15 21:39:33 -06001015 if skip_added:
1016 show = False
1017 else:
Tom Rini3c5f4152019-09-20 17:42:09 -04001018 sym = kconf.syms.get(iconfig[CONFIG_LEN:])
Simon Glass44116332017-06-15 21:39:33 -06001019 fname = ''
1020 if sym:
Simon Glass520b47a2021-07-21 21:35:53 -06001021 nodes = sym.nodes
1022 if len(nodes) == 1:
1023 fname, linenum = nodes[0].filename, nodes[0].linenr
Simon Glass44116332017-06-15 21:39:33 -06001024 if cwd and fname.startswith(cwd):
1025 fname = fname[len(cwd) + 1:]
1026 in_arch_board = not sym or (fname.startswith('arch') or
1027 fname.startswith('board'))
1028 if (not in_arch_board and
Simon Glassb3464eb2021-12-18 14:54:35 -07001029 not imply_flags & IMPLY_NON_ARCH_BOARD):
Simon Glass44116332017-06-15 21:39:33 -06001030 continue
1031
1032 if add_imply and (add_imply == 'all' or
1033 iconfig in add_imply):
1034 fname, linenum, kconfig_info = (check_imply_rule(kconf,
1035 config[CONFIG_LEN:], iconfig[CONFIG_LEN:]))
1036 if fname:
1037 add_list[fname].append(linenum)
Simon Glassc6e73cf2017-06-01 19:39:03 -06001038
Simon Glass44116332017-06-15 21:39:33 -06001039 if show and kconfig_info != 'skip':
Simon Glass96f8f312023-09-23 13:43:59 -06001040 print(f'{num_common:5d} : '
1041 f'{iconfig.ljust(30):-30s}{kconfig_info:-25s} {missing_str}')
Simon Glass44116332017-06-15 21:39:33 -06001042
1043 # Having collected a list of things to add, now we add them. We process
1044 # each file from the largest line number to the smallest so that
1045 # earlier additions do not affect our line numbers. E.g. if we added an
1046 # imply at line 20 it would change the position of each line after
1047 # that.
Simon Glass1f701862019-10-31 07:42:57 -06001048 for fname, linenums in add_list.items():
Simon Glass44116332017-06-15 21:39:33 -06001049 for linenum in sorted(linenums, reverse=True):
1050 add_imply_rule(config[CONFIG_LEN:], fname, linenum)
1051
Simon Glass99f79422022-02-08 11:49:46 -07001052def defconfig_matches(configs, re_match):
1053 """Check if any CONFIG option matches a regex
1054
1055 The match must be complete, i.e. from the start to end of the CONFIG option.
1056
1057 Args:
1058 configs (dict): Dict of CONFIG options:
1059 key: CONFIG option
1060 value: Value of option
1061 re_match (re.Pattern): Match to check
1062
1063 Returns:
1064 bool: True if any CONFIG matches the regex
1065 """
1066 for cfg in configs:
Simon Glassfea71c92022-03-05 20:18:54 -07001067 if re_match.fullmatch(cfg):
Simon Glass99f79422022-02-08 11:49:46 -07001068 return True
1069 return False
Simon Glassc6e73cf2017-06-01 19:39:03 -06001070
Simon Glass0082b2e2021-12-18 08:09:46 -07001071def do_find_config(config_list):
1072 """Find boards with a given combination of CONFIGs
1073
1074 Params:
Simon Glass99f79422022-02-08 11:49:46 -07001075 config_list: List of CONFIG options to check (each a regex consisting
Simon Glass0082b2e2021-12-18 08:09:46 -07001076 of a config option, with or without a CONFIG_ prefix. If an option
1077 is preceded by a tilde (~) then it must be false, otherwise it must
1078 be true)
1079 """
Simon Glassbeb825d2023-09-23 13:44:00 -06001080 _, all_defconfigs, config_db, _ = read_database()
Simon Glass0082b2e2021-12-18 08:09:46 -07001081
Simon Glass0082b2e2021-12-18 08:09:46 -07001082 # Start with all defconfigs
1083 out = all_defconfigs
1084
1085 # Work through each config in turn
Simon Glass0082b2e2021-12-18 08:09:46 -07001086 for item in config_list:
1087 # Get the real config name and whether we want this config or not
1088 cfg = item
1089 want = True
1090 if cfg[0] == '~':
1091 want = False
1092 cfg = cfg[1:]
1093
Simon Glass0082b2e2021-12-18 08:09:46 -07001094 # Search everything that is still in the running. If it has a config
1095 # that we want, or doesn't have one that we don't, add it into the
1096 # running for the next stage
1097 in_list = out
1098 out = set()
Simon Glass99f79422022-02-08 11:49:46 -07001099 re_match = re.compile(cfg)
Simon Glass0082b2e2021-12-18 08:09:46 -07001100 for defc in in_list:
Simon Glass99f79422022-02-08 11:49:46 -07001101 has_cfg = defconfig_matches(config_db[defc], re_match)
Simon Glass0082b2e2021-12-18 08:09:46 -07001102 if has_cfg == want:
1103 out.add(defc)
Tom Rinic1b64aa2022-12-04 10:14:16 -05001104 print(f'{len(out)} matches')
1105 print(' '.join(item.split('_defconfig')[0] for item in out))
Simon Glass0082b2e2021-12-18 08:09:46 -07001106
1107
1108def prefix_config(cfg):
1109 """Prefix a config with CONFIG_ if needed
1110
1111 This handles ~ operator, which indicates that the CONFIG should be disabled
1112
1113 >>> prefix_config('FRED')
1114 'CONFIG_FRED'
1115 >>> prefix_config('CONFIG_FRED')
1116 'CONFIG_FRED'
1117 >>> prefix_config('~FRED')
1118 '~CONFIG_FRED'
1119 >>> prefix_config('~CONFIG_FRED')
1120 '~CONFIG_FRED'
1121 >>> prefix_config('A123')
1122 'CONFIG_A123'
1123 """
Simon Glass4f6725c2023-09-23 13:44:01 -06001124 oper = ''
Simon Glass0082b2e2021-12-18 08:09:46 -07001125 if cfg[0] == '~':
Simon Glass4f6725c2023-09-23 13:44:01 -06001126 oper = cfg[0]
Simon Glass0082b2e2021-12-18 08:09:46 -07001127 cfg = cfg[1:]
1128 if not cfg.startswith('CONFIG_'):
1129 cfg = 'CONFIG_' + cfg
Simon Glass4f6725c2023-09-23 13:44:01 -06001130 return oper + cfg
Simon Glass0082b2e2021-12-18 08:09:46 -07001131
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001132
Simon Glass2deee262023-09-23 13:43:57 -06001133RE_MK_CONFIGS = re.compile(r'CONFIG_(\$\(SPL_(?:TPL_)?\))?([A-Za-z0-9_]*)')
1134RE_IFDEF = re.compile(r'(ifdef|ifndef)')
1135RE_C_CONFIGS = re.compile(r'CONFIG_([A-Za-z0-9_]*)')
1136RE_CONFIG_IS = re.compile(r'CONFIG_IS_ENABLED\(([A-Za-z0-9_]*)\)')
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001137
1138class ConfigUse:
1139 def __init__(self, cfg, is_spl, fname, rest):
1140 self.cfg = cfg
1141 self.is_spl = is_spl
1142 self.fname = fname
1143 self.rest = rest
1144
1145 def __hash__(self):
1146 return hash((self.cfg, self.is_spl))
1147
1148def scan_makefiles(fnames):
1149 """Scan Makefiles looking for Kconfig options
1150
1151 Looks for uses of CONFIG options in Makefiles
1152
1153 Args:
1154 fnames (list of tuple):
1155 str: Makefile filename where the option was found
1156 str: Line of the Makefile
1157
1158 Returns:
1159 tuple:
1160 dict: all_uses
1161 key (ConfigUse): object
1162 value (list of str): matching lines
1163 dict: Uses by filename
1164 key (str): filename
1165 value (set of ConfigUse): uses in that filename
1166
1167 >>> RE_MK_CONFIGS.search('CONFIG_FRED').groups()
1168 (None, 'FRED')
1169 >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_)MARY').groups()
1170 ('$(SPL_)', 'MARY')
1171 >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_TPL_)MARY').groups()
1172 ('$(SPL_TPL_)', 'MARY')
1173 """
1174 all_uses = collections.defaultdict(list)
1175 fname_uses = {}
1176 for fname, rest in fnames:
1177 m_iter = RE_MK_CONFIGS.finditer(rest)
Simon Glass4f6725c2023-09-23 13:44:01 -06001178 for mat in m_iter:
1179 real_opt = mat.group(2)
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001180 if real_opt == '':
1181 continue
1182 is_spl = False
Simon Glass4f6725c2023-09-23 13:44:01 -06001183 if mat.group(1):
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001184 is_spl = True
1185 use = ConfigUse(real_opt, is_spl, fname, rest)
1186 if fname not in fname_uses:
1187 fname_uses[fname] = set()
1188 fname_uses[fname].add(use)
1189 all_uses[use].append(rest)
1190 return all_uses, fname_uses
1191
1192
1193def scan_src_files(fnames):
1194 """Scan source files (other than Makefiles) looking for Kconfig options
1195
1196 Looks for uses of CONFIG options
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_C_CONFIGS.search('CONFIG_FRED').groups()
1213 ('FRED',)
1214 >>> RE_CONFIG_IS.search('CONFIG_IS_ENABLED(MARY)').groups()
1215 ('MARY',)
1216 >>> RE_CONFIG_IS.search('#if CONFIG_IS_ENABLED(OF_PLATDATA)').groups()
1217 ('OF_PLATDATA',)
1218 """
Simon Glassbeb825d2023-09-23 13:44:00 -06001219 fname = None
1220 rest = None
1221
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001222 def add_uses(m_iter, is_spl):
Simon Glass4f6725c2023-09-23 13:44:01 -06001223 for mat in m_iter:
1224 real_opt = mat.group(1)
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001225 if real_opt == '':
1226 continue
1227 use = ConfigUse(real_opt, is_spl, fname, rest)
1228 if fname not in fname_uses:
1229 fname_uses[fname] = set()
1230 fname_uses[fname].add(use)
1231 all_uses[use].append(rest)
1232
1233 all_uses = collections.defaultdict(list)
1234 fname_uses = {}
1235 for fname, rest in fnames:
1236 m_iter = RE_C_CONFIGS.finditer(rest)
1237 add_uses(m_iter, False)
1238
1239 m_iter2 = RE_CONFIG_IS.finditer(rest)
1240 add_uses(m_iter2, True)
1241
1242 return all_uses, fname_uses
1243
1244
1245MODE_NORMAL, MODE_SPL, MODE_PROPER = range(3)
1246
1247def do_scan_source(path, do_update):
1248 """Scan the source tree for Kconfig inconsistencies
1249
1250 Args:
1251 path (str): Path to source tree
1252 do_update (bool) : True to write to scripts/kconf_... files
1253 """
1254 def is_not_proper(name):
1255 for prefix in SPL_PREFIXES:
1256 if name.startswith(prefix):
1257 return name[len(prefix):]
1258 return False
1259
1260 def check_not_found(all_uses, spl_mode):
1261 """Check for Kconfig options mentioned in the source but not in Kconfig
1262
1263 Args:
1264 all_uses (dict):
1265 key (ConfigUse): object
1266 value (list of str): matching lines
1267 spl_mode (int): If MODE_SPL, look at source code which implies
1268 an SPL_ option, but for which there is none;
1269 for MOD_PROPER, look at source code which implies a Proper
1270 option (i.e. use of CONFIG_IS_ENABLED() or $(SPL_) or
1271 $(SPL_TPL_) but for which there none;
1272 if MODE_NORMAL, ignore SPL
1273
1274 Returns:
1275 dict:
1276 key (str): CONFIG name (without 'CONFIG_' prefix
1277 value (list of ConfigUse): List of uses of this CONFIG
1278 """
1279 # Make sure we know about all the options
1280 not_found = collections.defaultdict(list)
Simon Glassbeb825d2023-09-23 13:44:00 -06001281 for use, _ in all_uses.items():
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001282 name = use.cfg
1283 if name in IGNORE_SYMS:
1284 continue
1285 check = True
1286
1287 if spl_mode == MODE_SPL:
1288 check = use.is_spl
1289
1290 # If it is an SPL symbol, try prepending all SPL_ prefixes to
1291 # find at least one SPL symbol
1292 if use.is_spl:
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001293 for prefix in SPL_PREFIXES:
1294 try_name = prefix + name
1295 sym = kconf.syms.get(try_name)
1296 if sym:
1297 break
1298 if not sym:
1299 not_found[f'SPL_{name}'].append(use)
1300 continue
1301 elif spl_mode == MODE_PROPER:
1302 # Try to find the Proper version of this symbol, i.e. without
1303 # the SPL_ prefix
1304 proper_name = is_not_proper(name)
1305 if proper_name:
1306 name = proper_name
1307 elif not use.is_spl:
1308 check = False
1309 else: # MODE_NORMAL
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001310 sym = kconf.syms.get(name)
1311 if not sym:
1312 proper_name = is_not_proper(name)
1313 if proper_name:
1314 name = proper_name
1315 sym = kconf.syms.get(name)
1316 if not sym:
1317 for prefix in SPL_PREFIXES:
1318 try_name = prefix + name
1319 sym = kconf.syms.get(try_name)
1320 if sym:
1321 break
1322 if not sym:
1323 not_found[name].append(use)
1324 continue
1325
1326 sym = kconf.syms.get(name)
1327 if not sym and check:
1328 not_found[name].append(use)
1329 return not_found
1330
1331 def show_uses(uses):
1332 """Show a list of uses along with their filename and code snippet
1333
1334 Args:
1335 uses (dict):
1336 key (str): CONFIG name (without 'CONFIG_' prefix
1337 value (list of ConfigUse): List of uses of this CONFIG
1338 """
1339 for name in sorted(uses):
1340 print(f'{name}: ', end='')
1341 for i, use in enumerate(uses[name]):
1342 print(f'{" " if i else ""}{use.fname}: {use.rest.strip()}')
1343
1344
1345 print('Scanning Kconfig')
Simon Glassa0a61602024-07-17 16:56:51 +01001346 kconf = scan_kconfig()
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001347 print(f'Scanning source in {path}')
1348 args = ['git', 'grep', '-E', r'IS_ENABLED|\bCONFIG']
1349 with subprocess.Popen(args, stdout=subprocess.PIPE) as proc:
Simon Glassbeb825d2023-09-23 13:44:00 -06001350 out, _ = proc.communicate()
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001351 lines = out.splitlines()
1352 re_fname = re.compile('^([^:]*):(.*)')
1353 src_list = []
1354 mk_list = []
1355 for line in lines:
1356 linestr = line.decode('utf-8')
1357 m_fname = re_fname.search(linestr)
1358 if not m_fname:
1359 continue
1360 fname, rest = m_fname.groups()
1361 dirname, leaf = os.path.split(fname)
1362 root, ext = os.path.splitext(leaf)
1363 if ext == '.autoconf':
1364 pass
1365 elif ext in ['.c', '.h', '.S', '.lds', '.dts', '.dtsi', '.asl', '.cfg',
1366 '.env', '.tmpl']:
1367 src_list.append([fname, rest])
1368 elif 'Makefile' in root or ext == '.mk':
1369 mk_list.append([fname, rest])
1370 elif ext in ['.yml', '.sh', '.py', '.awk', '.pl', '.rst', '', '.sed']:
1371 pass
1372 elif 'Kconfig' in root or 'Kbuild' in root:
1373 pass
1374 elif 'README' in root:
1375 pass
1376 elif dirname in ['configs']:
1377 pass
1378 elif dirname.startswith('doc') or dirname.startswith('scripts/kconfig'):
1379 pass
1380 else:
1381 print(f'Not sure how to handle file {fname}')
1382
1383 # Scan the Makefiles
Simon Glassbeb825d2023-09-23 13:44:00 -06001384 all_uses, _ = scan_makefiles(mk_list)
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001385
1386 spl_not_found = set()
1387 proper_not_found = set()
1388
1389 # Make sure we know about all the options
1390 print('\nCONFIG options present in Makefiles but not Kconfig:')
1391 not_found = check_not_found(all_uses, MODE_NORMAL)
1392 show_uses(not_found)
1393
1394 print('\nCONFIG options present in Makefiles but not Kconfig (SPL):')
1395 not_found = check_not_found(all_uses, MODE_SPL)
1396 show_uses(not_found)
Simon Glassd708cf72023-09-23 13:44:03 -06001397 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001398
1399 print('\nCONFIG options used as Proper in Makefiles but without a non-SPL_ variant:')
1400 not_found = check_not_found(all_uses, MODE_PROPER)
1401 show_uses(not_found)
Simon Glassd708cf72023-09-23 13:44:03 -06001402 proper_not_found |= {not_found.keys()}
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001403
1404 # Scan the source code
Simon Glassbeb825d2023-09-23 13:44:00 -06001405 all_uses, _ = scan_src_files(src_list)
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001406
1407 # Make sure we know about all the options
1408 print('\nCONFIG options present in source but not Kconfig:')
1409 not_found = check_not_found(all_uses, MODE_NORMAL)
1410 show_uses(not_found)
1411
1412 print('\nCONFIG options present in source but not Kconfig (SPL):')
1413 not_found = check_not_found(all_uses, MODE_SPL)
1414 show_uses(not_found)
Simon Glassd708cf72023-09-23 13:44:03 -06001415 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001416
1417 print('\nCONFIG options used as Proper in source but without a non-SPL_ variant:')
1418 not_found = check_not_found(all_uses, MODE_PROPER)
1419 show_uses(not_found)
Simon Glassd708cf72023-09-23 13:44:03 -06001420 proper_not_found |= {not_found.keys()}
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001421
1422 print('\nCONFIG options used as SPL but without an SPL_ variant:')
1423 for item in sorted(spl_not_found):
1424 print(f' {item}')
1425
1426 print('\nCONFIG options used as Proper but without a non-SPL_ variant:')
1427 for item in sorted(proper_not_found):
1428 print(f' {item}')
1429
1430 # Write out the updated information
1431 if do_update:
Simon Glass47f22892023-09-23 13:44:04 -06001432 with open(os.path.join(path, 'scripts', 'conf_nospl'), 'w',
1433 encoding='utf-8') as out:
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001434 print('# These options should not be enabled in SPL builds\n',
1435 file=out)
1436 for item in sorted(spl_not_found):
1437 print(item, file=out)
Simon Glass47f22892023-09-23 13:44:04 -06001438 with open(os.path.join(path, 'scripts', 'conf_noproper'), 'w',
1439 encoding='utf-8') as out:
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001440 print('# These options should not be enabled in Proper builds\n',
1441 file=out)
1442 for item in sorted(proper_not_found):
1443 print(item, file=out)
1444
Simon Glass0082b2e2021-12-18 08:09:46 -07001445
Masahiro Yamadab6160812015-05-20 11:36:07 +09001446def main():
1447 try:
1448 cpu_count = multiprocessing.cpu_count()
1449 except NotImplementedError:
1450 cpu_count = 1
1451
Simon Glassd9c1da22021-12-18 14:54:31 -07001452 epilog = '''Move config options from headers to defconfig files. See
1453doc/develop/moveconfig.rst for documentation.'''
1454
1455 parser = ArgumentParser(epilog=epilog)
1456 # Add arguments here
1457 parser.add_argument('-a', '--add-imply', type=str, default='',
Simon Glass44116332017-06-15 21:39:33 -06001458 help='comma-separated list of CONFIG options to add '
1459 "an 'imply' statement to for the CONFIG in -i")
Simon Glassd9c1da22021-12-18 14:54:31 -07001460 parser.add_argument('-A', '--skip-added', action='store_true', default=False,
Simon Glass44116332017-06-15 21:39:33 -06001461 help="don't show options which are already marked as "
1462 'implying others')
Simon Glassd9c1da22021-12-18 14:54:31 -07001463 parser.add_argument('-b', '--build-db', action='store_true', default=False,
Simon Glass43cf08f2017-06-01 19:39:02 -06001464 help='build a CONFIG database')
Simon Glassd9c1da22021-12-18 14:54:31 -07001465 parser.add_argument('-C', '--commit', action='store_true', default=False,
Simon Glass8bf41c22016-09-12 23:18:21 -06001466 help='Create a git commit for the operation')
Simon Glass9b191102023-09-23 13:44:09 -06001467 parser.add_argument('--nocolour', action='store_true', default=False,
1468 help="don't display the log in colour")
Simon Glassd9c1da22021-12-18 14:54:31 -07001469 parser.add_argument('-d', '--defconfigs', type=str,
Simon Glass8f3cf312017-06-01 19:38:59 -06001470 help='a file containing a list of defconfigs to move, '
1471 "one per line (for example 'snow_defconfig') "
1472 "or '-' to read from stdin")
Simon Glassd9c1da22021-12-18 14:54:31 -07001473 parser.add_argument('-e', '--exit-on-error', action='store_true',
Masahiro Yamadab6160812015-05-20 11:36:07 +09001474 default=False,
1475 help='exit immediately on any error')
Simon Glassd9c1da22021-12-18 14:54:31 -07001476 parser.add_argument('-f', '--find', action='store_true', default=False,
Simon Glass0082b2e2021-12-18 08:09:46 -07001477 help='Find boards with a given config combination')
Simon Glassd9c1da22021-12-18 14:54:31 -07001478 parser.add_argument('-i', '--imply', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001479 help='find options which imply others')
Simon Glassd9c1da22021-12-18 14:54:31 -07001480 parser.add_argument('-I', '--imply-flags', type=str, default='',
Simon Glass0559a742021-12-18 08:09:44 -07001481 help="control the -i option ('help' for help")
Simon Glassd9c1da22021-12-18 14:54:31 -07001482 parser.add_argument('-j', '--jobs', type=int, default=cpu_count,
Masahiro Yamadab6160812015-05-20 11:36:07 +09001483 help='the number of jobs to run simultaneously')
Simon Glassd9c1da22021-12-18 14:54:31 -07001484 parser.add_argument('-n', '--dry-run', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001485 help='perform a trial run (show log with no changes)')
Simon Glassd9c1da22021-12-18 14:54:31 -07001486 parser.add_argument('-r', '--git-ref', type=str,
Joe Hershbergerb1a570f2016-06-10 14:53:32 -05001487 help='the git ref to clone for building the autoconf.mk')
Simon Glassd9c1da22021-12-18 14:54:31 -07001488 parser.add_argument('-s', '--force-sync', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001489 help='force sync by savedefconfig')
Simon Glassd9c1da22021-12-18 14:54:31 -07001490 parser.add_argument('-S', '--spl', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001491 help='parse config options defined for SPL build')
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001492 parser.add_argument('--scan-source', action='store_true', default=False,
1493 help='scan source for uses of CONFIG options')
Simon Glassd9c1da22021-12-18 14:54:31 -07001494 parser.add_argument('-t', '--test', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001495 help='run unit tests')
Simon Glassd9c1da22021-12-18 14:54:31 -07001496 parser.add_argument('-y', '--yes', action='store_true', default=False,
Simon Glass13e05a02016-09-12 23:18:20 -06001497 help="respond 'yes' to any prompts")
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001498 parser.add_argument('-u', '--update', action='store_true', default=False,
1499 help="update scripts/ files (use with --scan-source)")
Simon Glassd9c1da22021-12-18 14:54:31 -07001500 parser.add_argument('-v', '--verbose', action='store_true', default=False,
Joe Hershberger808b63f2015-05-19 13:21:24 -05001501 help='show any build errors as boards are built')
Simon Glassd9c1da22021-12-18 14:54:31 -07001502 parser.add_argument('configs', nargs='*')
Masahiro Yamadab6160812015-05-20 11:36:07 +09001503
Simon Glassd9c1da22021-12-18 14:54:31 -07001504 args = parser.parse_args()
Masahiro Yamadab6160812015-05-20 11:36:07 +09001505
Simon Glassd9c1da22021-12-18 14:54:31 -07001506 if args.test:
Simon Glassbb57be72021-12-18 08:09:45 -07001507 sys.argv = [sys.argv[0]]
Simon Glassbeb825d2023-09-23 13:44:00 -06001508 fail, _ = doctest.testmod()
Simon Glassbb57be72021-12-18 08:09:45 -07001509 if fail:
1510 return 1
1511 unittest.main()
1512
Simon Glass9b191102023-09-23 13:44:09 -06001513 col = terminal.Color(terminal.COLOR_NEVER if args.nocolour
1514 else terminal.COLOR_IF_TERMINAL)
1515
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001516 if args.scan_source:
1517 do_scan_source(os.getcwd(), args.update)
Simon Glasse19a9cd2023-09-23 13:44:05 -06001518 return 0
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001519
Simon Glass08d148a2023-09-23 13:43:54 -06001520 if not any((args.force_sync, args.build_db, args.imply, args.find)):
Masahiro Yamadab6160812015-05-20 11:36:07 +09001521 parser.print_usage()
1522 sys.exit(1)
1523
Masahiro Yamadab903c4e2016-05-19 15:51:58 +09001524 # prefix the option name with CONFIG_ if missing
Simon Glass08d148a2023-09-23 13:43:54 -06001525 configs = [prefix_config(cfg) for cfg in args.configs]
Masahiro Yamadab6160812015-05-20 11:36:07 +09001526
Joe Hershberger23475932015-05-19 13:21:20 -05001527 check_top_directory()
1528
Simon Glassd9c1da22021-12-18 14:54:31 -07001529 if args.imply:
Simon Glass92e55582017-06-15 21:39:32 -06001530 imply_flags = 0
Simon Glassd9c1da22021-12-18 14:54:31 -07001531 if args.imply_flags == 'all':
Simon Glass5f096922017-07-10 14:47:46 -06001532 imply_flags = -1
1533
Simon Glassd9c1da22021-12-18 14:54:31 -07001534 elif args.imply_flags:
1535 for flag in args.imply_flags.split(','):
Simon Glass5f096922017-07-10 14:47:46 -06001536 bad = flag not in IMPLY_FLAGS
1537 if bad:
Simon Glass96f8f312023-09-23 13:43:59 -06001538 print(f"Invalid flag '{flag}'")
Simon Glass5f096922017-07-10 14:47:46 -06001539 if flag == 'help' or bad:
Simon Glass1f701862019-10-31 07:42:57 -06001540 print("Imply flags: (separate with ',')")
1541 for name, info in IMPLY_FLAGS.items():
Simon Glass96f8f312023-09-23 13:43:59 -06001542 print(f' {name:-15s}: {info[1]}')
Simon Glass5f096922017-07-10 14:47:46 -06001543 parser.print_usage()
1544 sys.exit(1)
1545 imply_flags |= IMPLY_FLAGS[flag][0]
Simon Glass92e55582017-06-15 21:39:32 -06001546
Simon Glassd9c1da22021-12-18 14:54:31 -07001547 do_imply_config(configs, args.add_imply, imply_flags, args.skip_added)
Simon Glasse19a9cd2023-09-23 13:44:05 -06001548 return 0
Simon Glassc6e73cf2017-06-01 19:39:03 -06001549
Simon Glassd9c1da22021-12-18 14:54:31 -07001550 if args.find:
Simon Glass0082b2e2021-12-18 08:09:46 -07001551 do_find_config(configs)
Simon Glasse19a9cd2023-09-23 13:44:05 -06001552 return 0
Simon Glass0082b2e2021-12-18 08:09:46 -07001553
Simon Glass35688b62023-09-23 13:43:53 -06001554 # We are either building the database or forcing a sync of defconfigs
Simon Glass43cf08f2017-06-01 19:39:02 -06001555 config_db = {}
Simon Glass1f701862019-10-31 07:42:57 -06001556 db_queue = queue.Queue()
Simon Glass4f6725c2023-09-23 13:44:01 -06001557 dbt = DatabaseThread(config_db, db_queue)
1558 dbt.daemon = True
1559 dbt.start()
Simon Glass43cf08f2017-06-01 19:39:02 -06001560
Simon Glass1572b3f2023-09-23 13:43:50 -06001561 check_clean_directory()
1562 bsettings.setup('')
1563 toolchains = toolchain.Toolchains()
1564 toolchains.GetSettings()
1565 toolchains.Scan(verbose=False)
Simon Glass65709242023-09-23 13:44:13 -06001566 progress = move_config(toolchains, args, db_queue, col)
Simon Glass1572b3f2023-09-23 13:43:50 -06001567 db_queue.join()
Joe Hershberger23475932015-05-19 13:21:20 -05001568
Simon Glassd9c1da22021-12-18 14:54:31 -07001569 if args.commit:
Simon Glass8bf41c22016-09-12 23:18:21 -06001570 subprocess.call(['git', 'add', '-u'])
1571 if configs:
1572 msg = 'Convert %s %sto Kconfig' % (configs[0],
1573 'et al ' if len(configs) > 1 else '')
1574 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' %
1575 '\n '.join(configs))
1576 else:
1577 msg = 'configs: Resync with savedefconfig'
1578 msg += '\n\nRsync all defconfig files using moveconfig.py'
1579 subprocess.call(['git', 'commit', '-s', '-m', msg])
1580
Simon Glass65709242023-09-23 13:44:13 -06001581 failed = progress.total - progress.good
1582 failure = f'{failed} failed, ' if failed else ''
Simon Glassd9c1da22021-12-18 14:54:31 -07001583 if args.build_db:
Simon Glass4f6725c2023-09-23 13:44:01 -06001584 with open(CONFIG_DATABASE, 'w', encoding='utf-8') as outf:
Simon Glass1f701862019-10-31 07:42:57 -06001585 for defconfig, configs in config_db.items():
Simon Glass4f6725c2023-09-23 13:44:01 -06001586 outf.write(f'{defconfig}\n')
Simon Glass43cf08f2017-06-01 19:39:02 -06001587 for config in sorted(configs.keys()):
Simon Glass4f6725c2023-09-23 13:44:01 -06001588 outf.write(f' {config}={configs[config]}\n')
1589 outf.write('\n')
Simon Glass65709242023-09-23 13:44:13 -06001590 print(col.build(
1591 col.RED if failed else col.GREEN,
1592 f'{failure}{len(config_db)} boards written to {CONFIG_DATABASE}'))
1593 else:
1594 if failed:
1595 print(col.build(col.RED, f'{failure}see {FAILED_LIST}', True))
1596 else:
1597 # Add enough spaces to overwrite the progress indicator
1598 print(col.build(
1599 col.GREEN, f'{progress.total} processed ', bright=True))
1600
Simon Glasse19a9cd2023-09-23 13:44:05 -06001601 return 0
1602
Simon Glass43cf08f2017-06-01 19:39:02 -06001603
Masahiro Yamadab6160812015-05-20 11:36:07 +09001604if __name__ == '__main__':
Simon Glass0082b2e2021-12-18 08:09:46 -07001605 sys.exit(main())