Merge tag 'v2022.04-rc5' into next
[platform/kernel/u-boot.git] / tools / buildman / cfgutil.py
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
8 import re
9
10 from patman import tools
11
12 RE_LINE = re.compile(r'(# )?CONFIG_([A-Z0-9_]+)(=(.*)| is not set)')
13 RE_CFG = re.compile(r'(~?)(CONFIG_)?([A-Z0-9_]+)(=.*)?')
14
15 def 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
39 def 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
79 def 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
111 def 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
131 def 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
166 def 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
204 def 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'''
228 Some CONFIG adjustments did not take effect. This may be because
229 the request CONFIGs do not exist or conflict with others.
230
231 Failed adjustments:
232
233 {content}
234 '''
235     return None