1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright 2022 Google LLC
3 # Written by Simon Glass <sjg@chromium.org>
6 """Utility functions for dealing with Kconfig .confing files"""
10 from patman import tools
12 RE_LINE = re.compile(r'(# )?CONFIG_([A-Z0-9_]+)(=(.*)| is not set)')
13 RE_CFG = re.compile(r'(~?)(CONFIG_)?([A-Z0-9_]+)(=.*)?')
15 def make_cfg_line(opt, adj):
16 """Make a new config line for an option
19 opt (str): Option to process, without CONFIG_ prefix
20 adj (str): Adjustment to make (C is config option without prefix):
23 C=val to set the value of C (val must have quotes if C is
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)
34 return f'# CONFIG_{opt} is not set'
36 return f'CONFIG_{adj}'
37 return f'CONFIG_{opt}=y'
39 def adjust_cfg_line(line, adjust_cfg, done=None):
40 """Make an adjustment to a single of line from a .config file
42 This processes a .config line, producing a new line if a change for this
43 CONFIG is requested in adjust_cfg
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
50 key: str config to change, without the CONFIG_ prefix, e.g.
52 value: str change to make (C is config option without prefix):
55 C=val to set the value of C (val must have quotes if C is
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.
63 str: New string for this line (maybe unchanged)
64 str: Adjustment string that was used
67 m_line = RE_LINE.match(line)
70 _, opt, _, _ = m_line.groups()
71 adj = adjust_cfg.get(opt)
73 out_line = make_cfg_line(opt, adj)
79 def adjust_cfg_lines(lines, adjust_cfg):
80 """Make adjustments to a list of lines from a .config file
83 lines (list of str): List of lines to process
84 adjust_cfg (dict of str): Changes to make to .config file before
86 key: str config to change, without the CONFIG_ prefix, e.g.
88 value: str change to make (C is config option without prefix):
91 C=val to set the value of C (val must have quotes if C is
95 list of str: New list of lines resulting from the processing
100 out_line, _ = adjust_cfg_line(line, adjust_cfg, done)
101 out_lines.append(out_line)
103 for opt in adjust_cfg:
105 adj = adjust_cfg.get(opt)
106 out_line = make_cfg_line(opt, adj)
107 out_lines.append(out_line)
111 def adjust_cfg_file(fname, adjust_cfg):
112 """Make adjustments to a .config file
115 fname (str): Filename of .config file to change
116 adjust_cfg (dict of str): Changes to make to .config file before
118 key: str config to change, without the CONFIG_ prefix, e.g.
120 value: str change to make (C is config option without prefix):
123 C=val to set the value of C (val must have quotes if C is
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)
131 def convert_list_to_dict(adjust_cfg_list):
132 """Convert a list of config changes into the dict used by adjust_cfg_file()
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)
141 C=val to set the value of C (val must have quotes if C is
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):
150 C=val to set the value of C (val must have quotes if C is
154 ValueError: if an item in adjust_cfg_list has invalid syntax
157 for cfg in adjust_cfg_list or []:
158 m_cfg = RE_CFG.match(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 '')
166 def check_cfg_lines(lines, adjust_cfg):
167 """Check that lines do not conflict with the requested changes
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.
173 lines (list of str): List of lines to process
174 adjust_cfg (dict of str): Changes to make to .config file before
176 key: str config to change, without the CONFIG_ prefix, e.g.
178 value: str change to make (C is config option without prefix):
181 C=val to set the value of C (val must have quotes if C is
185 list of tuple: list of errors, each a tuple:
186 str: cfg adjustment requested
187 str: line of the config that conflicts
192 out_line, adj = adjust_cfg_line(line, adjust_cfg, done)
194 bad.append([adj, line])
196 for opt in adjust_cfg:
198 adj = adjust_cfg.get(opt)
199 out_line = make_cfg_line(opt, adj)
200 bad.append([adj, f'Missing expected line: {out_line}'])
204 def check_cfg_file(fname, adjust_cfg):
205 """Check that a config file has been adjusted according to adjust_cfg
208 fname (str): Filename of .config file to change
209 adjust_cfg (dict of str): Changes to make to .config file before
211 key: str config to change, without the CONFIG_ prefix, e.g.
213 value: str change to make (C is config option without prefix):
216 C=val to set the value of C (val must have quotes if C is
220 str: None if OK, else an error string listing the problems
222 lines = tools.read_file(fname, binary=False).splitlines()
223 bad_cfgs = check_cfg_lines(lines, adjust_cfg)
225 out = [f'{cfg:20} {line}' for cfg, line in bad_cfgs]
226 content = '\\n'.join(out)
228 Some CONFIG adjustments did not take effect. This may be because
229 the request CONFIGs do not exist or conflict with others.