blob: 9e9cb672a967f707acc7692a03cfcccba5b6d9b0 [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"""
8Move config options from headers to defconfig files.
9
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 Glassd73fcb12017-06-01 19:39:02 -060047CONFIG_DATABASE = 'moveconfig.db'
Simon Glassf3b8e642017-06-01 19:39:01 -060048
Simon Glasscb008832017-06-15 21:39:33 -060049CONFIG_LEN = len('CONFIG_')
Simon Glassf3b8e642017-06-01 19:39:01 -060050
Markus Klotzbuecherb237d352019-05-15 15:15:52 +020051SIZES = {
Simon Glassdaa694d2021-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 Klotzbuecherb237d352019-05-15 15:15:52 +020069}
70
Simon Glassb8d11da2022-02-08 11:49:45 -070071RE_REMOVE_DEFCONFIG = re.compile(r'(.*)_defconfig')
72
Simon Glass65e62032023-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 Yamada5a27c732015-05-20 11:36:07 +090085### helper functions ###
Masahiro Yamada5a27c732015-05-20 11:36:07 +090086def check_top_directory():
87 """Exit if we are not at the top of source directory."""
Simon Glass91197aa2021-12-18 14:54:35 -070088 for fname in 'README', 'Licenses':
89 if not os.path.exists(fname):
Masahiro Yamada5a27c732015-05-20 11:36:07 +090090 sys.exit('Please run at the top of source directory.')
91
Masahiro Yamadabd63e5b2016-05-19 15:51:54 +090092def check_clean_directory():
93 """Exit if the source tree is not clean."""
Simon Glass91197aa2021-12-18 14:54:35 -070094 for fname in '.config', 'include/config':
95 if os.path.exists(fname):
Masahiro Yamadabd63e5b2016-05-19 15:51:54 +090096 sys.exit("source tree is not clean, please run 'make mrproper'")
97
Masahiro Yamada5a27c732015-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 Glass91197aa2021-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 Yamada5a27c732015-05-20 11:36:07 +0900109 return ret[0].rstrip()
110
Simon Glass25f978c2017-06-01 19:38:58 -0600111def get_matched_defconfig(line):
112 """Get the defconfig files that match a pattern
113
114 Args:
Simon Glass91197aa2021-12-18 14:54:35 -0700115 line (str): Path or filename to match, e.g. 'configs/snow_defconfig' or
Simon Glass25f978c2017-06-01 19:38:58 -0600116 'k2*_defconfig'. If no directory is provided, 'configs/' is
117 prepended
118
119 Returns:
Simon Glass91197aa2021-12-18 14:54:35 -0700120 list of str: a list of matching defconfig files
Simon Glass25f978c2017-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 Yamada0dbc9b52016-10-19 14:39:54 +0900129def get_matched_defconfigs(defconfigs_file):
Simon Glassee4e61b2017-06-01 19:38:59 -0600130 """Get all the defconfig files that match the patterns in a file.
131
132 Args:
Simon Glass91197aa2021-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 Glassee4e61b2017-06-01 19:38:59 -0600135
136 Returns:
Simon Glass91197aa2021-12-18 14:54:35 -0700137 list of str: A list of paths to defconfig files, with no duplicates
Simon Glassee4e61b2017-06-01 19:38:59 -0600138 """
Masahiro Yamada0dbc9b52016-10-19 14:39:54 +0900139 defconfigs = []
Simon Glass91197aa2021-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 Yamada0dbc9b52016-10-19 14:39:54 +0900156
Simon Glass91197aa2021-12-18 14:54:35 -0700157 defconfigs += matched
Masahiro Yamada0dbc9b52016-10-19 14:39:54 +0900158
159 # use set() to drop multiple matching
Simon Glass91197aa2021-12-18 14:54:35 -0700160 return [defconfig[len('configs') + 1:] for defconfig in set(defconfigs)]
Masahiro Yamada0dbc9b52016-10-19 14:39:54 +0900161
Masahiro Yamada684c3062016-07-25 19:15:28 +0900162def get_all_defconfigs():
Simon Glass91197aa2021-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 Yamada684c3062016-07-25 19:15:28 +0900168 defconfigs = []
Simon Glass91197aa2021-12-18 14:54:35 -0700169 for (dirpath, _, filenames) in os.walk('configs'):
Masahiro Yamada684c3062016-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 Glass2fd85bd2021-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 Glass37f815c2021-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 Glass549d4222023-09-23 13:43:58 -0600196 as_lines (bool): Return file contents as a list of lines
Simon Glass37f815c2021-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 Glassf297ba32023-09-23 13:44:05 -0600211 return inf.read()
Simon Glassa4c9d172023-09-23 13:44:01 -0600212 except UnicodeDecodeError as exc:
Simon Glass37f815c2021-12-18 14:54:34 -0700213 if not skip_unicode:
Simon Glass68a0b712022-02-11 13:23:22 -0700214 raise
Simon Glassa4c9d172023-09-23 13:44:01 -0600215 print(f"Failed on file '{fname}: {exc}")
Simon Glass37f815c2021-12-18 14:54:34 -0700216 return None
217
Markus Klotzbuecherb237d352019-05-15 15:15:52 +0200218def try_expand(line):
219 """If value looks like an expression, try expanding it
220 Otherwise just return the existing value
221 """
222 if line.find('=') == -1:
223 return line
224
225 try:
Markus Klotzbuecherb3192f42020-02-12 20:46:44 +0100226 aeval = asteval.Interpreter( usersyms=SIZES, minimal=True )
Markus Klotzbuecherb237d352019-05-15 15:15:52 +0200227 cfg, val = re.split("=", line)
228 val= val.strip('\"')
Simon Glassdaa694d2021-12-18 14:54:30 -0700229 if re.search(r'[*+-/]|<<|SZ_+|\(([^\)]+)\)', val):
Markus Klotzbuecherb3192f42020-02-12 20:46:44 +0100230 newval = hex(aeval(val))
Simon Glass1bd43062023-09-23 13:43:59 -0600231 print(f'\tExpanded expression {val} to {newval}')
Markus Klotzbuecherb237d352019-05-15 15:15:52 +0200232 return cfg+'='+newval
233 except:
Simon Glass1bd43062023-09-23 13:43:59 -0600234 print(f'\tFailed to expand expression in {line}')
Markus Klotzbuecherb237d352019-05-15 15:15:52 +0200235
236 return line
237
Chris Packhamca438342017-05-02 21:30:47 +1200238
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900239### classes ###
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900240class Progress:
241
242 """Progress Indicator"""
243
244 def __init__(self, total):
245 """Create a new progress indicator.
246
Simon Glass91197aa2021-12-18 14:54:35 -0700247 Args:
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900248 total: A number of defconfig files to process.
249 """
250 self.current = 0
251 self.total = total
252
253 def inc(self):
254 """Increment the number of processed defconfig files."""
255
256 self.current += 1
257
258 def show(self):
259 """Display the progress."""
Simon Glass95f09142023-09-23 13:44:08 -0600260 if self.current != self.total:
261 print(f' {self.current} defconfigs out of {self.total}\r', end=' ')
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900262 sys.stdout.flush()
263
Simon Glasscb008832017-06-15 21:39:33 -0600264
265class KconfigScanner:
266 """Kconfig scanner."""
267
268 def __init__(self):
269 """Scan all the Kconfig files and create a Config object."""
270 # Define environment variables referenced from Kconfig
271 os.environ['srctree'] = os.getcwd()
272 os.environ['UBOOTVERSION'] = 'dummy'
273 os.environ['KCONFIG_OBJDIR'] = ''
Simon Glass65e62032023-02-01 13:19:12 -0700274 os.environ['CC'] = 'gcc'
Tom Rini65e05dd2019-09-20 17:42:09 -0400275 self.conf = kconfiglib.Kconfig()
Simon Glasscb008832017-06-15 21:39:33 -0600276
277
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900278class KconfigParser:
279
280 """A parser of .config and include/autoconf.mk."""
281
282 re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
283 re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
284
Simon Glass882c8e42023-09-23 13:43:54 -0600285 def __init__(self, args, build_dir):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900286 """Create a new parser.
287
Simon Glass91197aa2021-12-18 14:54:35 -0700288 Args:
Simon Glass91197aa2021-12-18 14:54:35 -0700289 args (Namespace): program arguments
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900290 build_dir: Build directory.
291 """
Simon Glassb2e83c62021-12-18 14:54:31 -0700292 self.args = args
Masahiro Yamada1f169922016-05-19 15:52:00 +0900293 self.dotconfig = os.path.join(build_dir, '.config')
294 self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
Masahiro Yamada07913d12016-08-22 22:18:22 +0900295 self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
296 'autoconf.mk')
Simon Glassf3b8e642017-06-01 19:39:01 -0600297 self.config_autoconf = os.path.join(build_dir, AUTO_CONF_PATH)
Masahiro Yamada5da4f852016-05-19 15:52:06 +0900298 self.defconfig = os.path.join(build_dir, 'defconfig')
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900299
Simon Glass6821a742017-07-10 14:47:47 -0600300 def get_arch(self):
301 """Parse .config file and return the architecture.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900302
303 Returns:
Simon Glass6821a742017-07-10 14:47:47 -0600304 Architecture name (e.g. 'arm').
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900305 """
306 arch = ''
307 cpu = ''
Simon Glass37f815c2021-12-18 14:54:34 -0700308 for line in read_file(self.dotconfig):
Simon Glassa4c9d172023-09-23 13:44:01 -0600309 m_arch = self.re_arch.match(line)
310 if m_arch:
311 arch = m_arch.group(1)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900312 continue
Simon Glassa4c9d172023-09-23 13:44:01 -0600313 m_cpu = self.re_cpu.match(line)
314 if m_cpu:
315 cpu = m_cpu.group(1)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900316
Masahiro Yamada90ed6cb2016-05-19 15:51:53 +0900317 if not arch:
318 return None
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900319
320 # fix-up for aarch64
321 if arch == 'arm' and cpu == 'armv8':
322 arch = 'aarch64'
323
Simon Glass6821a742017-07-10 14:47:47 -0600324 return arch
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900325
Simon Glassd73fcb12017-06-01 19:39:02 -0600326
327class DatabaseThread(threading.Thread):
328 """This thread processes results from Slot threads.
329
330 It collects the data in the master config directary. There is only one
331 result thread, and this helps to serialise the build output.
332 """
333 def __init__(self, config_db, db_queue):
334 """Set up a new result thread
335
336 Args:
337 builder: Builder which will be sent each result
338 """
339 threading.Thread.__init__(self)
340 self.config_db = config_db
341 self.db_queue= db_queue
342
343 def run(self):
344 """Called to start up the result thread.
345
346 We collect the next result job and pass it on to the build.
347 """
348 while True:
349 defconfig, configs = self.db_queue.get()
350 self.config_db[defconfig] = configs
351 self.db_queue.task_done()
352
353
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900354class Slot:
355
356 """A slot to store a subprocess.
357
358 Each instance of this class handles one subprocess.
359 This class is useful to control multiple threads
360 for faster processing.
361 """
362
Simon Glass15f19ab2023-09-23 13:44:09 -0600363 def __init__(self, toolchains, args, progress, devnull, make_cmd,
364 reference_src_dir, db_queue, col):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900365 """Create a new process slot.
366
Simon Glass91197aa2021-12-18 14:54:35 -0700367 Args:
Simon Glass6821a742017-07-10 14:47:47 -0600368 toolchains: Toolchains object containing toolchains.
Simon Glassb2e83c62021-12-18 14:54:31 -0700369 args: Program arguments
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900370 progress: A progress indicator.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900371 devnull: A file object of '/dev/null'.
372 make_cmd: command name of GNU Make.
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500373 reference_src_dir: Determine the true starting config state from this
374 source tree.
Simon Glassd73fcb12017-06-01 19:39:02 -0600375 db_queue: output queue to write config info for the database
Simon Glass15f19ab2023-09-23 13:44:09 -0600376 col (terminal.Color): Colour object
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900377 """
Simon Glass6821a742017-07-10 14:47:47 -0600378 self.toolchains = toolchains
Simon Glassb2e83c62021-12-18 14:54:31 -0700379 self.args = args
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900380 self.progress = progress
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900381 self.build_dir = tempfile.mkdtemp()
382 self.devnull = devnull
383 self.make_cmd = (make_cmd, 'O=' + self.build_dir)
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500384 self.reference_src_dir = reference_src_dir
Simon Glassd73fcb12017-06-01 19:39:02 -0600385 self.db_queue = db_queue
Simon Glass15f19ab2023-09-23 13:44:09 -0600386 self.col = col
Simon Glass882c8e42023-09-23 13:43:54 -0600387 self.parser = KconfigParser(args, self.build_dir)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900388 self.state = STATE_IDLE
Masahiro Yamada09c6c062016-08-22 22:18:20 +0900389 self.failed_boards = set()
Simon Glassa6ab4db2023-09-23 13:44:02 -0600390 self.defconfig = None
Simon Glass9461bf02023-09-23 13:44:07 -0600391 self.log = []
Simon Glassa6ab4db2023-09-23 13:44:02 -0600392 self.current_src_dir = None
393 self.proc = None
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900394
395 def __del__(self):
396 """Delete the working directory
397
398 This function makes sure the temporary directory is cleaned away
399 even if Python suddenly dies due to error. It should be done in here
Joe Hershbergerf2dae752016-06-10 14:53:29 -0500400 because it is guaranteed the destructor is always invoked when the
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900401 instance of the class gets unreferenced.
402
403 If the subprocess is still running, wait until it finishes.
404 """
405 if self.state != STATE_IDLE:
Simon Glassf297ba32023-09-23 13:44:05 -0600406 while self.proc.poll() is None:
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900407 pass
408 shutil.rmtree(self.build_dir)
409
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900410 def add(self, defconfig):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900411 """Assign a new subprocess for defconfig and add it to the slot.
412
413 If the slot is vacant, create a new subprocess for processing the
414 given defconfig and add it to the slot. Just returns False if
415 the slot is occupied (i.e. the current subprocess is still running).
416
Simon Glass91197aa2021-12-18 14:54:35 -0700417 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600418 defconfig (str): defconfig name.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900419
420 Returns:
421 Return True on success or False on failure
422 """
423 if self.state != STATE_IDLE:
424 return False
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900425
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900426 self.defconfig = defconfig
Simon Glass9461bf02023-09-23 13:44:07 -0600427 self.log = []
Masahiro Yamadaf432c332016-06-15 14:33:52 +0900428 self.current_src_dir = self.reference_src_dir
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900429 self.do_defconfig()
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900430 return True
431
432 def poll(self):
433 """Check the status of the subprocess and handle it as needed.
434
435 Returns True if the slot is vacant (i.e. in idle state).
436 If the configuration is successfully finished, assign a new
437 subprocess to build include/autoconf.mk.
438 If include/autoconf.mk is generated, invoke the parser to
Masahiro Yamada7fb0bac2016-05-19 15:52:04 +0900439 parse the .config and the include/autoconf.mk, moving
440 config options to the .config as needed.
441 If the .config was updated, run "make savedefconfig" to sync
442 it, update the original defconfig, and then set the slot back
443 to the idle state.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900444
445 Returns:
446 Return True if the subprocess is terminated, False otherwise
447 """
448 if self.state == STATE_IDLE:
449 return True
450
Simon Glassf297ba32023-09-23 13:44:05 -0600451 if self.proc.poll() is None:
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900452 return False
453
Simon Glassa4c9d172023-09-23 13:44:01 -0600454 if self.proc.poll() != 0:
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900455 self.handle_error()
456 elif self.state == STATE_DEFCONFIG:
Masahiro Yamadaf432c332016-06-15 14:33:52 +0900457 if self.reference_src_dir and not self.current_src_dir:
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500458 self.do_savedefconfig()
459 else:
460 self.do_autoconf()
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900461 elif self.state == STATE_AUTOCONF:
Masahiro Yamadaf432c332016-06-15 14:33:52 +0900462 if self.current_src_dir:
463 self.current_src_dir = None
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500464 self.do_defconfig()
Simon Glassb2e83c62021-12-18 14:54:31 -0700465 elif self.args.build_db:
Simon Glassd73fcb12017-06-01 19:39:02 -0600466 self.do_build_db()
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500467 else:
468 self.do_savedefconfig()
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900469 elif self.state == STATE_SAVEDEFCONFIG:
470 self.update_defconfig()
471 else:
Simon Glassdaa694d2021-12-18 14:54:30 -0700472 sys.exit('Internal Error. This should not happen.')
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900473
Simon Glassf297ba32023-09-23 13:44:05 -0600474 return self.state == STATE_IDLE
Joe Hershberger96464ba2015-05-19 13:21:17 -0500475
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900476 def handle_error(self):
477 """Handle error cases."""
Masahiro Yamada8513dc02016-05-19 15:52:08 +0900478
Simon Glass15f19ab2023-09-23 13:44:09 -0600479 self.log.append(self.col.build(self.col.RED, 'Failed to process',
480 bright=True))
Simon Glassb2e83c62021-12-18 14:54:31 -0700481 if self.args.verbose:
Simon Glass9461bf02023-09-23 13:44:07 -0600482 for line in self.proc.stderr.read().decode().splitlines():
Simon Glass15f19ab2023-09-23 13:44:09 -0600483 self.log.append(self.col.build(self.col.CYAN, line, True))
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900484 self.finish(False)
Joe Hershberger96464ba2015-05-19 13:21:17 -0500485
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900486 def do_defconfig(self):
487 """Run 'make <board>_defconfig' to create the .config file."""
Masahiro Yamadac8e1b102016-05-19 15:52:07 +0900488
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900489 cmd = list(self.make_cmd)
490 cmd.append(self.defconfig)
Simon Glassa4c9d172023-09-23 13:44:01 -0600491 self.proc = subprocess.Popen(cmd, stdout=self.devnull,
492 stderr=subprocess.PIPE,
493 cwd=self.current_src_dir)
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900494 self.state = STATE_DEFCONFIG
Masahiro Yamadac8e1b102016-05-19 15:52:07 +0900495
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900496 def do_autoconf(self):
Simon Glassf3b8e642017-06-01 19:39:01 -0600497 """Run 'make AUTO_CONF_PATH'."""
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900498
Simon Glass6821a742017-07-10 14:47:47 -0600499 arch = self.parser.get_arch()
500 try:
Simon Glassf297ba32023-09-23 13:44:05 -0600501 tchain = self.toolchains.Select(arch)
Simon Glass6821a742017-07-10 14:47:47 -0600502 except ValueError:
Simon Glass15f19ab2023-09-23 13:44:09 -0600503 self.log.append(self.col.build(
504 self.col.YELLOW,
505 f"Tool chain for '{arch}' is missing: do nothing"))
Masahiro Yamada4efef992016-05-19 15:52:03 +0900506 self.finish(False)
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900507 return
Simon Glassf297ba32023-09-23 13:44:05 -0600508 env = tchain.MakeEnvironment(False)
Masahiro Yamada90ed6cb2016-05-19 15:51:53 +0900509
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900510 cmd = list(self.make_cmd)
Joe Hershberger7740f652015-05-19 13:21:18 -0500511 cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
Simon Glassf3b8e642017-06-01 19:39:01 -0600512 cmd.append(AUTO_CONF_PATH)
Simon Glassa4c9d172023-09-23 13:44:01 -0600513 self.proc = subprocess.Popen(cmd, stdout=self.devnull, env=env,
514 stderr=subprocess.PIPE,
515 cwd=self.current_src_dir)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900516 self.state = STATE_AUTOCONF
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900517
Simon Glassd73fcb12017-06-01 19:39:02 -0600518 def do_build_db(self):
519 """Add the board to the database"""
520 configs = {}
Simon Glass37f815c2021-12-18 14:54:34 -0700521 for line in read_file(os.path.join(self.build_dir, AUTO_CONF_PATH)):
522 if line.startswith('CONFIG'):
523 config, value = line.split('=', 1)
524 configs[config] = value.rstrip()
Simon Glassd73fcb12017-06-01 19:39:02 -0600525 self.db_queue.put([self.defconfig, configs])
526 self.finish(True)
527
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900528 def do_savedefconfig(self):
529 """Update the .config and run 'make savedefconfig'."""
Simon Glassc7345612023-09-23 13:43:55 -0600530 if not self.args.force_sync:
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900531 self.finish(True)
532 return
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900533
534 cmd = list(self.make_cmd)
535 cmd.append('savedefconfig')
Simon Glassa4c9d172023-09-23 13:44:01 -0600536 self.proc = subprocess.Popen(cmd, stdout=self.devnull,
537 stderr=subprocess.PIPE)
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900538 self.state = STATE_SAVEDEFCONFIG
539
540 def update_defconfig(self):
541 """Update the input defconfig and go back to the idle state."""
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900542 orig_defconfig = os.path.join('configs', self.defconfig)
543 new_defconfig = os.path.join(self.build_dir, 'defconfig')
544 updated = not filecmp.cmp(orig_defconfig, new_defconfig)
545
546 if updated:
Simon Glass15f19ab2023-09-23 13:44:09 -0600547 self.log.append(
548 self.col.build(self.col.BLUE, 'defconfig updated', bright=True))
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900549
Simon Glassb2e83c62021-12-18 14:54:31 -0700550 if not self.args.dry_run and updated:
Masahiro Yamadae307fa92016-06-08 11:47:37 +0900551 shutil.move(new_defconfig, orig_defconfig)
552 self.finish(True)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900553
Masahiro Yamada4efef992016-05-19 15:52:03 +0900554 def finish(self, success):
555 """Display log along with progress and go to the idle state.
Masahiro Yamada1d085562016-05-19 15:52:02 +0900556
Simon Glass91197aa2021-12-18 14:54:35 -0700557 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600558 success (bool): Should be True when the defconfig was processed
Masahiro Yamada4efef992016-05-19 15:52:03 +0900559 successfully, or False when it fails.
Masahiro Yamada1d085562016-05-19 15:52:02 +0900560 """
561 # output at least 30 characters to hide the "* defconfigs out of *".
Simon Glass5aba58c2023-09-23 13:44:06 -0600562 if self.log:
Simon Glass9461bf02023-09-23 13:44:07 -0600563 name = self.defconfig[:-len('_defconfig')]
Masahiro Yamada1d085562016-05-19 15:52:02 +0900564
Simon Glass9461bf02023-09-23 13:44:07 -0600565 # Put the first log line on the first line
566 log = name.ljust(20) + ' ' + self.log[0]
567
568 if len(self.log) > 1:
569 log += '\n' + '\n'.join([' ' + s for s in self.log[1:]])
Simon Glass5aba58c2023-09-23 13:44:06 -0600570 # Some threads are running in parallel.
571 # Print log atomically to not mix up logs from different threads.
572 print(log, file=(sys.stdout if success else sys.stderr))
Masahiro Yamada4efef992016-05-19 15:52:03 +0900573
574 if not success:
Simon Glassb2e83c62021-12-18 14:54:31 -0700575 if self.args.exit_on_error:
Simon Glassdaa694d2021-12-18 14:54:30 -0700576 sys.exit('Exit on error.')
Masahiro Yamada4efef992016-05-19 15:52:03 +0900577 # If --exit-on-error flag is not set, skip this board and continue.
578 # Record the failed board.
Masahiro Yamada09c6c062016-08-22 22:18:20 +0900579 self.failed_boards.add(self.defconfig)
Masahiro Yamada4efef992016-05-19 15:52:03 +0900580
Masahiro Yamada1d085562016-05-19 15:52:02 +0900581 self.progress.inc()
582 self.progress.show()
Masahiro Yamada4efef992016-05-19 15:52:03 +0900583 self.state = STATE_IDLE
Masahiro Yamada1d085562016-05-19 15:52:02 +0900584
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900585 def get_failed_boards(self):
Masahiro Yamada09c6c062016-08-22 22:18:20 +0900586 """Returns a set of failed boards (defconfigs) in this slot.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900587 """
588 return self.failed_boards
589
590class Slots:
591
592 """Controller of the array of subprocess slots."""
593
Simon Glass15f19ab2023-09-23 13:44:09 -0600594 def __init__(self, toolchains, args, progress, reference_src_dir, db_queue,
595 col):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900596 """Create a new slots controller.
597
Simon Glass91197aa2021-12-18 14:54:35 -0700598 Args:
Simon Glass15f19ab2023-09-23 13:44:09 -0600599 toolchains (Toolchains): Toolchains object containing toolchains
600 args (Namespace): Program arguments
601 progress (Progress): A progress indicator.
602 reference_src_dir (str): Determine the true starting config state
603 from this source tree (None for none)
604 db_queue (Queue): output queue to write config info for the database
605 col (terminal.Color): Colour object
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900606 """
Simon Glassb2e83c62021-12-18 14:54:31 -0700607 self.args = args
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900608 self.slots = []
Simon Glass15f19ab2023-09-23 13:44:09 -0600609 self.col = col
Simon Glass478920d2021-12-18 14:54:32 -0700610 devnull = subprocess.DEVNULL
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900611 make_cmd = get_make_cmd()
Simon Glass62fae4b2023-09-23 13:44:00 -0600612 for _ in range(args.jobs):
Simon Glass882c8e42023-09-23 13:43:54 -0600613 self.slots.append(Slot(toolchains, args, progress, devnull,
Simon Glass15f19ab2023-09-23 13:44:09 -0600614 make_cmd, reference_src_dir, db_queue, col))
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900615
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900616 def add(self, defconfig):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900617 """Add a new subprocess if a vacant slot is found.
618
Simon Glass91197aa2021-12-18 14:54:35 -0700619 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600620 defconfig (str): defconfig name to be put into.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900621
622 Returns:
623 Return True on success or False on failure
624 """
625 for slot in self.slots:
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900626 if slot.add(defconfig):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900627 return True
628 return False
629
630 def available(self):
631 """Check if there is a vacant slot.
632
633 Returns:
634 Return True if at lease one vacant slot is found, False otherwise.
635 """
636 for slot in self.slots:
637 if slot.poll():
638 return True
639 return False
640
641 def empty(self):
642 """Check if all slots are vacant.
643
644 Returns:
645 Return True if all the slots are vacant, False otherwise.
646 """
647 ret = True
648 for slot in self.slots:
649 if not slot.poll():
650 ret = False
651 return ret
652
653 def show_failed_boards(self):
654 """Display all of the failed boards (defconfigs)."""
Masahiro Yamada09c6c062016-08-22 22:18:20 +0900655 boards = set()
Masahiro Yamada96dccd92016-06-15 14:33:53 +0900656 output_file = 'moveconfig.failed'
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900657
658 for slot in self.slots:
Masahiro Yamada09c6c062016-08-22 22:18:20 +0900659 boards |= slot.get_failed_boards()
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900660
Masahiro Yamada96dccd92016-06-15 14:33:53 +0900661 if boards:
662 boards = '\n'.join(boards) + '\n'
Simon Glassdaa694d2021-12-18 14:54:30 -0700663 msg = 'The following boards were not processed due to error:\n'
Masahiro Yamada96dccd92016-06-15 14:33:53 +0900664 msg += boards
Simon Glass1bd43062023-09-23 13:43:59 -0600665 msg += f'(the list has been saved in {output_file})\n'
Simon Glass15f19ab2023-09-23 13:44:09 -0600666 print(self.col.build(self.col.RED, msg, bright=True),
667 file=sys.stderr)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900668
Simon Glass2fd85bd2021-12-18 14:54:33 -0700669 write_file(output_file, boards)
Joe Hershberger2559cd82015-05-19 13:21:22 -0500670
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900671class ReferenceSource:
672
673 """Reference source against which original configs should be parsed."""
674
675 def __init__(self, commit):
676 """Create a reference source directory based on a specified commit.
677
Simon Glass91197aa2021-12-18 14:54:35 -0700678 Args:
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900679 commit: commit to git-clone
680 """
681 self.src_dir = tempfile.mkdtemp()
Simon Glassdaa694d2021-12-18 14:54:30 -0700682 print('Cloning git repo to a separate work directory...')
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900683 subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
684 cwd=self.src_dir)
Simon Glass1bd43062023-09-23 13:43:59 -0600685 rev = subprocess.check_output(['git', 'rev-parse', '--short',
686 commit]).strip()
687 print(f"Checkout '{rev}' to build the original autoconf.mk.")
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900688 subprocess.check_output(['git', 'checkout', commit],
689 stderr=subprocess.STDOUT, cwd=self.src_dir)
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500690
691 def __del__(self):
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900692 """Delete the reference source directory
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500693
694 This function makes sure the temporary directory is cleaned away
695 even if Python suddenly dies due to error. It should be done in here
696 because it is guaranteed the destructor is always invoked when the
697 instance of the class gets unreferenced.
698 """
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900699 shutil.rmtree(self.src_dir)
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500700
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900701 def get_dir(self):
702 """Return the absolute path to the reference source directory."""
703
704 return self.src_dir
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500705
Simon Glass15f19ab2023-09-23 13:44:09 -0600706def move_config(toolchains, args, db_queue, col):
Simon Glass882c8e42023-09-23 13:43:54 -0600707 """Build database or sync config options to defconfig files.
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900708
Simon Glass91197aa2021-12-18 14:54:35 -0700709 Args:
Simon Glass15f19ab2023-09-23 13:44:09 -0600710 toolchains (Toolchains): Toolchains to use
711 args (Namespace): Program arguments
712 db_queue (Queue): Queue for database updates
713 col (terminal.Color): Colour object
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900714 """
Simon Glass882c8e42023-09-23 13:43:54 -0600715 if args.force_sync:
716 print('Syncing defconfigs', end=' ')
717 elif args.build_db:
Simon Glass1bd43062023-09-23 13:43:59 -0600718 print(f'Building {CONFIG_DATABASE} database')
719 print(f'(jobs: {args.jobs})\n')
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900720
Simon Glassb2e83c62021-12-18 14:54:31 -0700721 if args.git_ref:
722 reference_src = ReferenceSource(args.git_ref)
Masahiro Yamada5cc42a52016-06-15 14:33:51 +0900723 reference_src_dir = reference_src.get_dir()
724 else:
Masahiro Yamadaf432c332016-06-15 14:33:52 +0900725 reference_src_dir = None
Joe Hershberger6b96c1a2016-06-10 14:53:32 -0500726
Simon Glassb2e83c62021-12-18 14:54:31 -0700727 if args.defconfigs:
728 defconfigs = get_matched_defconfigs(args.defconfigs)
Joe Hershberger91040e82015-05-19 13:21:19 -0500729 else:
Masahiro Yamada684c3062016-07-25 19:15:28 +0900730 defconfigs = get_all_defconfigs()
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900731
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900732 progress = Progress(len(defconfigs))
Simon Glass15f19ab2023-09-23 13:44:09 -0600733 slots = Slots(toolchains, args, progress, reference_src_dir, db_queue, col)
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900734
735 # Main loop to process defconfig files:
736 # Add a new subprocess into a vacant slot.
737 # Sleep if there is no available slot.
Masahiro Yamadac5e60fd2016-05-19 15:51:55 +0900738 for defconfig in defconfigs:
739 while not slots.add(defconfig):
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900740 while not slots.available():
741 # No available slot: sleep for a while
742 time.sleep(SLEEP_TIME)
743
744 # wait until all the subprocesses finish
745 while not slots.empty():
746 time.sleep(SLEEP_TIME)
747
Simon Glass793dca32019-10-31 07:42:57 -0600748 print('')
Masahiro Yamada5a27c732015-05-20 11:36:07 +0900749 slots.show_failed_boards()
750
Simon Glasscb008832017-06-15 21:39:33 -0600751def find_kconfig_rules(kconf, config, imply_config):
752 """Check whether a config has a 'select' or 'imply' keyword
753
754 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600755 kconf (Kconfiglib.Kconfig): Kconfig object
756 config (str): Name of config to check (without CONFIG_ prefix)
757 imply_config (str): Implying config (without CONFIG_ prefix) which may
758 or may not have an 'imply' for 'config')
Simon Glasscb008832017-06-15 21:39:33 -0600759
760 Returns:
761 Symbol object for 'config' if found, else None
762 """
Tom Rini65e05dd2019-09-20 17:42:09 -0400763 sym = kconf.syms.get(imply_config)
Simon Glasscb008832017-06-15 21:39:33 -0600764 if sym:
Simon Glass62fae4b2023-09-23 13:44:00 -0600765 for sel, _ in (sym.selects + sym.implies):
Simon Glassa3627082021-12-18 08:09:42 -0700766 if sel.name == config:
Simon Glasscb008832017-06-15 21:39:33 -0600767 return sym
768 return None
769
770def check_imply_rule(kconf, config, imply_config):
771 """Check if we can add an 'imply' option
772
773 This finds imply_config in the Kconfig and looks to see if it is possible
774 to add an 'imply' for 'config' to that part of the Kconfig.
775
776 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600777 kconf (Kconfiglib.Kconfig): Kconfig object
778 config (str): Name of config to check (without CONFIG_ prefix)
779 imply_config (str): Implying config (without CONFIG_ prefix) which may
780 or may not have an 'imply' for 'config')
Simon Glasscb008832017-06-15 21:39:33 -0600781
782 Returns:
783 tuple:
Simon Glass549d4222023-09-23 13:43:58 -0600784 str: filename of Kconfig file containing imply_config, or None if
785 none
786 int: line number within the Kconfig file, or 0 if none
787 str: message indicating the result
Simon Glasscb008832017-06-15 21:39:33 -0600788 """
Tom Rini65e05dd2019-09-20 17:42:09 -0400789 sym = kconf.syms.get(imply_config)
Simon Glasscb008832017-06-15 21:39:33 -0600790 if not sym:
791 return 'cannot find sym'
Simon Glassea40b202021-07-21 21:35:53 -0600792 nodes = sym.nodes
793 if len(nodes) != 1:
Simon Glass1bd43062023-09-23 13:43:59 -0600794 return f'{len(nodes)} locations'
Simon Glassa3627082021-12-18 08:09:42 -0700795 node = nodes[0]
796 fname, linenum = node.filename, node.linenr
Simon Glasscb008832017-06-15 21:39:33 -0600797 cwd = os.getcwd()
798 if cwd and fname.startswith(cwd):
799 fname = fname[len(cwd) + 1:]
Simon Glass1bd43062023-09-23 13:43:59 -0600800 file_line = f' at {fname}:{linenum}'
Simon Glass37f815c2021-12-18 14:54:34 -0700801 data = read_file(fname)
Simon Glass1bd43062023-09-23 13:43:59 -0600802 if data[linenum - 1] != f'config {imply_config}':
803 return None, 0, f'bad sym format {data[linenum]}{file_line})'
804 return fname, linenum, f'adding{file_line}'
Simon Glasscb008832017-06-15 21:39:33 -0600805
806def add_imply_rule(config, fname, linenum):
807 """Add a new 'imply' option to a Kconfig
808
809 Args:
Simon Glass549d4222023-09-23 13:43:58 -0600810 config (str): config option to add an imply for (without CONFIG_ prefix)
811 fname (str): Kconfig filename to update
812 linenum (int): Line number to place the 'imply' before
Simon Glasscb008832017-06-15 21:39:33 -0600813
814 Returns:
815 Message indicating the result
816 """
Simon Glass1bd43062023-09-23 13:43:59 -0600817 file_line = f' at {fname}:{linenum}'
Simon Glass37f815c2021-12-18 14:54:34 -0700818 data = read_file(fname)
Simon Glasscb008832017-06-15 21:39:33 -0600819 linenum -= 1
820
821 for offset, line in enumerate(data[linenum:]):
822 if line.strip().startswith('help') or not line:
Simon Glass1bd43062023-09-23 13:43:59 -0600823 data.insert(linenum + offset, f'\timply {config}')
Simon Glass2fd85bd2021-12-18 14:54:33 -0700824 write_file(fname, data)
Simon Glass1bd43062023-09-23 13:43:59 -0600825 return f'added{file_line}'
Simon Glasscb008832017-06-15 21:39:33 -0600826
827 return 'could not insert%s'
828
829(IMPLY_MIN_2, IMPLY_TARGET, IMPLY_CMD, IMPLY_NON_ARCH_BOARD) = (
830 1, 2, 4, 8)
Simon Glass9b2a2e82017-06-15 21:39:32 -0600831
832IMPLY_FLAGS = {
833 'min2': [IMPLY_MIN_2, 'Show options which imply >2 boards (normally >5)'],
834 'target': [IMPLY_TARGET, 'Allow CONFIG_TARGET_... options to imply'],
835 'cmd': [IMPLY_CMD, 'Allow CONFIG_CMD_... to imply'],
Simon Glasscb008832017-06-15 21:39:33 -0600836 'non-arch-board': [
837 IMPLY_NON_ARCH_BOARD,
838 'Allow Kconfig options outside arch/ and /board/ to imply'],
Simon Glass91197aa2021-12-18 14:54:35 -0700839}
Simon Glass9b2a2e82017-06-15 21:39:32 -0600840
Simon Glass9d603392021-12-18 08:09:43 -0700841
842def read_database():
843 """Read in the config database
844
845 Returns:
846 tuple:
847 set of all config options seen (each a str)
848 set of all defconfigs seen (each a str)
849 dict of configs for each defconfig:
850 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
851 value: dict:
852 key: CONFIG option
853 value: Value of option
854 dict of defconfigs for each config:
855 key: CONFIG option
856 value: set of boards using that option
857
858 """
859 configs = {}
860
861 # key is defconfig name, value is dict of (CONFIG_xxx, value)
862 config_db = {}
863
864 # Set of all config options we have seen
865 all_configs = set()
866
867 # Set of all defconfigs we have seen
868 all_defconfigs = set()
869
870 defconfig_db = collections.defaultdict(set)
Simon Glass37f815c2021-12-18 14:54:34 -0700871 for line in read_file(CONFIG_DATABASE):
872 line = line.rstrip()
873 if not line: # Separator between defconfigs
874 config_db[defconfig] = configs
875 all_defconfigs.add(defconfig)
876 configs = {}
877 elif line[0] == ' ': # CONFIG line
878 config, value = line.strip().split('=', 1)
879 configs[config] = value
880 defconfig_db[config].add(defconfig)
881 all_configs.add(config)
882 else: # New defconfig
883 defconfig = line
Simon Glass9d603392021-12-18 08:09:43 -0700884
885 return all_configs, all_defconfigs, config_db, defconfig_db
886
887
Simon Glasscb008832017-06-15 21:39:33 -0600888def do_imply_config(config_list, add_imply, imply_flags, skip_added,
889 check_kconfig=True, find_superset=False):
Simon Glass99b66602017-06-01 19:39:03 -0600890 """Find CONFIG options which imply those in the list
891
892 Some CONFIG options can be implied by others and this can help to reduce
893 the size of the defconfig files. For example, CONFIG_X86 implies
894 CONFIG_CMD_IRQ, so we can put 'imply CMD_IRQ' under 'config X86' and
895 all x86 boards will have that option, avoiding adding CONFIG_CMD_IRQ to
896 each of the x86 defconfig files.
897
898 This function uses the moveconfig database to find such options. It
899 displays a list of things that could possibly imply those in the list.
900 The algorithm ignores any that start with CONFIG_TARGET since these
901 typically refer to only a few defconfigs (often one). It also does not
902 display a config with less than 5 defconfigs.
903
904 The algorithm works using sets. For each target config in config_list:
905 - Get the set 'defconfigs' which use that target config
906 - For each config (from a list of all configs):
907 - Get the set 'imply_defconfig' of defconfigs which use that config
908 -
909 - If imply_defconfigs contains anything not in defconfigs then
910 this config does not imply the target config
911
912 Params:
913 config_list: List of CONFIG options to check (each a string)
Simon Glasscb008832017-06-15 21:39:33 -0600914 add_imply: Automatically add an 'imply' for each config.
Simon Glass9b2a2e82017-06-15 21:39:32 -0600915 imply_flags: Flags which control which implying configs are allowed
916 (IMPLY_...)
Simon Glasscb008832017-06-15 21:39:33 -0600917 skip_added: Don't show options which already have an imply added.
918 check_kconfig: Check if implied symbols already have an 'imply' or
919 'select' for the target config, and show this information if so.
Simon Glass99b66602017-06-01 19:39:03 -0600920 find_superset: True to look for configs which are a superset of those
921 already found. So for example if CONFIG_EXYNOS5 implies an option,
922 but CONFIG_EXYNOS covers a larger set of defconfigs and also
923 implies that option, this will drop the former in favour of the
924 latter. In practice this option has not proved very used.
925
926 Note the terminoloy:
927 config - a CONFIG_XXX options (a string, e.g. 'CONFIG_CMD_EEPROM')
928 defconfig - a defconfig file (a string, e.g. 'configs/snow_defconfig')
929 """
Simon Glasscb008832017-06-15 21:39:33 -0600930 kconf = KconfigScanner().conf if check_kconfig else None
931 if add_imply and add_imply != 'all':
Simon Glassa3627082021-12-18 08:09:42 -0700932 add_imply = add_imply.split(',')
Simon Glasscb008832017-06-15 21:39:33 -0600933
Simon Glass62fae4b2023-09-23 13:44:00 -0600934 all_configs, all_defconfigs, _, defconfig_db = read_database()
Simon Glass99b66602017-06-01 19:39:03 -0600935
Simon Glassa3627082021-12-18 08:09:42 -0700936 # Work through each target config option in turn, independently
Simon Glass99b66602017-06-01 19:39:03 -0600937 for config in config_list:
938 defconfigs = defconfig_db.get(config)
939 if not defconfigs:
Simon Glass1bd43062023-09-23 13:43:59 -0600940 print(f'{config} not found in any defconfig')
Simon Glass99b66602017-06-01 19:39:03 -0600941 continue
942
943 # Get the set of defconfigs without this one (since a config cannot
944 # imply itself)
945 non_defconfigs = all_defconfigs - defconfigs
946 num_defconfigs = len(defconfigs)
Simon Glass1bd43062023-09-23 13:43:59 -0600947 print(f'{config} found in {num_defconfigs}/{len(all_configs)} defconfigs')
Simon Glass99b66602017-06-01 19:39:03 -0600948
949 # This will hold the results: key=config, value=defconfigs containing it
950 imply_configs = {}
951 rest_configs = all_configs - set([config])
952
953 # Look at every possible config, except the target one
954 for imply_config in rest_configs:
Simon Glass9b2a2e82017-06-15 21:39:32 -0600955 if 'ERRATUM' in imply_config:
Simon Glass99b66602017-06-01 19:39:03 -0600956 continue
Simon Glass91197aa2021-12-18 14:54:35 -0700957 if not imply_flags & IMPLY_CMD:
Simon Glass9b2a2e82017-06-15 21:39:32 -0600958 if 'CONFIG_CMD' in imply_config:
959 continue
Simon Glass91197aa2021-12-18 14:54:35 -0700960 if not imply_flags & IMPLY_TARGET:
Simon Glass9b2a2e82017-06-15 21:39:32 -0600961 if 'CONFIG_TARGET' in imply_config:
962 continue
Simon Glass99b66602017-06-01 19:39:03 -0600963
964 # Find set of defconfigs that have this config
965 imply_defconfig = defconfig_db[imply_config]
966
967 # Get the intersection of this with defconfigs containing the
968 # target config
969 common_defconfigs = imply_defconfig & defconfigs
970
971 # Get the set of defconfigs containing this config which DO NOT
972 # also contain the taret config. If this set is non-empty it means
973 # that this config affects other defconfigs as well as (possibly)
974 # the ones affected by the target config. This means it implies
975 # things we don't want to imply.
976 not_common_defconfigs = imply_defconfig & non_defconfigs
977 if not_common_defconfigs:
978 continue
979
980 # If there are common defconfigs, imply_config may be useful
981 if common_defconfigs:
982 skip = False
983 if find_superset:
Simon Glass793dca32019-10-31 07:42:57 -0600984 for prev in list(imply_configs.keys()):
Simon Glass99b66602017-06-01 19:39:03 -0600985 prev_count = len(imply_configs[prev])
986 count = len(common_defconfigs)
987 if (prev_count > count and
988 (imply_configs[prev] & common_defconfigs ==
989 common_defconfigs)):
990 # skip imply_config because prev is a superset
991 skip = True
992 break
Simon Glassf297ba32023-09-23 13:44:05 -0600993 if count > prev_count:
Simon Glass99b66602017-06-01 19:39:03 -0600994 # delete prev because imply_config is a superset
995 del imply_configs[prev]
996 if not skip:
997 imply_configs[imply_config] = common_defconfigs
998
999 # Now we have a dict imply_configs of configs which imply each config
1000 # The value of each dict item is the set of defconfigs containing that
1001 # config. Rank them so that we print the configs that imply the largest
1002 # number of defconfigs first.
Simon Glasscb008832017-06-15 21:39:33 -06001003 ranked_iconfigs = sorted(imply_configs,
Simon Glass99b66602017-06-01 19:39:03 -06001004 key=lambda k: len(imply_configs[k]), reverse=True)
Simon Glasscb008832017-06-15 21:39:33 -06001005 kconfig_info = ''
1006 cwd = os.getcwd()
1007 add_list = collections.defaultdict(list)
1008 for iconfig in ranked_iconfigs:
1009 num_common = len(imply_configs[iconfig])
Simon Glass99b66602017-06-01 19:39:03 -06001010
1011 # Don't bother if there are less than 5 defconfigs affected.
Simon Glass9b2a2e82017-06-15 21:39:32 -06001012 if num_common < (2 if imply_flags & IMPLY_MIN_2 else 5):
Simon Glass99b66602017-06-01 19:39:03 -06001013 continue
Simon Glasscb008832017-06-15 21:39:33 -06001014 missing = defconfigs - imply_configs[iconfig]
Simon Glass99b66602017-06-01 19:39:03 -06001015 missing_str = ', '.join(missing) if missing else 'all'
1016 missing_str = ''
Simon Glasscb008832017-06-15 21:39:33 -06001017 show = True
1018 if kconf:
1019 sym = find_kconfig_rules(kconf, config[CONFIG_LEN:],
1020 iconfig[CONFIG_LEN:])
1021 kconfig_info = ''
1022 if sym:
Simon Glassea40b202021-07-21 21:35:53 -06001023 nodes = sym.nodes
1024 if len(nodes) == 1:
1025 fname, linenum = nodes[0].filename, nodes[0].linenr
Simon Glasscb008832017-06-15 21:39:33 -06001026 if cwd and fname.startswith(cwd):
1027 fname = fname[len(cwd) + 1:]
Simon Glass1bd43062023-09-23 13:43:59 -06001028 kconfig_info = f'{fname}:{linenum}'
Simon Glasscb008832017-06-15 21:39:33 -06001029 if skip_added:
1030 show = False
1031 else:
Tom Rini65e05dd2019-09-20 17:42:09 -04001032 sym = kconf.syms.get(iconfig[CONFIG_LEN:])
Simon Glasscb008832017-06-15 21:39:33 -06001033 fname = ''
1034 if sym:
Simon Glassea40b202021-07-21 21:35:53 -06001035 nodes = sym.nodes
1036 if len(nodes) == 1:
1037 fname, linenum = nodes[0].filename, nodes[0].linenr
Simon Glasscb008832017-06-15 21:39:33 -06001038 if cwd and fname.startswith(cwd):
1039 fname = fname[len(cwd) + 1:]
1040 in_arch_board = not sym or (fname.startswith('arch') or
1041 fname.startswith('board'))
1042 if (not in_arch_board and
Simon Glass91197aa2021-12-18 14:54:35 -07001043 not imply_flags & IMPLY_NON_ARCH_BOARD):
Simon Glasscb008832017-06-15 21:39:33 -06001044 continue
1045
1046 if add_imply and (add_imply == 'all' or
1047 iconfig in add_imply):
1048 fname, linenum, kconfig_info = (check_imply_rule(kconf,
1049 config[CONFIG_LEN:], iconfig[CONFIG_LEN:]))
1050 if fname:
1051 add_list[fname].append(linenum)
1052
1053 if show and kconfig_info != 'skip':
Simon Glass1bd43062023-09-23 13:43:59 -06001054 print(f'{num_common:5d} : '
1055 f'{iconfig.ljust(30):-30s}{kconfig_info:-25s} {missing_str}')
Simon Glasscb008832017-06-15 21:39:33 -06001056
1057 # Having collected a list of things to add, now we add them. We process
1058 # each file from the largest line number to the smallest so that
1059 # earlier additions do not affect our line numbers. E.g. if we added an
1060 # imply at line 20 it would change the position of each line after
1061 # that.
Simon Glass793dca32019-10-31 07:42:57 -06001062 for fname, linenums in add_list.items():
Simon Glasscb008832017-06-15 21:39:33 -06001063 for linenum in sorted(linenums, reverse=True):
1064 add_imply_rule(config[CONFIG_LEN:], fname, linenum)
Simon Glass99b66602017-06-01 19:39:03 -06001065
Simon Glass941671a2022-02-08 11:49:46 -07001066def defconfig_matches(configs, re_match):
1067 """Check if any CONFIG option matches a regex
1068
1069 The match must be complete, i.e. from the start to end of the CONFIG option.
1070
1071 Args:
1072 configs (dict): Dict of CONFIG options:
1073 key: CONFIG option
1074 value: Value of option
1075 re_match (re.Pattern): Match to check
1076
1077 Returns:
1078 bool: True if any CONFIG matches the regex
1079 """
1080 for cfg in configs:
Simon Glassd9c958f2022-03-05 20:18:54 -07001081 if re_match.fullmatch(cfg):
Simon Glass941671a2022-02-08 11:49:46 -07001082 return True
1083 return False
Simon Glass99b66602017-06-01 19:39:03 -06001084
Simon Glass65d7fce2021-12-18 08:09:46 -07001085def do_find_config(config_list):
1086 """Find boards with a given combination of CONFIGs
1087
1088 Params:
Simon Glass941671a2022-02-08 11:49:46 -07001089 config_list: List of CONFIG options to check (each a regex consisting
Simon Glass65d7fce2021-12-18 08:09:46 -07001090 of a config option, with or without a CONFIG_ prefix. If an option
1091 is preceded by a tilde (~) then it must be false, otherwise it must
1092 be true)
1093 """
Simon Glass62fae4b2023-09-23 13:44:00 -06001094 _, all_defconfigs, config_db, _ = read_database()
Simon Glass65d7fce2021-12-18 08:09:46 -07001095
Simon Glass65d7fce2021-12-18 08:09:46 -07001096 # Start with all defconfigs
1097 out = all_defconfigs
1098
1099 # Work through each config in turn
Simon Glass65d7fce2021-12-18 08:09:46 -07001100 for item in config_list:
1101 # Get the real config name and whether we want this config or not
1102 cfg = item
1103 want = True
1104 if cfg[0] == '~':
1105 want = False
1106 cfg = cfg[1:]
1107
Simon Glass65d7fce2021-12-18 08:09:46 -07001108 # Search everything that is still in the running. If it has a config
1109 # that we want, or doesn't have one that we don't, add it into the
1110 # running for the next stage
1111 in_list = out
1112 out = set()
Simon Glass941671a2022-02-08 11:49:46 -07001113 re_match = re.compile(cfg)
Simon Glass65d7fce2021-12-18 08:09:46 -07001114 for defc in in_list:
Simon Glass941671a2022-02-08 11:49:46 -07001115 has_cfg = defconfig_matches(config_db[defc], re_match)
Simon Glass65d7fce2021-12-18 08:09:46 -07001116 if has_cfg == want:
1117 out.add(defc)
Tom Rini9ef3ba82022-12-04 10:14:16 -05001118 print(f'{len(out)} matches')
1119 print(' '.join(item.split('_defconfig')[0] for item in out))
Simon Glass65d7fce2021-12-18 08:09:46 -07001120
1121
1122def prefix_config(cfg):
1123 """Prefix a config with CONFIG_ if needed
1124
1125 This handles ~ operator, which indicates that the CONFIG should be disabled
1126
1127 >>> prefix_config('FRED')
1128 'CONFIG_FRED'
1129 >>> prefix_config('CONFIG_FRED')
1130 'CONFIG_FRED'
1131 >>> prefix_config('~FRED')
1132 '~CONFIG_FRED'
1133 >>> prefix_config('~CONFIG_FRED')
1134 '~CONFIG_FRED'
1135 >>> prefix_config('A123')
1136 'CONFIG_A123'
1137 """
Simon Glassa4c9d172023-09-23 13:44:01 -06001138 oper = ''
Simon Glass65d7fce2021-12-18 08:09:46 -07001139 if cfg[0] == '~':
Simon Glassa4c9d172023-09-23 13:44:01 -06001140 oper = cfg[0]
Simon Glass65d7fce2021-12-18 08:09:46 -07001141 cfg = cfg[1:]
1142 if not cfg.startswith('CONFIG_'):
1143 cfg = 'CONFIG_' + cfg
Simon Glassa4c9d172023-09-23 13:44:01 -06001144 return oper + cfg
Simon Glass65d7fce2021-12-18 08:09:46 -07001145
1146
Simon Glass98275712023-09-23 13:43:57 -06001147RE_MK_CONFIGS = re.compile(r'CONFIG_(\$\(SPL_(?:TPL_)?\))?([A-Za-z0-9_]*)')
1148RE_IFDEF = re.compile(r'(ifdef|ifndef)')
1149RE_C_CONFIGS = re.compile(r'CONFIG_([A-Za-z0-9_]*)')
1150RE_CONFIG_IS = re.compile(r'CONFIG_IS_ENABLED\(([A-Za-z0-9_]*)\)')
Simon Glass65e62032023-02-01 13:19:12 -07001151
1152class ConfigUse:
1153 def __init__(self, cfg, is_spl, fname, rest):
1154 self.cfg = cfg
1155 self.is_spl = is_spl
1156 self.fname = fname
1157 self.rest = rest
1158
1159 def __hash__(self):
1160 return hash((self.cfg, self.is_spl))
1161
1162def scan_makefiles(fnames):
1163 """Scan Makefiles looking for Kconfig options
1164
1165 Looks for uses of CONFIG options in Makefiles
1166
1167 Args:
1168 fnames (list of tuple):
1169 str: Makefile filename where the option was found
1170 str: Line of the Makefile
1171
1172 Returns:
1173 tuple:
1174 dict: all_uses
1175 key (ConfigUse): object
1176 value (list of str): matching lines
1177 dict: Uses by filename
1178 key (str): filename
1179 value (set of ConfigUse): uses in that filename
1180
1181 >>> RE_MK_CONFIGS.search('CONFIG_FRED').groups()
1182 (None, 'FRED')
1183 >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_)MARY').groups()
1184 ('$(SPL_)', 'MARY')
1185 >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_TPL_)MARY').groups()
1186 ('$(SPL_TPL_)', 'MARY')
1187 """
1188 all_uses = collections.defaultdict(list)
1189 fname_uses = {}
1190 for fname, rest in fnames:
1191 m_iter = RE_MK_CONFIGS.finditer(rest)
Simon Glassa4c9d172023-09-23 13:44:01 -06001192 for mat in m_iter:
1193 real_opt = mat.group(2)
Simon Glass65e62032023-02-01 13:19:12 -07001194 if real_opt == '':
1195 continue
1196 is_spl = False
Simon Glassa4c9d172023-09-23 13:44:01 -06001197 if mat.group(1):
Simon Glass65e62032023-02-01 13:19:12 -07001198 is_spl = True
1199 use = ConfigUse(real_opt, is_spl, fname, rest)
1200 if fname not in fname_uses:
1201 fname_uses[fname] = set()
1202 fname_uses[fname].add(use)
1203 all_uses[use].append(rest)
1204 return all_uses, fname_uses
1205
1206
1207def scan_src_files(fnames):
1208 """Scan source files (other than Makefiles) looking for Kconfig options
1209
1210 Looks for uses of CONFIG options
1211
1212 Args:
1213 fnames (list of tuple):
1214 str: Makefile filename where the option was found
1215 str: Line of the Makefile
1216
1217 Returns:
1218 tuple:
1219 dict: all_uses
1220 key (ConfigUse): object
1221 value (list of str): matching lines
1222 dict: Uses by filename
1223 key (str): filename
1224 value (set of ConfigUse): uses in that filename
1225
1226 >>> RE_C_CONFIGS.search('CONFIG_FRED').groups()
1227 ('FRED',)
1228 >>> RE_CONFIG_IS.search('CONFIG_IS_ENABLED(MARY)').groups()
1229 ('MARY',)
1230 >>> RE_CONFIG_IS.search('#if CONFIG_IS_ENABLED(OF_PLATDATA)').groups()
1231 ('OF_PLATDATA',)
1232 """
Simon Glass62fae4b2023-09-23 13:44:00 -06001233 fname = None
1234 rest = None
1235
Simon Glass65e62032023-02-01 13:19:12 -07001236 def add_uses(m_iter, is_spl):
Simon Glassa4c9d172023-09-23 13:44:01 -06001237 for mat in m_iter:
1238 real_opt = mat.group(1)
Simon Glass65e62032023-02-01 13:19:12 -07001239 if real_opt == '':
1240 continue
1241 use = ConfigUse(real_opt, is_spl, fname, rest)
1242 if fname not in fname_uses:
1243 fname_uses[fname] = set()
1244 fname_uses[fname].add(use)
1245 all_uses[use].append(rest)
1246
1247 all_uses = collections.defaultdict(list)
1248 fname_uses = {}
1249 for fname, rest in fnames:
1250 m_iter = RE_C_CONFIGS.finditer(rest)
1251 add_uses(m_iter, False)
1252
1253 m_iter2 = RE_CONFIG_IS.finditer(rest)
1254 add_uses(m_iter2, True)
1255
1256 return all_uses, fname_uses
1257
1258
1259MODE_NORMAL, MODE_SPL, MODE_PROPER = range(3)
1260
1261def do_scan_source(path, do_update):
1262 """Scan the source tree for Kconfig inconsistencies
1263
1264 Args:
1265 path (str): Path to source tree
1266 do_update (bool) : True to write to scripts/kconf_... files
1267 """
1268 def is_not_proper(name):
1269 for prefix in SPL_PREFIXES:
1270 if name.startswith(prefix):
1271 return name[len(prefix):]
1272 return False
1273
1274 def check_not_found(all_uses, spl_mode):
1275 """Check for Kconfig options mentioned in the source but not in Kconfig
1276
1277 Args:
1278 all_uses (dict):
1279 key (ConfigUse): object
1280 value (list of str): matching lines
1281 spl_mode (int): If MODE_SPL, look at source code which implies
1282 an SPL_ option, but for which there is none;
1283 for MOD_PROPER, look at source code which implies a Proper
1284 option (i.e. use of CONFIG_IS_ENABLED() or $(SPL_) or
1285 $(SPL_TPL_) but for which there none;
1286 if MODE_NORMAL, ignore SPL
1287
1288 Returns:
1289 dict:
1290 key (str): CONFIG name (without 'CONFIG_' prefix
1291 value (list of ConfigUse): List of uses of this CONFIG
1292 """
1293 # Make sure we know about all the options
1294 not_found = collections.defaultdict(list)
Simon Glass62fae4b2023-09-23 13:44:00 -06001295 for use, _ in all_uses.items():
Simon Glass65e62032023-02-01 13:19:12 -07001296 name = use.cfg
1297 if name in IGNORE_SYMS:
1298 continue
1299 check = True
1300
1301 if spl_mode == MODE_SPL:
1302 check = use.is_spl
1303
1304 # If it is an SPL symbol, try prepending all SPL_ prefixes to
1305 # find at least one SPL symbol
1306 if use.is_spl:
Simon Glass65e62032023-02-01 13:19:12 -07001307 for prefix in SPL_PREFIXES:
1308 try_name = prefix + name
1309 sym = kconf.syms.get(try_name)
1310 if sym:
1311 break
1312 if not sym:
1313 not_found[f'SPL_{name}'].append(use)
1314 continue
1315 elif spl_mode == MODE_PROPER:
1316 # Try to find the Proper version of this symbol, i.e. without
1317 # the SPL_ prefix
1318 proper_name = is_not_proper(name)
1319 if proper_name:
1320 name = proper_name
1321 elif not use.is_spl:
1322 check = False
1323 else: # MODE_NORMAL
Simon Glass65e62032023-02-01 13:19:12 -07001324 sym = kconf.syms.get(name)
1325 if not sym:
1326 proper_name = is_not_proper(name)
1327 if proper_name:
1328 name = proper_name
1329 sym = kconf.syms.get(name)
1330 if not sym:
1331 for prefix in SPL_PREFIXES:
1332 try_name = prefix + name
1333 sym = kconf.syms.get(try_name)
1334 if sym:
1335 break
1336 if not sym:
1337 not_found[name].append(use)
1338 continue
1339
1340 sym = kconf.syms.get(name)
1341 if not sym and check:
1342 not_found[name].append(use)
1343 return not_found
1344
1345 def show_uses(uses):
1346 """Show a list of uses along with their filename and code snippet
1347
1348 Args:
1349 uses (dict):
1350 key (str): CONFIG name (without 'CONFIG_' prefix
1351 value (list of ConfigUse): List of uses of this CONFIG
1352 """
1353 for name in sorted(uses):
1354 print(f'{name}: ', end='')
1355 for i, use in enumerate(uses[name]):
1356 print(f'{" " if i else ""}{use.fname}: {use.rest.strip()}')
1357
1358
1359 print('Scanning Kconfig')
1360 kconf = KconfigScanner().conf
1361 print(f'Scanning source in {path}')
1362 args = ['git', 'grep', '-E', r'IS_ENABLED|\bCONFIG']
1363 with subprocess.Popen(args, stdout=subprocess.PIPE) as proc:
Simon Glass62fae4b2023-09-23 13:44:00 -06001364 out, _ = proc.communicate()
Simon Glass65e62032023-02-01 13:19:12 -07001365 lines = out.splitlines()
1366 re_fname = re.compile('^([^:]*):(.*)')
1367 src_list = []
1368 mk_list = []
1369 for line in lines:
1370 linestr = line.decode('utf-8')
1371 m_fname = re_fname.search(linestr)
1372 if not m_fname:
1373 continue
1374 fname, rest = m_fname.groups()
1375 dirname, leaf = os.path.split(fname)
1376 root, ext = os.path.splitext(leaf)
1377 if ext == '.autoconf':
1378 pass
1379 elif ext in ['.c', '.h', '.S', '.lds', '.dts', '.dtsi', '.asl', '.cfg',
1380 '.env', '.tmpl']:
1381 src_list.append([fname, rest])
1382 elif 'Makefile' in root or ext == '.mk':
1383 mk_list.append([fname, rest])
1384 elif ext in ['.yml', '.sh', '.py', '.awk', '.pl', '.rst', '', '.sed']:
1385 pass
1386 elif 'Kconfig' in root or 'Kbuild' in root:
1387 pass
1388 elif 'README' in root:
1389 pass
1390 elif dirname in ['configs']:
1391 pass
1392 elif dirname.startswith('doc') or dirname.startswith('scripts/kconfig'):
1393 pass
1394 else:
1395 print(f'Not sure how to handle file {fname}')
1396
1397 # Scan the Makefiles
Simon Glass62fae4b2023-09-23 13:44:00 -06001398 all_uses, _ = scan_makefiles(mk_list)
Simon Glass65e62032023-02-01 13:19:12 -07001399
1400 spl_not_found = set()
1401 proper_not_found = set()
1402
1403 # Make sure we know about all the options
1404 print('\nCONFIG options present in Makefiles but not Kconfig:')
1405 not_found = check_not_found(all_uses, MODE_NORMAL)
1406 show_uses(not_found)
1407
1408 print('\nCONFIG options present in Makefiles but not Kconfig (SPL):')
1409 not_found = check_not_found(all_uses, MODE_SPL)
1410 show_uses(not_found)
Simon Glassb774ba52023-09-23 13:44:03 -06001411 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
Simon Glass65e62032023-02-01 13:19:12 -07001412
1413 print('\nCONFIG options used as Proper in Makefiles but without a non-SPL_ variant:')
1414 not_found = check_not_found(all_uses, MODE_PROPER)
1415 show_uses(not_found)
Simon Glassb774ba52023-09-23 13:44:03 -06001416 proper_not_found |= {not_found.keys()}
Simon Glass65e62032023-02-01 13:19:12 -07001417
1418 # Scan the source code
Simon Glass62fae4b2023-09-23 13:44:00 -06001419 all_uses, _ = scan_src_files(src_list)
Simon Glass65e62032023-02-01 13:19:12 -07001420
1421 # Make sure we know about all the options
1422 print('\nCONFIG options present in source but not Kconfig:')
1423 not_found = check_not_found(all_uses, MODE_NORMAL)
1424 show_uses(not_found)
1425
1426 print('\nCONFIG options present in source but not Kconfig (SPL):')
1427 not_found = check_not_found(all_uses, MODE_SPL)
1428 show_uses(not_found)
Simon Glassb774ba52023-09-23 13:44:03 -06001429 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
Simon Glass65e62032023-02-01 13:19:12 -07001430
1431 print('\nCONFIG options used as Proper in source but without a non-SPL_ variant:')
1432 not_found = check_not_found(all_uses, MODE_PROPER)
1433 show_uses(not_found)
Simon Glassb774ba52023-09-23 13:44:03 -06001434 proper_not_found |= {not_found.keys()}
Simon Glass65e62032023-02-01 13:19:12 -07001435
1436 print('\nCONFIG options used as SPL but without an SPL_ variant:')
1437 for item in sorted(spl_not_found):
1438 print(f' {item}')
1439
1440 print('\nCONFIG options used as Proper but without a non-SPL_ variant:')
1441 for item in sorted(proper_not_found):
1442 print(f' {item}')
1443
1444 # Write out the updated information
1445 if do_update:
Simon Glasse6c686f2023-09-23 13:44:04 -06001446 with open(os.path.join(path, 'scripts', 'conf_nospl'), 'w',
1447 encoding='utf-8') as out:
Simon Glass65e62032023-02-01 13:19:12 -07001448 print('# These options should not be enabled in SPL builds\n',
1449 file=out)
1450 for item in sorted(spl_not_found):
1451 print(item, file=out)
Simon Glasse6c686f2023-09-23 13:44:04 -06001452 with open(os.path.join(path, 'scripts', 'conf_noproper'), 'w',
1453 encoding='utf-8') as out:
Simon Glass65e62032023-02-01 13:19:12 -07001454 print('# These options should not be enabled in Proper builds\n',
1455 file=out)
1456 for item in sorted(proper_not_found):
1457 print(item, file=out)
1458
1459
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001460def main():
1461 try:
1462 cpu_count = multiprocessing.cpu_count()
1463 except NotImplementedError:
1464 cpu_count = 1
1465
Simon Glassb2e83c62021-12-18 14:54:31 -07001466 epilog = '''Move config options from headers to defconfig files. See
1467doc/develop/moveconfig.rst for documentation.'''
1468
1469 parser = ArgumentParser(epilog=epilog)
1470 # Add arguments here
1471 parser.add_argument('-a', '--add-imply', type=str, default='',
Simon Glasscb008832017-06-15 21:39:33 -06001472 help='comma-separated list of CONFIG options to add '
1473 "an 'imply' statement to for the CONFIG in -i")
Simon Glassb2e83c62021-12-18 14:54:31 -07001474 parser.add_argument('-A', '--skip-added', action='store_true', default=False,
Simon Glasscb008832017-06-15 21:39:33 -06001475 help="don't show options which are already marked as "
1476 'implying others')
Simon Glassb2e83c62021-12-18 14:54:31 -07001477 parser.add_argument('-b', '--build-db', action='store_true', default=False,
Simon Glassd73fcb12017-06-01 19:39:02 -06001478 help='build a CONFIG database')
Simon Glassb2e83c62021-12-18 14:54:31 -07001479 parser.add_argument('-C', '--commit', action='store_true', default=False,
Simon Glass9ede2122016-09-12 23:18:21 -06001480 help='Create a git commit for the operation')
Simon Glass15f19ab2023-09-23 13:44:09 -06001481 parser.add_argument('--nocolour', action='store_true', default=False,
1482 help="don't display the log in colour")
Simon Glassb2e83c62021-12-18 14:54:31 -07001483 parser.add_argument('-d', '--defconfigs', type=str,
Simon Glassee4e61b2017-06-01 19:38:59 -06001484 help='a file containing a list of defconfigs to move, '
1485 "one per line (for example 'snow_defconfig') "
1486 "or '-' to read from stdin")
Simon Glassb2e83c62021-12-18 14:54:31 -07001487 parser.add_argument('-e', '--exit-on-error', action='store_true',
Simon Glasse1ae5632021-12-18 08:09:44 -07001488 default=False,
1489 help='exit immediately on any error')
Simon Glassb2e83c62021-12-18 14:54:31 -07001490 parser.add_argument('-f', '--find', action='store_true', default=False,
Simon Glass65d7fce2021-12-18 08:09:46 -07001491 help='Find boards with a given config combination')
Simon Glassb2e83c62021-12-18 14:54:31 -07001492 parser.add_argument('-i', '--imply', action='store_true', default=False,
Simon Glass99b66602017-06-01 19:39:03 -06001493 help='find options which imply others')
Simon Glassb2e83c62021-12-18 14:54:31 -07001494 parser.add_argument('-I', '--imply-flags', type=str, default='',
Simon Glass9b2a2e82017-06-15 21:39:32 -06001495 help="control the -i option ('help' for help")
Simon Glassb2e83c62021-12-18 14:54:31 -07001496 parser.add_argument('-j', '--jobs', type=int, default=cpu_count,
Simon Glasse1ae5632021-12-18 08:09:44 -07001497 help='the number of jobs to run simultaneously')
Simon Glassb2e83c62021-12-18 14:54:31 -07001498 parser.add_argument('-n', '--dry-run', action='store_true', default=False,
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001499 help='perform a trial run (show log with no changes)')
Simon Glassb2e83c62021-12-18 14:54:31 -07001500 parser.add_argument('-r', '--git-ref', type=str,
Simon Glasse1ae5632021-12-18 08:09:44 -07001501 help='the git ref to clone for building the autoconf.mk')
Simon Glassb2e83c62021-12-18 14:54:31 -07001502 parser.add_argument('-s', '--force-sync', action='store_true', default=False,
Masahiro Yamada8513dc02016-05-19 15:52:08 +09001503 help='force sync by savedefconfig')
Simon Glassb2e83c62021-12-18 14:54:31 -07001504 parser.add_argument('-S', '--spl', action='store_true', default=False,
Masahiro Yamada07913d12016-08-22 22:18:22 +09001505 help='parse config options defined for SPL build')
Simon Glass65e62032023-02-01 13:19:12 -07001506 parser.add_argument('--scan-source', action='store_true', default=False,
1507 help='scan source for uses of CONFIG options')
Simon Glassb2e83c62021-12-18 14:54:31 -07001508 parser.add_argument('-t', '--test', action='store_true', default=False,
Simon Glasse1ae5632021-12-18 08:09:44 -07001509 help='run unit tests')
Simon Glassb2e83c62021-12-18 14:54:31 -07001510 parser.add_argument('-y', '--yes', action='store_true', default=False,
Simon Glass6b403df2016-09-12 23:18:20 -06001511 help="respond 'yes' to any prompts")
Simon Glass65e62032023-02-01 13:19:12 -07001512 parser.add_argument('-u', '--update', action='store_true', default=False,
1513 help="update scripts/ files (use with --scan-source)")
Simon Glassb2e83c62021-12-18 14:54:31 -07001514 parser.add_argument('-v', '--verbose', action='store_true', default=False,
Joe Hershberger95bf9c72015-05-19 13:21:24 -05001515 help='show any build errors as boards are built')
Simon Glassb2e83c62021-12-18 14:54:31 -07001516 parser.add_argument('configs', nargs='*')
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001517
Simon Glassb2e83c62021-12-18 14:54:31 -07001518 args = parser.parse_args()
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001519
Simon Glassb2e83c62021-12-18 14:54:31 -07001520 if args.test:
Simon Glass84067a52021-12-18 08:09:45 -07001521 sys.argv = [sys.argv[0]]
Simon Glass62fae4b2023-09-23 13:44:00 -06001522 fail, _ = doctest.testmod()
Simon Glass84067a52021-12-18 08:09:45 -07001523 if fail:
1524 return 1
1525 unittest.main()
1526
Simon Glass15f19ab2023-09-23 13:44:09 -06001527 col = terminal.Color(terminal.COLOR_NEVER if args.nocolour
1528 else terminal.COLOR_IF_TERMINAL)
1529
Simon Glass65e62032023-02-01 13:19:12 -07001530 if args.scan_source:
1531 do_scan_source(os.getcwd(), args.update)
Simon Glassf297ba32023-09-23 13:44:05 -06001532 return 0
Simon Glass65e62032023-02-01 13:19:12 -07001533
Simon Glass882c8e42023-09-23 13:43:54 -06001534 if not any((args.force_sync, args.build_db, args.imply, args.find)):
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001535 parser.print_usage()
1536 sys.exit(1)
1537
Masahiro Yamadab6ef3932016-05-19 15:51:58 +09001538 # prefix the option name with CONFIG_ if missing
Simon Glass882c8e42023-09-23 13:43:54 -06001539 configs = [prefix_config(cfg) for cfg in args.configs]
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001540
Joe Hershberger2144f882015-05-19 13:21:20 -05001541 check_top_directory()
1542
Simon Glassb2e83c62021-12-18 14:54:31 -07001543 if args.imply:
Simon Glass9b2a2e82017-06-15 21:39:32 -06001544 imply_flags = 0
Simon Glassb2e83c62021-12-18 14:54:31 -07001545 if args.imply_flags == 'all':
Simon Glassdee36c72017-07-10 14:47:46 -06001546 imply_flags = -1
1547
Simon Glassb2e83c62021-12-18 14:54:31 -07001548 elif args.imply_flags:
1549 for flag in args.imply_flags.split(','):
Simon Glassdee36c72017-07-10 14:47:46 -06001550 bad = flag not in IMPLY_FLAGS
1551 if bad:
Simon Glass1bd43062023-09-23 13:43:59 -06001552 print(f"Invalid flag '{flag}'")
Simon Glassdee36c72017-07-10 14:47:46 -06001553 if flag == 'help' or bad:
Simon Glass793dca32019-10-31 07:42:57 -06001554 print("Imply flags: (separate with ',')")
1555 for name, info in IMPLY_FLAGS.items():
Simon Glass1bd43062023-09-23 13:43:59 -06001556 print(f' {name:-15s}: {info[1]}')
Simon Glassdee36c72017-07-10 14:47:46 -06001557 parser.print_usage()
1558 sys.exit(1)
1559 imply_flags |= IMPLY_FLAGS[flag][0]
Simon Glass9b2a2e82017-06-15 21:39:32 -06001560
Simon Glassb2e83c62021-12-18 14:54:31 -07001561 do_imply_config(configs, args.add_imply, imply_flags, args.skip_added)
Simon Glassf297ba32023-09-23 13:44:05 -06001562 return 0
Simon Glass99b66602017-06-01 19:39:03 -06001563
Simon Glassb2e83c62021-12-18 14:54:31 -07001564 if args.find:
Simon Glass65d7fce2021-12-18 08:09:46 -07001565 do_find_config(configs)
Simon Glassf297ba32023-09-23 13:44:05 -06001566 return 0
Simon Glass65d7fce2021-12-18 08:09:46 -07001567
Simon Glass3481e892023-09-23 13:43:53 -06001568 # We are either building the database or forcing a sync of defconfigs
Simon Glassd73fcb12017-06-01 19:39:02 -06001569 config_db = {}
Simon Glass793dca32019-10-31 07:42:57 -06001570 db_queue = queue.Queue()
Simon Glassa4c9d172023-09-23 13:44:01 -06001571 dbt = DatabaseThread(config_db, db_queue)
1572 dbt.daemon = True
1573 dbt.start()
Simon Glassd73fcb12017-06-01 19:39:02 -06001574
Simon Glass63df2022023-09-23 13:43:50 -06001575 check_clean_directory()
1576 bsettings.setup('')
1577 toolchains = toolchain.Toolchains()
1578 toolchains.GetSettings()
1579 toolchains.Scan(verbose=False)
Simon Glass15f19ab2023-09-23 13:44:09 -06001580 move_config(toolchains, args, db_queue, col)
Simon Glass63df2022023-09-23 13:43:50 -06001581 db_queue.join()
Joe Hershberger2144f882015-05-19 13:21:20 -05001582
Simon Glassb2e83c62021-12-18 14:54:31 -07001583 if args.commit:
Simon Glass9ede2122016-09-12 23:18:21 -06001584 subprocess.call(['git', 'add', '-u'])
1585 if configs:
1586 msg = 'Convert %s %sto Kconfig' % (configs[0],
1587 'et al ' if len(configs) > 1 else '')
1588 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' %
1589 '\n '.join(configs))
1590 else:
1591 msg = 'configs: Resync with savedefconfig'
1592 msg += '\n\nRsync all defconfig files using moveconfig.py'
1593 subprocess.call(['git', 'commit', '-s', '-m', msg])
1594
Simon Glassb2e83c62021-12-18 14:54:31 -07001595 if args.build_db:
Simon Glassa4c9d172023-09-23 13:44:01 -06001596 with open(CONFIG_DATABASE, 'w', encoding='utf-8') as outf:
Simon Glass793dca32019-10-31 07:42:57 -06001597 for defconfig, configs in config_db.items():
Simon Glassa4c9d172023-09-23 13:44:01 -06001598 outf.write(f'{defconfig}\n')
Simon Glassd73fcb12017-06-01 19:39:02 -06001599 for config in sorted(configs.keys()):
Simon Glassa4c9d172023-09-23 13:44:01 -06001600 outf.write(f' {config}={configs[config]}\n')
1601 outf.write('\n')
Simon Glassf297ba32023-09-23 13:44:05 -06001602 return 0
1603
Simon Glassd73fcb12017-06-01 19:39:02 -06001604
Masahiro Yamada5a27c732015-05-20 11:36:07 +09001605if __name__ == '__main__':
Simon Glass65d7fce2021-12-18 08:09:46 -07001606 sys.exit(main())