cfgutil.py 8.1 KB

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