blob: 058d72cf4bccad263efd981c343b317ae0b4d22d [file] [log] [blame]
Simon Glass1f701862019-10-31 07:42:57 -06001#!/usr/bin/env python3
Tom Rini10e47792018-05-06 17:58:06 -04002# SPDX-License-Identifier: GPL-2.0+
Masahiro Yamadab6160812015-05-20 11:36:07 +09003
Simon Glass28155572024-07-17 16:56:52 +01004"""Build and query a Kconfig database for boards.
Masahiro Yamadab6160812015-05-20 11:36:07 +09005
Simon Glass46960942024-07-17 16:56:54 +01006See doc/develop/qconfig.rst for documentation.
Simon Glass28155572024-07-17 16:56:52 +01007
8Author: Masahiro Yamada <yamada.masahiro@socionext.com>
9Author: Simon Glass <sjg@chromium.org>
Masahiro Yamadab6160812015-05-20 11:36:07 +090010"""
11
Simon Glassd9c1da22021-12-18 14:54:31 -070012from argparse import ArgumentParser
Simon Glassc6e73cf2017-06-01 19:39:03 -060013import collections
Simon Glassb3464eb2021-12-18 14:54:35 -070014from contextlib import ExitStack
Simon Glassbb57be72021-12-18 08:09:45 -070015import doctest
Masahiro Yamada0f6beda2016-05-19 15:52:07 +090016import filecmp
Masahiro Yamadab6160812015-05-20 11:36:07 +090017import fnmatch
Masahiro Yamada3984d6e2016-10-19 14:39:54 +090018import glob
Masahiro Yamadab6160812015-05-20 11:36:07 +090019import multiprocessing
Masahiro Yamadab6160812015-05-20 11:36:07 +090020import os
Simon Glass1f701862019-10-31 07:42:57 -060021import queue
Masahiro Yamadab6160812015-05-20 11:36:07 +090022import re
23import shutil
24import subprocess
25import sys
26import tempfile
Simon Glass43cf08f2017-06-01 19:39:02 -060027import threading
Masahiro Yamadab6160812015-05-20 11:36:07 +090028import time
Simon Glassbb57be72021-12-18 08:09:45 -070029import unittest
Masahiro Yamadab6160812015-05-20 11:36:07 +090030
Simon Glassf0d9c102020-04-17 18:09:02 -060031from buildman import bsettings
32from buildman import kconfiglib
33from buildman import toolchain
Simon Glass9b191102023-09-23 13:44:09 -060034from u_boot_pylib import terminal
Simon Glass44116332017-06-15 21:39:33 -060035
Masahiro Yamadab6160812015-05-20 11:36:07 +090036SHOW_GNU_MAKE = 'scripts/show-gnu-make'
37SLEEP_TIME=0.03
38
Masahiro Yamadab6160812015-05-20 11:36:07 +090039STATE_IDLE = 0
40STATE_DEFCONFIG = 1
41STATE_AUTOCONF = 2
Joe Hershberger166edec2015-05-19 13:21:17 -050042STATE_SAVEDEFCONFIG = 3
Masahiro Yamadab6160812015-05-20 11:36:07 +090043
Simon Glass8fb5bd02017-06-01 19:39:01 -060044AUTO_CONF_PATH = 'include/config/auto.conf'
Simon Glass8a372032023-09-23 13:44:15 -060045CONFIG_DATABASE = 'qconfig.db'
46FAILED_LIST = 'qconfig.failed'
Simon Glass8fb5bd02017-06-01 19:39:01 -060047
Simon Glass44116332017-06-15 21:39:33 -060048CONFIG_LEN = len('CONFIG_')
Simon Glass8fb5bd02017-06-01 19:39:01 -060049
Markus Klotzbuecher3d5d4182019-05-15 15:15:52 +020050SIZES = {
Simon Glassdc634d92021-12-18 14:54:30 -070051 'SZ_1': 0x00000001, 'SZ_2': 0x00000002,
52 'SZ_4': 0x00000004, 'SZ_8': 0x00000008,
53 'SZ_16': 0x00000010, 'SZ_32': 0x00000020,
54 'SZ_64': 0x00000040, 'SZ_128': 0x00000080,
55 'SZ_256': 0x00000100, 'SZ_512': 0x00000200,
56 'SZ_1K': 0x00000400, 'SZ_2K': 0x00000800,
57 'SZ_4K': 0x00001000, 'SZ_8K': 0x00002000,
58 'SZ_16K': 0x00004000, 'SZ_32K': 0x00008000,
59 'SZ_64K': 0x00010000, 'SZ_128K': 0x00020000,
60 'SZ_256K': 0x00040000, 'SZ_512K': 0x00080000,
61 'SZ_1M': 0x00100000, 'SZ_2M': 0x00200000,
62 'SZ_4M': 0x00400000, 'SZ_8M': 0x00800000,
63 'SZ_16M': 0x01000000, 'SZ_32M': 0x02000000,
64 'SZ_64M': 0x04000000, 'SZ_128M': 0x08000000,
65 'SZ_256M': 0x10000000, 'SZ_512M': 0x20000000,
66 'SZ_1G': 0x40000000, 'SZ_2G': 0x80000000,
67 'SZ_4G': 0x100000000
Markus Klotzbuecher3d5d4182019-05-15 15:15:52 +020068}
69
Simon Glasse8037552022-02-08 11:49:45 -070070RE_REMOVE_DEFCONFIG = re.compile(r'(.*)_defconfig')
71
Simon Glass4c4eb7c2023-02-01 13:19:12 -070072# CONFIG symbols present in the build system (from Linux) but not actually used
73# in U-Boot; KCONFIG symbols
74IGNORE_SYMS = ['DEBUG_SECTION_MISMATCH', 'FTRACE_MCOUNT_RECORD', 'GCOV_KERNEL',
75 'GCOV_PROFILE_ALL', 'KALLSYMS', 'KASAN', 'MODVERSIONS', 'SHELL',
76 'TPL_BUILD', 'VPL_BUILD', 'IS_ENABLED', 'FOO', 'IF_ENABLED_INT',
77 'IS_ENABLED_', 'IS_ENABLED_1', 'IS_ENABLED_2', 'IS_ENABLED_3',
78 'SPL_', 'TPL_', 'SPL_FOO', 'TPL_FOO', 'TOOLS_FOO',
79 'ACME', 'SPL_ACME', 'TPL_ACME', 'TRACE_BRANCH_PROFILING',
Simon Glass2b315b72024-09-29 19:49:33 -060080 'VAL', '_UNDEFINED', 'SPL_BUILD', 'XPL_BUILD', ]
Simon Glass4c4eb7c2023-02-01 13:19:12 -070081
82SPL_PREFIXES = ['SPL_', 'TPL_', 'VPL_', 'TOOLS_']
83
Masahiro Yamadab6160812015-05-20 11:36:07 +090084### helper functions ###
Masahiro Yamadab6160812015-05-20 11:36:07 +090085def check_top_directory():
86 """Exit if we are not at the top of source directory."""
Simon Glassb3464eb2021-12-18 14:54:35 -070087 for fname in 'README', 'Licenses':
88 if not os.path.exists(fname):
Masahiro Yamadab6160812015-05-20 11:36:07 +090089 sys.exit('Please run at the top of source directory.')
90
Masahiro Yamada990e6772016-05-19 15:51:54 +090091def check_clean_directory():
92 """Exit if the source tree is not clean."""
Simon Glassb3464eb2021-12-18 14:54:35 -070093 for fname in '.config', 'include/config':
94 if os.path.exists(fname):
Masahiro Yamada990e6772016-05-19 15:51:54 +090095 sys.exit("source tree is not clean, please run 'make mrproper'")
96
Masahiro Yamadab6160812015-05-20 11:36:07 +090097def get_make_cmd():
98 """Get the command name of GNU Make.
99
100 U-Boot needs GNU Make for building, but the command name is not
101 necessarily "make". (for example, "gmake" on FreeBSD).
102 Returns the most appropriate command name on your system.
103 """
Simon Glassb3464eb2021-12-18 14:54:35 -0700104 with subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE) as proc:
105 ret = proc.communicate()
106 if proc.returncode:
107 sys.exit('GNU Make not found')
Masahiro Yamadab6160812015-05-20 11:36:07 +0900108 return ret[0].rstrip()
109
Simon Glass18774bc2017-06-01 19:38:58 -0600110def get_matched_defconfig(line):
111 """Get the defconfig files that match a pattern
112
113 Args:
Simon Glassb3464eb2021-12-18 14:54:35 -0700114 line (str): Path or filename to match, e.g. 'configs/snow_defconfig' or
Simon Glass18774bc2017-06-01 19:38:58 -0600115 'k2*_defconfig'. If no directory is provided, 'configs/' is
116 prepended
117
118 Returns:
Simon Glassb3464eb2021-12-18 14:54:35 -0700119 list of str: a list of matching defconfig files
Simon Glass18774bc2017-06-01 19:38:58 -0600120 """
121 dirname = os.path.dirname(line)
122 if dirname:
123 pattern = line
124 else:
125 pattern = os.path.join('configs', line)
126 return glob.glob(pattern) + glob.glob(pattern + '_defconfig')
127
Masahiro Yamada3984d6e2016-10-19 14:39:54 +0900128def get_matched_defconfigs(defconfigs_file):
Simon Glass8f3cf312017-06-01 19:38:59 -0600129 """Get all the defconfig files that match the patterns in a file.
130
131 Args:
Simon Glassb3464eb2021-12-18 14:54:35 -0700132 defconfigs_file (str): File containing a list of defconfigs to process,
133 or '-' to read the list from stdin
Simon Glass8f3cf312017-06-01 19:38:59 -0600134
135 Returns:
Simon Glassb3464eb2021-12-18 14:54:35 -0700136 list of str: A list of paths to defconfig files, with no duplicates
Simon Glass8f3cf312017-06-01 19:38:59 -0600137 """
Masahiro Yamada3984d6e2016-10-19 14:39:54 +0900138 defconfigs = []
Simon Glassb3464eb2021-12-18 14:54:35 -0700139 with ExitStack() as stack:
140 if defconfigs_file == '-':
141 inf = sys.stdin
142 defconfigs_file = 'stdin'
143 else:
144 inf = stack.enter_context(open(defconfigs_file, encoding='utf-8'))
145 for i, line in enumerate(inf):
146 line = line.strip()
147 if not line:
148 continue # skip blank lines silently
149 if ' ' in line:
150 line = line.split(' ')[0] # handle 'git log' input
151 matched = get_matched_defconfig(line)
152 if not matched:
153 print(f"warning: {defconfigs_file}:{i + 1}: no defconfig matched '{line}'",
154 file=sys.stderr)
Masahiro Yamada3984d6e2016-10-19 14:39:54 +0900155
Simon Glassb3464eb2021-12-18 14:54:35 -0700156 defconfigs += matched
Masahiro Yamada3984d6e2016-10-19 14:39:54 +0900157
158 # use set() to drop multiple matching
Simon Glassb3464eb2021-12-18 14:54:35 -0700159 return [defconfig[len('configs') + 1:] for defconfig in set(defconfigs)]
Masahiro Yamada3984d6e2016-10-19 14:39:54 +0900160
Masahiro Yamada58175e32016-07-25 19:15:28 +0900161def get_all_defconfigs():
Simon Glassb3464eb2021-12-18 14:54:35 -0700162 """Get all the defconfig files under the configs/ directory.
163
164 Returns:
165 list of str: List of paths to defconfig files
166 """
Masahiro Yamada58175e32016-07-25 19:15:28 +0900167 defconfigs = []
Simon Glassb3464eb2021-12-18 14:54:35 -0700168 for (dirpath, _, filenames) in os.walk('configs'):
Masahiro Yamada58175e32016-07-25 19:15:28 +0900169 dirpath = dirpath[len('configs') + 1:]
170 for filename in fnmatch.filter(filenames, '*_defconfig'):
171 defconfigs.append(os.path.join(dirpath, filename))
172
173 return defconfigs
174
Simon Glassb09ae452021-12-18 14:54:33 -0700175def write_file(fname, data):
176 """Write data to a file
177
178 Args:
179 fname (str): Filename to write to
180 data (list of str): Lines to write (with or without trailing newline);
181 or str to write
182 """
183 with open(fname, 'w', encoding='utf-8') as out:
184 if isinstance(data, list):
185 for line in data:
186 print(line.rstrip('\n'), file=out)
187 else:
188 out.write(data)
189
Simon Glassaba238f2021-12-18 14:54:34 -0700190def read_file(fname, as_lines=True, skip_unicode=False):
191 """Read a file and return the contents
192
193 Args:
194 fname (str): Filename to read from
Simon Glassc89a2962023-09-23 13:43:58 -0600195 as_lines (bool): Return file contents as a list of lines
Simon Glassaba238f2021-12-18 14:54:34 -0700196 skip_unicode (bool): True to report unicode errors and continue
197
198 Returns:
199 iter of str: List of ;ines from the file with newline removed; str if
200 as_lines is False with newlines intact; or None if a unicode error
201 occurred
202
203 Raises:
204 UnicodeDecodeError: Unicode error occurred when reading
205 """
206 with open(fname, encoding='utf-8') as inf:
207 try:
208 if as_lines:
209 return [line.rstrip('\n') for line in inf.readlines()]
Simon Glasse19a9cd2023-09-23 13:44:05 -0600210 return inf.read()
Simon Glass4f6725c2023-09-23 13:44:01 -0600211 except UnicodeDecodeError as exc:
Simon Glassaba238f2021-12-18 14:54:34 -0700212 if not skip_unicode:
Simon Glassafaddc72022-02-11 13:23:22 -0700213 raise
Simon Glass4f6725c2023-09-23 13:44:01 -0600214 print(f"Failed on file '{fname}: {exc}")
Simon Glassaba238f2021-12-18 14:54:34 -0700215 return None
216
Chris Packham9d5274f2017-05-02 21:30:47 +1200217
Masahiro Yamadab6160812015-05-20 11:36:07 +0900218### classes ###
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900219class Progress:
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900220 """Progress Indicator"""
221
Simon Glassbef67362023-09-23 13:44:10 -0600222 def __init__(self, col, total):
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900223 """Create a new progress indicator.
224
Simon Glassb3464eb2021-12-18 14:54:35 -0700225 Args:
Simon Glass19d91da2024-07-17 16:57:07 +0100226 col (terminal.Color): Colour-output class
Simon Glassbef67362023-09-23 13:44:10 -0600227 total (int): A number of defconfig files to process.
Simon Glass19d91da2024-07-17 16:57:07 +0100228
229 current (int): Number of boards processed so far
230 failed (int): Number of failed boards
231 failure_msg (str): Message indicating number of failures, '' if none
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900232 """
Simon Glassbef67362023-09-23 13:44:10 -0600233 self.col = col
Simon Glass19d91da2024-07-17 16:57:07 +0100234 self.total = total
235
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900236 self.current = 0
Simon Glassbef67362023-09-23 13:44:10 -0600237 self.good = 0
Simon Glass19d91da2024-07-17 16:57:07 +0100238 self.failed = None
239 self.failure_msg = None
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900240
Simon Glassbef67362023-09-23 13:44:10 -0600241 def inc(self, success):
242 """Increment the number of processed defconfig files.
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900243
Simon Glassbef67362023-09-23 13:44:10 -0600244 Args:
245 success (bool): True if processing succeeded
246 """
247 self.good += success
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900248 self.current += 1
249
250 def show(self):
251 """Display the progress."""
Simon Glass99c28cd2023-09-23 13:44:08 -0600252 if self.current != self.total:
Simon Glassbef67362023-09-23 13:44:10 -0600253 line = self.col.build(self.col.GREEN, f'{self.good:5d}')
254 line += self.col.build(self.col.RED,
255 f'{self.current - self.good:5d}')
256 line += self.col.build(self.col.MAGENTA,
257 f'/{self.total - self.current}')
258 print(f'{line} \r', end='')
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900259 sys.stdout.flush()
260
Simon Glass19d91da2024-07-17 16:57:07 +0100261 def completed(self):
262 """Set up extra properties when completed"""
263 self.failed = self.total - self.good
264 self.failure_msg = f'{self.failed} failed, ' if self.failed else ''
265
Simon Glass44116332017-06-15 21:39:33 -0600266
Simon Glassa0a61602024-07-17 16:56:51 +0100267def scan_kconfig():
268 """Scan all the Kconfig files and create a Config object
Simon Glass44116332017-06-15 21:39:33 -0600269
Simon Glassa0a61602024-07-17 16:56:51 +0100270 Returns:
271 Kconfig object
272 """
273 # Define environment variables referenced from Kconfig
274 os.environ['srctree'] = os.getcwd()
275 os.environ['UBOOTVERSION'] = 'dummy'
276 os.environ['KCONFIG_OBJDIR'] = ''
277 os.environ['CC'] = 'gcc'
278 return kconfiglib.Kconfig()
Simon Glass44116332017-06-15 21:39:33 -0600279
280
Simon Glass28155572024-07-17 16:56:52 +0100281# pylint: disable=R0903
Masahiro Yamadab6160812015-05-20 11:36:07 +0900282class KconfigParser:
Masahiro Yamadab6160812015-05-20 11:36:07 +0900283 """A parser of .config and include/autoconf.mk."""
284
285 re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
286 re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
287
Simon Glass08d148a2023-09-23 13:43:54 -0600288 def __init__(self, args, build_dir):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900289 """Create a new parser.
290
Simon Glassb3464eb2021-12-18 14:54:35 -0700291 Args:
Simon Glassb3464eb2021-12-18 14:54:35 -0700292 args (Namespace): program arguments
Masahiro Yamadab6160812015-05-20 11:36:07 +0900293 build_dir: Build directory.
294 """
Simon Glassd9c1da22021-12-18 14:54:31 -0700295 self.args = args
Masahiro Yamada5393b612016-05-19 15:52:00 +0900296 self.dotconfig = os.path.join(build_dir, '.config')
297 self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
Masahiro Yamada6d139172016-08-22 22:18:22 +0900298 self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
299 'autoconf.mk')
Simon Glass8fb5bd02017-06-01 19:39:01 -0600300 self.config_autoconf = os.path.join(build_dir, AUTO_CONF_PATH)
Masahiro Yamada07f98522016-05-19 15:52:06 +0900301 self.defconfig = os.path.join(build_dir, 'defconfig')
Masahiro Yamadab6160812015-05-20 11:36:07 +0900302
Simon Glass257f5232017-07-10 14:47:47 -0600303 def get_arch(self):
304 """Parse .config file and return the architecture.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900305
306 Returns:
Simon Glass257f5232017-07-10 14:47:47 -0600307 Architecture name (e.g. 'arm').
Masahiro Yamadab6160812015-05-20 11:36:07 +0900308 """
309 arch = ''
310 cpu = ''
Simon Glassaba238f2021-12-18 14:54:34 -0700311 for line in read_file(self.dotconfig):
Simon Glass4f6725c2023-09-23 13:44:01 -0600312 m_arch = self.re_arch.match(line)
313 if m_arch:
314 arch = m_arch.group(1)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900315 continue
Simon Glass4f6725c2023-09-23 13:44:01 -0600316 m_cpu = self.re_cpu.match(line)
317 if m_cpu:
318 cpu = m_cpu.group(1)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900319
Masahiro Yamadac4d76eb2016-05-19 15:51:53 +0900320 if not arch:
321 return None
Masahiro Yamadab6160812015-05-20 11:36:07 +0900322
323 # fix-up for aarch64
324 if arch == 'arm' and cpu == 'armv8':
325 arch = 'aarch64'
326
Simon Glass257f5232017-07-10 14:47:47 -0600327 return arch
Masahiro Yamadab6160812015-05-20 11:36:07 +0900328
Simon Glass43cf08f2017-06-01 19:39:02 -0600329
330class DatabaseThread(threading.Thread):
331 """This thread processes results from Slot threads.
332
333 It collects the data in the master config directary. There is only one
334 result thread, and this helps to serialise the build output.
335 """
336 def __init__(self, config_db, db_queue):
337 """Set up a new result thread
338
339 Args:
340 builder: Builder which will be sent each result
341 """
342 threading.Thread.__init__(self)
343 self.config_db = config_db
344 self.db_queue= db_queue
345
346 def run(self):
347 """Called to start up the result thread.
348
349 We collect the next result job and pass it on to the build.
350 """
351 while True:
352 defconfig, configs = self.db_queue.get()
353 self.config_db[defconfig] = configs
354 self.db_queue.task_done()
355
356
Masahiro Yamadab6160812015-05-20 11:36:07 +0900357class Slot:
358
359 """A slot to store a subprocess.
360
361 Each instance of this class handles one subprocess.
362 This class is useful to control multiple threads
363 for faster processing.
364 """
365
Simon Glass9b191102023-09-23 13:44:09 -0600366 def __init__(self, toolchains, args, progress, devnull, make_cmd,
Simon Glassd7552892024-07-17 16:57:10 +0100367 reference_src_dir, db_queue):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900368 """Create a new process slot.
369
Simon Glassb3464eb2021-12-18 14:54:35 -0700370 Args:
Simon Glass257f5232017-07-10 14:47:47 -0600371 toolchains: Toolchains object containing toolchains.
Simon Glassd9c1da22021-12-18 14:54:31 -0700372 args: Program arguments
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900373 progress: A progress indicator.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900374 devnull: A file object of '/dev/null'.
375 make_cmd: command name of GNU Make.
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500376 reference_src_dir: Determine the true starting config state from this
377 source tree.
Simon Glass43cf08f2017-06-01 19:39:02 -0600378 db_queue: output queue to write config info for the database
Simon Glass9b191102023-09-23 13:44:09 -0600379 col (terminal.Color): Colour object
Masahiro Yamadab6160812015-05-20 11:36:07 +0900380 """
Simon Glass257f5232017-07-10 14:47:47 -0600381 self.toolchains = toolchains
Simon Glassd9c1da22021-12-18 14:54:31 -0700382 self.args = args
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900383 self.progress = progress
Masahiro Yamadab6160812015-05-20 11:36:07 +0900384 self.build_dir = tempfile.mkdtemp()
385 self.devnull = devnull
386 self.make_cmd = (make_cmd, 'O=' + self.build_dir)
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500387 self.reference_src_dir = reference_src_dir
Simon Glass43cf08f2017-06-01 19:39:02 -0600388 self.db_queue = db_queue
Simon Glassd7552892024-07-17 16:57:10 +0100389 self.col = progress.col
Simon Glass08d148a2023-09-23 13:43:54 -0600390 self.parser = KconfigParser(args, self.build_dir)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900391 self.state = STATE_IDLE
Masahiro Yamada1271b672016-08-22 22:18:20 +0900392 self.failed_boards = set()
Simon Glass67ee0112023-09-23 13:44:02 -0600393 self.defconfig = None
Simon Glasse24ac992023-09-23 13:44:07 -0600394 self.log = []
Simon Glass67ee0112023-09-23 13:44:02 -0600395 self.current_src_dir = None
396 self.proc = None
Masahiro Yamadab6160812015-05-20 11:36:07 +0900397
398 def __del__(self):
399 """Delete the working directory
400
401 This function makes sure the temporary directory is cleaned away
402 even if Python suddenly dies due to error. It should be done in here
Joe Hershberger640de872016-06-10 14:53:29 -0500403 because it is guaranteed the destructor is always invoked when the
Masahiro Yamadab6160812015-05-20 11:36:07 +0900404 instance of the class gets unreferenced.
405
406 If the subprocess is still running, wait until it finishes.
407 """
408 if self.state != STATE_IDLE:
Simon Glasse19a9cd2023-09-23 13:44:05 -0600409 while self.proc.poll() is None:
Masahiro Yamadab6160812015-05-20 11:36:07 +0900410 pass
411 shutil.rmtree(self.build_dir)
412
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900413 def add(self, defconfig):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900414 """Assign a new subprocess for defconfig and add it to the slot.
415
416 If the slot is vacant, create a new subprocess for processing the
417 given defconfig and add it to the slot. Just returns False if
418 the slot is occupied (i.e. the current subprocess is still running).
419
Simon Glassb3464eb2021-12-18 14:54:35 -0700420 Args:
Simon Glassc89a2962023-09-23 13:43:58 -0600421 defconfig (str): defconfig name.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900422
423 Returns:
424 Return True on success or False on failure
425 """
426 if self.state != STATE_IDLE:
427 return False
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900428
Masahiro Yamadab6160812015-05-20 11:36:07 +0900429 self.defconfig = defconfig
Simon Glasse24ac992023-09-23 13:44:07 -0600430 self.log = []
Masahiro Yamada8f5256a2016-06-15 14:33:52 +0900431 self.current_src_dir = self.reference_src_dir
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900432 self.do_defconfig()
Masahiro Yamadab6160812015-05-20 11:36:07 +0900433 return True
434
435 def poll(self):
436 """Check the status of the subprocess and handle it as needed.
437
438 Returns True if the slot is vacant (i.e. in idle state).
439 If the configuration is successfully finished, assign a new
440 subprocess to build include/autoconf.mk.
441 If include/autoconf.mk is generated, invoke the parser to
Masahiro Yamada263d1372016-05-19 15:52:04 +0900442 parse the .config and the include/autoconf.mk, moving
443 config options to the .config as needed.
444 If the .config was updated, run "make savedefconfig" to sync
445 it, update the original defconfig, and then set the slot back
446 to the idle state.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900447
448 Returns:
449 Return True if the subprocess is terminated, False otherwise
450 """
451 if self.state == STATE_IDLE:
452 return True
453
Simon Glasse19a9cd2023-09-23 13:44:05 -0600454 if self.proc.poll() is None:
Masahiro Yamadab6160812015-05-20 11:36:07 +0900455 return False
456
Simon Glass4f6725c2023-09-23 13:44:01 -0600457 if self.proc.poll() != 0:
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900458 self.handle_error()
459 elif self.state == STATE_DEFCONFIG:
Masahiro Yamada8f5256a2016-06-15 14:33:52 +0900460 if self.reference_src_dir and not self.current_src_dir:
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500461 self.do_savedefconfig()
462 else:
463 self.do_autoconf()
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900464 elif self.state == STATE_AUTOCONF:
Masahiro Yamada8f5256a2016-06-15 14:33:52 +0900465 if self.current_src_dir:
466 self.current_src_dir = None
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500467 self.do_defconfig()
Simon Glassd9c1da22021-12-18 14:54:31 -0700468 elif self.args.build_db:
Simon Glass43cf08f2017-06-01 19:39:02 -0600469 self.do_build_db()
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500470 else:
471 self.do_savedefconfig()
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900472 elif self.state == STATE_SAVEDEFCONFIG:
473 self.update_defconfig()
474 else:
Simon Glassdc634d92021-12-18 14:54:30 -0700475 sys.exit('Internal Error. This should not happen.')
Masahiro Yamadab6160812015-05-20 11:36:07 +0900476
Simon Glasse19a9cd2023-09-23 13:44:05 -0600477 return self.state == STATE_IDLE
Joe Hershberger166edec2015-05-19 13:21:17 -0500478
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900479 def handle_error(self):
480 """Handle error cases."""
Masahiro Yamada83c17672016-05-19 15:52:08 +0900481
Simon Glass9b191102023-09-23 13:44:09 -0600482 self.log.append(self.col.build(self.col.RED, 'Failed to process',
483 bright=True))
Simon Glassd9c1da22021-12-18 14:54:31 -0700484 if self.args.verbose:
Simon Glasse24ac992023-09-23 13:44:07 -0600485 for line in self.proc.stderr.read().decode().splitlines():
Simon Glass9b191102023-09-23 13:44:09 -0600486 self.log.append(self.col.build(self.col.CYAN, line, True))
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900487 self.finish(False)
Joe Hershberger166edec2015-05-19 13:21:17 -0500488
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900489 def do_defconfig(self):
490 """Run 'make <board>_defconfig' to create the .config file."""
Masahiro Yamada0f6beda2016-05-19 15:52:07 +0900491
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900492 cmd = list(self.make_cmd)
493 cmd.append(self.defconfig)
Simon Glass28155572024-07-17 16:56:52 +0100494 # pylint: disable=R1732
Simon Glass4f6725c2023-09-23 13:44:01 -0600495 self.proc = subprocess.Popen(cmd, stdout=self.devnull,
496 stderr=subprocess.PIPE,
497 cwd=self.current_src_dir)
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900498 self.state = STATE_DEFCONFIG
Masahiro Yamada0f6beda2016-05-19 15:52:07 +0900499
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900500 def do_autoconf(self):
Simon Glass8fb5bd02017-06-01 19:39:01 -0600501 """Run 'make AUTO_CONF_PATH'."""
Masahiro Yamadab6160812015-05-20 11:36:07 +0900502
Simon Glass257f5232017-07-10 14:47:47 -0600503 arch = self.parser.get_arch()
504 try:
Simon Glasse19a9cd2023-09-23 13:44:05 -0600505 tchain = self.toolchains.Select(arch)
Simon Glass257f5232017-07-10 14:47:47 -0600506 except ValueError:
Simon Glass9b191102023-09-23 13:44:09 -0600507 self.log.append(self.col.build(
508 self.col.YELLOW,
509 f"Tool chain for '{arch}' is missing: do nothing"))
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900510 self.finish(False)
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900511 return
Simon Glasse19a9cd2023-09-23 13:44:05 -0600512 env = tchain.MakeEnvironment(False)
Masahiro Yamadac4d76eb2016-05-19 15:51:53 +0900513
Masahiro Yamadab6160812015-05-20 11:36:07 +0900514 cmd = list(self.make_cmd)
Joe Hershberger765442b2015-05-19 13:21:18 -0500515 cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
Simon Glass8fb5bd02017-06-01 19:39:01 -0600516 cmd.append(AUTO_CONF_PATH)
Simon Glass28155572024-07-17 16:56:52 +0100517 # pylint: disable=R1732
Simon Glass4f6725c2023-09-23 13:44:01 -0600518 self.proc = subprocess.Popen(cmd, stdout=self.devnull, env=env,
519 stderr=subprocess.PIPE,
520 cwd=self.current_src_dir)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900521 self.state = STATE_AUTOCONF
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900522
Simon Glass43cf08f2017-06-01 19:39:02 -0600523 def do_build_db(self):
524 """Add the board to the database"""
525 configs = {}
Simon Glassaba238f2021-12-18 14:54:34 -0700526 for line in read_file(os.path.join(self.build_dir, AUTO_CONF_PATH)):
527 if line.startswith('CONFIG'):
528 config, value = line.split('=', 1)
529 configs[config] = value.rstrip()
Simon Glass43cf08f2017-06-01 19:39:02 -0600530 self.db_queue.put([self.defconfig, configs])
531 self.finish(True)
532
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900533 def do_savedefconfig(self):
534 """Update the .config and run 'make savedefconfig'."""
Simon Glassc1c10c22023-09-23 13:43:55 -0600535 if not self.args.force_sync:
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900536 self.finish(True)
537 return
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900538
539 cmd = list(self.make_cmd)
540 cmd.append('savedefconfig')
Simon Glass28155572024-07-17 16:56:52 +0100541 # pylint: disable=R1732
Simon Glass4f6725c2023-09-23 13:44:01 -0600542 self.proc = subprocess.Popen(cmd, stdout=self.devnull,
543 stderr=subprocess.PIPE)
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900544 self.state = STATE_SAVEDEFCONFIG
545
546 def update_defconfig(self):
547 """Update the input defconfig and go back to the idle state."""
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900548 orig_defconfig = os.path.join('configs', self.defconfig)
549 new_defconfig = os.path.join(self.build_dir, 'defconfig')
550 updated = not filecmp.cmp(orig_defconfig, new_defconfig)
551
552 if updated:
Simon Glass9b191102023-09-23 13:44:09 -0600553 self.log.append(
554 self.col.build(self.col.BLUE, 'defconfig updated', bright=True))
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900555
Simon Glassd9c1da22021-12-18 14:54:31 -0700556 if not self.args.dry_run and updated:
Masahiro Yamadacb256cb2016-06-08 11:47:37 +0900557 shutil.move(new_defconfig, orig_defconfig)
558 self.finish(True)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900559
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900560 def finish(self, success):
561 """Display log along with progress and go to the idle state.
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900562
Simon Glassb3464eb2021-12-18 14:54:35 -0700563 Args:
Simon Glassc89a2962023-09-23 13:43:58 -0600564 success (bool): Should be True when the defconfig was processed
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900565 successfully, or False when it fails.
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900566 """
567 # output at least 30 characters to hide the "* defconfigs out of *".
Simon Glass29790432023-09-23 13:44:11 -0600568 name = self.defconfig[:-len('_defconfig')]
Simon Glass4fd16e62023-09-23 13:44:06 -0600569 if self.log:
Simon Glasse24ac992023-09-23 13:44:07 -0600570
571 # Put the first log line on the first line
572 log = name.ljust(20) + ' ' + self.log[0]
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900573
Simon Glasse24ac992023-09-23 13:44:07 -0600574 if len(self.log) > 1:
575 log += '\n' + '\n'.join([' ' + s for s in self.log[1:]])
Simon Glass4fd16e62023-09-23 13:44:06 -0600576 # Some threads are running in parallel.
577 # Print log atomically to not mix up logs from different threads.
578 print(log, file=(sys.stdout if success else sys.stderr))
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900579
580 if not success:
Simon Glassd9c1da22021-12-18 14:54:31 -0700581 if self.args.exit_on_error:
Simon Glassdc634d92021-12-18 14:54:30 -0700582 sys.exit('Exit on error.')
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900583 # If --exit-on-error flag is not set, skip this board and continue.
584 # Record the failed board.
Simon Glass29790432023-09-23 13:44:11 -0600585 self.failed_boards.add(name)
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900586
Simon Glassbef67362023-09-23 13:44:10 -0600587 self.progress.inc(success)
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900588 self.progress.show()
Masahiro Yamada274a5ee2016-05-19 15:52:03 +0900589 self.state = STATE_IDLE
Masahiro Yamada465b7c02016-05-19 15:52:02 +0900590
Masahiro Yamadab6160812015-05-20 11:36:07 +0900591 def get_failed_boards(self):
Masahiro Yamada1271b672016-08-22 22:18:20 +0900592 """Returns a set of failed boards (defconfigs) in this slot.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900593 """
594 return self.failed_boards
595
596class Slots:
Masahiro Yamadab6160812015-05-20 11:36:07 +0900597 """Controller of the array of subprocess slots."""
598
Simon Glassd7552892024-07-17 16:57:10 +0100599 def __init__(self, toolchains, args, progress, reference_src_dir, db_queue):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900600 """Create a new slots controller.
601
Simon Glassb3464eb2021-12-18 14:54:35 -0700602 Args:
Simon Glass9b191102023-09-23 13:44:09 -0600603 toolchains (Toolchains): Toolchains object containing toolchains
604 args (Namespace): Program arguments
605 progress (Progress): A progress indicator.
606 reference_src_dir (str): Determine the true starting config state
607 from this source tree (None for none)
608 db_queue (Queue): output queue to write config info for the database
Masahiro Yamadab6160812015-05-20 11:36:07 +0900609 """
Simon Glassd9c1da22021-12-18 14:54:31 -0700610 self.args = args
Masahiro Yamadab6160812015-05-20 11:36:07 +0900611 self.slots = []
Simon Glass29790432023-09-23 13:44:11 -0600612 self.progress = progress
Simon Glassd7552892024-07-17 16:57:10 +0100613 self.col = progress.col
Simon Glass34c505f2021-12-18 14:54:32 -0700614 devnull = subprocess.DEVNULL
Masahiro Yamadab6160812015-05-20 11:36:07 +0900615 make_cmd = get_make_cmd()
Simon Glassbeb825d2023-09-23 13:44:00 -0600616 for _ in range(args.jobs):
Simon Glass08d148a2023-09-23 13:43:54 -0600617 self.slots.append(Slot(toolchains, args, progress, devnull,
Simon Glassd7552892024-07-17 16:57:10 +0100618 make_cmd, reference_src_dir, db_queue))
Masahiro Yamadab6160812015-05-20 11:36:07 +0900619
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900620 def add(self, defconfig):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900621 """Add a new subprocess if a vacant slot is found.
622
Simon Glassb3464eb2021-12-18 14:54:35 -0700623 Args:
Simon Glassc89a2962023-09-23 13:43:58 -0600624 defconfig (str): defconfig name to be put into.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900625
626 Returns:
627 Return True on success or False on failure
628 """
629 for slot in self.slots:
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900630 if slot.add(defconfig):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900631 return True
632 return False
633
634 def available(self):
635 """Check if there is a vacant slot.
636
637 Returns:
638 Return True if at lease one vacant slot is found, False otherwise.
639 """
640 for slot in self.slots:
641 if slot.poll():
642 return True
643 return False
644
645 def empty(self):
646 """Check if all slots are vacant.
647
648 Returns:
649 Return True if all the slots are vacant, False otherwise.
650 """
651 ret = True
652 for slot in self.slots:
653 if not slot.poll():
654 ret = False
655 return ret
656
Simon Glass65709242023-09-23 13:44:13 -0600657 def write_failed_boards(self):
Simon Glass29790432023-09-23 13:44:11 -0600658 """Show the results of processing"""
Masahiro Yamada1271b672016-08-22 22:18:20 +0900659 boards = set()
Masahiro Yamadab6160812015-05-20 11:36:07 +0900660
661 for slot in self.slots:
Masahiro Yamada1271b672016-08-22 22:18:20 +0900662 boards |= slot.get_failed_boards()
Masahiro Yamadab6160812015-05-20 11:36:07 +0900663
Masahiro Yamada0153f032016-06-15 14:33:53 +0900664 if boards:
Simon Glass29790432023-09-23 13:44:11 -0600665 boards = '\n'.join(sorted(boards)) + '\n'
Simon Glass65709242023-09-23 13:44:13 -0600666 write_file(FAILED_LIST, boards)
667
Joe Hershbergerdade12e2015-05-19 13:21:22 -0500668
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900669class ReferenceSource:
670
671 """Reference source against which original configs should be parsed."""
672
673 def __init__(self, commit):
674 """Create a reference source directory based on a specified commit.
675
Simon Glassb3464eb2021-12-18 14:54:35 -0700676 Args:
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900677 commit: commit to git-clone
678 """
679 self.src_dir = tempfile.mkdtemp()
Simon Glassdc634d92021-12-18 14:54:30 -0700680 print('Cloning git repo to a separate work directory...')
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900681 subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
682 cwd=self.src_dir)
Simon Glass96f8f312023-09-23 13:43:59 -0600683 rev = subprocess.check_output(['git', 'rev-parse', '--short',
684 commit]).strip()
685 print(f"Checkout '{rev}' to build the original autoconf.mk.")
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900686 subprocess.check_output(['git', 'checkout', commit],
687 stderr=subprocess.STDOUT, cwd=self.src_dir)
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500688
689 def __del__(self):
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900690 """Delete the reference source directory
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500691
692 This function makes sure the temporary directory is cleaned away
693 even if Python suddenly dies due to error. It should be done in here
694 because it is guaranteed the destructor is always invoked when the
695 instance of the class gets unreferenced.
696 """
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900697 shutil.rmtree(self.src_dir)
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500698
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900699 def get_dir(self):
700 """Return the absolute path to the reference source directory."""
701
702 return self.src_dir
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500703
Simon Glassc2466b72024-07-17 16:57:09 +0100704def move_config(args):
Simon Glass08d148a2023-09-23 13:43:54 -0600705 """Build database or sync config options to defconfig files.
Masahiro Yamadab6160812015-05-20 11:36:07 +0900706
Simon Glassb3464eb2021-12-18 14:54:35 -0700707 Args:
Simon Glass9b191102023-09-23 13:44:09 -0600708 args (Namespace): Program arguments
Simon Glass65709242023-09-23 13:44:13 -0600709
710 Returns:
Simon Glass4c059032024-07-17 16:57:05 +0100711 tuple:
712 config_db (dict of configs for each defconfig):
713 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
714 value: dict:
715 key: CONFIG option
716 value: Value of option
717 Progress: Progress indicator
Masahiro Yamadab6160812015-05-20 11:36:07 +0900718 """
Simon Glass4c059032024-07-17 16:57:05 +0100719 config_db = {}
720 db_queue = queue.Queue()
721 dbt = DatabaseThread(config_db, db_queue)
722 dbt.daemon = True
723 dbt.start()
724
725 check_clean_directory()
726 bsettings.setup('')
727
728 # Get toolchains to use
729 toolchains = toolchain.Toolchains()
730 toolchains.GetSettings()
731 toolchains.Scan(verbose=False)
732
Simon Glassd9c1da22021-12-18 14:54:31 -0700733 if args.git_ref:
734 reference_src = ReferenceSource(args.git_ref)
Masahiro Yamada2e74fee2016-06-15 14:33:51 +0900735 reference_src_dir = reference_src.get_dir()
736 else:
Masahiro Yamada8f5256a2016-06-15 14:33:52 +0900737 reference_src_dir = None
Joe Hershbergerb1a570f2016-06-10 14:53:32 -0500738
Simon Glassd9c1da22021-12-18 14:54:31 -0700739 if args.defconfigs:
740 defconfigs = get_matched_defconfigs(args.defconfigs)
Joe Hershbergerc6e043a2015-05-19 13:21:19 -0500741 else:
Masahiro Yamada58175e32016-07-25 19:15:28 +0900742 defconfigs = get_all_defconfigs()
Masahiro Yamadab6160812015-05-20 11:36:07 +0900743
Simon Glassc2466b72024-07-17 16:57:09 +0100744 col = terminal.Color(terminal.COLOR_NEVER if args.nocolour
745 else terminal.COLOR_IF_TERMINAL)
Simon Glassbef67362023-09-23 13:44:10 -0600746 progress = Progress(col, len(defconfigs))
Simon Glassd7552892024-07-17 16:57:10 +0100747 slots = Slots(toolchains, args, progress, reference_src_dir, db_queue)
Masahiro Yamadab6160812015-05-20 11:36:07 +0900748
749 # Main loop to process defconfig files:
750 # Add a new subprocess into a vacant slot.
751 # Sleep if there is no available slot.
Masahiro Yamadacefaa582016-05-19 15:51:55 +0900752 for defconfig in defconfigs:
753 while not slots.add(defconfig):
Masahiro Yamadab6160812015-05-20 11:36:07 +0900754 while not slots.available():
755 # No available slot: sleep for a while
756 time.sleep(SLEEP_TIME)
757
758 # wait until all the subprocesses finish
759 while not slots.empty():
760 time.sleep(SLEEP_TIME)
761
Simon Glass65709242023-09-23 13:44:13 -0600762 slots.write_failed_boards()
Simon Glass4c059032024-07-17 16:57:05 +0100763 db_queue.join()
Simon Glass19d91da2024-07-17 16:57:07 +0100764 progress.completed()
Simon Glass4c059032024-07-17 16:57:05 +0100765 return config_db, progress
Masahiro Yamadab6160812015-05-20 11:36:07 +0900766
Simon Glass44116332017-06-15 21:39:33 -0600767def find_kconfig_rules(kconf, config, imply_config):
768 """Check whether a config has a 'select' or 'imply' keyword
769
770 Args:
Simon Glassc89a2962023-09-23 13:43:58 -0600771 kconf (Kconfiglib.Kconfig): Kconfig object
772 config (str): Name of config to check (without CONFIG_ prefix)
773 imply_config (str): Implying config (without CONFIG_ prefix) which may
774 or may not have an 'imply' for 'config')
Simon Glass44116332017-06-15 21:39:33 -0600775
776 Returns:
777 Symbol object for 'config' if found, else None
778 """
Tom Rini3c5f4152019-09-20 17:42:09 -0400779 sym = kconf.syms.get(imply_config)
Simon Glass44116332017-06-15 21:39:33 -0600780 if sym:
Simon Glassbeb825d2023-09-23 13:44:00 -0600781 for sel, _ in (sym.selects + sym.implies):
Simon Glass93c0a9e2021-12-18 08:09:42 -0700782 if sel.name == config:
Simon Glass44116332017-06-15 21:39:33 -0600783 return sym
784 return None
785
Simon Glass28155572024-07-17 16:56:52 +0100786def check_imply_rule(kconf, imply_config):
Simon Glass44116332017-06-15 21:39:33 -0600787 """Check if we can add an 'imply' option
788
789 This finds imply_config in the Kconfig and looks to see if it is possible
790 to add an 'imply' for 'config' to that part of the Kconfig.
791
792 Args:
Simon Glassc89a2962023-09-23 13:43:58 -0600793 kconf (Kconfiglib.Kconfig): Kconfig object
Simon Glassc89a2962023-09-23 13:43:58 -0600794 imply_config (str): Implying config (without CONFIG_ prefix) which may
795 or may not have an 'imply' for 'config')
Simon Glass44116332017-06-15 21:39:33 -0600796
797 Returns:
798 tuple:
Simon Glassc89a2962023-09-23 13:43:58 -0600799 str: filename of Kconfig file containing imply_config, or None if
800 none
801 int: line number within the Kconfig file, or 0 if none
802 str: message indicating the result
Simon Glass44116332017-06-15 21:39:33 -0600803 """
Tom Rini3c5f4152019-09-20 17:42:09 -0400804 sym = kconf.syms.get(imply_config)
Simon Glass44116332017-06-15 21:39:33 -0600805 if not sym:
806 return 'cannot find sym'
Simon Glass520b47a2021-07-21 21:35:53 -0600807 nodes = sym.nodes
808 if len(nodes) != 1:
Simon Glass96f8f312023-09-23 13:43:59 -0600809 return f'{len(nodes)} locations'
Simon Glass93c0a9e2021-12-18 08:09:42 -0700810 node = nodes[0]
811 fname, linenum = node.filename, node.linenr
Simon Glass44116332017-06-15 21:39:33 -0600812 cwd = os.getcwd()
813 if cwd and fname.startswith(cwd):
814 fname = fname[len(cwd) + 1:]
Simon Glass96f8f312023-09-23 13:43:59 -0600815 file_line = f' at {fname}:{linenum}'
Simon Glassaba238f2021-12-18 14:54:34 -0700816 data = read_file(fname)
Simon Glass96f8f312023-09-23 13:43:59 -0600817 if data[linenum - 1] != f'config {imply_config}':
818 return None, 0, f'bad sym format {data[linenum]}{file_line})'
819 return fname, linenum, f'adding{file_line}'
Simon Glass44116332017-06-15 21:39:33 -0600820
821def add_imply_rule(config, fname, linenum):
822 """Add a new 'imply' option to a Kconfig
823
824 Args:
Simon Glassc89a2962023-09-23 13:43:58 -0600825 config (str): config option to add an imply for (without CONFIG_ prefix)
826 fname (str): Kconfig filename to update
827 linenum (int): Line number to place the 'imply' before
Simon Glass44116332017-06-15 21:39:33 -0600828
829 Returns:
830 Message indicating the result
831 """
Simon Glass96f8f312023-09-23 13:43:59 -0600832 file_line = f' at {fname}:{linenum}'
Simon Glassaba238f2021-12-18 14:54:34 -0700833 data = read_file(fname)
Simon Glass44116332017-06-15 21:39:33 -0600834 linenum -= 1
835
836 for offset, line in enumerate(data[linenum:]):
837 if line.strip().startswith('help') or not line:
Simon Glass96f8f312023-09-23 13:43:59 -0600838 data.insert(linenum + offset, f'\timply {config}')
Simon Glassb09ae452021-12-18 14:54:33 -0700839 write_file(fname, data)
Simon Glass96f8f312023-09-23 13:43:59 -0600840 return f'added{file_line}'
Simon Glass44116332017-06-15 21:39:33 -0600841
842 return 'could not insert%s'
843
844(IMPLY_MIN_2, IMPLY_TARGET, IMPLY_CMD, IMPLY_NON_ARCH_BOARD) = (
845 1, 2, 4, 8)
Simon Glass92e55582017-06-15 21:39:32 -0600846
847IMPLY_FLAGS = {
848 'min2': [IMPLY_MIN_2, 'Show options which imply >2 boards (normally >5)'],
849 'target': [IMPLY_TARGET, 'Allow CONFIG_TARGET_... options to imply'],
850 'cmd': [IMPLY_CMD, 'Allow CONFIG_CMD_... to imply'],
Simon Glass44116332017-06-15 21:39:33 -0600851 'non-arch-board': [
852 IMPLY_NON_ARCH_BOARD,
853 'Allow Kconfig options outside arch/ and /board/ to imply'],
Simon Glassb3464eb2021-12-18 14:54:35 -0700854}
Simon Glass92e55582017-06-15 21:39:32 -0600855
Simon Glassf931c2f2021-12-18 08:09:43 -0700856
857def read_database():
858 """Read in the config database
859
860 Returns:
861 tuple:
862 set of all config options seen (each a str)
863 set of all defconfigs seen (each a str)
864 dict of configs for each defconfig:
865 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
866 value: dict:
867 key: CONFIG option
868 value: Value of option
869 dict of defconfigs for each config:
870 key: CONFIG option
871 value: set of boards using that option
872
873 """
874 configs = {}
875
876 # key is defconfig name, value is dict of (CONFIG_xxx, value)
877 config_db = {}
878
879 # Set of all config options we have seen
880 all_configs = set()
881
882 # Set of all defconfigs we have seen
883 all_defconfigs = set()
884
885 defconfig_db = collections.defaultdict(set)
Simon Glass3a315fa2024-07-17 16:56:49 +0100886 defconfig = None
Simon Glassaba238f2021-12-18 14:54:34 -0700887 for line in read_file(CONFIG_DATABASE):
888 line = line.rstrip()
889 if not line: # Separator between defconfigs
890 config_db[defconfig] = configs
891 all_defconfigs.add(defconfig)
892 configs = {}
893 elif line[0] == ' ': # CONFIG line
894 config, value = line.strip().split('=', 1)
895 configs[config] = value
896 defconfig_db[config].add(defconfig)
897 all_configs.add(config)
898 else: # New defconfig
899 defconfig = line
Simon Glassf931c2f2021-12-18 08:09:43 -0700900
901 return all_configs, all_defconfigs, config_db, defconfig_db
902
903
Simon Glass44116332017-06-15 21:39:33 -0600904def do_imply_config(config_list, add_imply, imply_flags, skip_added,
905 check_kconfig=True, find_superset=False):
Simon Glassc6e73cf2017-06-01 19:39:03 -0600906 """Find CONFIG options which imply those in the list
907
908 Some CONFIG options can be implied by others and this can help to reduce
909 the size of the defconfig files. For example, CONFIG_X86 implies
910 CONFIG_CMD_IRQ, so we can put 'imply CMD_IRQ' under 'config X86' and
911 all x86 boards will have that option, avoiding adding CONFIG_CMD_IRQ to
912 each of the x86 defconfig files.
913
Simon Glassbf0e11a2023-09-23 13:44:14 -0600914 This function uses the qconfig database to find such options. It
Simon Glassc6e73cf2017-06-01 19:39:03 -0600915 displays a list of things that could possibly imply those in the list.
916 The algorithm ignores any that start with CONFIG_TARGET since these
917 typically refer to only a few defconfigs (often one). It also does not
918 display a config with less than 5 defconfigs.
919
920 The algorithm works using sets. For each target config in config_list:
921 - Get the set 'defconfigs' which use that target config
922 - For each config (from a list of all configs):
923 - Get the set 'imply_defconfig' of defconfigs which use that config
924 -
925 - If imply_defconfigs contains anything not in defconfigs then
926 this config does not imply the target config
927
Simon Glass18352112024-07-17 16:57:03 +0100928 Args:
929 config_list (list of str): List of CONFIG options to check
930 add_imply (bool): Automatically add an 'imply' for each config.
931 imply_flags (int): Flags which control which implying configs are allowed
Simon Glass92e55582017-06-15 21:39:32 -0600932 (IMPLY_...)
Simon Glass18352112024-07-17 16:57:03 +0100933 skip_added (bool): Don't show options which already have an imply added.
934 check_kconfig (bool): Check if implied symbols already have an 'imply' or
Simon Glass44116332017-06-15 21:39:33 -0600935 'select' for the target config, and show this information if so.
Simon Glass18352112024-07-17 16:57:03 +0100936 find_superset (bool): True to look for configs which are a superset of those
Simon Glassc6e73cf2017-06-01 19:39:03 -0600937 already found. So for example if CONFIG_EXYNOS5 implies an option,
938 but CONFIG_EXYNOS covers a larger set of defconfigs and also
939 implies that option, this will drop the former in favour of the
940 latter. In practice this option has not proved very used.
941
942 Note the terminoloy:
943 config - a CONFIG_XXX options (a string, e.g. 'CONFIG_CMD_EEPROM')
944 defconfig - a defconfig file (a string, e.g. 'configs/snow_defconfig')
945 """
Simon Glassa0a61602024-07-17 16:56:51 +0100946 kconf = scan_kconfig() if check_kconfig else None
Simon Glass44116332017-06-15 21:39:33 -0600947 if add_imply and add_imply != 'all':
Simon Glass93c0a9e2021-12-18 08:09:42 -0700948 add_imply = add_imply.split(',')
Simon Glass44116332017-06-15 21:39:33 -0600949
Simon Glassbeb825d2023-09-23 13:44:00 -0600950 all_configs, all_defconfigs, _, defconfig_db = read_database()
Simon Glassc6e73cf2017-06-01 19:39:03 -0600951
Simon Glass93c0a9e2021-12-18 08:09:42 -0700952 # Work through each target config option in turn, independently
Simon Glassc6e73cf2017-06-01 19:39:03 -0600953 for config in config_list:
954 defconfigs = defconfig_db.get(config)
955 if not defconfigs:
Simon Glass96f8f312023-09-23 13:43:59 -0600956 print(f'{config} not found in any defconfig')
Simon Glassc6e73cf2017-06-01 19:39:03 -0600957 continue
958
959 # Get the set of defconfigs without this one (since a config cannot
960 # imply itself)
961 non_defconfigs = all_defconfigs - defconfigs
962 num_defconfigs = len(defconfigs)
Simon Glass96f8f312023-09-23 13:43:59 -0600963 print(f'{config} found in {num_defconfigs}/{len(all_configs)} defconfigs')
Simon Glassc6e73cf2017-06-01 19:39:03 -0600964
965 # This will hold the results: key=config, value=defconfigs containing it
966 imply_configs = {}
967 rest_configs = all_configs - set([config])
968
969 # Look at every possible config, except the target one
970 for imply_config in rest_configs:
Simon Glass92e55582017-06-15 21:39:32 -0600971 if 'ERRATUM' in imply_config:
Simon Glassc6e73cf2017-06-01 19:39:03 -0600972 continue
Simon Glassb3464eb2021-12-18 14:54:35 -0700973 if not imply_flags & IMPLY_CMD:
Simon Glass92e55582017-06-15 21:39:32 -0600974 if 'CONFIG_CMD' in imply_config:
975 continue
Simon Glassb3464eb2021-12-18 14:54:35 -0700976 if not imply_flags & IMPLY_TARGET:
Simon Glass92e55582017-06-15 21:39:32 -0600977 if 'CONFIG_TARGET' in imply_config:
978 continue
Simon Glassc6e73cf2017-06-01 19:39:03 -0600979
980 # Find set of defconfigs that have this config
981 imply_defconfig = defconfig_db[imply_config]
982
983 # Get the intersection of this with defconfigs containing the
984 # target config
985 common_defconfigs = imply_defconfig & defconfigs
986
987 # Get the set of defconfigs containing this config which DO NOT
988 # also contain the taret config. If this set is non-empty it means
989 # that this config affects other defconfigs as well as (possibly)
990 # the ones affected by the target config. This means it implies
991 # things we don't want to imply.
992 not_common_defconfigs = imply_defconfig & non_defconfigs
993 if not_common_defconfigs:
994 continue
995
996 # If there are common defconfigs, imply_config may be useful
997 if common_defconfigs:
998 skip = False
999 if find_superset:
Simon Glass1f701862019-10-31 07:42:57 -06001000 for prev in list(imply_configs.keys()):
Simon Glassc6e73cf2017-06-01 19:39:03 -06001001 prev_count = len(imply_configs[prev])
1002 count = len(common_defconfigs)
1003 if (prev_count > count and
1004 (imply_configs[prev] & common_defconfigs ==
1005 common_defconfigs)):
1006 # skip imply_config because prev is a superset
1007 skip = True
1008 break
Simon Glasse19a9cd2023-09-23 13:44:05 -06001009 if count > prev_count:
Simon Glassc6e73cf2017-06-01 19:39:03 -06001010 # delete prev because imply_config is a superset
1011 del imply_configs[prev]
1012 if not skip:
1013 imply_configs[imply_config] = common_defconfigs
1014
1015 # Now we have a dict imply_configs of configs which imply each config
1016 # The value of each dict item is the set of defconfigs containing that
1017 # config. Rank them so that we print the configs that imply the largest
1018 # number of defconfigs first.
Simon Glass44116332017-06-15 21:39:33 -06001019 ranked_iconfigs = sorted(imply_configs,
Simon Glassc6e73cf2017-06-01 19:39:03 -06001020 key=lambda k: len(imply_configs[k]), reverse=True)
Simon Glass44116332017-06-15 21:39:33 -06001021 kconfig_info = ''
1022 cwd = os.getcwd()
1023 add_list = collections.defaultdict(list)
1024 for iconfig in ranked_iconfigs:
1025 num_common = len(imply_configs[iconfig])
Simon Glassc6e73cf2017-06-01 19:39:03 -06001026
1027 # Don't bother if there are less than 5 defconfigs affected.
Simon Glass92e55582017-06-15 21:39:32 -06001028 if num_common < (2 if imply_flags & IMPLY_MIN_2 else 5):
Simon Glassc6e73cf2017-06-01 19:39:03 -06001029 continue
Simon Glass44116332017-06-15 21:39:33 -06001030 missing = defconfigs - imply_configs[iconfig]
Simon Glassc6e73cf2017-06-01 19:39:03 -06001031 missing_str = ', '.join(missing) if missing else 'all'
1032 missing_str = ''
Simon Glass44116332017-06-15 21:39:33 -06001033 show = True
1034 if kconf:
1035 sym = find_kconfig_rules(kconf, config[CONFIG_LEN:],
1036 iconfig[CONFIG_LEN:])
1037 kconfig_info = ''
1038 if sym:
Simon Glass520b47a2021-07-21 21:35:53 -06001039 nodes = sym.nodes
1040 if len(nodes) == 1:
1041 fname, linenum = nodes[0].filename, nodes[0].linenr
Simon Glass44116332017-06-15 21:39:33 -06001042 if cwd and fname.startswith(cwd):
1043 fname = fname[len(cwd) + 1:]
Simon Glass96f8f312023-09-23 13:43:59 -06001044 kconfig_info = f'{fname}:{linenum}'
Simon Glass44116332017-06-15 21:39:33 -06001045 if skip_added:
1046 show = False
1047 else:
Tom Rini3c5f4152019-09-20 17:42:09 -04001048 sym = kconf.syms.get(iconfig[CONFIG_LEN:])
Simon Glass44116332017-06-15 21:39:33 -06001049 fname = ''
1050 if sym:
Simon Glass520b47a2021-07-21 21:35:53 -06001051 nodes = sym.nodes
1052 if len(nodes) == 1:
1053 fname, linenum = nodes[0].filename, nodes[0].linenr
Simon Glass44116332017-06-15 21:39:33 -06001054 if cwd and fname.startswith(cwd):
1055 fname = fname[len(cwd) + 1:]
1056 in_arch_board = not sym or (fname.startswith('arch') or
1057 fname.startswith('board'))
1058 if (not in_arch_board and
Simon Glassb3464eb2021-12-18 14:54:35 -07001059 not imply_flags & IMPLY_NON_ARCH_BOARD):
Simon Glass44116332017-06-15 21:39:33 -06001060 continue
1061
1062 if add_imply and (add_imply == 'all' or
1063 iconfig in add_imply):
1064 fname, linenum, kconfig_info = (check_imply_rule(kconf,
Simon Glass28155572024-07-17 16:56:52 +01001065 iconfig[CONFIG_LEN:]))
Simon Glass44116332017-06-15 21:39:33 -06001066 if fname:
1067 add_list[fname].append(linenum)
Simon Glassc6e73cf2017-06-01 19:39:03 -06001068
Simon Glass44116332017-06-15 21:39:33 -06001069 if show and kconfig_info != 'skip':
Simon Glass0f439202024-07-17 16:56:53 +01001070 print(f'{num_common:5} : '
1071 f'{iconfig.ljust(30)}{kconfig_info.ljust(25)} {missing_str}')
Simon Glass44116332017-06-15 21:39:33 -06001072
1073 # Having collected a list of things to add, now we add them. We process
1074 # each file from the largest line number to the smallest so that
1075 # earlier additions do not affect our line numbers. E.g. if we added an
1076 # imply at line 20 it would change the position of each line after
1077 # that.
Simon Glass1f701862019-10-31 07:42:57 -06001078 for fname, linenums in add_list.items():
Simon Glass44116332017-06-15 21:39:33 -06001079 for linenum in sorted(linenums, reverse=True):
1080 add_imply_rule(config[CONFIG_LEN:], fname, linenum)
1081
Simon Glass9c9ec3a2024-07-18 10:11:23 +01001082def defconfig_matches(configs, re_match, re_val):
Simon Glass99f79422022-02-08 11:49:46 -07001083 """Check if any CONFIG option matches a regex
1084
1085 The match must be complete, i.e. from the start to end of the CONFIG option.
1086
1087 Args:
1088 configs (dict): Dict of CONFIG options:
1089 key: CONFIG option
1090 value: Value of option
1091 re_match (re.Pattern): Match to check
Simon Glass9c9ec3a2024-07-18 10:11:23 +01001092 re_val (re.Pattern): Regular expression to check against value (or None)
Simon Glass99f79422022-02-08 11:49:46 -07001093
1094 Returns:
1095 bool: True if any CONFIG matches the regex
1096 """
Simon Glass9c9ec3a2024-07-18 10:11:23 +01001097 for cfg, val in configs.items():
Simon Glassfea71c92022-03-05 20:18:54 -07001098 if re_match.fullmatch(cfg):
Simon Glass9c9ec3a2024-07-18 10:11:23 +01001099 if not re_val or re_val.fullmatch(val):
1100 return True
Simon Glass99f79422022-02-08 11:49:46 -07001101 return False
Simon Glassc6e73cf2017-06-01 19:39:03 -06001102
Simon Glassa84b6c52024-07-18 10:11:25 +01001103def do_find_config(config_list, list_format):
Simon Glass0082b2e2021-12-18 08:09:46 -07001104 """Find boards with a given combination of CONFIGs
1105
Simon Glass3c4cb202024-07-17 16:57:04 +01001106 Args:
1107 config_list (list of str): List of CONFIG options to check (each a regex
1108 consisting of a config option, with or without a CONFIG_ prefix. If
1109 an option is preceded by a tilde (~) then it must be false,
1110 otherwise it must be true)
Simon Glassa84b6c52024-07-18 10:11:25 +01001111 list_format (bool): True to write in 'list' format, one board name per
1112 line
Simon Glass3c4cb202024-07-17 16:57:04 +01001113
1114 Returns:
1115 int: exit code (0 for success)
Simon Glass0082b2e2021-12-18 08:09:46 -07001116 """
Simon Glassbeb825d2023-09-23 13:44:00 -06001117 _, all_defconfigs, config_db, _ = read_database()
Simon Glass0082b2e2021-12-18 08:09:46 -07001118
Simon Glass0082b2e2021-12-18 08:09:46 -07001119 # Start with all defconfigs
1120 out = all_defconfigs
1121
1122 # Work through each config in turn
Simon Glass0082b2e2021-12-18 08:09:46 -07001123 for item in config_list:
1124 # Get the real config name and whether we want this config or not
1125 cfg = item
1126 want = True
1127 if cfg[0] == '~':
1128 want = False
1129 cfg = cfg[1:]
Simon Glass9c9ec3a2024-07-18 10:11:23 +01001130 val = None
1131 re_val = None
1132 if '=' in cfg:
1133 cfg, val = cfg.split('=', maxsplit=1)
1134 re_val = re.compile(val)
Simon Glass0082b2e2021-12-18 08:09:46 -07001135
Simon Glass0082b2e2021-12-18 08:09:46 -07001136 # Search everything that is still in the running. If it has a config
1137 # that we want, or doesn't have one that we don't, add it into the
1138 # running for the next stage
1139 in_list = out
1140 out = set()
Simon Glass99f79422022-02-08 11:49:46 -07001141 re_match = re.compile(cfg)
Simon Glass0082b2e2021-12-18 08:09:46 -07001142 for defc in in_list:
Simon Glass9c9ec3a2024-07-18 10:11:23 +01001143 has_cfg = defconfig_matches(config_db[defc], re_match, re_val)
Simon Glass0082b2e2021-12-18 08:09:46 -07001144 if has_cfg == want:
1145 out.add(defc)
Simon Glassa84b6c52024-07-18 10:11:25 +01001146 if not list_format:
1147 print(f'{len(out)} matches')
1148 sep = '\n' if list_format else ' '
1149 print(sep.join(item.split('_defconfig')[0] for item in sorted(list(out))))
Simon Glass3c4cb202024-07-17 16:57:04 +01001150 return 0
Simon Glass0082b2e2021-12-18 08:09:46 -07001151
1152
1153def prefix_config(cfg):
1154 """Prefix a config with CONFIG_ if needed
1155
1156 This handles ~ operator, which indicates that the CONFIG should be disabled
1157
1158 >>> prefix_config('FRED')
1159 'CONFIG_FRED'
1160 >>> prefix_config('CONFIG_FRED')
1161 'CONFIG_FRED'
1162 >>> prefix_config('~FRED')
1163 '~CONFIG_FRED'
1164 >>> prefix_config('~CONFIG_FRED')
1165 '~CONFIG_FRED'
1166 >>> prefix_config('A123')
1167 'CONFIG_A123'
1168 """
Simon Glass4f6725c2023-09-23 13:44:01 -06001169 oper = ''
Simon Glass0082b2e2021-12-18 08:09:46 -07001170 if cfg[0] == '~':
Simon Glass4f6725c2023-09-23 13:44:01 -06001171 oper = cfg[0]
Simon Glass0082b2e2021-12-18 08:09:46 -07001172 cfg = cfg[1:]
1173 if not cfg.startswith('CONFIG_'):
1174 cfg = 'CONFIG_' + cfg
Simon Glass4f6725c2023-09-23 13:44:01 -06001175 return oper + cfg
Simon Glass0082b2e2021-12-18 08:09:46 -07001176
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001177
Simon Glassc2ed2e32024-09-29 19:49:56 -06001178RE_MK_CONFIGS = re.compile(r'CONFIG_(\$\(XPL_\)|\$\(PHASE_\))?([A-Za-z0-9_]*)')
Simon Glass2deee262023-09-23 13:43:57 -06001179RE_IFDEF = re.compile(r'(ifdef|ifndef)')
1180RE_C_CONFIGS = re.compile(r'CONFIG_([A-Za-z0-9_]*)')
1181RE_CONFIG_IS = re.compile(r'CONFIG_IS_ENABLED\(([A-Za-z0-9_]*)\)')
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001182
1183class ConfigUse:
Simon Glass28155572024-07-17 16:56:52 +01001184 """Tracks whether a config relates to SPL or not"""
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001185 def __init__(self, cfg, is_spl, fname, rest):
Simon Glass28155572024-07-17 16:56:52 +01001186 """Set up a new ConfigUse
1187
1188 Args:
Simon Glassc2ed2e32024-09-29 19:49:56 -06001189 cfg (str): CONFIG option, without any CONFIG_ or xPL_ prefix
Simon Glass28155572024-07-17 16:56:52 +01001190 is_spl (bool): True if this option relates to SPL
1191 fname (str): Makefile filename where the CONFIG option was found
1192 rest (str): Line of the Makefile
1193 """
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001194 self.cfg = cfg
1195 self.is_spl = is_spl
1196 self.fname = fname
1197 self.rest = rest
1198
1199 def __hash__(self):
1200 return hash((self.cfg, self.is_spl))
1201
1202def scan_makefiles(fnames):
1203 """Scan Makefiles looking for Kconfig options
1204
1205 Looks for uses of CONFIG options in Makefiles
1206
1207 Args:
1208 fnames (list of tuple):
1209 str: Makefile filename where the option was found
1210 str: Line of the Makefile
1211
1212 Returns:
1213 tuple:
1214 dict: all_uses
1215 key (ConfigUse): object
1216 value (list of str): matching lines
1217 dict: Uses by filename
1218 key (str): filename
1219 value (set of ConfigUse): uses in that filename
1220
1221 >>> RE_MK_CONFIGS.search('CONFIG_FRED').groups()
1222 (None, 'FRED')
Simon Glass86adc2e2024-09-29 19:49:53 -06001223 >>> RE_MK_CONFIGS.search('CONFIG_$(XPL_)MARY').groups()
1224 ('$(XPL_)', 'MARY')
Simon Glass4cafa212024-09-29 19:49:54 -06001225 >>> RE_MK_CONFIGS.search('CONFIG_$(PHASE_)MARY').groups()
1226 ('$(PHASE_)', 'MARY')
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001227 """
1228 all_uses = collections.defaultdict(list)
1229 fname_uses = {}
1230 for fname, rest in fnames:
1231 m_iter = RE_MK_CONFIGS.finditer(rest)
Simon Glass4f6725c2023-09-23 13:44:01 -06001232 for mat in m_iter:
1233 real_opt = mat.group(2)
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001234 if real_opt == '':
1235 continue
1236 is_spl = False
Simon Glass4f6725c2023-09-23 13:44:01 -06001237 if mat.group(1):
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001238 is_spl = True
1239 use = ConfigUse(real_opt, is_spl, fname, rest)
1240 if fname not in fname_uses:
1241 fname_uses[fname] = set()
1242 fname_uses[fname].add(use)
1243 all_uses[use].append(rest)
1244 return all_uses, fname_uses
1245
1246
1247def scan_src_files(fnames):
1248 """Scan source files (other than Makefiles) looking for Kconfig options
1249
1250 Looks for uses of CONFIG options
1251
1252 Args:
1253 fnames (list of tuple):
1254 str: Makefile filename where the option was found
1255 str: Line of the Makefile
1256
1257 Returns:
1258 tuple:
1259 dict: all_uses
1260 key (ConfigUse): object
1261 value (list of str): matching lines
1262 dict: Uses by filename
1263 key (str): filename
1264 value (set of ConfigUse): uses in that filename
1265
1266 >>> RE_C_CONFIGS.search('CONFIG_FRED').groups()
1267 ('FRED',)
1268 >>> RE_CONFIG_IS.search('CONFIG_IS_ENABLED(MARY)').groups()
1269 ('MARY',)
1270 >>> RE_CONFIG_IS.search('#if CONFIG_IS_ENABLED(OF_PLATDATA)').groups()
1271 ('OF_PLATDATA',)
1272 """
Simon Glassbeb825d2023-09-23 13:44:00 -06001273 fname = None
1274 rest = None
1275
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001276 def add_uses(m_iter, is_spl):
Simon Glass4f6725c2023-09-23 13:44:01 -06001277 for mat in m_iter:
1278 real_opt = mat.group(1)
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001279 if real_opt == '':
1280 continue
1281 use = ConfigUse(real_opt, is_spl, fname, rest)
1282 if fname not in fname_uses:
1283 fname_uses[fname] = set()
1284 fname_uses[fname].add(use)
1285 all_uses[use].append(rest)
1286
1287 all_uses = collections.defaultdict(list)
1288 fname_uses = {}
1289 for fname, rest in fnames:
1290 m_iter = RE_C_CONFIGS.finditer(rest)
1291 add_uses(m_iter, False)
1292
1293 m_iter2 = RE_CONFIG_IS.finditer(rest)
1294 add_uses(m_iter2, True)
1295
1296 return all_uses, fname_uses
1297
1298
1299MODE_NORMAL, MODE_SPL, MODE_PROPER = range(3)
1300
1301def do_scan_source(path, do_update):
1302 """Scan the source tree for Kconfig inconsistencies
1303
1304 Args:
1305 path (str): Path to source tree
1306 do_update (bool) : True to write to scripts/kconf_... files
1307 """
1308 def is_not_proper(name):
1309 for prefix in SPL_PREFIXES:
1310 if name.startswith(prefix):
1311 return name[len(prefix):]
1312 return False
1313
1314 def check_not_found(all_uses, spl_mode):
1315 """Check for Kconfig options mentioned in the source but not in Kconfig
1316
1317 Args:
1318 all_uses (dict):
1319 key (ConfigUse): object
1320 value (list of str): matching lines
1321 spl_mode (int): If MODE_SPL, look at source code which implies
Simon Glassc2ed2e32024-09-29 19:49:56 -06001322 an xPL_ option, but for which there is none;
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001323 for MOD_PROPER, look at source code which implies a Proper
Simon Glass86adc2e2024-09-29 19:49:53 -06001324 option (i.e. use of CONFIG_IS_ENABLED() or $(XPL_) or
Simon Glass4cafa212024-09-29 19:49:54 -06001325 $(PHASE_) but for which there none;
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001326 if MODE_NORMAL, ignore SPL
1327
1328 Returns:
1329 dict:
1330 key (str): CONFIG name (without 'CONFIG_' prefix
1331 value (list of ConfigUse): List of uses of this CONFIG
1332 """
1333 # Make sure we know about all the options
1334 not_found = collections.defaultdict(list)
Simon Glassbeb825d2023-09-23 13:44:00 -06001335 for use, _ in all_uses.items():
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001336 name = use.cfg
1337 if name in IGNORE_SYMS:
1338 continue
1339 check = True
1340
1341 if spl_mode == MODE_SPL:
1342 check = use.is_spl
1343
Simon Glassc2ed2e32024-09-29 19:49:56 -06001344 # If it is an SPL symbol, try prepending all xPL_ prefixes to
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001345 # find at least one SPL symbol
1346 if use.is_spl:
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001347 for prefix in SPL_PREFIXES:
1348 try_name = prefix + name
1349 sym = kconf.syms.get(try_name)
1350 if sym:
1351 break
1352 if not sym:
1353 not_found[f'SPL_{name}'].append(use)
1354 continue
1355 elif spl_mode == MODE_PROPER:
1356 # Try to find the Proper version of this symbol, i.e. without
Simon Glassc2ed2e32024-09-29 19:49:56 -06001357 # the xPL_ prefix
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001358 proper_name = is_not_proper(name)
1359 if proper_name:
1360 name = proper_name
1361 elif not use.is_spl:
1362 check = False
1363 else: # MODE_NORMAL
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001364 sym = kconf.syms.get(name)
1365 if not sym:
1366 proper_name = is_not_proper(name)
1367 if proper_name:
1368 name = proper_name
1369 sym = kconf.syms.get(name)
1370 if not sym:
1371 for prefix in SPL_PREFIXES:
1372 try_name = prefix + name
1373 sym = kconf.syms.get(try_name)
1374 if sym:
1375 break
1376 if not sym:
1377 not_found[name].append(use)
1378 continue
1379
1380 sym = kconf.syms.get(name)
1381 if not sym and check:
1382 not_found[name].append(use)
1383 return not_found
1384
1385 def show_uses(uses):
1386 """Show a list of uses along with their filename and code snippet
1387
1388 Args:
1389 uses (dict):
1390 key (str): CONFIG name (without 'CONFIG_' prefix
1391 value (list of ConfigUse): List of uses of this CONFIG
1392 """
1393 for name in sorted(uses):
1394 print(f'{name}: ', end='')
1395 for i, use in enumerate(uses[name]):
1396 print(f'{" " if i else ""}{use.fname}: {use.rest.strip()}')
1397
1398
1399 print('Scanning Kconfig')
Simon Glassa0a61602024-07-17 16:56:51 +01001400 kconf = scan_kconfig()
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001401 print(f'Scanning source in {path}')
1402 args = ['git', 'grep', '-E', r'IS_ENABLED|\bCONFIG']
1403 with subprocess.Popen(args, stdout=subprocess.PIPE) as proc:
Simon Glassbeb825d2023-09-23 13:44:00 -06001404 out, _ = proc.communicate()
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001405 lines = out.splitlines()
1406 re_fname = re.compile('^([^:]*):(.*)')
1407 src_list = []
1408 mk_list = []
1409 for line in lines:
1410 linestr = line.decode('utf-8')
1411 m_fname = re_fname.search(linestr)
1412 if not m_fname:
1413 continue
1414 fname, rest = m_fname.groups()
1415 dirname, leaf = os.path.split(fname)
1416 root, ext = os.path.splitext(leaf)
1417 if ext == '.autoconf':
1418 pass
1419 elif ext in ['.c', '.h', '.S', '.lds', '.dts', '.dtsi', '.asl', '.cfg',
1420 '.env', '.tmpl']:
1421 src_list.append([fname, rest])
1422 elif 'Makefile' in root or ext == '.mk':
1423 mk_list.append([fname, rest])
1424 elif ext in ['.yml', '.sh', '.py', '.awk', '.pl', '.rst', '', '.sed']:
1425 pass
1426 elif 'Kconfig' in root or 'Kbuild' in root:
1427 pass
1428 elif 'README' in root:
1429 pass
1430 elif dirname in ['configs']:
1431 pass
1432 elif dirname.startswith('doc') or dirname.startswith('scripts/kconfig'):
1433 pass
1434 else:
1435 print(f'Not sure how to handle file {fname}')
1436
1437 # Scan the Makefiles
Simon Glassbeb825d2023-09-23 13:44:00 -06001438 all_uses, _ = scan_makefiles(mk_list)
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001439
1440 spl_not_found = set()
1441 proper_not_found = set()
1442
1443 # Make sure we know about all the options
1444 print('\nCONFIG options present in Makefiles but not Kconfig:')
1445 not_found = check_not_found(all_uses, MODE_NORMAL)
1446 show_uses(not_found)
1447
1448 print('\nCONFIG options present in Makefiles but not Kconfig (SPL):')
1449 not_found = check_not_found(all_uses, MODE_SPL)
1450 show_uses(not_found)
Simon Glassd708cf72023-09-23 13:44:03 -06001451 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001452
Simon Glassc2ed2e32024-09-29 19:49:56 -06001453 print('\nCONFIG options used as Proper in Makefiles but without a non-xPL_ variant:')
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001454 not_found = check_not_found(all_uses, MODE_PROPER)
1455 show_uses(not_found)
Simon Glassd708cf72023-09-23 13:44:03 -06001456 proper_not_found |= {not_found.keys()}
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001457
1458 # Scan the source code
Simon Glassbeb825d2023-09-23 13:44:00 -06001459 all_uses, _ = scan_src_files(src_list)
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001460
1461 # Make sure we know about all the options
1462 print('\nCONFIG options present in source but not Kconfig:')
1463 not_found = check_not_found(all_uses, MODE_NORMAL)
1464 show_uses(not_found)
1465
1466 print('\nCONFIG options present in source but not Kconfig (SPL):')
1467 not_found = check_not_found(all_uses, MODE_SPL)
1468 show_uses(not_found)
Simon Glassd708cf72023-09-23 13:44:03 -06001469 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001470
Simon Glassc2ed2e32024-09-29 19:49:56 -06001471 print('\nCONFIG options used as Proper in source but without a non-xPL_ variant:')
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001472 not_found = check_not_found(all_uses, MODE_PROPER)
1473 show_uses(not_found)
Simon Glassd708cf72023-09-23 13:44:03 -06001474 proper_not_found |= {not_found.keys()}
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001475
Simon Glassc2ed2e32024-09-29 19:49:56 -06001476 print('\nCONFIG options used as SPL but without an xPL_ variant:')
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001477 for item in sorted(spl_not_found):
1478 print(f' {item}')
1479
Simon Glassc2ed2e32024-09-29 19:49:56 -06001480 print('\nCONFIG options used as Proper but without a non-xPL_ variant:')
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001481 for item in sorted(proper_not_found):
1482 print(f' {item}')
1483
1484 # Write out the updated information
1485 if do_update:
Simon Glass47f22892023-09-23 13:44:04 -06001486 with open(os.path.join(path, 'scripts', 'conf_nospl'), 'w',
1487 encoding='utf-8') as out:
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001488 print('# These options should not be enabled in SPL builds\n',
1489 file=out)
1490 for item in sorted(spl_not_found):
1491 print(item, file=out)
Simon Glass47f22892023-09-23 13:44:04 -06001492 with open(os.path.join(path, 'scripts', 'conf_noproper'), 'w',
1493 encoding='utf-8') as out:
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001494 print('# These options should not be enabled in Proper builds\n',
1495 file=out)
1496 for item in sorted(proper_not_found):
1497 print(item, file=out)
Simon Glass6901a642024-07-17 16:57:02 +01001498 return 0
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001499
Simon Glass0082b2e2021-12-18 08:09:46 -07001500
Simon Glassfe11dcf2024-07-17 16:56:55 +01001501def parse_args():
1502 """Parse the program arguments
1503
1504 Returns:
1505 tuple:
1506 argparse.ArgumentParser: parser
1507 argparse.Namespace: Parsed arguments
1508 """
Masahiro Yamadab6160812015-05-20 11:36:07 +09001509 try:
1510 cpu_count = multiprocessing.cpu_count()
1511 except NotImplementedError:
1512 cpu_count = 1
1513
Simon Glassd9c1da22021-12-18 14:54:31 -07001514 epilog = '''Move config options from headers to defconfig files. See
1515doc/develop/moveconfig.rst for documentation.'''
1516
1517 parser = ArgumentParser(epilog=epilog)
1518 # Add arguments here
1519 parser.add_argument('-a', '--add-imply', type=str, default='',
Simon Glass44116332017-06-15 21:39:33 -06001520 help='comma-separated list of CONFIG options to add '
1521 "an 'imply' statement to for the CONFIG in -i")
Simon Glassd9c1da22021-12-18 14:54:31 -07001522 parser.add_argument('-A', '--skip-added', action='store_true', default=False,
Simon Glass44116332017-06-15 21:39:33 -06001523 help="don't show options which are already marked as "
1524 'implying others')
Simon Glassd9c1da22021-12-18 14:54:31 -07001525 parser.add_argument('-b', '--build-db', action='store_true', default=False,
Simon Glass43cf08f2017-06-01 19:39:02 -06001526 help='build a CONFIG database')
Simon Glassd9c1da22021-12-18 14:54:31 -07001527 parser.add_argument('-C', '--commit', action='store_true', default=False,
Simon Glass8bf41c22016-09-12 23:18:21 -06001528 help='Create a git commit for the operation')
Simon Glass9b191102023-09-23 13:44:09 -06001529 parser.add_argument('--nocolour', action='store_true', default=False,
1530 help="don't display the log in colour")
Simon Glassd9c1da22021-12-18 14:54:31 -07001531 parser.add_argument('-d', '--defconfigs', type=str,
Simon Glass8f3cf312017-06-01 19:38:59 -06001532 help='a file containing a list of defconfigs to move, '
1533 "one per line (for example 'snow_defconfig') "
1534 "or '-' to read from stdin")
Simon Glassd9c1da22021-12-18 14:54:31 -07001535 parser.add_argument('-e', '--exit-on-error', action='store_true',
Masahiro Yamadab6160812015-05-20 11:36:07 +09001536 default=False,
1537 help='exit immediately on any error')
Simon Glassd9c1da22021-12-18 14:54:31 -07001538 parser.add_argument('-f', '--find', action='store_true', default=False,
Simon Glass0082b2e2021-12-18 08:09:46 -07001539 help='Find boards with a given config combination')
Simon Glassd9c1da22021-12-18 14:54:31 -07001540 parser.add_argument('-i', '--imply', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001541 help='find options which imply others')
Simon Glassa84b6c52024-07-18 10:11:25 +01001542 parser.add_argument('-l', '--list', action='store_true', default=False,
1543 help='Show a sorted list of board names, one per line')
Simon Glassd9c1da22021-12-18 14:54:31 -07001544 parser.add_argument('-I', '--imply-flags', type=str, default='',
Simon Glass0559a742021-12-18 08:09:44 -07001545 help="control the -i option ('help' for help")
Simon Glassd9c1da22021-12-18 14:54:31 -07001546 parser.add_argument('-j', '--jobs', type=int, default=cpu_count,
Masahiro Yamadab6160812015-05-20 11:36:07 +09001547 help='the number of jobs to run simultaneously')
Simon Glassd9c1da22021-12-18 14:54:31 -07001548 parser.add_argument('-n', '--dry-run', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001549 help='perform a trial run (show log with no changes)')
Simon Glassd9c1da22021-12-18 14:54:31 -07001550 parser.add_argument('-r', '--git-ref', type=str,
Joe Hershbergerb1a570f2016-06-10 14:53:32 -05001551 help='the git ref to clone for building the autoconf.mk')
Simon Glassd9c1da22021-12-18 14:54:31 -07001552 parser.add_argument('-s', '--force-sync', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001553 help='force sync by savedefconfig')
Simon Glassd9c1da22021-12-18 14:54:31 -07001554 parser.add_argument('-S', '--spl', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001555 help='parse config options defined for SPL build')
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001556 parser.add_argument('--scan-source', action='store_true', default=False,
1557 help='scan source for uses of CONFIG options')
Simon Glassd9c1da22021-12-18 14:54:31 -07001558 parser.add_argument('-t', '--test', action='store_true', default=False,
Simon Glass0559a742021-12-18 08:09:44 -07001559 help='run unit tests')
Simon Glassd9c1da22021-12-18 14:54:31 -07001560 parser.add_argument('-y', '--yes', action='store_true', default=False,
Simon Glass13e05a02016-09-12 23:18:20 -06001561 help="respond 'yes' to any prompts")
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001562 parser.add_argument('-u', '--update', action='store_true', default=False,
1563 help="update scripts/ files (use with --scan-source)")
Simon Glassd9c1da22021-12-18 14:54:31 -07001564 parser.add_argument('-v', '--verbose', action='store_true', default=False,
Joe Hershberger808b63f2015-05-19 13:21:24 -05001565 help='show any build errors as boards are built')
Simon Glassd9c1da22021-12-18 14:54:31 -07001566 parser.add_argument('configs', nargs='*')
Masahiro Yamadab6160812015-05-20 11:36:07 +09001567
Simon Glass3314e902024-07-17 16:57:11 +01001568 args = parser.parse_args()
1569 if not any((args.force_sync, args.build_db, args.imply, args.find,
1570 args.scan_source, args.test)):
1571 parser.print_usage()
1572 sys.exit(1)
1573
1574 return parser, args
Masahiro Yamadab6160812015-05-20 11:36:07 +09001575
Simon Glassfe11dcf2024-07-17 16:56:55 +01001576
Simon Glass18352112024-07-17 16:57:03 +01001577def imply(args):
1578 """Handle checking for flags which imply others
1579
1580 Args:
1581 args (argparse.Namespace): Program arguments
1582
1583 Returns:
1584 int: exit code (0 for success)
1585 """
1586 imply_flags = 0
1587 if args.imply_flags == 'all':
1588 imply_flags = -1
1589
1590 elif args.imply_flags:
1591 for flag in args.imply_flags.split(','):
1592 bad = flag not in IMPLY_FLAGS
1593 if bad:
1594 print(f"Invalid flag '{flag}'")
1595 if flag == 'help' or bad:
1596 print("Imply flags: (separate with ',')")
1597 for name, info in IMPLY_FLAGS.items():
Simon Glass023bb612024-08-21 17:52:24 -06001598 print(f' {name.ljust(15)}: {info[1]}')
Simon Glass18352112024-07-17 16:57:03 +01001599 return 1
1600 imply_flags |= IMPLY_FLAGS[flag][0]
1601
1602 do_imply_config(args.configs, args.add_imply, imply_flags, args.skip_added)
1603 return 0
1604
1605
Simon Glasse054a752024-07-17 16:57:06 +01001606def add_commit(configs):
1607 """Add a commit indicating which CONFIG options were converted
1608
1609 Args:
1610 configs (list of str) List of CONFIG_... options to process
1611 """
1612 subprocess.call(['git', 'add', '-u'])
1613 if configs:
1614 part = 'et al ' if len(configs) > 1 else ''
1615 msg = f'Convert {configs[0]} {part}to Kconfig'
1616 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' %
1617 '\n '.join(configs))
1618 else:
1619 msg = 'configs: Resync with savedefconfig'
1620 msg += '\n\nRsync all defconfig files using moveconfig.py'
1621 subprocess.call(['git', 'commit', '-s', '-m', msg])
1622
1623
Simon Glassc2466b72024-07-17 16:57:09 +01001624def write_db(config_db, progress):
Simon Glassc5ca9dc2024-07-17 16:57:08 +01001625 """Write the database to a file
1626
1627 Args:
1628 config_db (dict of dict): configs for each defconfig
1629 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
1630 value: dict:
1631 key: CONFIG option
1632 value: Value of option
Simon Glassc5ca9dc2024-07-17 16:57:08 +01001633 progress (Progress): Progress indicator.
1634
1635 Returns:
1636 int: exit code (0 for success)
1637 """
Simon Glassc2466b72024-07-17 16:57:09 +01001638 col = progress.col
Simon Glassc5ca9dc2024-07-17 16:57:08 +01001639 with open(CONFIG_DATABASE, 'w', encoding='utf-8') as outf:
1640 for defconfig, configs in config_db.items():
1641 outf.write(f'{defconfig}\n')
1642 for config in sorted(configs.keys()):
1643 outf.write(f' {config}={configs[config]}\n')
1644 outf.write('\n')
1645 print(col.build(
1646 col.RED if progress.failed else col.GREEN,
1647 f'{progress.failure_msg}{len(config_db)} boards written to {CONFIG_DATABASE}'))
1648 return 0
1649
1650
Simon Glassc2466b72024-07-17 16:57:09 +01001651def move_done(progress):
Simon Glassc5ca9dc2024-07-17 16:57:08 +01001652 """Write a message indicating that the move is done
1653
1654 Args:
Simon Glassc5ca9dc2024-07-17 16:57:08 +01001655 progress (Progress): Progress indicator.
1656
1657 Returns:
1658 int: exit code (0 for success)
1659 """
Simon Glassc2466b72024-07-17 16:57:09 +01001660 col = progress.col
Simon Glassc5ca9dc2024-07-17 16:57:08 +01001661 if progress.failed:
1662 print(col.build(col.RED, f'{progress.failure_msg}see {FAILED_LIST}', True))
1663 else:
1664 # Add enough spaces to overwrite the progress indicator
1665 print(col.build(
1666 col.GREEN, f'{progress.total} processed ', bright=True))
1667 return 0
1668
Simon Glassac261cc2024-07-17 16:57:01 +01001669def do_tests():
1670 """Run doctests and unit tests (so far there are no unit tests)"""
1671 sys.argv = [sys.argv[0]]
1672 fail, _ = doctest.testmod()
1673 if fail:
1674 return 1
1675 unittest.main()
1676 return 0
1677
1678
Simon Glassfe11dcf2024-07-17 16:56:55 +01001679def main():
1680 """Main program"""
1681 parser, args = parse_args()
Simon Glass9a042c52024-07-17 16:56:59 +01001682 check_top_directory()
1683
Simon Glass84f541a2024-07-17 16:57:00 +01001684 # prefix the option name with CONFIG_ if missing
1685 args.configs = [prefix_config(cfg) for cfg in args.configs]
1686
Simon Glassd9c1da22021-12-18 14:54:31 -07001687 if args.test:
Simon Glassac261cc2024-07-17 16:57:01 +01001688 return do_tests()
Simon Glass4c4eb7c2023-02-01 13:19:12 -07001689 if args.scan_source:
Simon Glass6901a642024-07-17 16:57:02 +01001690 return do_scan_source(os.getcwd(), args.update)
Simon Glassd9c1da22021-12-18 14:54:31 -07001691 if args.imply:
Simon Glass18352112024-07-17 16:57:03 +01001692 if imply(args):
1693 parser.print_usage()
1694 sys.exit(1)
Simon Glasse19a9cd2023-09-23 13:44:05 -06001695 return 0
Simon Glassd9c1da22021-12-18 14:54:31 -07001696 if args.find:
Simon Glassa84b6c52024-07-18 10:11:25 +01001697 return do_find_config(args.configs, args.list)
Simon Glass0082b2e2021-12-18 08:09:46 -07001698
Simon Glassc2466b72024-07-17 16:57:09 +01001699 config_db, progress = move_config(args)
Joe Hershberger23475932015-05-19 13:21:20 -05001700
Simon Glassd9c1da22021-12-18 14:54:31 -07001701 if args.commit:
Simon Glasse054a752024-07-17 16:57:06 +01001702 add_commit(args.configs)
Simon Glass8bf41c22016-09-12 23:18:21 -06001703
Simon Glassd9c1da22021-12-18 14:54:31 -07001704 if args.build_db:
Simon Glassc2466b72024-07-17 16:57:09 +01001705 return write_db(config_db, progress)
1706 return move_done(progress)
Simon Glasse19a9cd2023-09-23 13:44:05 -06001707
Simon Glass43cf08f2017-06-01 19:39:02 -06001708
Masahiro Yamadab6160812015-05-20 11:36:07 +09001709if __name__ == '__main__':
Simon Glass0082b2e2021-12-18 08:09:46 -07001710 sys.exit(main())