blob: 4eba50868f5a5956c48158bb87957ed71bf8bfb9 [file] [log] [blame]
Simon Glass19133b72022-01-22 05:07:31 -07001# 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 patman 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.ReadFile(fname, binary=False).splitlines()
127 out_lines = adjust_cfg_lines(lines, adjust_cfg)
128 out = '\n'.join(out_lines) + '\n'
129 tools.WriteFile(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.ReadFile(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