# SPDX-License-Identifier: GPL-2.0+ # Copyright 2022 Google LLC # Written by Simon Glass # """Utility functions for dealing with Kconfig .confing files""" import re from u_boot_pylib import tools RE_LINE = re.compile(r'(# )?CONFIG_([A-Z0-9_]+)(=(.*)| is not set)') RE_CFG = re.compile(r'(~?)(CONFIG_)?([A-Z0-9_]+)(=.*)?') def make_cfg_line(opt, adj): """Make a new config line for an option Args: opt (str): Option to process, without CONFIG_ prefix adj (str): Adjustment to make (C is config option without prefix): C to enable C ~C to disable C C=val to set the value of C (val must have quotes if C is a string Kconfig) Returns: str: New line to use, one of: CONFIG_opt=y - option is enabled # CONFIG_opt is not set - option is disabled CONFIG_opt=val - option is getting a new value (val is in quotes if this is a string) """ if adj[0] == '~': return f'# CONFIG_{opt} is not set' if '=' in adj: return f'CONFIG_{adj}' return f'CONFIG_{opt}=y' def adjust_cfg_line(line, adjust_cfg, done=None): """Make an adjustment to a single of line from a .config file This processes a .config line, producing a new line if a change for this CONFIG is requested in adjust_cfg Args: line (str): line to process, e.g. '# CONFIG_FRED is not set' or 'CONFIG_FRED=y' or 'CONFIG_FRED=0x123' or 'CONFIG_FRED="fred"' adjust_cfg (dict of str): Changes to make to .config file before building: key: str config to change, without the CONFIG_ prefix, e.g. FRED value: str change to make (C is config option without prefix): C to enable C ~C to disable C C=val to set the value of C (val must have quotes if C is a string Kconfig) done (set of set): Adds the config option to this set if it is changed in some way. This is used to track which ones have been processed. None to skip. Returns: tuple: str: New string for this line (maybe unchanged) str: Adjustment string that was used """ out_line = line m_line = RE_LINE.match(line) adj = None if m_line: _, opt, _, _ = m_line.groups() adj = adjust_cfg.get(opt) if adj: out_line = make_cfg_line(opt, adj) if done is not None: done.add(opt) return out_line, adj def adjust_cfg_lines(lines, adjust_cfg): """Make adjustments to a list of lines from a .config file Args: lines (list of str): List of lines to process adjust_cfg (dict of str): Changes to make to .config file before building: key: str config to change, without the CONFIG_ prefix, e.g. FRED value: str change to make (C is config option without prefix): C to enable C ~C to disable C C=val to set the value of C (val must have quotes if C is a string Kconfig) Returns: list of str: New list of lines resulting from the processing """ out_lines = [] done = set() for line in lines: out_line, _ = adjust_cfg_line(line, adjust_cfg, done) out_lines.append(out_line) for opt in adjust_cfg: if opt not in done: adj = adjust_cfg.get(opt) out_line = make_cfg_line(opt, adj) out_lines.append(out_line) return out_lines def adjust_cfg_file(fname, adjust_cfg): """Make adjustments to a .config file Args: fname (str): Filename of .config file to change adjust_cfg (dict of str): Changes to make to .config file before building: key: str config to change, without the CONFIG_ prefix, e.g. FRED value: str change to make (C is config option without prefix): C to enable C ~C to disable C C=val to set the value of C (val must have quotes if C is a string Kconfig) """ lines = tools.read_file(fname, binary=False).splitlines() out_lines = adjust_cfg_lines(lines, adjust_cfg) out = '\n'.join(out_lines) + '\n' tools.write_file(fname, out, binary=False) def convert_list_to_dict(adjust_cfg_list): """Convert a list of config changes into the dict used by adjust_cfg_file() Args: adjust_cfg_list (list of str): List of changes to make to .config file before building. Each is one of (where C is the config option with or without the CONFIG_ prefix) C to enable C ~C to disable C C=val to set the value of C (val must have quotes if C is a string Kconfig Returns: dict of str: Changes to make to .config file before building: key: str config to change, without the CONFIG_ prefix, e.g. FRED value: str change to make (C is config option without prefix): C to enable C ~C to disable C C=val to set the value of C (val must have quotes if C is a string Kconfig) Raises: ValueError: if an item in adjust_cfg_list has invalid syntax """ result = {} for cfg in adjust_cfg_list or []: m_cfg = RE_CFG.match(cfg) if not m_cfg: raise ValueError(f"Invalid CONFIG adjustment '{cfg}'") negate, _, opt, val = m_cfg.groups() result[opt] = f'%s{opt}%s' % (negate or '', val or '') return result def check_cfg_lines(lines, adjust_cfg): """Check that lines do not conflict with the requested changes If a line enables a CONFIG which was requested to be disabled, etc., then this is an error. This function finds such errors. Args: lines (list of str): List of lines to process adjust_cfg (dict of str): Changes to make to .config file before building: key: str config to change, without the CONFIG_ prefix, e.g. FRED value: str change to make (C is config option without prefix): C to enable C ~C to disable C C=val to set the value of C (val must have quotes if C is a string Kconfig) Returns: list of tuple: list of errors, each a tuple: str: cfg adjustment requested str: line of the config that conflicts """ bad = [] done = set() for line in lines: out_line, adj = adjust_cfg_line(line, adjust_cfg, done) if out_line != line: bad.append([adj, line]) for opt in adjust_cfg: if opt not in done: adj = adjust_cfg.get(opt) out_line = make_cfg_line(opt, adj) bad.append([adj, f'Missing expected line: {out_line}']) return bad def check_cfg_file(fname, adjust_cfg): """Check that a config file has been adjusted according to adjust_cfg Args: fname (str): Filename of .config file to change adjust_cfg (dict of str): Changes to make to .config file before building: key: str config to change, without the CONFIG_ prefix, e.g. FRED value: str change to make (C is config option without prefix): C to enable C ~C to disable C C=val to set the value of C (val must have quotes if C is a string Kconfig) Returns: str: None if OK, else an error string listing the problems """ lines = tools.read_file(fname, binary=False).splitlines() bad_cfgs = check_cfg_lines(lines, adjust_cfg) if bad_cfgs: out = [f'{cfg:20} {line}' for cfg, line in bad_cfgs] content = '\\n'.join(out) return f''' Some CONFIG adjustments did not take effect. This may be because the request CONFIGs do not exist or conflict with others. Failed adjustments: {content} ''' return None