1# SPDX-License-Identifier: GPL-2.0+ 2# Copyright 2022 Google LLC 3# Written by Simon Glass <sjg@chromium.org> 4# 5 6"""Utility functions for dealing with Kconfig .confing files""" 7 8import re 9 10from u_boot_pylib import tools 11 12RE_LINE = re.compile(r'(# )?CONFIG_([A-Z0-9_]+)(=(.*)| is not set)') 13RE_CFG = re.compile(r'(~?)(CONFIG_)?([A-Z0-9_]+)(=.*)?') 14 15def make_cfg_line(opt, adj): 16 """Make a new config line for an option 17 18 Args: 19 opt (str): Option to process, without CONFIG_ prefix 20 adj (str): Adjustment to make (C is config option without prefix): 21 C to enable C 22 ~C to disable C 23 C=val to set the value of C (val must have quotes if C is 24 a string Kconfig) 25 26 Returns: 27 str: New line to use, one of: 28 CONFIG_opt=y - option is enabled 29 # CONFIG_opt is not set - option is disabled 30 CONFIG_opt=val - option is getting a new value (val is 31 in quotes if this is a string) 32 """ 33 if adj[0] == '~': 34 return f'# CONFIG_{opt} is not set' 35 if '=' in adj: 36 return f'CONFIG_{adj}' 37 return f'CONFIG_{opt}=y' 38 39def adjust_cfg_line(line, adjust_cfg, done=None): 40 """Make an adjustment to a single of line from a .config file 41 42 This processes a .config line, producing a new line if a change for this 43 CONFIG is requested in adjust_cfg 44 45 Args: 46 line (str): line to process, e.g. '# CONFIG_FRED is not set' or 47 'CONFIG_FRED=y' or 'CONFIG_FRED=0x123' or 'CONFIG_FRED="fred"' 48 adjust_cfg (dict of str): Changes to make to .config file before 49 building: 50 key: str config to change, without the CONFIG_ prefix, e.g. 51 FRED 52 value: str change to make (C is config option without prefix): 53 C to enable C 54 ~C to disable C 55 C=val to set the value of C (val must have quotes if C is 56 a string Kconfig) 57 done (set of set): Adds the config option to this set if it is changed 58 in some way. This is used to track which ones have been processed. 59 None to skip. 60 61 Returns: 62 tuple: 63 str: New string for this line (maybe unchanged) 64 str: Adjustment string that was used 65 """ 66 out_line = line 67 m_line = RE_LINE.match(line) 68 adj = None 69 if m_line: 70 _, opt, _, _ = m_line.groups() 71 adj = adjust_cfg.get(opt) 72 if adj: 73 out_line = make_cfg_line(opt, adj) 74 if done is not None: 75 done.add(opt) 76 77 return out_line, adj 78 79def adjust_cfg_lines(lines, adjust_cfg): 80 """Make adjustments to a list of lines from a .config file 81 82 Args: 83 lines (list of str): List of lines to process 84 adjust_cfg (dict of str): Changes to make to .config file before 85 building: 86 key: str config to change, without the CONFIG_ prefix, e.g. 87 FRED 88 value: str change to make (C is config option without prefix): 89 C to enable C 90 ~C to disable C 91 C=val to set the value of C (val must have quotes if C is 92 a string Kconfig) 93 94 Returns: 95 list of str: New list of lines resulting from the processing 96 """ 97 out_lines = [] 98 done = set() 99 for line in lines: 100 out_line, _ = adjust_cfg_line(line, adjust_cfg, done) 101 out_lines.append(out_line) 102 103 for opt in adjust_cfg: 104 if opt not in done: 105 adj = adjust_cfg.get(opt) 106 out_line = make_cfg_line(opt, adj) 107 out_lines.append(out_line) 108 109 return out_lines 110 111def adjust_cfg_file(fname, adjust_cfg): 112 """Make adjustments to a .config file 113 114 Args: 115 fname (str): Filename of .config file to change 116 adjust_cfg (dict of str): Changes to make to .config file before 117 building: 118 key: str config to change, without the CONFIG_ prefix, e.g. 119 FRED 120 value: str change to make (C is config option without prefix): 121 C to enable C 122 ~C to disable C 123 C=val to set the value of C (val must have quotes if C is 124 a string Kconfig) 125 """ 126 lines = tools.read_file(fname, binary=False).splitlines() 127 out_lines = adjust_cfg_lines(lines, adjust_cfg) 128 out = '\n'.join(out_lines) + '\n' 129 tools.write_file(fname, out, binary=False) 130 131def convert_list_to_dict(adjust_cfg_list): 132 """Convert a list of config changes into the dict used by adjust_cfg_file() 133 134 Args: 135 adjust_cfg_list (list of str): List of changes to make to .config file 136 before building. Each is one of (where C is the config option with 137 or without the CONFIG_ prefix) 138 139 C to enable C 140 ~C to disable C 141 C=val to set the value of C (val must have quotes if C is 142 a string Kconfig 143 144 Returns: 145 dict of str: Changes to make to .config file before building: 146 key: str config to change, without the CONFIG_ prefix, e.g. FRED 147 value: str change to make (C is config option without prefix): 148 C to enable C 149 ~C to disable C 150 C=val to set the value of C (val must have quotes if C is 151 a string Kconfig) 152 153 Raises: 154 ValueError: if an item in adjust_cfg_list has invalid syntax 155 """ 156 result = {} 157 for cfg in adjust_cfg_list or []: 158 m_cfg = RE_CFG.match(cfg) 159 if not m_cfg: 160 raise ValueError(f"Invalid CONFIG adjustment '{cfg}'") 161 negate, _, opt, val = m_cfg.groups() 162 result[opt] = f'%s{opt}%s' % (negate or '', val or '') 163 164 return result 165 166def check_cfg_lines(lines, adjust_cfg): 167 """Check that lines do not conflict with the requested changes 168 169 If a line enables a CONFIG which was requested to be disabled, etc., then 170 this is an error. This function finds such errors. 171 172 Args: 173 lines (list of str): List of lines to process 174 adjust_cfg (dict of str): Changes to make to .config file before 175 building: 176 key: str config to change, without the CONFIG_ prefix, e.g. 177 FRED 178 value: str change to make (C is config option without prefix): 179 C to enable C 180 ~C to disable C 181 C=val to set the value of C (val must have quotes if C is 182 a string Kconfig) 183 184 Returns: 185 list of tuple: list of errors, each a tuple: 186 str: cfg adjustment requested 187 str: line of the config that conflicts 188 """ 189 bad = [] 190 done = set() 191 for line in lines: 192 out_line, adj = adjust_cfg_line(line, adjust_cfg, done) 193 if out_line != line: 194 bad.append([adj, line]) 195 196 for opt in adjust_cfg: 197 if opt not in done: 198 adj = adjust_cfg.get(opt) 199 out_line = make_cfg_line(opt, adj) 200 bad.append([adj, f'Missing expected line: {out_line}']) 201 202 return bad 203 204def check_cfg_file(fname, adjust_cfg): 205 """Check that a config file has been adjusted according to adjust_cfg 206 207 Args: 208 fname (str): Filename of .config file to change 209 adjust_cfg (dict of str): Changes to make to .config file before 210 building: 211 key: str config to change, without the CONFIG_ prefix, e.g. 212 FRED 213 value: str change to make (C is config option without prefix): 214 C to enable C 215 ~C to disable C 216 C=val to set the value of C (val must have quotes if C is 217 a string Kconfig) 218 219 Returns: 220 str: None if OK, else an error string listing the problems 221 """ 222 lines = tools.read_file(fname, binary=False).splitlines() 223 bad_cfgs = check_cfg_lines(lines, adjust_cfg) 224 if bad_cfgs: 225 out = [f'{cfg:20} {line}' for cfg, line in bad_cfgs] 226 content = '\\n'.join(out) 227 return f''' 228Some CONFIG adjustments did not take effect. This may be because 229the request CONFIGs do not exist or conflict with others. 230 231Failed adjustments: 232 233{content} 234''' 235 return None 236