]>
git.proxmox.com Git - mirror_ubuntu-kernels.git/blob - debian/scripts/misc/kconfig/annotations.py
3 # python module to manage Ubuntu kernel .config and annotations
4 # Copyright © 2022 Canonical Ltd.
10 from ast
import literal_eval
11 from os
.path
import dirname
, abspath
14 def __init__(self
, fname
: str, arch
: str = None, flavour
: str = None):
16 Basic configuration file object
19 raw_data
= self
._load
(fname
)
22 def _load(self
, fname
: str) -> str:
23 with
open(fname
, 'rt') as fd
:
28 """ Return a JSON representation of the config """
29 return json
.dumps(self
.config
, indent
=4)
31 class KConfig(Config
):
33 Parse a .config file, individual config options can be accessed via
34 .config[<CONFIG_OPTION>]
36 def _parse(self
, data
: str) -> dict:
38 for line
in data
.splitlines():
39 m
= re
.match(r
'^# (CONFIG_.*) is not set$', line
)
41 self
.config
[m
.group(1)] = literal_eval("'n'")
43 m
= re
.match(r
'^(CONFIG_[A-Za-z0-9_]+)=(.*)$', line
)
45 self
.config
[m
.group(1)] = literal_eval("'" + m
.group(2) + "'")
48 class Annotation(Config
):
50 Parse body of annotations file
52 def _parse_body(self
, data
: str):
54 data
= re
.sub(r
'(?m)^\s*#.*\n?', '', data
)
56 # Convert multiple spaces to single space to simplifly parsing
57 data
= re
.sub(r
' *', ' ', data
)
59 # Handle includes (recursively)
60 for line
in data
.splitlines():
61 m
= re
.match(r
'^include\s+"?([^"]*)"?', line
)
63 self
.include
.append(m
.group(1))
64 include_fname
= dirname(abspath(self
.fname
)) + '/' + m
.group(1)
65 include_data
= self
._load
(include_fname
)
66 self
._parse
_body
(include_data
)
68 # Skip empty, non-policy and non-note lines
69 if re
.match('.* policy<', line
) or re
.match('.* note<', line
):
71 # Parse single policy or note rule
72 conf
= line
.split(' ')[0]
73 if conf
in self
.config
:
74 entry
= self
.config
[conf
]
76 entry
= {'policy': {}}
77 m
= re
.match(r
'.*policy<(.*)>', line
)
79 entry
['policy'] |
= literal_eval(m
.group(1))
81 m
= re
.match(r
'.*note<(.*?)>', line
)
83 entry
['note'] = "'" + m
.group(1).replace("'", '') + "'"
85 raise Exception('syntax error')
86 self
.config
[conf
] = entry
87 except Exception as e
:
88 raise Exception(str(e
) + f
', line = {line}')
91 Parse main annotations file, individual config options can be accessed via
92 self.config[<CONFIG_OPTION>]
94 def _parse(self
, data
: str) -> dict:
102 # Parse header (only main header will considered, headers in includes
103 # will be treated as comments)
104 for line
in data
.splitlines():
105 if re
.match(r
'^#.*', line
):
106 m
= re
.match(r
'^# ARCH: (.*)', line
)
108 self
.arch
= list(m
.group(1).split(' '))
109 m
= re
.match(r
'^# FLAVOUR: (.*)', line
)
111 self
.flavour
= list(m
.group(1).split(' '))
112 m
= re
.match(r
'^# FLAVOUR_DEP: (.*)', line
)
114 self
.flavour_dep
= eval(m
.group(1))
115 self
.header
+= line
+ "\n"
119 # Parse body (handle includes recursively)
120 self
._parse
_body
(data
)
122 def _remove_entry(self
, config
: str):
123 if 'policy' in self
.config
[config
]:
124 del self
.config
[config
]['policy']
125 if 'note' in self
.config
[config
]:
126 del self
.config
[config
]['note']
127 if not self
.config
[config
]:
128 del self
.config
[config
]
130 def remove(self
, config
: str, arch
: str = None, flavour
: str = None):
131 if config
not in self
.config
:
134 if flavour
is not None:
135 flavour
= f
'{arch}-{flavour}'
138 del self
.config
[config
]['policy'][flavour
]
139 if not self
.config
[config
]['policy']:
140 self
._remove
_entry
(config
)
142 self
._remove
_entry
(config
)
144 def set(self
, config
: str, arch
: str = None, flavour
: str = None,
145 value
: str = None, note
: str = None):
146 if value
is not None:
147 if config
not in self
.config
:
148 self
.config
[config
] = { 'policy': {} }
150 if flavour
is not None:
151 flavour
= f
'{arch}-{flavour}'
154 self
.config
[config
]['policy'][flavour
] = value
156 for arch
in self
.arch
:
157 self
.config
[config
]['policy'][arch
] = value
159 self
.config
[config
]['note'] = "'" + note
.replace("'", '') + "'"
161 def update(self
, c
: KConfig
, arch
: str, flavour
: str = None, configs
: list = []):
162 """ Merge configs from a Kconfig object into Annotation object """
164 # Determine if we need to import all configs or a single config
166 configs
= c
.config
.keys()
167 configs |
= self
.search_config(arch
=arch
, flavour
=flavour
).keys()
169 # Import configs from the Kconfig object into Annotations
170 if flavour
is not None:
171 flavour
= arch
+ f
'-{flavour}'
179 if conf
in self
.config
:
180 if 'policy' in self
.config
[conf
]:
181 self
.config
[conf
]['policy'][flavour
] = val
183 self
.config
[conf
]['policy'] = {flavour
: val
}
185 self
.config
[conf
] = {'policy': {flavour
: val
}}
188 # Try to remove redundant settings: if the config value of a flavour is
189 # the same as the one of the main arch simply drop it.
190 for conf
in self
.config
.copy():
191 if 'policy' not in self
.config
[conf
]:
193 for flavour
in self
.flavour
:
194 if flavour
not in self
.config
[conf
]['policy']:
196 m
= re
.match(r
'^(.*?)-(.*)$', flavour
)
200 if arch
in self
.config
[conf
]['policy']:
201 if self
.config
[conf
]['policy'][flavour
] == self
.config
[conf
]['policy'][arch
]:
202 del self
.config
[conf
]['policy'][flavour
]
204 if flavour
not in self
.flavour_dep
:
206 generic
= self
.flavour_dep
[flavour
]
207 if generic
in self
.config
[conf
]['policy']:
208 if self
.config
[conf
]['policy'][flavour
] == self
.config
[conf
]['policy'][generic
]:
209 del self
.config
[conf
]['policy'][flavour
]
211 # Remove rules for flavours / arches that are not supported (not
212 # listed in the annotations header).
213 for flavour
in self
.config
[conf
]['policy'].copy():
214 if flavour
not in list(set(self
.arch
+ self
.flavour
)):
215 del self
.config
[conf
]['policy'][flavour
]
217 if not self
.config
[conf
]['policy']:
218 del self
.config
[conf
]
220 # Compact same value across all flavour within the same arch
221 for arch
in self
.arch
:
222 arch_flavours
= [i
for i
in self
.flavour
if i
.startswith(arch
)]
224 for flavour
in arch_flavours
:
225 if flavour
not in self
.config
[conf
]['policy']:
228 value
= self
.config
[conf
]['policy'][flavour
]
229 elif value
!= self
.config
[conf
]['policy'][flavour
]:
232 for flavour
in arch_flavours
:
233 del self
.config
[conf
]['policy'][flavour
]
234 self
.config
[conf
]['policy'][arch
] = value
236 def save(self
, fname
: str):
237 """ Save annotations data to the annotation file """
238 # Compact annotations structure
241 # Save annotations to disk
242 with tempfile
.NamedTemporaryFile(mode
='w+t', delete
=False) as tmp
:
244 tmp
.write(self
.header
+ '\n')
247 for i
in self
.include
:
248 tmp
.write(f
'include "{i}"\n')
252 # Write config annotations and notes
254 shutil
.copy(tmp
.name
, fname
)
255 tmp_a
= Annotation(fname
)
257 # Only save local differences (preserve includes)
258 for conf
in sorted(self
.config
):
259 old_val
= tmp_a
.config
[conf
] if conf
in tmp_a
.config
else None
260 new_val
= self
.config
[conf
]
261 # If new_val is a subset of old_val, skip it
262 if old_val
and 'policy' in old_val
and 'policy' in new_val
:
263 if old_val
['policy'] == old_val
['policy'] | new_val
['policy']:
265 if 'policy' in new_val
:
266 val
= dict(sorted(new_val
['policy'].items()))
267 line
= f
"{conf : <47} policy<{val}>"
268 tmp
.write(line
+ "\n")
269 if 'note' in new_val
:
270 val
= new_val
['note']
271 line
= f
"{conf : <47} note<{val}>"
272 tmp
.write(line
+ "\n\n")
274 # Replace annotations with the updated version
276 shutil
.move(tmp
.name
, fname
)
278 def search_config(self
, config
: str = None, arch
: str = None, flavour
: str = None) -> dict:
279 """ Return config value of a specific config option or architecture """
282 flavour
= f
'{arch}-{flavour}'
283 if flavour
in self
.flavour_dep
:
284 generic
= self
.flavour_dep
[flavour
]
287 if config
is None and arch
is None:
288 # Get all config options for all architectures
290 elif config
is None and arch
is not None:
291 # Get config options of a specific architecture
293 for c
in self
.config
:
294 if not 'policy' in self
.config
[c
]:
296 if flavour
in self
.config
[c
]['policy']:
297 ret
[c
] = self
.config
[c
]['policy'][flavour
]
298 elif generic
!= flavour
and generic
in self
.config
[c
]['policy']:
299 ret
[c
] = self
.config
[c
]['policy'][generic
]
300 elif arch
in self
.config
[c
]['policy']:
301 ret
[c
] = self
.config
[c
]['policy'][arch
]
303 elif config
is not None and arch
is None:
304 # Get a specific config option for all architectures
305 return self
.config
[config
] if config
in self
.config
else None
306 elif config
is not None and arch
is not None:
307 # Get a specific config option for a specific architecture
308 if config
in self
.config
:
309 if 'policy' in self
.config
[config
]:
310 if flavour
in self
.config
[config
]['policy']:
311 return {config
: self
.config
[config
]['policy'][flavour
]}
312 elif generic
!= flavour
and generic
in self
.config
[config
]['policy']:
313 return {config
: self
.config
[config
]['policy'][generic
]}
314 elif arch
in self
.config
[config
]['policy']:
315 return {config
: self
.config
[config
]['policy'][arch
]}
319 def to_config(data
: dict) -> str:
320 """ Convert annotations data to .config format """
325 s
+= f
"# {c} is not set\n"