]> Git Repo - J-u-boot.git/blame - tools/qconfig.py
configs: stm32mp1: Restore boot SPL from sdcard for Engicam i.Core STM32MP1 EDIMM2.2
[J-u-boot.git] / tools / qconfig.py
CommitLineData
793dca34 1#!/usr/bin/env python3
83d290c5 2# SPDX-License-Identifier: GPL-2.0+
5a27c734 3
f876e96f 4"""Build and query a Kconfig database for boards.
5a27c734 5
495e58c9 6See doc/develop/qconfig.rst for documentation.
f876e96f
SG
7
8Author: Masahiro Yamada <[email protected]>
9Author: Simon Glass <[email protected]>
5a27c734
MY
10"""
11
b2e83c63 12from argparse import ArgumentParser
99b66605 13import collections
91197aa6 14from contextlib import ExitStack
84067a58 15import doctest
c8e1b10d 16import filecmp
5a27c734 17import fnmatch
0dbc9b59 18import glob
5a27c734 19import multiprocessing
5a27c734 20import os
793dca34 21import queue
5a27c734
MY
22import re
23import shutil
24import subprocess
25import sys
26import tempfile
d73fcb12 27import threading
5a27c734 28import time
84067a58 29import unittest
5a27c734 30
0ede00fd
SG
31from buildman import bsettings
32from buildman import kconfiglib
33from buildman import toolchain
15f19ab5 34from u_boot_pylib import terminal
cb008830 35
5a27c734
MY
36SHOW_GNU_MAKE = 'scripts/show-gnu-make'
37SLEEP_TIME=0.03
38
5a27c734
MY
39STATE_IDLE = 0
40STATE_DEFCONFIG = 1
41STATE_AUTOCONF = 2
96464bad 42STATE_SAVEDEFCONFIG = 3
5a27c734 43
f3b8e647 44AUTO_CONF_PATH = 'include/config/auto.conf'
51963792
SG
45CONFIG_DATABASE = 'qconfig.db'
46FAILED_LIST = 'qconfig.failed'
f3b8e647 47
cb008830 48CONFIG_LEN = len('CONFIG_')
f3b8e647 49
b237d358 50SIZES = {
daa694d3
SG
51 '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
b237d358
MK
68}
69
b8d11da0
SG
70RE_REMOVE_DEFCONFIG = re.compile(r'(.*)_defconfig')
71
65e62037
SG
72# 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',
90fb55e0 80 'VAL', '_UNDEFINED', 'SPL_BUILD', 'XPL_BUILD', ]
65e62037
SG
81
82SPL_PREFIXES = ['SPL_', 'TPL_', 'VPL_', 'TOOLS_']
83
5a27c734 84### helper functions ###
5a27c734
MY
85def check_top_directory():
86 """Exit if we are not at the top of source directory."""
91197aa6
SG
87 for fname in 'README', 'Licenses':
88 if not os.path.exists(fname):
5a27c734
MY
89 sys.exit('Please run at the top of source directory.')
90
bd63e5ba
MY
91def check_clean_directory():
92 """Exit if the source tree is not clean."""
91197aa6
SG
93 for fname in '.config', 'include/config':
94 if os.path.exists(fname):
bd63e5ba
MY
95 sys.exit("source tree is not clean, please run 'make mrproper'")
96
5a27c734
MY
97def 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 """
91197aa6
SG
104 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')
5a27c734
MY
108 return ret[0].rstrip()
109
25f978cb
SG
110def get_matched_defconfig(line):
111 """Get the defconfig files that match a pattern
112
113 Args:
91197aa6 114 line (str): Path or filename to match, e.g. 'configs/snow_defconfig' or
25f978cb
SG
115 'k2*_defconfig'. If no directory is provided, 'configs/' is
116 prepended
117
118 Returns:
91197aa6 119 list of str: a list of matching defconfig files
25f978cb
SG
120 """
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
0dbc9b59 128def get_matched_defconfigs(defconfigs_file):
ee4e61bd
SG
129 """Get all the defconfig files that match the patterns in a file.
130
131 Args:
91197aa6
SG
132 defconfigs_file (str): File containing a list of defconfigs to process,
133 or '-' to read the list from stdin
ee4e61bd
SG
134
135 Returns:
91197aa6 136 list of str: A list of paths to defconfig files, with no duplicates
ee4e61bd 137 """
0dbc9b59 138 defconfigs = []
91197aa6
SG
139 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)
155
156 defconfigs += matched
0dbc9b59
MY
157
158 # use set() to drop multiple matching
91197aa6 159 return [defconfig[len('configs') + 1:] for defconfig in set(defconfigs)]
0dbc9b59 160
684c306e 161def get_all_defconfigs():
91197aa6
SG
162 """Get all the defconfig files under the configs/ directory.
163
164 Returns:
165 list of str: List of paths to defconfig files
166 """
684c306e 167 defconfigs = []
91197aa6 168 for (dirpath, _, filenames) in os.walk('configs'):
684c306e
MY
169 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
2fd85bd3
SG
175def 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
37f815ca
SG
190def 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
549d4223 195 as_lines (bool): Return file contents as a list of lines
37f815ca
SG
196 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()]
f297ba33 210 return inf.read()
a4c9d178 211 except UnicodeDecodeError as exc:
37f815ca 212 if not skip_unicode:
68a0b715 213 raise
a4c9d178 214 print(f"Failed on file '{fname}: {exc}")
37f815ca
SG
215 return None
216
ca43834d 217
5a27c734 218### classes ###
c5e60fd4 219class Progress:
c5e60fd4
MY
220 """Progress Indicator"""
221
6b25d21c 222 def __init__(self, col, total):
c5e60fd4
MY
223 """Create a new progress indicator.
224
91197aa6 225 Args:
0229eeef 226 col (terminal.Color): Colour-output class
6b25d21c 227 total (int): A number of defconfig files to process.
0229eeef
SG
228
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
c5e60fd4 232 """
6b25d21c 233 self.col = col
0229eeef
SG
234 self.total = total
235
c5e60fd4 236 self.current = 0
6b25d21c 237 self.good = 0
0229eeef
SG
238 self.failed = None
239 self.failure_msg = None
c5e60fd4 240
6b25d21c
SG
241 def inc(self, success):
242 """Increment the number of processed defconfig files.
c5e60fd4 243
6b25d21c
SG
244 Args:
245 success (bool): True if processing succeeded
246 """
247 self.good += success
c5e60fd4
MY
248 self.current += 1
249
250 def show(self):
251 """Display the progress."""
95f0914e 252 if self.current != self.total:
6b25d21c
SG
253 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='')
c5e60fd4
MY
259 sys.stdout.flush()
260
0229eeef
SG
261 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
cb008830 266
0a0c1240
SG
267def scan_kconfig():
268 """Scan all the Kconfig files and create a Config object
cb008830 269
0a0c1240
SG
270 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()
cb008830
SG
279
280
f876e96f 281# pylint: disable=R0903
5a27c734 282class KconfigParser:
5a27c734
MY
283 """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
882c8e47 288 def __init__(self, args, build_dir):
5a27c734
MY
289 """Create a new parser.
290
91197aa6 291 Args:
91197aa6 292 args (Namespace): program arguments
5a27c734
MY
293 build_dir: Build directory.
294 """
b2e83c63 295 self.args = args
1f16992e
MY
296 self.dotconfig = os.path.join(build_dir, '.config')
297 self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
07913d1e
MY
298 self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
299 'autoconf.mk')
f3b8e647 300 self.config_autoconf = os.path.join(build_dir, AUTO_CONF_PATH)
5da4f857 301 self.defconfig = os.path.join(build_dir, 'defconfig')
5a27c734 302
6821a745
SG
303 def get_arch(self):
304 """Parse .config file and return the architecture.
5a27c734
MY
305
306 Returns:
6821a745 307 Architecture name (e.g. 'arm').
5a27c734
MY
308 """
309 arch = ''
310 cpu = ''
37f815ca 311 for line in read_file(self.dotconfig):
a4c9d178
SG
312 m_arch = self.re_arch.match(line)
313 if m_arch:
314 arch = m_arch.group(1)
5a27c734 315 continue
a4c9d178
SG
316 m_cpu = self.re_cpu.match(line)
317 if m_cpu:
318 cpu = m_cpu.group(1)
5a27c734 319
90ed6cba
MY
320 if not arch:
321 return None
5a27c734
MY
322
323 # fix-up for aarch64
324 if arch == 'arm' and cpu == 'armv8':
325 arch = 'aarch64'
326
6821a745 327 return arch
5a27c734 328
d73fcb12
SG
329
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
5a27c734
MY
357class 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
15f19ab5 366 def __init__(self, toolchains, args, progress, devnull, make_cmd,
e23a5839 367 reference_src_dir, db_queue):
5a27c734
MY
368 """Create a new process slot.
369
91197aa6 370 Args:
6821a745 371 toolchains: Toolchains object containing toolchains.
b2e83c63 372 args: Program arguments
c5e60fd4 373 progress: A progress indicator.
5a27c734
MY
374 devnull: A file object of '/dev/null'.
375 make_cmd: command name of GNU Make.
6b96c1a1
JH
376 reference_src_dir: Determine the true starting config state from this
377 source tree.
d73fcb12 378 db_queue: output queue to write config info for the database
15f19ab5 379 col (terminal.Color): Colour object
5a27c734 380 """
6821a745 381 self.toolchains = toolchains
b2e83c63 382 self.args = args
c5e60fd4 383 self.progress = progress
5a27c734
MY
384 self.build_dir = tempfile.mkdtemp()
385 self.devnull = devnull
386 self.make_cmd = (make_cmd, 'O=' + self.build_dir)
6b96c1a1 387 self.reference_src_dir = reference_src_dir
d73fcb12 388 self.db_queue = db_queue
e23a5839 389 self.col = progress.col
882c8e47 390 self.parser = KconfigParser(args, self.build_dir)
5a27c734 391 self.state = STATE_IDLE
09c6c066 392 self.failed_boards = set()
a6ab4dbd 393 self.defconfig = None
9461bf0d 394 self.log = []
a6ab4dbd
SG
395 self.current_src_dir = None
396 self.proc = None
5a27c734
MY
397
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
f2dae751 403 because it is guaranteed the destructor is always invoked when the
5a27c734
MY
404 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:
f297ba33 409 while self.proc.poll() is None:
5a27c734
MY
410 pass
411 shutil.rmtree(self.build_dir)
412
c5e60fd4 413 def add(self, defconfig):
5a27c734
MY
414 """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
91197aa6 420 Args:
549d4223 421 defconfig (str): defconfig name.
5a27c734
MY
422
423 Returns:
424 Return True on success or False on failure
425 """
426 if self.state != STATE_IDLE:
427 return False
e307fa9d 428
5a27c734 429 self.defconfig = defconfig
9461bf0d 430 self.log = []
f432c33f 431 self.current_src_dir = self.reference_src_dir
e307fa9d 432 self.do_defconfig()
5a27c734
MY
433 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
7fb0bacd
MY
442 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.
5a27c734
MY
447
448 Returns:
449 Return True if the subprocess is terminated, False otherwise
450 """
451 if self.state == STATE_IDLE:
452 return True
453
f297ba33 454 if self.proc.poll() is None:
5a27c734
MY
455 return False
456
a4c9d178 457 if self.proc.poll() != 0:
e307fa9d
MY
458 self.handle_error()
459 elif self.state == STATE_DEFCONFIG:
f432c33f 460 if self.reference_src_dir and not self.current_src_dir:
6b96c1a1
JH
461 self.do_savedefconfig()
462 else:
463 self.do_autoconf()
e307fa9d 464 elif self.state == STATE_AUTOCONF:
f432c33f
MY
465 if self.current_src_dir:
466 self.current_src_dir = None
6b96c1a1 467 self.do_defconfig()
b2e83c63 468 elif self.args.build_db:
d73fcb12 469 self.do_build_db()
6b96c1a1
JH
470 else:
471 self.do_savedefconfig()
e307fa9d
MY
472 elif self.state == STATE_SAVEDEFCONFIG:
473 self.update_defconfig()
474 else:
daa694d3 475 sys.exit('Internal Error. This should not happen.')
5a27c734 476
f297ba33 477 return self.state == STATE_IDLE
96464bad 478
e307fa9d
MY
479 def handle_error(self):
480 """Handle error cases."""
8513dc04 481
15f19ab5
SG
482 self.log.append(self.col.build(self.col.RED, 'Failed to process',
483 bright=True))
b2e83c63 484 if self.args.verbose:
9461bf0d 485 for line in self.proc.stderr.read().decode().splitlines():
15f19ab5 486 self.log.append(self.col.build(self.col.CYAN, line, True))
e307fa9d 487 self.finish(False)
96464bad 488
e307fa9d
MY
489 def do_defconfig(self):
490 """Run 'make <board>_defconfig' to create the .config file."""
c8e1b10d 491
e307fa9d
MY
492 cmd = list(self.make_cmd)
493 cmd.append(self.defconfig)
f876e96f 494 # pylint: disable=R1732
a4c9d178
SG
495 self.proc = subprocess.Popen(cmd, stdout=self.devnull,
496 stderr=subprocess.PIPE,
497 cwd=self.current_src_dir)
e307fa9d 498 self.state = STATE_DEFCONFIG
c8e1b10d 499
e307fa9d 500 def do_autoconf(self):
f3b8e647 501 """Run 'make AUTO_CONF_PATH'."""
5a27c734 502
6821a745
SG
503 arch = self.parser.get_arch()
504 try:
f297ba33 505 tchain = self.toolchains.Select(arch)
6821a745 506 except ValueError:
15f19ab5
SG
507 self.log.append(self.col.build(
508 self.col.YELLOW,
509 f"Tool chain for '{arch}' is missing: do nothing"))
4efef998 510 self.finish(False)
e307fa9d 511 return
f297ba33 512 env = tchain.MakeEnvironment(False)
90ed6cba 513
5a27c734 514 cmd = list(self.make_cmd)
7740f653 515 cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
f3b8e647 516 cmd.append(AUTO_CONF_PATH)
f876e96f 517 # pylint: disable=R1732
a4c9d178
SG
518 self.proc = subprocess.Popen(cmd, stdout=self.devnull, env=env,
519 stderr=subprocess.PIPE,
520 cwd=self.current_src_dir)
5a27c734 521 self.state = STATE_AUTOCONF
e307fa9d 522
d73fcb12
SG
523 def do_build_db(self):
524 """Add the board to the database"""
525 configs = {}
37f815ca
SG
526 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()
d73fcb12
SG
530 self.db_queue.put([self.defconfig, configs])
531 self.finish(True)
532
e307fa9d
MY
533 def do_savedefconfig(self):
534 """Update the .config and run 'make savedefconfig'."""
c7345615 535 if not self.args.force_sync:
e307fa9d
MY
536 self.finish(True)
537 return
e307fa9d
MY
538
539 cmd = list(self.make_cmd)
540 cmd.append('savedefconfig')
f876e96f 541 # pylint: disable=R1732
a4c9d178
SG
542 self.proc = subprocess.Popen(cmd, stdout=self.devnull,
543 stderr=subprocess.PIPE)
e307fa9d
MY
544 self.state = STATE_SAVEDEFCONFIG
545
546 def update_defconfig(self):
547 """Update the input defconfig and go back to the idle state."""
e307fa9d
MY
548 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:
15f19ab5
SG
553 self.log.append(
554 self.col.build(self.col.BLUE, 'defconfig updated', bright=True))
e307fa9d 555
b2e83c63 556 if not self.args.dry_run and updated:
e307fa9d
MY
557 shutil.move(new_defconfig, orig_defconfig)
558 self.finish(True)
5a27c734 559
4efef998
MY
560 def finish(self, success):
561 """Display log along with progress and go to the idle state.
1d085568 562
91197aa6 563 Args:
549d4223 564 success (bool): Should be True when the defconfig was processed
4efef998 565 successfully, or False when it fails.
1d085568
MY
566 """
567 # output at least 30 characters to hide the "* defconfigs out of *".
dc1d2e6c 568 name = self.defconfig[:-len('_defconfig')]
5aba58cf 569 if self.log:
1d085568 570
9461bf0d
SG
571 # Put the first log line on the first line
572 log = name.ljust(20) + ' ' + self.log[0]
573
574 if len(self.log) > 1:
575 log += '\n' + '\n'.join([' ' + s for s in self.log[1:]])
5aba58cf
SG
576 # 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))
4efef998
MY
579
580 if not success:
b2e83c63 581 if self.args.exit_on_error:
daa694d3 582 sys.exit('Exit on error.')
4efef998
MY
583 # If --exit-on-error flag is not set, skip this board and continue.
584 # Record the failed board.
dc1d2e6c 585 self.failed_boards.add(name)
4efef998 586
6b25d21c 587 self.progress.inc(success)
1d085568 588 self.progress.show()
4efef998 589 self.state = STATE_IDLE
1d085568 590
5a27c734 591 def get_failed_boards(self):
09c6c066 592 """Returns a set of failed boards (defconfigs) in this slot.
5a27c734
MY
593 """
594 return self.failed_boards
595
596class Slots:
5a27c734
MY
597 """Controller of the array of subprocess slots."""
598
e23a5839 599 def __init__(self, toolchains, args, progress, reference_src_dir, db_queue):
5a27c734
MY
600 """Create a new slots controller.
601
91197aa6 602 Args:
15f19ab5
SG
603 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
5a27c734 609 """
b2e83c63 610 self.args = args
5a27c734 611 self.slots = []
dc1d2e6c 612 self.progress = progress
e23a5839 613 self.col = progress.col
478920dc 614 devnull = subprocess.DEVNULL
5a27c734 615 make_cmd = get_make_cmd()
62fae4bf 616 for _ in range(args.jobs):
882c8e47 617 self.slots.append(Slot(toolchains, args, progress, devnull,
e23a5839 618 make_cmd, reference_src_dir, db_queue))
5a27c734 619
c5e60fd4 620 def add(self, defconfig):
5a27c734
MY
621 """Add a new subprocess if a vacant slot is found.
622
91197aa6 623 Args:
549d4223 624 defconfig (str): defconfig name to be put into.
5a27c734
MY
625
626 Returns:
627 Return True on success or False on failure
628 """
629 for slot in self.slots:
c5e60fd4 630 if slot.add(defconfig):
5a27c734
MY
631 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
94e2ed7c 657 def write_failed_boards(self):
dc1d2e6c 658 """Show the results of processing"""
09c6c066 659 boards = set()
5a27c734
MY
660
661 for slot in self.slots:
09c6c066 662 boards |= slot.get_failed_boards()
96dccd97
MY
663
664 if boards:
dc1d2e6c 665 boards = '\n'.join(sorted(boards)) + '\n'
94e2ed7c
SG
666 write_file(FAILED_LIST, boards)
667
2559cd89 668
5cc42a51
MY
669class 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
91197aa6 676 Args:
5cc42a51
MY
677 commit: commit to git-clone
678 """
679 self.src_dir = tempfile.mkdtemp()
daa694d3 680 print('Cloning git repo to a separate work directory...')
5cc42a51
MY
681 subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
682 cwd=self.src_dir)
1bd43060
SG
683 rev = subprocess.check_output(['git', 'rev-parse', '--short',
684 commit]).strip()
685 print(f"Checkout '{rev}' to build the original autoconf.mk.")
5cc42a51
MY
686 subprocess.check_output(['git', 'checkout', commit],
687 stderr=subprocess.STDOUT, cwd=self.src_dir)
6b96c1a1
JH
688
689 def __del__(self):
5cc42a51 690 """Delete the reference source directory
6b96c1a1
JH
691
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 """
5cc42a51
MY
697 shutil.rmtree(self.src_dir)
698
699 def get_dir(self):
700 """Return the absolute path to the reference source directory."""
6b96c1a1 701
5cc42a51 702 return self.src_dir
6b96c1a1 703
948d0b45 704def move_config(args):
882c8e47 705 """Build database or sync config options to defconfig files.
5a27c734 706
91197aa6 707 Args:
15f19ab5 708 args (Namespace): Program arguments
94e2ed7c
SG
709
710 Returns:
eb1df3fd
SG
711 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
5a27c734 718 """
eb1df3fd
SG
719 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
b2e83c63
SG
733 if args.git_ref:
734 reference_src = ReferenceSource(args.git_ref)
5cc42a51
MY
735 reference_src_dir = reference_src.get_dir()
736 else:
f432c33f 737 reference_src_dir = None
6b96c1a1 738
b2e83c63
SG
739 if args.defconfigs:
740 defconfigs = get_matched_defconfigs(args.defconfigs)
91040e85 741 else:
684c306e 742 defconfigs = get_all_defconfigs()
5a27c734 743
948d0b45
SG
744 col = terminal.Color(terminal.COLOR_NEVER if args.nocolour
745 else terminal.COLOR_IF_TERMINAL)
6b25d21c 746 progress = Progress(col, len(defconfigs))
e23a5839 747 slots = Slots(toolchains, args, progress, reference_src_dir, db_queue)
5a27c734
MY
748
749 # Main loop to process defconfig files:
750 # Add a new subprocess into a vacant slot.
751 # Sleep if there is no available slot.
c5e60fd4
MY
752 for defconfig in defconfigs:
753 while not slots.add(defconfig):
5a27c734
MY
754 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
94e2ed7c 762 slots.write_failed_boards()
eb1df3fd 763 db_queue.join()
0229eeef 764 progress.completed()
eb1df3fd 765 return config_db, progress
5a27c734 766
cb008830
SG
767def find_kconfig_rules(kconf, config, imply_config):
768 """Check whether a config has a 'select' or 'imply' keyword
769
770 Args:
549d4223
SG
771 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')
cb008830
SG
775
776 Returns:
777 Symbol object for 'config' if found, else None
778 """
65e05ddc 779 sym = kconf.syms.get(imply_config)
cb008830 780 if sym:
62fae4bf 781 for sel, _ in (sym.selects + sym.implies):
a3627086 782 if sel.name == config:
cb008830
SG
783 return sym
784 return None
785
f876e96f 786def check_imply_rule(kconf, imply_config):
cb008830
SG
787 """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:
549d4223 793 kconf (Kconfiglib.Kconfig): Kconfig object
549d4223
SG
794 imply_config (str): Implying config (without CONFIG_ prefix) which may
795 or may not have an 'imply' for 'config')
cb008830
SG
796
797 Returns:
798 tuple:
549d4223
SG
799 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
cb008830 803 """
65e05ddc 804 sym = kconf.syms.get(imply_config)
cb008830
SG
805 if not sym:
806 return 'cannot find sym'
ea40b204
SG
807 nodes = sym.nodes
808 if len(nodes) != 1:
1bd43060 809 return f'{len(nodes)} locations'
a3627086
SG
810 node = nodes[0]
811 fname, linenum = node.filename, node.linenr
cb008830
SG
812 cwd = os.getcwd()
813 if cwd and fname.startswith(cwd):
814 fname = fname[len(cwd) + 1:]
1bd43060 815 file_line = f' at {fname}:{linenum}'
37f815ca 816 data = read_file(fname)
1bd43060
SG
817 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}'
cb008830
SG
820
821def add_imply_rule(config, fname, linenum):
822 """Add a new 'imply' option to a Kconfig
823
824 Args:
549d4223
SG
825 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
cb008830
SG
828
829 Returns:
830 Message indicating the result
831 """
1bd43060 832 file_line = f' at {fname}:{linenum}'
37f815ca 833 data = read_file(fname)
cb008830
SG
834 linenum -= 1
835
836 for offset, line in enumerate(data[linenum:]):
837 if line.strip().startswith('help') or not line:
1bd43060 838 data.insert(linenum + offset, f'\timply {config}')
2fd85bd3 839 write_file(fname, data)
1bd43060 840 return f'added{file_line}'
cb008830
SG
841
842 return 'could not insert%s'
843
844(IMPLY_MIN_2, IMPLY_TARGET, IMPLY_CMD, IMPLY_NON_ARCH_BOARD) = (
845 1, 2, 4, 8)
9b2a2e87
SG
846
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'],
cb008830
SG
851 'non-arch-board': [
852 IMPLY_NON_ARCH_BOARD,
853 'Allow Kconfig options outside arch/ and /board/ to imply'],
91197aa6 854}
9b2a2e87 855
9d603391
SG
856
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)
61d555d8 886 defconfig = None
37f815ca
SG
887 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
9d603391
SG
900
901 return all_configs, all_defconfigs, config_db, defconfig_db
902
903
cb008830
SG
904def do_imply_config(config_list, add_imply, imply_flags, skip_added,
905 check_kconfig=True, find_superset=False):
99b66605
SG
906 """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
ea4d6dea 914 This function uses the qconfig database to find such options. It
99b66605
SG
915 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
cc628f58
SG
928 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
9b2a2e87 932 (IMPLY_...)
cc628f58
SG
933 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
cb008830 935 'select' for the target config, and show this information if so.
cc628f58 936 find_superset (bool): True to look for configs which are a superset of those
99b66605
SG
937 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 """
0a0c1240 946 kconf = scan_kconfig() if check_kconfig else None
cb008830 947 if add_imply and add_imply != 'all':
a3627086 948 add_imply = add_imply.split(',')
cb008830 949
62fae4bf 950 all_configs, all_defconfigs, _, defconfig_db = read_database()
99b66605 951
a3627086 952 # Work through each target config option in turn, independently
99b66605
SG
953 for config in config_list:
954 defconfigs = defconfig_db.get(config)
955 if not defconfigs:
1bd43060 956 print(f'{config} not found in any defconfig')
99b66605
SG
957 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)
1bd43060 963 print(f'{config} found in {num_defconfigs}/{len(all_configs)} defconfigs')
99b66605
SG
964
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:
9b2a2e87 971 if 'ERRATUM' in imply_config:
99b66605 972 continue
91197aa6 973 if not imply_flags & IMPLY_CMD:
9b2a2e87
SG
974 if 'CONFIG_CMD' in imply_config:
975 continue
91197aa6 976 if not imply_flags & IMPLY_TARGET:
9b2a2e87
SG
977 if 'CONFIG_TARGET' in imply_config:
978 continue
99b66605
SG
979
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:
793dca34 1000 for prev in list(imply_configs.keys()):
99b66605
SG
1001 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
f297ba33 1009 if count > prev_count:
99b66605
SG
1010 # 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.
cb008830 1019 ranked_iconfigs = sorted(imply_configs,
99b66605 1020 key=lambda k: len(imply_configs[k]), reverse=True)
cb008830
SG
1021 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])
99b66605
SG
1026
1027 # Don't bother if there are less than 5 defconfigs affected.
9b2a2e87 1028 if num_common < (2 if imply_flags & IMPLY_MIN_2 else 5):
99b66605 1029 continue
cb008830 1030 missing = defconfigs - imply_configs[iconfig]
99b66605
SG
1031 missing_str = ', '.join(missing) if missing else 'all'
1032 missing_str = ''
cb008830
SG
1033 show = True
1034 if kconf:
1035 sym = find_kconfig_rules(kconf, config[CONFIG_LEN:],
1036 iconfig[CONFIG_LEN:])
1037 kconfig_info = ''
1038 if sym:
ea40b204
SG
1039 nodes = sym.nodes
1040 if len(nodes) == 1:
1041 fname, linenum = nodes[0].filename, nodes[0].linenr
cb008830
SG
1042 if cwd and fname.startswith(cwd):
1043 fname = fname[len(cwd) + 1:]
1bd43060 1044 kconfig_info = f'{fname}:{linenum}'
cb008830
SG
1045 if skip_added:
1046 show = False
1047 else:
65e05ddc 1048 sym = kconf.syms.get(iconfig[CONFIG_LEN:])
cb008830
SG
1049 fname = ''
1050 if sym:
ea40b204
SG
1051 nodes = sym.nodes
1052 if len(nodes) == 1:
1053 fname, linenum = nodes[0].filename, nodes[0].linenr
cb008830
SG
1054 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
91197aa6 1059 not imply_flags & IMPLY_NON_ARCH_BOARD):
cb008830
SG
1060 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,
f876e96f 1065 iconfig[CONFIG_LEN:]))
cb008830
SG
1066 if fname:
1067 add_list[fname].append(linenum)
1068
1069 if show and kconfig_info != 'skip':
0e03fb18
SG
1070 print(f'{num_common:5} : '
1071 f'{iconfig.ljust(30)}{kconfig_info.ljust(25)} {missing_str}')
cb008830
SG
1072
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.
793dca34 1078 for fname, linenums in add_list.items():
cb008830
SG
1079 for linenum in sorted(linenums, reverse=True):
1080 add_imply_rule(config[CONFIG_LEN:], fname, linenum)
99b66605 1081
7ff80ece 1082def defconfig_matches(configs, re_match, re_val):
941671a1
SG
1083 """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
7ff80ece 1092 re_val (re.Pattern): Regular expression to check against value (or None)
941671a1
SG
1093
1094 Returns:
1095 bool: True if any CONFIG matches the regex
1096 """
7ff80ece 1097 for cfg, val in configs.items():
d9c958f4 1098 if re_match.fullmatch(cfg):
7ff80ece
SG
1099 if not re_val or re_val.fullmatch(val):
1100 return True
941671a1 1101 return False
99b66605 1102
00b0855a 1103def do_find_config(config_list, list_format):
65d7fcec
SG
1104 """Find boards with a given combination of CONFIGs
1105
630a9c9a
SG
1106 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)
00b0855a
SG
1111 list_format (bool): True to write in 'list' format, one board name per
1112 line
630a9c9a
SG
1113
1114 Returns:
1115 int: exit code (0 for success)
65d7fcec 1116 """
62fae4bf 1117 _, all_defconfigs, config_db, _ = read_database()
65d7fcec 1118
65d7fcec
SG
1119 # Start with all defconfigs
1120 out = all_defconfigs
1121
1122 # Work through each config in turn
65d7fcec
SG
1123 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:]
7ff80ece
SG
1130 val = None
1131 re_val = None
1132 if '=' in cfg:
1133 cfg, val = cfg.split('=', maxsplit=1)
1134 re_val = re.compile(val)
65d7fcec 1135
65d7fcec
SG
1136 # 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()
941671a1 1141 re_match = re.compile(cfg)
65d7fcec 1142 for defc in in_list:
7ff80ece 1143 has_cfg = defconfig_matches(config_db[defc], re_match, re_val)
65d7fcec
SG
1144 if has_cfg == want:
1145 out.add(defc)
00b0855a
SG
1146 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))))
630a9c9a 1150 return 0
65d7fcec
SG
1151
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 """
a4c9d178 1169 oper = ''
65d7fcec 1170 if cfg[0] == '~':
a4c9d178 1171 oper = cfg[0]
65d7fcec
SG
1172 cfg = cfg[1:]
1173 if not cfg.startswith('CONFIG_'):
1174 cfg = 'CONFIG_' + cfg
a4c9d178 1175 return oper + cfg
65d7fcec
SG
1176
1177
0220a68c 1178RE_MK_CONFIGS = re.compile(r'CONFIG_(\$\(XPL_\)|\$\(PHASE_\))?([A-Za-z0-9_]*)')
98275712
SG
1179RE_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_]*)\)')
65e62037
SG
1182
1183class ConfigUse:
f876e96f 1184 """Tracks whether a config relates to SPL or not"""
65e62037 1185 def __init__(self, cfg, is_spl, fname, rest):
f876e96f
SG
1186 """Set up a new ConfigUse
1187
1188 Args:
0220a68c 1189 cfg (str): CONFIG option, without any CONFIG_ or xPL_ prefix
f876e96f
SG
1190 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 """
65e62037
SG
1194 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')
c46760d5
SG
1223 >>> RE_MK_CONFIGS.search('CONFIG_$(XPL_)MARY').groups()
1224 ('$(XPL_)', 'MARY')
5c10c8ba
SG
1225 >>> RE_MK_CONFIGS.search('CONFIG_$(PHASE_)MARY').groups()
1226 ('$(PHASE_)', 'MARY')
65e62037
SG
1227 """
1228 all_uses = collections.defaultdict(list)
1229 fname_uses = {}
1230 for fname, rest in fnames:
1231 m_iter = RE_MK_CONFIGS.finditer(rest)
a4c9d178
SG
1232 for mat in m_iter:
1233 real_opt = mat.group(2)
65e62037
SG
1234 if real_opt == '':
1235 continue
1236 is_spl = False
a4c9d178 1237 if mat.group(1):
65e62037
SG
1238 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 """
62fae4bf
SG
1273 fname = None
1274 rest = None
1275
65e62037 1276 def add_uses(m_iter, is_spl):
a4c9d178
SG
1277 for mat in m_iter:
1278 real_opt = mat.group(1)
65e62037
SG
1279 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
0220a68c 1322 an xPL_ option, but for which there is none;
65e62037 1323 for MOD_PROPER, look at source code which implies a Proper
c46760d5 1324 option (i.e. use of CONFIG_IS_ENABLED() or $(XPL_) or
5c10c8ba 1325 $(PHASE_) but for which there none;
65e62037
SG
1326 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)
62fae4bf 1335 for use, _ in all_uses.items():
65e62037
SG
1336 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
0220a68c 1344 # If it is an SPL symbol, try prepending all xPL_ prefixes to
65e62037
SG
1345 # find at least one SPL symbol
1346 if use.is_spl:
65e62037
SG
1347 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
0220a68c 1357 # the xPL_ prefix
65e62037
SG
1358 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
65e62037
SG
1364 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')
0a0c1240 1400 kconf = scan_kconfig()
65e62037
SG
1401 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:
62fae4bf 1404 out, _ = proc.communicate()
65e62037
SG
1405 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
62fae4bf 1438 all_uses, _ = scan_makefiles(mk_list)
65e62037
SG
1439
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)
b774ba52 1451 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
65e62037 1452
0220a68c 1453 print('\nCONFIG options used as Proper in Makefiles but without a non-xPL_ variant:')
65e62037
SG
1454 not_found = check_not_found(all_uses, MODE_PROPER)
1455 show_uses(not_found)
b774ba52 1456 proper_not_found |= {not_found.keys()}
65e62037
SG
1457
1458 # Scan the source code
62fae4bf 1459 all_uses, _ = scan_src_files(src_list)
65e62037
SG
1460
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)
b774ba52 1469 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
65e62037 1470
0220a68c 1471 print('\nCONFIG options used as Proper in source but without a non-xPL_ variant:')
65e62037
SG
1472 not_found = check_not_found(all_uses, MODE_PROPER)
1473 show_uses(not_found)
b774ba52 1474 proper_not_found |= {not_found.keys()}
65e62037 1475
0220a68c 1476 print('\nCONFIG options used as SPL but without an xPL_ variant:')
65e62037
SG
1477 for item in sorted(spl_not_found):
1478 print(f' {item}')
1479
0220a68c 1480 print('\nCONFIG options used as Proper but without a non-xPL_ variant:')
65e62037
SG
1481 for item in sorted(proper_not_found):
1482 print(f' {item}')
1483
1484 # Write out the updated information
1485 if do_update:
e6c686f4
SG
1486 with open(os.path.join(path, 'scripts', 'conf_nospl'), 'w',
1487 encoding='utf-8') as out:
65e62037
SG
1488 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)
e6c686f4
SG
1492 with open(os.path.join(path, 'scripts', 'conf_noproper'), 'w',
1493 encoding='utf-8') as out:
65e62037
SG
1494 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)
d63357ee 1498 return 0
65e62037
SG
1499
1500
a056c427
SG
1501def parse_args():
1502 """Parse the program arguments
1503
1504 Returns:
1505 tuple:
1506 argparse.ArgumentParser: parser
1507 argparse.Namespace: Parsed arguments
1508 """
5a27c734
MY
1509 try:
1510 cpu_count = multiprocessing.cpu_count()
1511 except NotImplementedError:
1512 cpu_count = 1
1513
b2e83c63
SG
1514 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='',
cb008830
SG
1520 help='comma-separated list of CONFIG options to add '
1521 "an 'imply' statement to for the CONFIG in -i")
b2e83c63 1522 parser.add_argument('-A', '--skip-added', action='store_true', default=False,
cb008830
SG
1523 help="don't show options which are already marked as "
1524 'implying others')
b2e83c63 1525 parser.add_argument('-b', '--build-db', action='store_true', default=False,
d73fcb12 1526 help='build a CONFIG database')
b2e83c63 1527 parser.add_argument('-C', '--commit', action='store_true', default=False,
9ede2123 1528 help='Create a git commit for the operation')
15f19ab5
SG
1529 parser.add_argument('--nocolour', action='store_true', default=False,
1530 help="don't display the log in colour")
b2e83c63 1531 parser.add_argument('-d', '--defconfigs', type=str,
ee4e61bd
SG
1532 help='a file containing a list of defconfigs to move, '
1533 "one per line (for example 'snow_defconfig') "
1534 "or '-' to read from stdin")
b2e83c63 1535 parser.add_argument('-e', '--exit-on-error', action='store_true',
5a27c734
MY
1536 default=False,
1537 help='exit immediately on any error')
b2e83c63 1538 parser.add_argument('-f', '--find', action='store_true', default=False,
65d7fcec 1539 help='Find boards with a given config combination')
b2e83c63 1540 parser.add_argument('-i', '--imply', action='store_true', default=False,
e1ae5632 1541 help='find options which imply others')
00b0855a
SG
1542 parser.add_argument('-l', '--list', action='store_true', default=False,
1543 help='Show a sorted list of board names, one per line')
b2e83c63 1544 parser.add_argument('-I', '--imply-flags', type=str, default='',
e1ae5632 1545 help="control the -i option ('help' for help")
b2e83c63 1546 parser.add_argument('-j', '--jobs', type=int, default=cpu_count,
5a27c734 1547 help='the number of jobs to run simultaneously')
b2e83c63 1548 parser.add_argument('-n', '--dry-run', action='store_true', default=False,
e1ae5632 1549 help='perform a trial run (show log with no changes)')
b2e83c63 1550 parser.add_argument('-r', '--git-ref', type=str,
6b96c1a1 1551 help='the git ref to clone for building the autoconf.mk')
b2e83c63 1552 parser.add_argument('-s', '--force-sync', action='store_true', default=False,
e1ae5632 1553 help='force sync by savedefconfig')
b2e83c63 1554 parser.add_argument('-S', '--spl', action='store_true', default=False,
e1ae5632 1555 help='parse config options defined for SPL build')
65e62037
SG
1556 parser.add_argument('--scan-source', action='store_true', default=False,
1557 help='scan source for uses of CONFIG options')
b2e83c63 1558 parser.add_argument('-t', '--test', action='store_true', default=False,
e1ae5632 1559 help='run unit tests')
b2e83c63 1560 parser.add_argument('-y', '--yes', action='store_true', default=False,
6b403dfd 1561 help="respond 'yes' to any prompts")
65e62037
SG
1562 parser.add_argument('-u', '--update', action='store_true', default=False,
1563 help="update scripts/ files (use with --scan-source)")
b2e83c63 1564 parser.add_argument('-v', '--verbose', action='store_true', default=False,
95bf9c7e 1565 help='show any build errors as boards are built')
b2e83c63 1566 parser.add_argument('configs', nargs='*')
5a27c734 1567
c57d4061
SG
1568 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
a056c427 1575
5a27c734 1576
cc628f58
SG
1577def 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():
4597acbd 1598 print(f' {name.ljust(15)}: {info[1]}')
cc628f58
SG
1599 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
6c2a4385
SG
1606def 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
948d0b45 1624def write_db(config_db, progress):
ab1bfd4b
SG
1625 """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
ab1bfd4b
SG
1633 progress (Progress): Progress indicator.
1634
1635 Returns:
1636 int: exit code (0 for success)
1637 """
948d0b45 1638 col = progress.col
ab1bfd4b
SG
1639 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
948d0b45 1651def move_done(progress):
ab1bfd4b
SG
1652 """Write a message indicating that the move is done
1653
1654 Args:
ab1bfd4b
SG
1655 progress (Progress): Progress indicator.
1656
1657 Returns:
1658 int: exit code (0 for success)
1659 """
948d0b45 1660 col = progress.col
ab1bfd4b
SG
1661 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
7e68804f
SG
1669def 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
a056c427
SG
1679def main():
1680 """Main program"""
1681 parser, args = parse_args()
c50b6f18
SG
1682 check_top_directory()
1683
fd35fbe2
SG
1684 # prefix the option name with CONFIG_ if missing
1685 args.configs = [prefix_config(cfg) for cfg in args.configs]
1686
b2e83c63 1687 if args.test:
7e68804f 1688 return do_tests()
65e62037 1689 if args.scan_source:
d63357ee 1690 return do_scan_source(os.getcwd(), args.update)
b2e83c63 1691 if args.imply:
cc628f58
SG
1692 if imply(args):
1693 parser.print_usage()
1694 sys.exit(1)
f297ba33 1695 return 0
b2e83c63 1696 if args.find:
00b0855a 1697 return do_find_config(args.configs, args.list)
65d7fcec 1698
948d0b45 1699 config_db, progress = move_config(args)
2144f880 1700
b2e83c63 1701 if args.commit:
6c2a4385 1702 add_commit(args.configs)
9ede2123 1703
b2e83c63 1704 if args.build_db:
948d0b45
SG
1705 return write_db(config_db, progress)
1706 return move_done(progress)
f297ba33 1707
d73fcb12 1708
5a27c734 1709if __name__ == '__main__':
65d7fcec 1710 sys.exit(main())
This page took 0.654871 seconds and 4 git commands to generate.