blob: 2492b37444a3e0294a84e91272a87ded33eb9e81 [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#
4# Author: Masahiro Yamada <yamada.masahiro@socionext.com>
5#
Masahiro Yamada5a27c732015-05-20 11:36:07 +09006
7"""
Simon Glassea4d6de2023-09-23 13:44:14 -06008Build and query a Kconfig database for boards.
Masahiro Yamada5a27c732015-05-20 11:36:07 +09009
Simon Glass5c72c0e2021-07-21 21:35:51 -060010See doc/develop/moveconfig.rst for documentation.
Masahiro Yamada5a27c732015-05-20 11:36:07 +090011"""
12
Simon Glassb2e83c62021-12-18 14:54:31 -070013from argparse import ArgumentParser
Simon Glass99b66602017-06-01 19:39:03 -060014import collections
Simon Glass91197aa2021-12-18 14:54:35 -070015from contextlib import ExitStack
Simon Glass84067a52021-12-18 08:09:45 -070016import doctest
Masahiro Yamadac8e1b102016-05-19 15:52:07 +090017import filecmp
Masahiro Yamada5a27c732015-05-20 11:36:07 +090018import fnmatch
Masahiro Yamada0dbc9b52016-10-19 14:39:54 +090019import glob
Masahiro Yamada5a27c732015-05-20 11:36:07 +090020import multiprocessing
Masahiro Yamada5a27c732015-05-20 11:36:07 +090021import os
Simon Glass793dca32019-10-31 07:42:57 -060022import queue
Masahiro Yamada5a27c732015-05-20 11:36:07 +090023import re
24import shutil
25import subprocess
26import sys
27import tempfile
Simon Glassd73fcb12017-06-01 19:39:02 -060028import threading
Masahiro Yamada5a27c732015-05-20 11:36:07 +090029import time
Simon Glass84067a52021-12-18 08:09:45 -070030import unittest
Masahiro Yamada5a27c732015-05-20 11:36:07 +090031
Simon Glassb5aa5a32023-09-23 13:43:52 -060032import asteval
Simon Glass0ede00f2020-04-17 18:09:02 -060033from buildman import bsettings
34from buildman import kconfiglib
35from buildman import toolchain
Simon Glass15f19ab2023-09-23 13:44:09 -060036from u_boot_pylib import terminal
Simon Glasscb008832017-06-15 21:39:33 -060037
Masahiro Yamada5a27c732015-05-20 11:36:07 +090038SHOW_GNU_MAKE = 'scripts/show-gnu-make'
39SLEEP_TIME=0.03
40
Masahiro Yamada5a27c732015-05-20 11:36:07 +090041STATE_IDLE = 0
42STATE_DEFCONFIG = 1
43STATE_AUTOCONF = 2
Joe Hershberger96464ba2015-05-19 13:21:17 -050044STATE_SAVEDEFCONFIG = 3
Masahiro Yamada5a27c732015-05-20 11:36:07 +090045
Simon Glassf3b8e642017-06-01 19:39:01 -060046AUTO_CONF_PATH = 'include/config/auto.conf'
Simon Glass51963792023-09-23 13:44:15 -060047CONFIG_DATABASE = 'qconfig.db'
48FAILED_LIST = 'qconfig.failed'
Simon Glassf3b8e642017-06-01 19:39:01 -060049
Simon Glasscb008832017-06-15 21:39:33 -060050CONFIG_LEN = len('CONFIG_')
Simon Glassf3b8e642017-06-01 19:39:01 -060051
Markus Klotzbuecherb237d352019-05-15 15:15:52 +020052SIZES = {
Simon Glassdaa694d2021-12-18 14:54:30 -070053 'SZ_1': 0x00000001, 'SZ_2': 0x00000002,
54 'SZ_4': 0x00000004, 'SZ_8': 0x00000008,
55 'SZ_16': 0x00000010, 'SZ_32': 0x00000020,
56 'SZ_64': 0x00000040, 'SZ_128': 0x00000080,
57 'SZ_256': 0x00000100, 'SZ_512': 0x00000200,
58 'SZ_1K': 0x00000400, 'SZ_2K': 0x00000800,
59 'SZ_4K': 0x00001000, 'SZ_8K': 0x00002000,
60 'SZ_16K': 0x00004000, 'SZ_32K': 0x00008000,
61 'SZ_64K': 0x00010000, 'SZ_128K': 0x00020000,
62 'SZ_256K': 0x00040000, 'SZ_512K': 0x00080000,
63 'SZ_1M': 0x00100000, 'SZ_2M': 0x00200000,
64 'SZ_4M': 0x00400000, 'SZ_8M': 0x00800000,
65 'SZ_16M': 0x01000000, 'SZ_32M': 0x02000000,
66 'SZ_64M': 0x04000000, 'SZ_128M': 0x08000000,
67 'SZ_256M': 0x10000000, 'SZ_512M': 0x20000000,
68 'SZ_1G': 0x40000000, 'SZ_2G': 0x80000000,
69 'SZ_4G': 0x100000000
Markus Klotzbuecherb237d352019-05-15 15:15:52 +020070}
71
Simon Glassb8d11da2022-02-08 11:49:45 -070072RE_REMOVE_DEFCONFIG = re.compile(r'(.*)_defconfig')
73
Simon Glass65e62032023-02-01 13:19:12 -070074# CONFIG symbols present in the build system (from Linux) but not actually used
75# in U-Boot; KCONFIG symbols
76IGNORE_SYMS = ['DEBUG_SECTION_MISMATCH', 'FTRACE_MCOUNT_RECORD', 'GCOV_KERNEL',
77 'GCOV_PROFILE_ALL', 'KALLSYMS', 'KASAN', 'MODVERSIONS', 'SHELL',
78 'TPL_BUILD', 'VPL_BUILD', 'IS_ENABLED', 'FOO', 'IF_ENABLED_INT',
79 'IS_ENABLED_', 'IS_ENABLED_1', 'IS_ENABLED_2', 'IS_ENABLED_3',
80 'SPL_', 'TPL_', 'SPL_FOO', 'TPL_FOO', 'TOOLS_FOO',
81 'ACME', 'SPL_ACME', 'TPL_ACME', 'TRACE_BRANCH_PROFILING',
82 'VAL', '_UNDEFINED', 'SPL_BUILD', ]
83
84SPL_PREFIXES = ['SPL_', 'TPL_', 'VPL_', 'TOOLS_']
85
Masahiro Yamada5a27c732015-05-20 11:36:07 +090086### helper functions ###
Masahiro Yamada5a27c732015-05-20 11:36:07 +090087def check_top_directory():
88 """Exit if we are not at the top of source directory."""
Simon Glass91197aa2021-12-18 14:54:35 -070089 for fname in 'README', 'Licenses':
90 if not os.path.exists(fname):
Masahiro Yamada5a27c732015-05-20 11:36:07 +090091 sys.exit('Please run at the top of source directory.')
92
Masahiro Yamadabd63e5b2016-05-19 15:51:54 +090093def check_clean_directory():
94 """Exit if the source tree is not clean."""
Simon Glass91197aa2021-12-18 14:54:35 -070095 for fname in '.config', 'include/config':
96 if os.path.exists(fname):
Masahiro Yamadabd63e5b2016-05-19 15:51:54 +090097 sys.exit("source tree is not clean, please run 'make mrproper'")
98
Masahiro Yamada5a27c732015-05-20 11:36:07 +090099def get_make_cmd():
100 """Get the command name of GNU Make.
101
102 U-Boot needs GNU Make for building, but the command name is not
103 necessarily "make". (for example, "gmake" on FreeBSD).
104 Returns the most appropriate command name on your system.
105 """
Simon Glass91197aa2021-12-18 14:54:35 -0700106 with subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE) as proc:
107 ret = proc.communicate()
108 if proc.returncode:
109 sys.exit('GNU Make not found')
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900110 return ret[0].rstrip()
111
Simon Glass25f978c2017-06-01 19:38:58 -0600112def get_matched_defconfig(line):
113 """Get the defconfig files that match a pattern
114
115 Args:
Simon Glass91197aa2021-12-18 14:54:35 -0700116 line (str): Path or filename to match, e.g. 'configs/snow_defconfig' or
Simon Glass25f978c2017-06-01 19:38:58 -0600117 'k2*_defconfig'. If no directory is provided, 'configs/' is
118 prepended
119
120 Returns:
Simon Glass91197aa2021-12-18 14:54:35 -0700121 list of str: a list of matching defconfig files
Simon Glass25f978c2017-06-01 19:38:58 -0600122 """
123 dirname = os.path.dirname(line)
124 if dirname:
125 pattern = line
126 else:
127 pattern = os.path.join('configs', line)
128 return glob.glob(pattern) + glob.glob(pattern + '_defconfig')
129
Masahiro Yamada0dbc9b52016-10-19 14:39:54 +0900130def get_matched_defconfigs(defconfigs_file):
Simon Glassee4e61b2017-06-01 19:38:59 -0600131 """Get all the defconfig files that match the patterns in a file.
132
133 Args:
Simon Glass91197aa2021-12-18 14:54:35 -0700134 defconfigs_file (str): File containing a list of defconfigs to process,
135 or '-' to read the list from stdin
Simon Glassee4e61b2017-06-01 19:38:59 -0600136
137 Returns:
Simon Glass91197aa2021-12-18 14:54:35 -0700138 list of str: A list of paths to defconfig files, with no duplicates
Simon Glassee4e61b2017-06-01 19:38:59 -0600139 """
Masahiro Yamada0dbc9b52016-10-19 14:39:54 +0900140 defconfigs = []
Simon Glass91197aa2021-12-18 14:54:35 -0700141 with ExitStack() as stack:
142 if defconfigs_file == '-':
143 inf = sys.stdin
144 defconfigs_file = 'stdin'
145 else:
146 inf = stack.enter_context(open(defconfigs_file, encoding='utf-8'))
147 for i, line in enumerate(inf):
148 line = line.strip()
149 if not line:
150 continue # skip blank lines silently
151 if ' ' in line:
152 line = line.split(' ')[0] # handle 'git log' input
153 matched = get_matched_defconfig(line)
154 if not matched:
155 print(f"warning: {defconfigs_file}:{i + 1}: no defconfig matched '{line}'",
156 file=sys.stderr)
Masahiro Yamada0dbc9b52016-10-19 14:39:54 +0900157
Simon Glass91197aa2021-12-18 14:54:35 -0700158 defconfigs += matched
Masahiro Yamada0dbc9b52016-10-19 14:39:54 +0900159
160 # use set() to drop multiple matching
Simon Glass91197aa2021-12-18 14:54:35 -0700161 return [defconfig[len('configs') + 1:] for defconfig in set(defconfigs)]
Masahiro Yamada0dbc9b52016-10-19 14:39:54 +0900162
Masahiro Yamada684c3062016-07-25 19:15:28 +0900163def get_all_defconfigs():
Simon Glass91197aa2021-12-18 14:54:35 -0700164 """Get all the defconfig files under the configs/ directory.
165
166 Returns:
167 list of str: List of paths to defconfig files
168 """
Masahiro Yamada684c3062016-07-25 19:15:28 +0900169 defconfigs = []
Simon Glass91197aa2021-12-18 14:54:35 -0700170 for (dirpath, _, filenames) in os.walk('configs'):
Masahiro Yamada684c3062016-07-25 19:15:28 +0900171 dirpath = dirpath[len('configs') + 1:]
172 for filename in fnmatch.filter(filenames, '*_defconfig'):
173 defconfigs.append(os.path.join(dirpath, filename))
174
175 return defconfigs
176
Simon Glass2fd85bd2021-12-18 14:54:33 -0700177def write_file(fname, data):
178 """Write data to a file
179
180 Args:
181 fname (str): Filename to write to
182 data (list of str): Lines to write (with or without trailing newline);
183 or str to write
184 """
185 with open(fname, 'w', encoding='utf-8') as out:
186 if isinstance(data, list):
187 for line in data:
188 print(line.rstrip('\n'), file=out)
189 else:
190 out.write(data)
191
Simon Glass37f815c2021-12-18 14:54:34 -0700192def read_file(fname, as_lines=True, skip_unicode=False):
193 """Read a file and return the contents
194
195 Args:
196 fname (str): Filename to read from
Simon Glass549d4222023-09-23 13:43:58 -0600197 as_lines (bool): Return file contents as a list of lines
Simon Glass37f815c2021-12-18 14:54:34 -0700198 skip_unicode (bool): True to report unicode errors and continue
199
200 Returns:
201 iter of str: List of ;ines from the file with newline removed; str if
202 as_lines is False with newlines intact; or None if a unicode error
203 occurred
204
205 Raises:
206 UnicodeDecodeError: Unicode error occurred when reading
207 """
208 with open(fname, encoding='utf-8') as inf:
209 try:
210 if as_lines:
211 return [line.rstrip('\n') for line in inf.readlines()]
Simon Glassf297ba32023-09-23 13:44:05 -0600212 return inf.read()
Simon Glassa4c9d172023-09-23 13:44:01 -0600213 except UnicodeDecodeError as exc:
Simon Glass37f815c2021-12-18 14:54:34 -0700214 if not skip_unicode:
Simon Glass68a0b712022-02-11 13:23:22 -0700215 raise
Simon Glassa4c9d172023-09-23 13:44:01 -0600216 print(f"Failed on file '{fname}: {exc}")
Simon Glass37f815c2021-12-18 14:54:34 -0700217 return None
218
Markus Klotzbuecherb237d352019-05-15 15:15:52 +0200219def try_expand(line):
220 """If value looks like an expression, try expanding it
221 Otherwise just return the existing value
222 """
223 if line.find('=') == -1:
224 return line
225
226 try:
Markus Klotzbuecherb3192f42020-02-12 20:46:44 +0100227 aeval = asteval.Interpreter( usersyms=SIZES, minimal=True )
Markus Klotzbuecherb237d352019-05-15 15:15:52 +0200228 cfg, val = re.split("=", line)
229 val= val.strip('\"')
Simon Glassdaa694d2021-12-18 14:54:30 -0700230 if re.search(r'[*+-/]|<<|SZ_+|\(([^\)]+)\)', val):
Markus Klotzbuecherb3192f42020-02-12 20:46:44 +0100231 newval = hex(aeval(val))
Simon Glass1bd43062023-09-23 13:43:59 -0600232 print(f'\tExpanded expression {val} to {newval}')
Markus Klotzbuecherb237d352019-05-15 15:15:52 +0200233 return cfg+'='+newval
234 except:
Simon Glass1bd43062023-09-23 13:43:59 -0600235 print(f'\tFailed to expand expression in {line}')
Markus Klotzbuecherb237d352019-05-15 15:15:52 +0200236
237 return line
238
Chris Packhamca438342017-05-02 21:30:47 +1200239
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900240### classes ###
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900241class Progress:
242
243 """Progress Indicator"""
244
Simon Glass6b25d212023-09-23 13:44:10 -0600245 def __init__(self, col, total):
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900246 """Create a new progress indicator.
247
Simon Glass91197aa2021-12-18 14:54:35 -0700248 Args:
Simon Glass6b25d212023-09-23 13:44:10 -0600249 color_enabled (bool): True for colour output
250 total (int): A number of defconfig files to process.
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900251 """
Simon Glass6b25d212023-09-23 13:44:10 -0600252 self.col = col
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900253 self.current = 0
Simon Glass6b25d212023-09-23 13:44:10 -0600254 self.good = 0
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900255 self.total = total
256
Simon Glass6b25d212023-09-23 13:44:10 -0600257 def inc(self, success):
258 """Increment the number of processed defconfig files.
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900259
Simon Glass6b25d212023-09-23 13:44:10 -0600260 Args:
261 success (bool): True if processing succeeded
262 """
263 self.good += success
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900264 self.current += 1
265
266 def show(self):
267 """Display the progress."""
Simon Glass95f09142023-09-23 13:44:08 -0600268 if self.current != self.total:
Simon Glass6b25d212023-09-23 13:44:10 -0600269 line = self.col.build(self.col.GREEN, f'{self.good:5d}')
270 line += self.col.build(self.col.RED,
271 f'{self.current - self.good:5d}')
272 line += self.col.build(self.col.MAGENTA,
273 f'/{self.total - self.current}')
274 print(f'{line} \r', end='')
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900275 sys.stdout.flush()
276
Simon Glasscb008832017-06-15 21:39:33 -0600277
278class KconfigScanner:
279 """Kconfig scanner."""
280
281 def __init__(self):
282 """Scan all the Kconfig files and create a Config object."""
283 # Define environment variables referenced from Kconfig
284 os.environ['srctree'] = os.getcwd()
285 os.environ['UBOOTVERSION'] = 'dummy'
286 os.environ['KCONFIG_OBJDIR'] = ''
Simon Glass65e62032023-02-01 13:19:12 -0700287 os.environ['CC'] = 'gcc'
Tom Rini65e05dd2019-09-20 17:42:09 -0400288 self.conf = kconfiglib.Kconfig()
Simon Glasscb008832017-06-15 21:39:33 -0600289
290
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900291class KconfigParser:
292
293 """A parser of .config and include/autoconf.mk."""
294
295 re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
296 re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
297
Simon Glass882c8e42023-09-23 13:43:54 -0600298 def __init__(self, args, build_dir):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900299 """Create a new parser.
300
Simon Glass91197aa2021-12-18 14:54:35 -0700301 Args:
Simon Glass91197aa2021-12-18 14:54:35 -0700302 args (Namespace): program arguments
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900303 build_dir: Build directory.
304 """
Simon Glassb2e83c62021-12-18 14:54:31 -0700305 self.args = args
Masahiro Yamada1f169922016-05-19 15:52:00 +0900306 self.dotconfig = os.path.join(build_dir, '.config')
307 self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
Masahiro Yamada07913d12016-08-22 22:18:22 +0900308 self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
309 'autoconf.mk')
Simon Glassf3b8e642017-06-01 19:39:01 -0600310 self.config_autoconf = os.path.join(build_dir, AUTO_CONF_PATH)
Masahiro Yamada5da4f852016-05-19 15:52:06 +0900311 self.defconfig = os.path.join(build_dir, 'defconfig')
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900312
Simon Glass6821a742017-07-10 14:47:47 -0600313 def get_arch(self):
314 """Parse .config file and return the architecture.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900315
316 Returns:
Simon Glass6821a742017-07-10 14:47:47 -0600317 Architecture name (e.g. 'arm').
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900318 """
319 arch = ''
320 cpu = ''
Simon Glass37f815c2021-12-18 14:54:34 -0700321 for line in read_file(self.dotconfig):
Simon Glassa4c9d172023-09-23 13:44:01 -0600322 m_arch = self.re_arch.match(line)
323 if m_arch:
324 arch = m_arch.group(1)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900325 continue
Simon Glassa4c9d172023-09-23 13:44:01 -0600326 m_cpu = self.re_cpu.match(line)
327 if m_cpu:
328 cpu = m_cpu.group(1)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900329
Masahiro Yamada90ed6cb2016-05-19 15:51:53 +0900330 if not arch:
331 return None
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900332
333 # fix-up for aarch64
334 if arch == 'arm' and cpu == 'armv8':
335 arch = 'aarch64'
336
Simon Glass6821a742017-07-10 14:47:47 -0600337 return arch
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900338
Simon Glassd73fcb12017-06-01 19:39:02 -0600339
340class DatabaseThread(threading.Thread):
341 """This thread processes results from Slot threads.
342
343 It collects the data in the master config directary. There is only one
344 result thread, and this helps to serialise the build output.
345 """
346 def __init__(self, config_db, db_queue):
347 """Set up a new result thread
348
349 Args:
350 builder: Builder which will be sent each result
351 """
352 threading.Thread.__init__(self)
353 self.config_db = config_db
354 self.db_queue= db_queue
355
356 def run(self):
357 """Called to start up the result thread.
358
359 We collect the next result job and pass it on to the build.
360 """
361 while True:
362 defconfig, configs = self.db_queue.get()
363 self.config_db[defconfig] = configs
364 self.db_queue.task_done()
365
366
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900367class Slot:
368
369 """A slot to store a subprocess.
370
371 Each instance of this class handles one subprocess.
372 This class is useful to control multiple threads
373 for faster processing.
374 """
375
Simon Glass15f19ab2023-09-23 13:44:09 -0600376 def __init__(self, toolchains, args, progress, devnull, make_cmd,
377 reference_src_dir, db_queue, col):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900378 """Create a new process slot.
379
Simon Glass91197aa2021-12-18 14:54:35 -0700380 Args:
Simon Glass6821a742017-07-10 14:47:47 -0600381 toolchains: Toolchains object containing toolchains.
Simon Glassb2e83c62021-12-18 14:54:31 -0700382 args: Program arguments
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900383 progress: A progress indicator.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900384 devnull: A file object of '/dev/null'.
385 make_cmd: command name of GNU Make.
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500386 reference_src_dir: Determine the true starting config state from this
387 source tree.
Simon Glassd73fcb12017-06-01 19:39:02 -0600388 db_queue: output queue to write config info for the database
Simon Glass15f19ab2023-09-23 13:44:09 -0600389 col (terminal.Color): Colour object
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900390 """
Simon Glass6821a742017-07-10 14:47:47 -0600391 self.toolchains = toolchains
Simon Glassb2e83c62021-12-18 14:54:31 -0700392 self.args = args
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900393 self.progress = progress
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900394 self.build_dir = tempfile.mkdtemp()
395 self.devnull = devnull
396 self.make_cmd = (make_cmd, 'O=' + self.build_dir)
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500397 self.reference_src_dir = reference_src_dir
Simon Glassd73fcb12017-06-01 19:39:02 -0600398 self.db_queue = db_queue
Simon Glass15f19ab2023-09-23 13:44:09 -0600399 self.col = col
Simon Glass882c8e42023-09-23 13:43:54 -0600400 self.parser = KconfigParser(args, self.build_dir)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900401 self.state = STATE_IDLE
Masahiro Yamada09c6c062016-08-22 22:18:20 +0900402 self.failed_boards = set()
Simon Glassa6ab4db2023-09-23 13:44:02 -0600403 self.defconfig = None
Simon Glass9461bf02023-09-23 13:44:07 -0600404 self.log = []
Simon Glassa6ab4db2023-09-23 13:44:02 -0600405 self.current_src_dir = None
406 self.proc = None
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900407
408 def __del__(self):
409 """Delete the working directory
410
411 This function makes sure the temporary directory is cleaned away
412 even if Python suddenly dies due to error. It should be done in here
Joe Hershbergerf2dae752016-06-10 14:53:29 -0500413 because it is guaranteed the destructor is always invoked when the
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900414 instance of the class gets unreferenced.
415
416 If the subprocess is still running, wait until it finishes.
417 """
418 if self.state != STATE_IDLE:
Simon Glassf297ba32023-09-23 13:44:05 -0600419 while self.proc.poll() is None:
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900420 pass
421 shutil.rmtree(self.build_dir)
422
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900423 def add(self, defconfig):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900424 """Assign a new subprocess for defconfig and add it to the slot.
425
426 If the slot is vacant, create a new subprocess for processing the
427 given defconfig and add it to the slot. Just returns False if
428 the slot is occupied (i.e. the current subprocess is still running).
429
Simon Glass91197aa2021-12-18 14:54:35 -0700430 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600431 defconfig (str): defconfig name.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900432
433 Returns:
434 Return True on success or False on failure
435 """
436 if self.state != STATE_IDLE:
437 return False
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900438
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900439 self.defconfig = defconfig
Simon Glass9461bf02023-09-23 13:44:07 -0600440 self.log = []
Masahiro Yamadaf432c332016-06-15 14:33:52 +0900441 self.current_src_dir = self.reference_src_dir
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900442 self.do_defconfig()
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900443 return True
444
445 def poll(self):
446 """Check the status of the subprocess and handle it as needed.
447
448 Returns True if the slot is vacant (i.e. in idle state).
449 If the configuration is successfully finished, assign a new
450 subprocess to build include/autoconf.mk.
451 If include/autoconf.mk is generated, invoke the parser to
Masahiro Yamada7fb0bac2016-05-19 15:52:04 +0900452 parse the .config and the include/autoconf.mk, moving
453 config options to the .config as needed.
454 If the .config was updated, run "make savedefconfig" to sync
455 it, update the original defconfig, and then set the slot back
456 to the idle state.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900457
458 Returns:
459 Return True if the subprocess is terminated, False otherwise
460 """
461 if self.state == STATE_IDLE:
462 return True
463
Simon Glassf297ba32023-09-23 13:44:05 -0600464 if self.proc.poll() is None:
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900465 return False
466
Simon Glassa4c9d172023-09-23 13:44:01 -0600467 if self.proc.poll() != 0:
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900468 self.handle_error()
469 elif self.state == STATE_DEFCONFIG:
Masahiro Yamadaf432c332016-06-15 14:33:52 +0900470 if self.reference_src_dir and not self.current_src_dir:
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500471 self.do_savedefconfig()
472 else:
473 self.do_autoconf()
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900474 elif self.state == STATE_AUTOCONF:
Masahiro Yamadaf432c332016-06-15 14:33:52 +0900475 if self.current_src_dir:
476 self.current_src_dir = None
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500477 self.do_defconfig()
Simon Glassb2e83c62021-12-18 14:54:31 -0700478 elif self.args.build_db:
Simon Glassd73fcb12017-06-01 19:39:02 -0600479 self.do_build_db()
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500480 else:
481 self.do_savedefconfig()
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900482 elif self.state == STATE_SAVEDEFCONFIG:
483 self.update_defconfig()
484 else:
Simon Glassdaa694d2021-12-18 14:54:30 -0700485 sys.exit('Internal Error. This should not happen.')
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900486
Simon Glassf297ba32023-09-23 13:44:05 -0600487 return self.state == STATE_IDLE
Joe Hershberger96464ba2015-05-19 13:21:17 -0500488
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900489 def handle_error(self):
490 """Handle error cases."""
Masahiro Yamada8513dc02016-05-19 15:52:08 +0900491
Simon Glass15f19ab2023-09-23 13:44:09 -0600492 self.log.append(self.col.build(self.col.RED, 'Failed to process',
493 bright=True))
Simon Glassb2e83c62021-12-18 14:54:31 -0700494 if self.args.verbose:
Simon Glass9461bf02023-09-23 13:44:07 -0600495 for line in self.proc.stderr.read().decode().splitlines():
Simon Glass15f19ab2023-09-23 13:44:09 -0600496 self.log.append(self.col.build(self.col.CYAN, line, True))
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900497 self.finish(False)
Joe Hershberger96464ba2015-05-19 13:21:17 -0500498
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900499 def do_defconfig(self):
500 """Run 'make <board>_defconfig' to create the .config file."""
Masahiro Yamadac8e1b102016-05-19 15:52:07 +0900501
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900502 cmd = list(self.make_cmd)
503 cmd.append(self.defconfig)
Simon Glassa4c9d172023-09-23 13:44:01 -0600504 self.proc = subprocess.Popen(cmd, stdout=self.devnull,
505 stderr=subprocess.PIPE,
506 cwd=self.current_src_dir)
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900507 self.state = STATE_DEFCONFIG
Masahiro Yamadac8e1b102016-05-19 15:52:07 +0900508
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900509 def do_autoconf(self):
Simon Glassf3b8e642017-06-01 19:39:01 -0600510 """Run 'make AUTO_CONF_PATH'."""
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900511
Simon Glass6821a742017-07-10 14:47:47 -0600512 arch = self.parser.get_arch()
513 try:
Simon Glassf297ba32023-09-23 13:44:05 -0600514 tchain = self.toolchains.Select(arch)
Simon Glass6821a742017-07-10 14:47:47 -0600515 except ValueError:
Simon Glass15f19ab2023-09-23 13:44:09 -0600516 self.log.append(self.col.build(
517 self.col.YELLOW,
518 f"Tool chain for '{arch}' is missing: do nothing"))
Masahiro Yamada4efef992016-05-19 15:52:03 +0900519 self.finish(False)
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900520 return
Simon Glassf297ba32023-09-23 13:44:05 -0600521 env = tchain.MakeEnvironment(False)
Masahiro Yamada90ed6cb2016-05-19 15:51:53 +0900522
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900523 cmd = list(self.make_cmd)
Joe Hershberger7740f652015-05-19 13:21:18 -0500524 cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
Simon Glassf3b8e642017-06-01 19:39:01 -0600525 cmd.append(AUTO_CONF_PATH)
Simon Glassa4c9d172023-09-23 13:44:01 -0600526 self.proc = subprocess.Popen(cmd, stdout=self.devnull, env=env,
527 stderr=subprocess.PIPE,
528 cwd=self.current_src_dir)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900529 self.state = STATE_AUTOCONF
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900530
Simon Glassd73fcb12017-06-01 19:39:02 -0600531 def do_build_db(self):
532 """Add the board to the database"""
533 configs = {}
Simon Glass37f815c2021-12-18 14:54:34 -0700534 for line in read_file(os.path.join(self.build_dir, AUTO_CONF_PATH)):
535 if line.startswith('CONFIG'):
536 config, value = line.split('=', 1)
537 configs[config] = value.rstrip()
Simon Glassd73fcb12017-06-01 19:39:02 -0600538 self.db_queue.put([self.defconfig, configs])
539 self.finish(True)
540
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900541 def do_savedefconfig(self):
542 """Update the .config and run 'make savedefconfig'."""
Simon Glassc7345612023-09-23 13:43:55 -0600543 if not self.args.force_sync:
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900544 self.finish(True)
545 return
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900546
547 cmd = list(self.make_cmd)
548 cmd.append('savedefconfig')
Simon Glassa4c9d172023-09-23 13:44:01 -0600549 self.proc = subprocess.Popen(cmd, stdout=self.devnull,
550 stderr=subprocess.PIPE)
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900551 self.state = STATE_SAVEDEFCONFIG
552
553 def update_defconfig(self):
554 """Update the input defconfig and go back to the idle state."""
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900555 orig_defconfig = os.path.join('configs', self.defconfig)
556 new_defconfig = os.path.join(self.build_dir, 'defconfig')
557 updated = not filecmp.cmp(orig_defconfig, new_defconfig)
558
559 if updated:
Simon Glass15f19ab2023-09-23 13:44:09 -0600560 self.log.append(
561 self.col.build(self.col.BLUE, 'defconfig updated', bright=True))
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900562
Simon Glassb2e83c62021-12-18 14:54:31 -0700563 if not self.args.dry_run and updated:
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900564 shutil.move(new_defconfig, orig_defconfig)
565 self.finish(True)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900566
Masahiro Yamada4efef992016-05-19 15:52:03 +0900567 def finish(self, success):
568 """Display log along with progress and go to the idle state.
Masahiro Yamada1d085562016-05-19 15:52:02 +0900569
Simon Glass91197aa2021-12-18 14:54:35 -0700570 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600571 success (bool): Should be True when the defconfig was processed
Masahiro Yamada4efef992016-05-19 15:52:03 +0900572 successfully, or False when it fails.
Masahiro Yamada1d085562016-05-19 15:52:02 +0900573 """
574 # output at least 30 characters to hide the "* defconfigs out of *".
Simon Glassdc1d2e62023-09-23 13:44:11 -0600575 name = self.defconfig[:-len('_defconfig')]
Simon Glass5aba58c2023-09-23 13:44:06 -0600576 if self.log:
Masahiro Yamada1d085562016-05-19 15:52:02 +0900577
Simon Glass9461bf02023-09-23 13:44:07 -0600578 # Put the first log line on the first line
579 log = name.ljust(20) + ' ' + self.log[0]
580
581 if len(self.log) > 1:
582 log += '\n' + '\n'.join([' ' + s for s in self.log[1:]])
Simon Glass5aba58c2023-09-23 13:44:06 -0600583 # Some threads are running in parallel.
584 # Print log atomically to not mix up logs from different threads.
585 print(log, file=(sys.stdout if success else sys.stderr))
Masahiro Yamada4efef992016-05-19 15:52:03 +0900586
587 if not success:
Simon Glassb2e83c62021-12-18 14:54:31 -0700588 if self.args.exit_on_error:
Simon Glassdaa694d2021-12-18 14:54:30 -0700589 sys.exit('Exit on error.')
Masahiro Yamada4efef992016-05-19 15:52:03 +0900590 # If --exit-on-error flag is not set, skip this board and continue.
591 # Record the failed board.
Simon Glassdc1d2e62023-09-23 13:44:11 -0600592 self.failed_boards.add(name)
Masahiro Yamada4efef992016-05-19 15:52:03 +0900593
Simon Glass6b25d212023-09-23 13:44:10 -0600594 self.progress.inc(success)
Masahiro Yamada1d085562016-05-19 15:52:02 +0900595 self.progress.show()
Masahiro Yamada4efef992016-05-19 15:52:03 +0900596 self.state = STATE_IDLE
Masahiro Yamada1d085562016-05-19 15:52:02 +0900597
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900598 def get_failed_boards(self):
Masahiro Yamada09c6c062016-08-22 22:18:20 +0900599 """Returns a set of failed boards (defconfigs) in this slot.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900600 """
601 return self.failed_boards
602
603class Slots:
604
605 """Controller of the array of subprocess slots."""
606
Simon Glass15f19ab2023-09-23 13:44:09 -0600607 def __init__(self, toolchains, args, progress, reference_src_dir, db_queue,
608 col):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900609 """Create a new slots controller.
610
Simon Glass91197aa2021-12-18 14:54:35 -0700611 Args:
Simon Glass15f19ab2023-09-23 13:44:09 -0600612 toolchains (Toolchains): Toolchains object containing toolchains
613 args (Namespace): Program arguments
614 progress (Progress): A progress indicator.
615 reference_src_dir (str): Determine the true starting config state
616 from this source tree (None for none)
617 db_queue (Queue): output queue to write config info for the database
618 col (terminal.Color): Colour object
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900619 """
Simon Glassb2e83c62021-12-18 14:54:31 -0700620 self.args = args
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900621 self.slots = []
Simon Glassdc1d2e62023-09-23 13:44:11 -0600622 self.progress = progress
Simon Glass15f19ab2023-09-23 13:44:09 -0600623 self.col = col
Simon Glass478920d2021-12-18 14:54:32 -0700624 devnull = subprocess.DEVNULL
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900625 make_cmd = get_make_cmd()
Simon Glass62fae4b2023-09-23 13:44:00 -0600626 for _ in range(args.jobs):
Simon Glass882c8e42023-09-23 13:43:54 -0600627 self.slots.append(Slot(toolchains, args, progress, devnull,
Simon Glass15f19ab2023-09-23 13:44:09 -0600628 make_cmd, reference_src_dir, db_queue, col))
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900629
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900630 def add(self, defconfig):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900631 """Add a new subprocess if a vacant slot is found.
632
Simon Glass91197aa2021-12-18 14:54:35 -0700633 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600634 defconfig (str): defconfig name to be put into.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900635
636 Returns:
637 Return True on success or False on failure
638 """
639 for slot in self.slots:
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900640 if slot.add(defconfig):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900641 return True
642 return False
643
644 def available(self):
645 """Check if there is a vacant slot.
646
647 Returns:
648 Return True if at lease one vacant slot is found, False otherwise.
649 """
650 for slot in self.slots:
651 if slot.poll():
652 return True
653 return False
654
655 def empty(self):
656 """Check if all slots are vacant.
657
658 Returns:
659 Return True if all the slots are vacant, False otherwise.
660 """
661 ret = True
662 for slot in self.slots:
663 if not slot.poll():
664 ret = False
665 return ret
666
Simon Glass94e2ed72023-09-23 13:44:13 -0600667 def write_failed_boards(self):
Simon Glassdc1d2e62023-09-23 13:44:11 -0600668 """Show the results of processing"""
Masahiro Yamada09c6c062016-08-22 22:18:20 +0900669 boards = set()
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900670
671 for slot in self.slots:
Masahiro Yamada09c6c062016-08-22 22:18:20 +0900672 boards |= slot.get_failed_boards()
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900673
Masahiro Yamada96dccd92016-06-15 14:33:53 +0900674 if boards:
Simon Glassdc1d2e62023-09-23 13:44:11 -0600675 boards = '\n'.join(sorted(boards)) + '\n'
Simon Glass94e2ed72023-09-23 13:44:13 -0600676 write_file(FAILED_LIST, boards)
677
Joe Hershberger2559cd82015-05-19 13:21:22 -0500678
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900679class ReferenceSource:
680
681 """Reference source against which original configs should be parsed."""
682
683 def __init__(self, commit):
684 """Create a reference source directory based on a specified commit.
685
Simon Glass91197aa2021-12-18 14:54:35 -0700686 Args:
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900687 commit: commit to git-clone
688 """
689 self.src_dir = tempfile.mkdtemp()
Simon Glassdaa694d2021-12-18 14:54:30 -0700690 print('Cloning git repo to a separate work directory...')
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900691 subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
692 cwd=self.src_dir)
Simon Glass1bd43062023-09-23 13:43:59 -0600693 rev = subprocess.check_output(['git', 'rev-parse', '--short',
694 commit]).strip()
695 print(f"Checkout '{rev}' to build the original autoconf.mk.")
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900696 subprocess.check_output(['git', 'checkout', commit],
697 stderr=subprocess.STDOUT, cwd=self.src_dir)
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500698
699 def __del__(self):
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900700 """Delete the reference source directory
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500701
702 This function makes sure the temporary directory is cleaned away
703 even if Python suddenly dies due to error. It should be done in here
704 because it is guaranteed the destructor is always invoked when the
705 instance of the class gets unreferenced.
706 """
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900707 shutil.rmtree(self.src_dir)
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500708
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900709 def get_dir(self):
710 """Return the absolute path to the reference source directory."""
711
712 return self.src_dir
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500713
Simon Glass15f19ab2023-09-23 13:44:09 -0600714def move_config(toolchains, args, db_queue, col):
Simon Glass882c8e42023-09-23 13:43:54 -0600715 """Build database or sync config options to defconfig files.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900716
Simon Glass91197aa2021-12-18 14:54:35 -0700717 Args:
Simon Glass15f19ab2023-09-23 13:44:09 -0600718 toolchains (Toolchains): Toolchains to use
719 args (Namespace): Program arguments
720 db_queue (Queue): Queue for database updates
721 col (terminal.Color): Colour object
Simon Glass94e2ed72023-09-23 13:44:13 -0600722
723 Returns:
724 Progress: Progress indicator
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900725 """
Simon Glassb2e83c62021-12-18 14:54:31 -0700726 if args.git_ref:
727 reference_src = ReferenceSource(args.git_ref)
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900728 reference_src_dir = reference_src.get_dir()
729 else:
Masahiro Yamadaf432c332016-06-15 14:33:52 +0900730 reference_src_dir = None
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500731
Simon Glassb2e83c62021-12-18 14:54:31 -0700732 if args.defconfigs:
733 defconfigs = get_matched_defconfigs(args.defconfigs)
Joe Hershberger91040e82015-05-19 13:21:19 -0500734 else:
Masahiro Yamada684c3062016-07-25 19:15:28 +0900735 defconfigs = get_all_defconfigs()
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900736
Simon Glass6b25d212023-09-23 13:44:10 -0600737 progress = Progress(col, len(defconfigs))
Simon Glass15f19ab2023-09-23 13:44:09 -0600738 slots = Slots(toolchains, args, progress, reference_src_dir, db_queue, col)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900739
740 # Main loop to process defconfig files:
741 # Add a new subprocess into a vacant slot.
742 # Sleep if there is no available slot.
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900743 for defconfig in defconfigs:
744 while not slots.add(defconfig):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900745 while not slots.available():
746 # No available slot: sleep for a while
747 time.sleep(SLEEP_TIME)
748
749 # wait until all the subprocesses finish
750 while not slots.empty():
751 time.sleep(SLEEP_TIME)
752
Simon Glass94e2ed72023-09-23 13:44:13 -0600753 slots.write_failed_boards()
754 return progress
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900755
Simon Glasscb008832017-06-15 21:39:33 -0600756def find_kconfig_rules(kconf, config, imply_config):
757 """Check whether a config has a 'select' or 'imply' keyword
758
759 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600760 kconf (Kconfiglib.Kconfig): Kconfig object
761 config (str): Name of config to check (without CONFIG_ prefix)
762 imply_config (str): Implying config (without CONFIG_ prefix) which may
763 or may not have an 'imply' for 'config')
Simon Glasscb008832017-06-15 21:39:33 -0600764
765 Returns:
766 Symbol object for 'config' if found, else None
767 """
Tom Rini65e05dd2019-09-20 17:42:09 -0400768 sym = kconf.syms.get(imply_config)
Simon Glasscb008832017-06-15 21:39:33 -0600769 if sym:
Simon Glass62fae4b2023-09-23 13:44:00 -0600770 for sel, _ in (sym.selects + sym.implies):
Simon Glassa3627082021-12-18 08:09:42 -0700771 if sel.name == config:
Simon Glasscb008832017-06-15 21:39:33 -0600772 return sym
773 return None
774
775def check_imply_rule(kconf, config, imply_config):
776 """Check if we can add an 'imply' option
777
778 This finds imply_config in the Kconfig and looks to see if it is possible
779 to add an 'imply' for 'config' to that part of the Kconfig.
780
781 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600782 kconf (Kconfiglib.Kconfig): Kconfig object
783 config (str): Name of config to check (without CONFIG_ prefix)
784 imply_config (str): Implying config (without CONFIG_ prefix) which may
785 or may not have an 'imply' for 'config')
Simon Glasscb008832017-06-15 21:39:33 -0600786
787 Returns:
788 tuple:
Simon Glass549d4222023-09-23 13:43:58 -0600789 str: filename of Kconfig file containing imply_config, or None if
790 none
791 int: line number within the Kconfig file, or 0 if none
792 str: message indicating the result
Simon Glasscb008832017-06-15 21:39:33 -0600793 """
Tom Rini65e05dd2019-09-20 17:42:09 -0400794 sym = kconf.syms.get(imply_config)
Simon Glasscb008832017-06-15 21:39:33 -0600795 if not sym:
796 return 'cannot find sym'
Simon Glassea40b202021-07-21 21:35:53 -0600797 nodes = sym.nodes
798 if len(nodes) != 1:
Simon Glass1bd43062023-09-23 13:43:59 -0600799 return f'{len(nodes)} locations'
Simon Glassa3627082021-12-18 08:09:42 -0700800 node = nodes[0]
801 fname, linenum = node.filename, node.linenr
Simon Glasscb008832017-06-15 21:39:33 -0600802 cwd = os.getcwd()
803 if cwd and fname.startswith(cwd):
804 fname = fname[len(cwd) + 1:]
Simon Glass1bd43062023-09-23 13:43:59 -0600805 file_line = f' at {fname}:{linenum}'
Simon Glass37f815c2021-12-18 14:54:34 -0700806 data = read_file(fname)
Simon Glass1bd43062023-09-23 13:43:59 -0600807 if data[linenum - 1] != f'config {imply_config}':
808 return None, 0, f'bad sym format {data[linenum]}{file_line})'
809 return fname, linenum, f'adding{file_line}'
Simon Glasscb008832017-06-15 21:39:33 -0600810
811def add_imply_rule(config, fname, linenum):
812 """Add a new 'imply' option to a Kconfig
813
814 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600815 config (str): config option to add an imply for (without CONFIG_ prefix)
816 fname (str): Kconfig filename to update
817 linenum (int): Line number to place the 'imply' before
Simon Glasscb008832017-06-15 21:39:33 -0600818
819 Returns:
820 Message indicating the result
821 """
Simon Glass1bd43062023-09-23 13:43:59 -0600822 file_line = f' at {fname}:{linenum}'
Simon Glass37f815c2021-12-18 14:54:34 -0700823 data = read_file(fname)
Simon Glasscb008832017-06-15 21:39:33 -0600824 linenum -= 1
825
826 for offset, line in enumerate(data[linenum:]):
827 if line.strip().startswith('help') or not line:
Simon Glass1bd43062023-09-23 13:43:59 -0600828 data.insert(linenum + offset, f'\timply {config}')
Simon Glass2fd85bd2021-12-18 14:54:33 -0700829 write_file(fname, data)
Simon Glass1bd43062023-09-23 13:43:59 -0600830 return f'added{file_line}'
Simon Glasscb008832017-06-15 21:39:33 -0600831
832 return 'could not insert%s'
833
834(IMPLY_MIN_2, IMPLY_TARGET, IMPLY_CMD, IMPLY_NON_ARCH_BOARD) = (
835 1, 2, 4, 8)
Simon Glass9b2a2e82017-06-15 21:39:32 -0600836
837IMPLY_FLAGS = {
838 'min2': [IMPLY_MIN_2, 'Show options which imply >2 boards (normally >5)'],
839 'target': [IMPLY_TARGET, 'Allow CONFIG_TARGET_... options to imply'],
840 'cmd': [IMPLY_CMD, 'Allow CONFIG_CMD_... to imply'],
Simon Glasscb008832017-06-15 21:39:33 -0600841 'non-arch-board': [
842 IMPLY_NON_ARCH_BOARD,
843 'Allow Kconfig options outside arch/ and /board/ to imply'],
Simon Glass91197aa2021-12-18 14:54:35 -0700844}
Simon Glass9b2a2e82017-06-15 21:39:32 -0600845
Simon Glass9d603392021-12-18 08:09:43 -0700846
847def read_database():
848 """Read in the config database
849
850 Returns:
851 tuple:
852 set of all config options seen (each a str)
853 set of all defconfigs seen (each a str)
854 dict of configs for each defconfig:
855 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
856 value: dict:
857 key: CONFIG option
858 value: Value of option
859 dict of defconfigs for each config:
860 key: CONFIG option
861 value: set of boards using that option
862
863 """
864 configs = {}
865
866 # key is defconfig name, value is dict of (CONFIG_xxx, value)
867 config_db = {}
868
869 # Set of all config options we have seen
870 all_configs = set()
871
872 # Set of all defconfigs we have seen
873 all_defconfigs = set()
874
875 defconfig_db = collections.defaultdict(set)
Simon Glass61d555d2024-07-17 16:56:49 +0100876 defconfig = None
Simon Glass37f815c2021-12-18 14:54:34 -0700877 for line in read_file(CONFIG_DATABASE):
878 line = line.rstrip()
879 if not line: # Separator between defconfigs
880 config_db[defconfig] = configs
881 all_defconfigs.add(defconfig)
882 configs = {}
883 elif line[0] == ' ': # CONFIG line
884 config, value = line.strip().split('=', 1)
885 configs[config] = value
886 defconfig_db[config].add(defconfig)
887 all_configs.add(config)
888 else: # New defconfig
889 defconfig = line
Simon Glass9d603392021-12-18 08:09:43 -0700890
891 return all_configs, all_defconfigs, config_db, defconfig_db
892
893
Simon Glasscb008832017-06-15 21:39:33 -0600894def do_imply_config(config_list, add_imply, imply_flags, skip_added,
895 check_kconfig=True, find_superset=False):
Simon Glass99b66602017-06-01 19:39:03 -0600896 """Find CONFIG options which imply those in the list
897
898 Some CONFIG options can be implied by others and this can help to reduce
899 the size of the defconfig files. For example, CONFIG_X86 implies
900 CONFIG_CMD_IRQ, so we can put 'imply CMD_IRQ' under 'config X86' and
901 all x86 boards will have that option, avoiding adding CONFIG_CMD_IRQ to
902 each of the x86 defconfig files.
903
Simon Glassea4d6de2023-09-23 13:44:14 -0600904 This function uses the qconfig database to find such options. It
Simon Glass99b66602017-06-01 19:39:03 -0600905 displays a list of things that could possibly imply those in the list.
906 The algorithm ignores any that start with CONFIG_TARGET since these
907 typically refer to only a few defconfigs (often one). It also does not
908 display a config with less than 5 defconfigs.
909
910 The algorithm works using sets. For each target config in config_list:
911 - Get the set 'defconfigs' which use that target config
912 - For each config (from a list of all configs):
913 - Get the set 'imply_defconfig' of defconfigs which use that config
914 -
915 - If imply_defconfigs contains anything not in defconfigs then
916 this config does not imply the target config
917
918 Params:
919 config_list: List of CONFIG options to check (each a string)
Simon Glasscb008832017-06-15 21:39:33 -0600920 add_imply: Automatically add an 'imply' for each config.
Simon Glass9b2a2e82017-06-15 21:39:32 -0600921 imply_flags: Flags which control which implying configs are allowed
922 (IMPLY_...)
Simon Glasscb008832017-06-15 21:39:33 -0600923 skip_added: Don't show options which already have an imply added.
924 check_kconfig: Check if implied symbols already have an 'imply' or
925 'select' for the target config, and show this information if so.
Simon Glass99b66602017-06-01 19:39:03 -0600926 find_superset: True to look for configs which are a superset of those
927 already found. So for example if CONFIG_EXYNOS5 implies an option,
928 but CONFIG_EXYNOS covers a larger set of defconfigs and also
929 implies that option, this will drop the former in favour of the
930 latter. In practice this option has not proved very used.
931
932 Note the terminoloy:
933 config - a CONFIG_XXX options (a string, e.g. 'CONFIG_CMD_EEPROM')
934 defconfig - a defconfig file (a string, e.g. 'configs/snow_defconfig')
935 """
Simon Glasscb008832017-06-15 21:39:33 -0600936 kconf = KconfigScanner().conf if check_kconfig else None
937 if add_imply and add_imply != 'all':
Simon Glassa3627082021-12-18 08:09:42 -0700938 add_imply = add_imply.split(',')
Simon Glasscb008832017-06-15 21:39:33 -0600939
Simon Glass62fae4b2023-09-23 13:44:00 -0600940 all_configs, all_defconfigs, _, defconfig_db = read_database()
Simon Glass99b66602017-06-01 19:39:03 -0600941
Simon Glassa3627082021-12-18 08:09:42 -0700942 # Work through each target config option in turn, independently
Simon Glass99b66602017-06-01 19:39:03 -0600943 for config in config_list:
944 defconfigs = defconfig_db.get(config)
945 if not defconfigs:
Simon Glass1bd43062023-09-23 13:43:59 -0600946 print(f'{config} not found in any defconfig')
Simon Glass99b66602017-06-01 19:39:03 -0600947 continue
948
949 # Get the set of defconfigs without this one (since a config cannot
950 # imply itself)
951 non_defconfigs = all_defconfigs - defconfigs
952 num_defconfigs = len(defconfigs)
Simon Glass1bd43062023-09-23 13:43:59 -0600953 print(f'{config} found in {num_defconfigs}/{len(all_configs)} defconfigs')
Simon Glass99b66602017-06-01 19:39:03 -0600954
955 # This will hold the results: key=config, value=defconfigs containing it
956 imply_configs = {}
957 rest_configs = all_configs - set([config])
958
959 # Look at every possible config, except the target one
960 for imply_config in rest_configs:
Simon Glass9b2a2e82017-06-15 21:39:32 -0600961 if 'ERRATUM' in imply_config:
Simon Glass99b66602017-06-01 19:39:03 -0600962 continue
Simon Glass91197aa2021-12-18 14:54:35 -0700963 if not imply_flags & IMPLY_CMD:
Simon Glass9b2a2e82017-06-15 21:39:32 -0600964 if 'CONFIG_CMD' in imply_config:
965 continue
Simon Glass91197aa2021-12-18 14:54:35 -0700966 if not imply_flags & IMPLY_TARGET:
Simon Glass9b2a2e82017-06-15 21:39:32 -0600967 if 'CONFIG_TARGET' in imply_config:
968 continue
Simon Glass99b66602017-06-01 19:39:03 -0600969
970 # Find set of defconfigs that have this config
971 imply_defconfig = defconfig_db[imply_config]
972
973 # Get the intersection of this with defconfigs containing the
974 # target config
975 common_defconfigs = imply_defconfig & defconfigs
976
977 # Get the set of defconfigs containing this config which DO NOT
978 # also contain the taret config. If this set is non-empty it means
979 # that this config affects other defconfigs as well as (possibly)
980 # the ones affected by the target config. This means it implies
981 # things we don't want to imply.
982 not_common_defconfigs = imply_defconfig & non_defconfigs
983 if not_common_defconfigs:
984 continue
985
986 # If there are common defconfigs, imply_config may be useful
987 if common_defconfigs:
988 skip = False
989 if find_superset:
Simon Glass793dca32019-10-31 07:42:57 -0600990 for prev in list(imply_configs.keys()):
Simon Glass99b66602017-06-01 19:39:03 -0600991 prev_count = len(imply_configs[prev])
992 count = len(common_defconfigs)
993 if (prev_count > count and
994 (imply_configs[prev] & common_defconfigs ==
995 common_defconfigs)):
996 # skip imply_config because prev is a superset
997 skip = True
998 break
Simon Glassf297ba32023-09-23 13:44:05 -0600999 if count > prev_count:
Simon Glass99b66602017-06-01 19:39:03 -06001000 # delete prev because imply_config is a superset
1001 del imply_configs[prev]
1002 if not skip:
1003 imply_configs[imply_config] = common_defconfigs
1004
1005 # Now we have a dict imply_configs of configs which imply each config
1006 # The value of each dict item is the set of defconfigs containing that
1007 # config. Rank them so that we print the configs that imply the largest
1008 # number of defconfigs first.
Simon Glasscb008832017-06-15 21:39:33 -06001009 ranked_iconfigs = sorted(imply_configs,
Simon Glass99b66602017-06-01 19:39:03 -06001010 key=lambda k: len(imply_configs[k]), reverse=True)
Simon Glasscb008832017-06-15 21:39:33 -06001011 kconfig_info = ''
1012 cwd = os.getcwd()
1013 add_list = collections.defaultdict(list)
1014 for iconfig in ranked_iconfigs:
1015 num_common = len(imply_configs[iconfig])
Simon Glass99b66602017-06-01 19:39:03 -06001016
1017 # Don't bother if there are less than 5 defconfigs affected.
Simon Glass9b2a2e82017-06-15 21:39:32 -06001018 if num_common < (2 if imply_flags & IMPLY_MIN_2 else 5):
Simon Glass99b66602017-06-01 19:39:03 -06001019 continue
Simon Glasscb008832017-06-15 21:39:33 -06001020 missing = defconfigs - imply_configs[iconfig]
Simon Glass99b66602017-06-01 19:39:03 -06001021 missing_str = ', '.join(missing) if missing else 'all'
1022 missing_str = ''
Simon Glasscb008832017-06-15 21:39:33 -06001023 show = True
1024 if kconf:
1025 sym = find_kconfig_rules(kconf, config[CONFIG_LEN:],
1026 iconfig[CONFIG_LEN:])
1027 kconfig_info = ''
1028 if sym:
Simon Glassea40b202021-07-21 21:35:53 -06001029 nodes = sym.nodes
1030 if len(nodes) == 1:
1031 fname, linenum = nodes[0].filename, nodes[0].linenr
Simon Glasscb008832017-06-15 21:39:33 -06001032 if cwd and fname.startswith(cwd):
1033 fname = fname[len(cwd) + 1:]
Simon Glass1bd43062023-09-23 13:43:59 -06001034 kconfig_info = f'{fname}:{linenum}'
Simon Glasscb008832017-06-15 21:39:33 -06001035 if skip_added:
1036 show = False
1037 else:
Tom Rini65e05dd2019-09-20 17:42:09 -04001038 sym = kconf.syms.get(iconfig[CONFIG_LEN:])
Simon Glasscb008832017-06-15 21:39:33 -06001039 fname = ''
1040 if sym:
Simon Glassea40b202021-07-21 21:35:53 -06001041 nodes = sym.nodes
1042 if len(nodes) == 1:
1043 fname, linenum = nodes[0].filename, nodes[0].linenr
Simon Glasscb008832017-06-15 21:39:33 -06001044 if cwd and fname.startswith(cwd):
1045 fname = fname[len(cwd) + 1:]
1046 in_arch_board = not sym or (fname.startswith('arch') or
1047 fname.startswith('board'))
1048 if (not in_arch_board and
Simon Glass91197aa2021-12-18 14:54:35 -07001049 not imply_flags & IMPLY_NON_ARCH_BOARD):
Simon Glasscb008832017-06-15 21:39:33 -06001050 continue
1051
1052 if add_imply and (add_imply == 'all' or
1053 iconfig in add_imply):
1054 fname, linenum, kconfig_info = (check_imply_rule(kconf,
1055 config[CONFIG_LEN:], iconfig[CONFIG_LEN:]))
1056 if fname:
1057 add_list[fname].append(linenum)
1058
1059 if show and kconfig_info != 'skip':
Simon Glass1bd43062023-09-23 13:43:59 -06001060 print(f'{num_common:5d} : '
1061 f'{iconfig.ljust(30):-30s}{kconfig_info:-25s} {missing_str}')
Simon Glasscb008832017-06-15 21:39:33 -06001062
1063 # Having collected a list of things to add, now we add them. We process
1064 # each file from the largest line number to the smallest so that
1065 # earlier additions do not affect our line numbers. E.g. if we added an
1066 # imply at line 20 it would change the position of each line after
1067 # that.
Simon Glass793dca32019-10-31 07:42:57 -06001068 for fname, linenums in add_list.items():
Simon Glasscb008832017-06-15 21:39:33 -06001069 for linenum in sorted(linenums, reverse=True):
1070 add_imply_rule(config[CONFIG_LEN:], fname, linenum)
Simon Glass99b66602017-06-01 19:39:03 -06001071
Simon Glass941671a2022-02-08 11:49:46 -07001072def defconfig_matches(configs, re_match):
1073 """Check if any CONFIG option matches a regex
1074
1075 The match must be complete, i.e. from the start to end of the CONFIG option.
1076
1077 Args:
1078 configs (dict): Dict of CONFIG options:
1079 key: CONFIG option
1080 value: Value of option
1081 re_match (re.Pattern): Match to check
1082
1083 Returns:
1084 bool: True if any CONFIG matches the regex
1085 """
1086 for cfg in configs:
Simon Glassd9c958f2022-03-05 20:18:54 -07001087 if re_match.fullmatch(cfg):
Simon Glass941671a2022-02-08 11:49:46 -07001088 return True
1089 return False
Simon Glass99b66602017-06-01 19:39:03 -06001090
Simon Glass65d7fce2021-12-18 08:09:46 -07001091def do_find_config(config_list):
1092 """Find boards with a given combination of CONFIGs
1093
1094 Params:
Simon Glass941671a2022-02-08 11:49:46 -07001095 config_list: List of CONFIG options to check (each a regex consisting
Simon Glass65d7fce2021-12-18 08:09:46 -07001096 of a config option, with or without a CONFIG_ prefix. If an option
1097 is preceded by a tilde (~) then it must be false, otherwise it must
1098 be true)
1099 """
Simon Glass62fae4b2023-09-23 13:44:00 -06001100 _, all_defconfigs, config_db, _ = read_database()
Simon Glass65d7fce2021-12-18 08:09:46 -07001101
Simon Glass65d7fce2021-12-18 08:09:46 -07001102 # Start with all defconfigs
1103 out = all_defconfigs
1104
1105 # Work through each config in turn
Simon Glass65d7fce2021-12-18 08:09:46 -07001106 for item in config_list:
1107 # Get the real config name and whether we want this config or not
1108 cfg = item
1109 want = True
1110 if cfg[0] == '~':
1111 want = False
1112 cfg = cfg[1:]
1113
Simon Glass65d7fce2021-12-18 08:09:46 -07001114 # Search everything that is still in the running. If it has a config
1115 # that we want, or doesn't have one that we don't, add it into the
1116 # running for the next stage
1117 in_list = out
1118 out = set()
Simon Glass941671a2022-02-08 11:49:46 -07001119 re_match = re.compile(cfg)
Simon Glass65d7fce2021-12-18 08:09:46 -07001120 for defc in in_list:
Simon Glass941671a2022-02-08 11:49:46 -07001121 has_cfg = defconfig_matches(config_db[defc], re_match)
Simon Glass65d7fce2021-12-18 08:09:46 -07001122 if has_cfg == want:
1123 out.add(defc)
Tom Rini9ef3ba82022-12-04 10:14:16 -05001124 print(f'{len(out)} matches')
1125 print(' '.join(item.split('_defconfig')[0] for item in out))
Simon Glass65d7fce2021-12-18 08:09:46 -07001126
1127
1128def prefix_config(cfg):
1129 """Prefix a config with CONFIG_ if needed
1130
1131 This handles ~ operator, which indicates that the CONFIG should be disabled
1132
1133 >>> prefix_config('FRED')
1134 'CONFIG_FRED'
1135 >>> prefix_config('CONFIG_FRED')
1136 'CONFIG_FRED'
1137 >>> prefix_config('~FRED')
1138 '~CONFIG_FRED'
1139 >>> prefix_config('~CONFIG_FRED')
1140 '~CONFIG_FRED'
1141 >>> prefix_config('A123')
1142 'CONFIG_A123'
1143 """
Simon Glassa4c9d172023-09-23 13:44:01 -06001144 oper = ''
Simon Glass65d7fce2021-12-18 08:09:46 -07001145 if cfg[0] == '~':
Simon Glassa4c9d172023-09-23 13:44:01 -06001146 oper = cfg[0]
Simon Glass65d7fce2021-12-18 08:09:46 -07001147 cfg = cfg[1:]
1148 if not cfg.startswith('CONFIG_'):
1149 cfg = 'CONFIG_' + cfg
Simon Glassa4c9d172023-09-23 13:44:01 -06001150 return oper + cfg
Simon Glass65d7fce2021-12-18 08:09:46 -07001151
1152
Simon Glass98275712023-09-23 13:43:57 -06001153RE_MK_CONFIGS = re.compile(r'CONFIG_(\$\(SPL_(?:TPL_)?\))?([A-Za-z0-9_]*)')
1154RE_IFDEF = re.compile(r'(ifdef|ifndef)')
1155RE_C_CONFIGS = re.compile(r'CONFIG_([A-Za-z0-9_]*)')
1156RE_CONFIG_IS = re.compile(r'CONFIG_IS_ENABLED\(([A-Za-z0-9_]*)\)')
Simon Glass65e62032023-02-01 13:19:12 -07001157
1158class ConfigUse:
1159 def __init__(self, cfg, is_spl, fname, rest):
1160 self.cfg = cfg
1161 self.is_spl = is_spl
1162 self.fname = fname
1163 self.rest = rest
1164
1165 def __hash__(self):
1166 return hash((self.cfg, self.is_spl))
1167
1168def scan_makefiles(fnames):
1169 """Scan Makefiles looking for Kconfig options
1170
1171 Looks for uses of CONFIG options in Makefiles
1172
1173 Args:
1174 fnames (list of tuple):
1175 str: Makefile filename where the option was found
1176 str: Line of the Makefile
1177
1178 Returns:
1179 tuple:
1180 dict: all_uses
1181 key (ConfigUse): object
1182 value (list of str): matching lines
1183 dict: Uses by filename
1184 key (str): filename
1185 value (set of ConfigUse): uses in that filename
1186
1187 >>> RE_MK_CONFIGS.search('CONFIG_FRED').groups()
1188 (None, 'FRED')
1189 >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_)MARY').groups()
1190 ('$(SPL_)', 'MARY')
1191 >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_TPL_)MARY').groups()
1192 ('$(SPL_TPL_)', 'MARY')
1193 """
1194 all_uses = collections.defaultdict(list)
1195 fname_uses = {}
1196 for fname, rest in fnames:
1197 m_iter = RE_MK_CONFIGS.finditer(rest)
Simon Glassa4c9d172023-09-23 13:44:01 -06001198 for mat in m_iter:
1199 real_opt = mat.group(2)
Simon Glass65e62032023-02-01 13:19:12 -07001200 if real_opt == '':
1201 continue
1202 is_spl = False
Simon Glassa4c9d172023-09-23 13:44:01 -06001203 if mat.group(1):
Simon Glass65e62032023-02-01 13:19:12 -07001204 is_spl = True
1205 use = ConfigUse(real_opt, is_spl, fname, rest)
1206 if fname not in fname_uses:
1207 fname_uses[fname] = set()
1208 fname_uses[fname].add(use)
1209 all_uses[use].append(rest)
1210 return all_uses, fname_uses
1211
1212
1213def scan_src_files(fnames):
1214 """Scan source files (other than Makefiles) looking for Kconfig options
1215
1216 Looks for uses of CONFIG options
1217
1218 Args:
1219 fnames (list of tuple):
1220 str: Makefile filename where the option was found
1221 str: Line of the Makefile
1222
1223 Returns:
1224 tuple:
1225 dict: all_uses
1226 key (ConfigUse): object
1227 value (list of str): matching lines
1228 dict: Uses by filename
1229 key (str): filename
1230 value (set of ConfigUse): uses in that filename
1231
1232 >>> RE_C_CONFIGS.search('CONFIG_FRED').groups()
1233 ('FRED',)
1234 >>> RE_CONFIG_IS.search('CONFIG_IS_ENABLED(MARY)').groups()
1235 ('MARY',)
1236 >>> RE_CONFIG_IS.search('#if CONFIG_IS_ENABLED(OF_PLATDATA)').groups()
1237 ('OF_PLATDATA',)
1238 """
Simon Glass62fae4b2023-09-23 13:44:00 -06001239 fname = None
1240 rest = None
1241
Simon Glass65e62032023-02-01 13:19:12 -07001242 def add_uses(m_iter, is_spl):
Simon Glassa4c9d172023-09-23 13:44:01 -06001243 for mat in m_iter:
1244 real_opt = mat.group(1)
Simon Glass65e62032023-02-01 13:19:12 -07001245 if real_opt == '':
1246 continue
1247 use = ConfigUse(real_opt, is_spl, fname, rest)
1248 if fname not in fname_uses:
1249 fname_uses[fname] = set()
1250 fname_uses[fname].add(use)
1251 all_uses[use].append(rest)
1252
1253 all_uses = collections.defaultdict(list)
1254 fname_uses = {}
1255 for fname, rest in fnames:
1256 m_iter = RE_C_CONFIGS.finditer(rest)
1257 add_uses(m_iter, False)
1258
1259 m_iter2 = RE_CONFIG_IS.finditer(rest)
1260 add_uses(m_iter2, True)
1261
1262 return all_uses, fname_uses
1263
1264
1265MODE_NORMAL, MODE_SPL, MODE_PROPER = range(3)
1266
1267def do_scan_source(path, do_update):
1268 """Scan the source tree for Kconfig inconsistencies
1269
1270 Args:
1271 path (str): Path to source tree
1272 do_update (bool) : True to write to scripts/kconf_... files
1273 """
1274 def is_not_proper(name):
1275 for prefix in SPL_PREFIXES:
1276 if name.startswith(prefix):
1277 return name[len(prefix):]
1278 return False
1279
1280 def check_not_found(all_uses, spl_mode):
1281 """Check for Kconfig options mentioned in the source but not in Kconfig
1282
1283 Args:
1284 all_uses (dict):
1285 key (ConfigUse): object
1286 value (list of str): matching lines
1287 spl_mode (int): If MODE_SPL, look at source code which implies
1288 an SPL_ option, but for which there is none;
1289 for MOD_PROPER, look at source code which implies a Proper
1290 option (i.e. use of CONFIG_IS_ENABLED() or $(SPL_) or
1291 $(SPL_TPL_) but for which there none;
1292 if MODE_NORMAL, ignore SPL
1293
1294 Returns:
1295 dict:
1296 key (str): CONFIG name (without 'CONFIG_' prefix
1297 value (list of ConfigUse): List of uses of this CONFIG
1298 """
1299 # Make sure we know about all the options
1300 not_found = collections.defaultdict(list)
Simon Glass62fae4b2023-09-23 13:44:00 -06001301 for use, _ in all_uses.items():
Simon Glass65e62032023-02-01 13:19:12 -07001302 name = use.cfg
1303 if name in IGNORE_SYMS:
1304 continue
1305 check = True
1306
1307 if spl_mode == MODE_SPL:
1308 check = use.is_spl
1309
1310 # If it is an SPL symbol, try prepending all SPL_ prefixes to
1311 # find at least one SPL symbol
1312 if use.is_spl:
Simon Glass65e62032023-02-01 13:19:12 -07001313 for prefix in SPL_PREFIXES:
1314 try_name = prefix + name
1315 sym = kconf.syms.get(try_name)
1316 if sym:
1317 break
1318 if not sym:
1319 not_found[f'SPL_{name}'].append(use)
1320 continue
1321 elif spl_mode == MODE_PROPER:
1322 # Try to find the Proper version of this symbol, i.e. without
1323 # the SPL_ prefix
1324 proper_name = is_not_proper(name)
1325 if proper_name:
1326 name = proper_name
1327 elif not use.is_spl:
1328 check = False
1329 else: # MODE_NORMAL
Simon Glass65e62032023-02-01 13:19:12 -07001330 sym = kconf.syms.get(name)
1331 if not sym:
1332 proper_name = is_not_proper(name)
1333 if proper_name:
1334 name = proper_name
1335 sym = kconf.syms.get(name)
1336 if not sym:
1337 for prefix in SPL_PREFIXES:
1338 try_name = prefix + name
1339 sym = kconf.syms.get(try_name)
1340 if sym:
1341 break
1342 if not sym:
1343 not_found[name].append(use)
1344 continue
1345
1346 sym = kconf.syms.get(name)
1347 if not sym and check:
1348 not_found[name].append(use)
1349 return not_found
1350
1351 def show_uses(uses):
1352 """Show a list of uses along with their filename and code snippet
1353
1354 Args:
1355 uses (dict):
1356 key (str): CONFIG name (without 'CONFIG_' prefix
1357 value (list of ConfigUse): List of uses of this CONFIG
1358 """
1359 for name in sorted(uses):
1360 print(f'{name}: ', end='')
1361 for i, use in enumerate(uses[name]):
1362 print(f'{" " if i else ""}{use.fname}: {use.rest.strip()}')
1363
1364
1365 print('Scanning Kconfig')
1366 kconf = KconfigScanner().conf
1367 print(f'Scanning source in {path}')
1368 args = ['git', 'grep', '-E', r'IS_ENABLED|\bCONFIG']
1369 with subprocess.Popen(args, stdout=subprocess.PIPE) as proc:
Simon Glass62fae4b2023-09-23 13:44:00 -06001370 out, _ = proc.communicate()
Simon Glass65e62032023-02-01 13:19:12 -07001371 lines = out.splitlines()
1372 re_fname = re.compile('^([^:]*):(.*)')
1373 src_list = []
1374 mk_list = []
1375 for line in lines:
1376 linestr = line.decode('utf-8')
1377 m_fname = re_fname.search(linestr)
1378 if not m_fname:
1379 continue
1380 fname, rest = m_fname.groups()
1381 dirname, leaf = os.path.split(fname)
1382 root, ext = os.path.splitext(leaf)
1383 if ext == '.autoconf':
1384 pass
1385 elif ext in ['.c', '.h', '.S', '.lds', '.dts', '.dtsi', '.asl', '.cfg',
1386 '.env', '.tmpl']:
1387 src_list.append([fname, rest])
1388 elif 'Makefile' in root or ext == '.mk':
1389 mk_list.append([fname, rest])
1390 elif ext in ['.yml', '.sh', '.py', '.awk', '.pl', '.rst', '', '.sed']:
1391 pass
1392 elif 'Kconfig' in root or 'Kbuild' in root:
1393 pass
1394 elif 'README' in root:
1395 pass
1396 elif dirname in ['configs']:
1397 pass
1398 elif dirname.startswith('doc') or dirname.startswith('scripts/kconfig'):
1399 pass
1400 else:
1401 print(f'Not sure how to handle file {fname}')
1402
1403 # Scan the Makefiles
Simon Glass62fae4b2023-09-23 13:44:00 -06001404 all_uses, _ = scan_makefiles(mk_list)
Simon Glass65e62032023-02-01 13:19:12 -07001405
1406 spl_not_found = set()
1407 proper_not_found = set()
1408
1409 # Make sure we know about all the options
1410 print('\nCONFIG options present in Makefiles but not Kconfig:')
1411 not_found = check_not_found(all_uses, MODE_NORMAL)
1412 show_uses(not_found)
1413
1414 print('\nCONFIG options present in Makefiles but not Kconfig (SPL):')
1415 not_found = check_not_found(all_uses, MODE_SPL)
1416 show_uses(not_found)
Simon Glassb774ba52023-09-23 13:44:03 -06001417 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
Simon Glass65e62032023-02-01 13:19:12 -07001418
1419 print('\nCONFIG options used as Proper in Makefiles but without a non-SPL_ variant:')
1420 not_found = check_not_found(all_uses, MODE_PROPER)
1421 show_uses(not_found)
Simon Glassb774ba52023-09-23 13:44:03 -06001422 proper_not_found |= {not_found.keys()}
Simon Glass65e62032023-02-01 13:19:12 -07001423
1424 # Scan the source code
Simon Glass62fae4b2023-09-23 13:44:00 -06001425 all_uses, _ = scan_src_files(src_list)
Simon Glass65e62032023-02-01 13:19:12 -07001426
1427 # Make sure we know about all the options
1428 print('\nCONFIG options present in source but not Kconfig:')
1429 not_found = check_not_found(all_uses, MODE_NORMAL)
1430 show_uses(not_found)
1431
1432 print('\nCONFIG options present in source but not Kconfig (SPL):')
1433 not_found = check_not_found(all_uses, MODE_SPL)
1434 show_uses(not_found)
Simon Glassb774ba52023-09-23 13:44:03 -06001435 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
Simon Glass65e62032023-02-01 13:19:12 -07001436
1437 print('\nCONFIG options used as Proper in source but without a non-SPL_ variant:')
1438 not_found = check_not_found(all_uses, MODE_PROPER)
1439 show_uses(not_found)
Simon Glassb774ba52023-09-23 13:44:03 -06001440 proper_not_found |= {not_found.keys()}
Simon Glass65e62032023-02-01 13:19:12 -07001441
1442 print('\nCONFIG options used as SPL but without an SPL_ variant:')
1443 for item in sorted(spl_not_found):
1444 print(f' {item}')
1445
1446 print('\nCONFIG options used as Proper but without a non-SPL_ variant:')
1447 for item in sorted(proper_not_found):
1448 print(f' {item}')
1449
1450 # Write out the updated information
1451 if do_update:
Simon Glasse6c686f2023-09-23 13:44:04 -06001452 with open(os.path.join(path, 'scripts', 'conf_nospl'), 'w',
1453 encoding='utf-8') as out:
Simon Glass65e62032023-02-01 13:19:12 -07001454 print('# These options should not be enabled in SPL builds\n',
1455 file=out)
1456 for item in sorted(spl_not_found):
1457 print(item, file=out)
Simon Glasse6c686f2023-09-23 13:44:04 -06001458 with open(os.path.join(path, 'scripts', 'conf_noproper'), 'w',
1459 encoding='utf-8') as out:
Simon Glass65e62032023-02-01 13:19:12 -07001460 print('# These options should not be enabled in Proper builds\n',
1461 file=out)
1462 for item in sorted(proper_not_found):
1463 print(item, file=out)
1464
1465
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001466def main():
1467 try:
1468 cpu_count = multiprocessing.cpu_count()
1469 except NotImplementedError:
1470 cpu_count = 1
1471
Simon Glassb2e83c62021-12-18 14:54:31 -07001472 epilog = '''Move config options from headers to defconfig files. See
1473doc/develop/moveconfig.rst for documentation.'''
1474
1475 parser = ArgumentParser(epilog=epilog)
1476 # Add arguments here
1477 parser.add_argument('-a', '--add-imply', type=str, default='',
Simon Glasscb008832017-06-15 21:39:33 -06001478 help='comma-separated list of CONFIG options to add '
1479 "an 'imply' statement to for the CONFIG in -i")
Simon Glassb2e83c62021-12-18 14:54:31 -07001480 parser.add_argument('-A', '--skip-added', action='store_true', default=False,
Simon Glasscb008832017-06-15 21:39:33 -06001481 help="don't show options which are already marked as "
1482 'implying others')
Simon Glassb2e83c62021-12-18 14:54:31 -07001483 parser.add_argument('-b', '--build-db', action='store_true', default=False,
Simon Glassd73fcb12017-06-01 19:39:02 -06001484 help='build a CONFIG database')
Simon Glassb2e83c62021-12-18 14:54:31 -07001485 parser.add_argument('-C', '--commit', action='store_true', default=False,
Simon Glass9ede2122016-09-12 23:18:21 -06001486 help='Create a git commit for the operation')
Simon Glass15f19ab2023-09-23 13:44:09 -06001487 parser.add_argument('--nocolour', action='store_true', default=False,
1488 help="don't display the log in colour")
Simon Glassb2e83c62021-12-18 14:54:31 -07001489 parser.add_argument('-d', '--defconfigs', type=str,
Simon Glassee4e61b2017-06-01 19:38:59 -06001490 help='a file containing a list of defconfigs to move, '
1491 "one per line (for example 'snow_defconfig') "
1492 "or '-' to read from stdin")
Simon Glassb2e83c62021-12-18 14:54:31 -07001493 parser.add_argument('-e', '--exit-on-error', action='store_true',
Simon Glasse1ae5632021-12-18 08:09:44 -07001494 default=False,
1495 help='exit immediately on any error')
Simon Glassb2e83c62021-12-18 14:54:31 -07001496 parser.add_argument('-f', '--find', action='store_true', default=False,
Simon Glass65d7fce2021-12-18 08:09:46 -07001497 help='Find boards with a given config combination')
Simon Glassb2e83c62021-12-18 14:54:31 -07001498 parser.add_argument('-i', '--imply', action='store_true', default=False,
Simon Glass99b66602017-06-01 19:39:03 -06001499 help='find options which imply others')
Simon Glassb2e83c62021-12-18 14:54:31 -07001500 parser.add_argument('-I', '--imply-flags', type=str, default='',
Simon Glass9b2a2e82017-06-15 21:39:32 -06001501 help="control the -i option ('help' for help")
Simon Glassb2e83c62021-12-18 14:54:31 -07001502 parser.add_argument('-j', '--jobs', type=int, default=cpu_count,
Simon Glasse1ae5632021-12-18 08:09:44 -07001503 help='the number of jobs to run simultaneously')
Simon Glassb2e83c62021-12-18 14:54:31 -07001504 parser.add_argument('-n', '--dry-run', action='store_true', default=False,
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001505 help='perform a trial run (show log with no changes)')
Simon Glassb2e83c62021-12-18 14:54:31 -07001506 parser.add_argument('-r', '--git-ref', type=str,
Simon Glasse1ae5632021-12-18 08:09:44 -07001507 help='the git ref to clone for building the autoconf.mk')
Simon Glassb2e83c62021-12-18 14:54:31 -07001508 parser.add_argument('-s', '--force-sync', action='store_true', default=False,
Masahiro Yamada8513dc02016-05-19 15:52:08 +09001509 help='force sync by savedefconfig')
Simon Glassb2e83c62021-12-18 14:54:31 -07001510 parser.add_argument('-S', '--spl', action='store_true', default=False,
Masahiro Yamada07913d12016-08-22 22:18:22 +09001511 help='parse config options defined for SPL build')
Simon Glass65e62032023-02-01 13:19:12 -07001512 parser.add_argument('--scan-source', action='store_true', default=False,
1513 help='scan source for uses of CONFIG options')
Simon Glassb2e83c62021-12-18 14:54:31 -07001514 parser.add_argument('-t', '--test', action='store_true', default=False,
Simon Glasse1ae5632021-12-18 08:09:44 -07001515 help='run unit tests')
Simon Glassb2e83c62021-12-18 14:54:31 -07001516 parser.add_argument('-y', '--yes', action='store_true', default=False,
Simon Glass6b403df2016-09-12 23:18:20 -06001517 help="respond 'yes' to any prompts")
Simon Glass65e62032023-02-01 13:19:12 -07001518 parser.add_argument('-u', '--update', action='store_true', default=False,
1519 help="update scripts/ files (use with --scan-source)")
Simon Glassb2e83c62021-12-18 14:54:31 -07001520 parser.add_argument('-v', '--verbose', action='store_true', default=False,
Joe Hershberger95bf9c72015-05-19 13:21:24 -05001521 help='show any build errors as boards are built')
Simon Glassb2e83c62021-12-18 14:54:31 -07001522 parser.add_argument('configs', nargs='*')
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001523
Simon Glassb2e83c62021-12-18 14:54:31 -07001524 args = parser.parse_args()
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001525
Simon Glassb2e83c62021-12-18 14:54:31 -07001526 if args.test:
Simon Glass84067a52021-12-18 08:09:45 -07001527 sys.argv = [sys.argv[0]]
Simon Glass62fae4b2023-09-23 13:44:00 -06001528 fail, _ = doctest.testmod()
Simon Glass84067a52021-12-18 08:09:45 -07001529 if fail:
1530 return 1
1531 unittest.main()
1532
Simon Glass15f19ab2023-09-23 13:44:09 -06001533 col = terminal.Color(terminal.COLOR_NEVER if args.nocolour
1534 else terminal.COLOR_IF_TERMINAL)
1535
Simon Glass65e62032023-02-01 13:19:12 -07001536 if args.scan_source:
1537 do_scan_source(os.getcwd(), args.update)
Simon Glassf297ba32023-09-23 13:44:05 -06001538 return 0
Simon Glass65e62032023-02-01 13:19:12 -07001539
Simon Glass882c8e42023-09-23 13:43:54 -06001540 if not any((args.force_sync, args.build_db, args.imply, args.find)):
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001541 parser.print_usage()
1542 sys.exit(1)
1543
Masahiro Yamadab6ef3932016-05-19 15:51:58 +09001544 # prefix the option name with CONFIG_ if missing
Simon Glass882c8e42023-09-23 13:43:54 -06001545 configs = [prefix_config(cfg) for cfg in args.configs]
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001546
Joe Hershberger2144f882015-05-19 13:21:20 -05001547 check_top_directory()
1548
Simon Glassb2e83c62021-12-18 14:54:31 -07001549 if args.imply:
Simon Glass9b2a2e82017-06-15 21:39:32 -06001550 imply_flags = 0
Simon Glassb2e83c62021-12-18 14:54:31 -07001551 if args.imply_flags == 'all':
Simon Glassdee36c72017-07-10 14:47:46 -06001552 imply_flags = -1
1553
Simon Glassb2e83c62021-12-18 14:54:31 -07001554 elif args.imply_flags:
1555 for flag in args.imply_flags.split(','):
Simon Glassdee36c72017-07-10 14:47:46 -06001556 bad = flag not in IMPLY_FLAGS
1557 if bad:
Simon Glass1bd43062023-09-23 13:43:59 -06001558 print(f"Invalid flag '{flag}'")
Simon Glassdee36c72017-07-10 14:47:46 -06001559 if flag == 'help' or bad:
Simon Glass793dca32019-10-31 07:42:57 -06001560 print("Imply flags: (separate with ',')")
1561 for name, info in IMPLY_FLAGS.items():
Simon Glass1bd43062023-09-23 13:43:59 -06001562 print(f' {name:-15s}: {info[1]}')
Simon Glassdee36c72017-07-10 14:47:46 -06001563 parser.print_usage()
1564 sys.exit(1)
1565 imply_flags |= IMPLY_FLAGS[flag][0]
Simon Glass9b2a2e82017-06-15 21:39:32 -06001566
Simon Glassb2e83c62021-12-18 14:54:31 -07001567 do_imply_config(configs, args.add_imply, imply_flags, args.skip_added)
Simon Glassf297ba32023-09-23 13:44:05 -06001568 return 0
Simon Glass99b66602017-06-01 19:39:03 -06001569
Simon Glassb2e83c62021-12-18 14:54:31 -07001570 if args.find:
Simon Glass65d7fce2021-12-18 08:09:46 -07001571 do_find_config(configs)
Simon Glassf297ba32023-09-23 13:44:05 -06001572 return 0
Simon Glass65d7fce2021-12-18 08:09:46 -07001573
Simon Glass3481e892023-09-23 13:43:53 -06001574 # We are either building the database or forcing a sync of defconfigs
Simon Glassd73fcb12017-06-01 19:39:02 -06001575 config_db = {}
Simon Glass793dca32019-10-31 07:42:57 -06001576 db_queue = queue.Queue()
Simon Glassa4c9d172023-09-23 13:44:01 -06001577 dbt = DatabaseThread(config_db, db_queue)
1578 dbt.daemon = True
1579 dbt.start()
Simon Glassd73fcb12017-06-01 19:39:02 -06001580
Simon Glass63df2022023-09-23 13:43:50 -06001581 check_clean_directory()
1582 bsettings.setup('')
1583 toolchains = toolchain.Toolchains()
1584 toolchains.GetSettings()
1585 toolchains.Scan(verbose=False)
Simon Glass94e2ed72023-09-23 13:44:13 -06001586 progress = move_config(toolchains, args, db_queue, col)
Simon Glass63df2022023-09-23 13:43:50 -06001587 db_queue.join()
Joe Hershberger2144f882015-05-19 13:21:20 -05001588
Simon Glassb2e83c62021-12-18 14:54:31 -07001589 if args.commit:
Simon Glass9ede2122016-09-12 23:18:21 -06001590 subprocess.call(['git', 'add', '-u'])
1591 if configs:
1592 msg = 'Convert %s %sto Kconfig' % (configs[0],
1593 'et al ' if len(configs) > 1 else '')
1594 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' %
1595 '\n '.join(configs))
1596 else:
1597 msg = 'configs: Resync with savedefconfig'
1598 msg += '\n\nRsync all defconfig files using moveconfig.py'
1599 subprocess.call(['git', 'commit', '-s', '-m', msg])
1600
Simon Glass94e2ed72023-09-23 13:44:13 -06001601 failed = progress.total - progress.good
1602 failure = f'{failed} failed, ' if failed else ''
Simon Glassb2e83c62021-12-18 14:54:31 -07001603 if args.build_db:
Simon Glassa4c9d172023-09-23 13:44:01 -06001604 with open(CONFIG_DATABASE, 'w', encoding='utf-8') as outf:
Simon Glass793dca32019-10-31 07:42:57 -06001605 for defconfig, configs in config_db.items():
Simon Glassa4c9d172023-09-23 13:44:01 -06001606 outf.write(f'{defconfig}\n')
Simon Glassd73fcb12017-06-01 19:39:02 -06001607 for config in sorted(configs.keys()):
Simon Glassa4c9d172023-09-23 13:44:01 -06001608 outf.write(f' {config}={configs[config]}\n')
1609 outf.write('\n')
Simon Glass94e2ed72023-09-23 13:44:13 -06001610 print(col.build(
1611 col.RED if failed else col.GREEN,
1612 f'{failure}{len(config_db)} boards written to {CONFIG_DATABASE}'))
1613 else:
1614 if failed:
1615 print(col.build(col.RED, f'{failure}see {FAILED_LIST}', True))
1616 else:
1617 # Add enough spaces to overwrite the progress indicator
1618 print(col.build(
1619 col.GREEN, f'{progress.total} processed ', bright=True))
1620
Simon Glassf297ba32023-09-23 13:44:05 -06001621 return 0
1622
Simon Glassd73fcb12017-06-01 19:39:02 -06001623
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001624if __name__ == '__main__':
Simon Glass65d7fce2021-12-18 08:09:46 -07001625 sys.exit(main())