confgen.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. #!/usr/bin/env python
  2. #
  3. # Command line tool to take in ESP-IDF sdkconfig files with project
  4. # settings and output data in multiple formats (update config, generate
  5. # header file, generate .cmake include file, documentation, etc).
  6. #
  7. # Used internally by the ESP-IDF build system. But designed to be
  8. # non-IDF-specific.
  9. #
  10. # Copyright 2018-2020 Espressif Systems (Shanghai) PTE LTD
  11. #
  12. # Licensed under the Apache License, Version 2.0 (the "License");
  13. # you may not use this file except in compliance with the License.
  14. # You may obtain a copy of the License at
  15. #
  16. # http:#www.apache.org/licenses/LICENSE-2.0
  17. #
  18. # Unless required by applicable law or agreed to in writing, software
  19. # distributed under the License is distributed on an "AS IS" BASIS,
  20. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  21. # See the License for the specific language governing permissions and
  22. # limitations under the License.
  23. from __future__ import print_function
  24. import argparse
  25. import json
  26. import os
  27. import os.path
  28. import re
  29. import sys
  30. import tempfile
  31. from future.utils import iteritems
  32. import gen_kconfig_doc
  33. try:
  34. from . import kconfiglib
  35. except Exception:
  36. sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
  37. import kconfiglib
  38. __version__ = "0.1"
  39. if "IDF_CMAKE" not in os.environ:
  40. os.environ["IDF_CMAKE"] = ""
  41. class DeprecatedOptions(object):
  42. _REN_FILE = 'sdkconfig.rename'
  43. _DEP_OP_BEGIN = '# Deprecated options for backward compatibility'
  44. _DEP_OP_END = '# End of deprecated options'
  45. _RE_DEP_OP_BEGIN = re.compile(_DEP_OP_BEGIN)
  46. _RE_DEP_OP_END = re.compile(_DEP_OP_END)
  47. def __init__(self, config_prefix, path_rename_files=[]):
  48. self.config_prefix = config_prefix
  49. # r_dic maps deprecated options to new options; rev_r_dic maps in the opposite direction
  50. self.r_dic, self.rev_r_dic = self._parse_replacements(path_rename_files)
  51. # note the '=' at the end of regex for not getting partial match of configs
  52. self._RE_CONFIG = re.compile(r'{}(\w+)='.format(self.config_prefix))
  53. def _parse_replacements(self, repl_paths):
  54. rep_dic = {}
  55. rev_rep_dic = {}
  56. def remove_config_prefix(string):
  57. if string.startswith(self.config_prefix):
  58. return string[len(self.config_prefix):]
  59. raise RuntimeError('Error in {} (line {}): Config {} is not prefixed with {}'
  60. ''.format(rep_path, line_number, string, self.config_prefix))
  61. for rep_path in repl_paths:
  62. with open(rep_path) as f_rep:
  63. for line_number, line in enumerate(f_rep, start=1):
  64. sp_line = line.split()
  65. if len(sp_line) == 0 or sp_line[0].startswith('#'):
  66. # empty line or comment
  67. continue
  68. if len(sp_line) != 2 or not all(x.startswith(self.config_prefix) for x in sp_line):
  69. raise RuntimeError('Syntax error in {} (line {})'.format(rep_path, line_number))
  70. if sp_line[0] in rep_dic:
  71. raise RuntimeError('Error in {} (line {}): Replacement {} exist for {} and new '
  72. 'replacement {} is defined'.format(rep_path, line_number,
  73. rep_dic[sp_line[0]], sp_line[0],
  74. sp_line[1]))
  75. (dep_opt, new_opt) = (remove_config_prefix(x) for x in sp_line)
  76. rep_dic[dep_opt] = new_opt
  77. rev_rep_dic[new_opt] = dep_opt
  78. return rep_dic, rev_rep_dic
  79. def get_deprecated_option(self, new_option):
  80. return self.rev_r_dic.get(new_option, None)
  81. def get_new_option(self, deprecated_option):
  82. return self.r_dic.get(deprecated_option, None)
  83. def replace(self, sdkconfig_in, sdkconfig_out):
  84. replace_enabled = True
  85. with open(sdkconfig_in, 'r') as f_in, open(sdkconfig_out, 'w') as f_out:
  86. for line_num, line in enumerate(f_in, start=1):
  87. if self._RE_DEP_OP_BEGIN.search(line):
  88. replace_enabled = False
  89. elif self._RE_DEP_OP_END.search(line):
  90. replace_enabled = True
  91. elif replace_enabled:
  92. m = self._RE_CONFIG.search(line)
  93. if m and m.group(1) in self.r_dic:
  94. depr_opt = self.config_prefix + m.group(1)
  95. new_opt = self.config_prefix + self.r_dic[m.group(1)]
  96. line = line.replace(depr_opt, new_opt)
  97. print('{}:{} {} was replaced with {}'.format(sdkconfig_in, line_num, depr_opt, new_opt))
  98. f_out.write(line)
  99. def append_doc(self, config, visibility, path_output):
  100. def option_was_written(opt):
  101. # named choices were written if any of the symbols in the choice were visible
  102. if new_opt in config.named_choices:
  103. syms = config.named_choices[new_opt].syms
  104. for s in syms:
  105. if any(visibility.visible(node) for node in s.nodes):
  106. return True
  107. return False
  108. else:
  109. # otherwise if any of the nodes associated with the option was visible
  110. return any(visibility.visible(node) for node in config.syms[opt].nodes)
  111. if len(self.r_dic) > 0:
  112. with open(path_output, 'a') as f_o:
  113. header = 'Deprecated options and their replacements'
  114. f_o.write('.. _configuration-deprecated-options:\n\n{}\n{}\n\n'.format(header, '-' * len(header)))
  115. for dep_opt in sorted(self.r_dic):
  116. new_opt = self.r_dic[dep_opt]
  117. if option_was_written(new_opt) and (new_opt not in config.syms or config.syms[new_opt].choice is None):
  118. # everything except config for a choice (no link reference for those in the docs)
  119. f_o.write('- {}{} (:ref:`{}{}`)\n'.format(config.config_prefix, dep_opt,
  120. config.config_prefix, new_opt))
  121. if new_opt in config.named_choices:
  122. # here are printed config options which were filtered out
  123. syms = config.named_choices[new_opt].syms
  124. for sym in syms:
  125. if sym.name in self.rev_r_dic:
  126. # only if the symbol has been renamed
  127. dep_name = self.rev_r_dic[sym.name]
  128. # config options doesn't have references
  129. f_o.write(' - {}{}\n'.format(config.config_prefix, dep_name))
  130. def append_config(self, config, path_output):
  131. tmp_list = []
  132. def append_config_node_process(node):
  133. item = node.item
  134. if isinstance(item, kconfiglib.Symbol) and item.env_var is None:
  135. if item.name in self.rev_r_dic:
  136. c_string = item.config_string
  137. if c_string:
  138. tmp_list.append(c_string.replace(self.config_prefix + item.name,
  139. self.config_prefix + self.rev_r_dic[item.name]))
  140. for n in config.node_iter():
  141. append_config_node_process(n)
  142. if len(tmp_list) > 0:
  143. with open(path_output, 'a') as f_o:
  144. f_o.write('\n{}\n'.format(self._DEP_OP_BEGIN))
  145. f_o.writelines(tmp_list)
  146. f_o.write('{}\n'.format(self._DEP_OP_END))
  147. def append_header(self, config, path_output):
  148. def _opt_defined(opt):
  149. if not opt.visibility:
  150. return False
  151. return not (opt.orig_type in (kconfiglib.BOOL, kconfiglib.TRISTATE) and opt.str_value == "n")
  152. if len(self.r_dic) > 0:
  153. with open(path_output, 'a') as f_o:
  154. f_o.write('\n/* List of deprecated options */\n')
  155. for dep_opt in sorted(self.r_dic):
  156. new_opt = self.r_dic[dep_opt]
  157. if new_opt in config.syms and _opt_defined(config.syms[new_opt]):
  158. f_o.write('#define {}{} {}{}\n'.format(self.config_prefix, dep_opt, self.config_prefix, new_opt))
  159. def dict_enc_for_env(dic, encoding=sys.getfilesystemencoding() or 'utf-8'):
  160. """
  161. This function can be deleted after dropping support for Python 2.
  162. There is no rule for it that environment variables cannot be Unicode but usually people try to avoid it.
  163. The upstream kconfiglib cannot detect strings properly if the environment variables are "unicode". This is problem
  164. only in Python 2.
  165. """
  166. if sys.version_info[0] >= 3:
  167. return dic
  168. ret = dict()
  169. for (key, value) in iteritems(dic):
  170. ret[key.encode(encoding)] = value.encode(encoding)
  171. return ret
  172. def main():
  173. parser = argparse.ArgumentParser(description='confgen.py v%s - Config Generation Tool' % __version__, prog=os.path.basename(sys.argv[0]))
  174. parser.add_argument('--config',
  175. help='Project configuration settings',
  176. nargs='?',
  177. default=None)
  178. parser.add_argument('--defaults',
  179. help='Optional project defaults file, used if --config file doesn\'t exist. '
  180. 'Multiple files can be specified using multiple --defaults arguments.',
  181. nargs='?',
  182. default=[],
  183. action='append')
  184. parser.add_argument('--kconfig',
  185. help='KConfig file with config item definitions',
  186. required=True)
  187. parser.add_argument('--sdkconfig-rename',
  188. help='File with deprecated Kconfig options',
  189. required=False)
  190. parser.add_argument('--dont-write-deprecated',
  191. help='Do not write compatibility statements for deprecated values',
  192. action='store_true')
  193. parser.add_argument('--output', nargs=2, action='append',
  194. help='Write output file (format and output filename)',
  195. metavar=('FORMAT', 'FILENAME'),
  196. default=[])
  197. parser.add_argument('--env', action='append', default=[],
  198. help='Environment to set when evaluating the config file', metavar='NAME=VAL')
  199. parser.add_argument('--env-file', type=argparse.FileType('r'),
  200. help='Optional file to load environment variables from. Contents '
  201. 'should be a JSON object where each key/value pair is a variable.')
  202. args = parser.parse_args()
  203. for fmt, filename in args.output:
  204. if fmt not in OUTPUT_FORMATS.keys():
  205. print("Format '%s' not recognised. Known formats: %s" % (fmt, OUTPUT_FORMATS.keys()))
  206. sys.exit(1)
  207. try:
  208. args.env = [(name,value) for (name,value) in (e.split("=",1) for e in args.env)]
  209. except ValueError:
  210. print("--env arguments must each contain =. To unset an environment variable, use 'ENV='")
  211. sys.exit(1)
  212. for name, value in args.env:
  213. os.environ[name] = value
  214. if args.env_file is not None:
  215. env = json.load(args.env_file)
  216. os.environ.update(dict_enc_for_env(env))
  217. config = kconfiglib.Kconfig(args.kconfig)
  218. config.warn_assign_redun = False
  219. config.warn_assign_override = False
  220. sdkconfig_renames = [args.sdkconfig_rename] if args.sdkconfig_rename else []
  221. sdkconfig_renames += os.environ.get("COMPONENT_SDKCONFIG_RENAMES", "").split()
  222. deprecated_options = DeprecatedOptions(config.config_prefix, path_rename_files=sdkconfig_renames)
  223. if len(args.defaults) > 0:
  224. def _replace_empty_assignments(path_in, path_out):
  225. with open(path_in, 'r') as f_in, open(path_out, 'w') as f_out:
  226. for line_num, line in enumerate(f_in, start=1):
  227. line = line.strip()
  228. if line.endswith('='):
  229. line += 'n'
  230. print('{}:{} line was updated to {}'.format(path_out, line_num, line))
  231. f_out.write(line)
  232. f_out.write('\n')
  233. # always load defaults first, so any items which are not defined in that config
  234. # will have the default defined in the defaults file
  235. for name in args.defaults:
  236. print("Loading defaults file %s..." % name)
  237. if not os.path.exists(name):
  238. raise RuntimeError("Defaults file not found: %s" % name)
  239. try:
  240. with tempfile.NamedTemporaryFile(prefix="confgen_tmp", delete=False) as f:
  241. temp_file1 = f.name
  242. with tempfile.NamedTemporaryFile(prefix="confgen_tmp", delete=False) as f:
  243. temp_file2 = f.name
  244. deprecated_options.replace(sdkconfig_in=name, sdkconfig_out=temp_file1)
  245. _replace_empty_assignments(temp_file1, temp_file2)
  246. config.load_config(temp_file2, replace=False)
  247. finally:
  248. try:
  249. os.remove(temp_file1)
  250. os.remove(temp_file2)
  251. except OSError:
  252. pass
  253. # If config file previously exists, load it
  254. if args.config and os.path.exists(args.config):
  255. # ... but replace deprecated options before that
  256. with tempfile.NamedTemporaryFile(prefix="confgen_tmp", delete=False) as f:
  257. temp_file = f.name
  258. try:
  259. deprecated_options.replace(sdkconfig_in=args.config, sdkconfig_out=temp_file)
  260. config.load_config(temp_file, replace=False)
  261. update_if_changed(temp_file, args.config)
  262. finally:
  263. try:
  264. os.remove(temp_file)
  265. except OSError:
  266. pass
  267. if args.dont_write_deprecated:
  268. # The deprecated object was useful until now for replacements. Now it will be redefined with no configurations
  269. # and as the consequence, it won't generate output with deprecated statements.
  270. deprecated_options = DeprecatedOptions('', path_rename_files=[])
  271. # Output the files specified in the arguments
  272. for output_type, filename in args.output:
  273. with tempfile.NamedTemporaryFile(prefix="confgen_tmp", delete=False) as f:
  274. temp_file = f.name
  275. try:
  276. output_function = OUTPUT_FORMATS[output_type]
  277. output_function(deprecated_options, config, temp_file)
  278. update_if_changed(temp_file, filename)
  279. finally:
  280. try:
  281. os.remove(temp_file)
  282. except OSError:
  283. pass
  284. def write_config(deprecated_options, config, filename):
  285. CONFIG_HEADING = """#
  286. # Automatically generated file. DO NOT EDIT.
  287. # Espressif IoT Development Framework (ESP-IDF) Project Configuration
  288. #
  289. """
  290. config.write_config(filename, header=CONFIG_HEADING)
  291. deprecated_options.append_config(config, filename)
  292. def write_makefile(deprecated_options, config, filename):
  293. CONFIG_HEADING = """#
  294. # Automatically generated file. DO NOT EDIT.
  295. # Espressif IoT Development Framework (ESP-IDF) Project Makefile Configuration
  296. #
  297. """
  298. with open(filename, "w") as f:
  299. tmp_dep_lines = []
  300. f.write(CONFIG_HEADING)
  301. def get_makefile_config_string(name, value, orig_type):
  302. if orig_type in (kconfiglib.BOOL, kconfiglib.TRISTATE):
  303. value = '' if value == 'n' else value
  304. elif orig_type == kconfiglib.INT:
  305. try:
  306. value = int(value)
  307. except ValueError:
  308. value = ""
  309. elif orig_type == kconfiglib.HEX:
  310. try:
  311. value = hex(int(value, 16)) # ensure 0x prefix
  312. except ValueError:
  313. value = ""
  314. elif orig_type == kconfiglib.STRING:
  315. value = '"{}"'.format(kconfiglib.escape(value))
  316. else:
  317. raise RuntimeError('{}{}: unknown type {}'.format(config.config_prefix, name, orig_type))
  318. return '{}{}={}\n'.format(config.config_prefix, name, value)
  319. def write_makefile_node(node):
  320. item = node.item
  321. if isinstance(item, kconfiglib.Symbol) and item.env_var is None:
  322. # item.config_string cannot be used because it ignores hidden config items
  323. val = item.str_value
  324. f.write(get_makefile_config_string(item.name, val, item.orig_type))
  325. dep_opt = deprecated_options.get_deprecated_option(item.name)
  326. if dep_opt:
  327. # the same string but with the deprecated name
  328. tmp_dep_lines.append(get_makefile_config_string(dep_opt, val, item.orig_type))
  329. for n in config.node_iter(True):
  330. write_makefile_node(n)
  331. if len(tmp_dep_lines) > 0:
  332. f.write('\n# List of deprecated options\n')
  333. f.writelines(tmp_dep_lines)
  334. def write_header(deprecated_options, config, filename):
  335. CONFIG_HEADING = """/*
  336. * Automatically generated file. DO NOT EDIT.
  337. * Espressif IoT Development Framework (ESP-IDF) Configuration Header
  338. */
  339. #pragma once
  340. """
  341. config.write_autoconf(filename, header=CONFIG_HEADING)
  342. deprecated_options.append_header(config, filename)
  343. def write_cmake(deprecated_options, config, filename):
  344. with open(filename, "w") as f:
  345. tmp_dep_list = []
  346. write = f.write
  347. prefix = config.config_prefix
  348. write("""#
  349. # Automatically generated file. DO NOT EDIT.
  350. # Espressif IoT Development Framework (ESP-IDF) Configuration cmake include file
  351. #
  352. """)
  353. configs_list = list()
  354. def write_node(node):
  355. sym = node.item
  356. if not isinstance(sym, kconfiglib.Symbol):
  357. return
  358. if sym.config_string:
  359. val = sym.str_value
  360. if sym.orig_type in (kconfiglib.BOOL, kconfiglib.TRISTATE) and val == "n":
  361. val = "" # write unset values as empty variables
  362. elif sym.orig_type == kconfiglib.STRING:
  363. val = kconfiglib.escape(val)
  364. elif sym.orig_type == kconfiglib.HEX:
  365. val = hex(int(val, 16)) # ensure 0x prefix
  366. write('set({}{} "{}")\n'.format(prefix, sym.name, val))
  367. configs_list.append(prefix + sym.name)
  368. dep_opt = deprecated_options.get_deprecated_option(sym.name)
  369. if dep_opt:
  370. tmp_dep_list.append('set({}{} "{}")\n'.format(prefix, dep_opt, val))
  371. configs_list.append(prefix + dep_opt)
  372. for n in config.node_iter():
  373. write_node(n)
  374. write("set(CONFIGS_LIST {})".format(";".join(configs_list)))
  375. if len(tmp_dep_list) > 0:
  376. write('\n# List of deprecated options for backward compatibility\n')
  377. f.writelines(tmp_dep_list)
  378. def get_json_values(config):
  379. config_dict = {}
  380. def write_node(node):
  381. sym = node.item
  382. if not isinstance(sym, kconfiglib.Symbol):
  383. return
  384. if sym.config_string:
  385. val = sym.str_value
  386. if sym.type in [kconfiglib.BOOL, kconfiglib.TRISTATE]:
  387. val = (val != "n")
  388. elif sym.type == kconfiglib.HEX:
  389. val = int(val, 16)
  390. elif sym.type == kconfiglib.INT:
  391. val = int(val)
  392. config_dict[sym.name] = val
  393. for n in config.node_iter(False):
  394. write_node(n)
  395. return config_dict
  396. def write_json(deprecated_options, config, filename):
  397. config_dict = get_json_values(config)
  398. with open(filename, "w") as f:
  399. json.dump(config_dict, f, indent=4, sort_keys=True)
  400. def get_menu_node_id(node):
  401. """ Given a menu node, return a unique id
  402. which can be used to identify it in the menu structure
  403. Will either be the config symbol name, or a menu identifier
  404. 'slug'
  405. """
  406. try:
  407. if not isinstance(node.item, kconfiglib.Choice):
  408. return node.item.name
  409. except AttributeError:
  410. pass
  411. result = []
  412. while node.parent is not None:
  413. slug = re.sub(r'\W+', '-', node.prompt[0]).lower()
  414. result.append(slug)
  415. node = node.parent
  416. result = "-".join(reversed(result))
  417. return result
  418. def write_json_menus(deprecated_options, config, filename):
  419. existing_ids = set()
  420. result = [] # root level items
  421. node_lookup = {} # lookup from MenuNode to an item in result
  422. def write_node(node):
  423. try:
  424. json_parent = node_lookup[node.parent]["children"]
  425. except KeyError:
  426. assert node.parent not in node_lookup # if fails, we have a parent node with no "children" entity (ie a bug)
  427. json_parent = result # root level node
  428. # node.kconfig.y means node has no dependency,
  429. if node.dep is node.kconfig.y:
  430. depends = None
  431. else:
  432. depends = kconfiglib.expr_str(node.dep)
  433. try:
  434. # node.is_menuconfig is True in newer kconfiglibs for menus and choices as well
  435. is_menuconfig = node.is_menuconfig and isinstance(node.item, kconfiglib.Symbol)
  436. except AttributeError:
  437. is_menuconfig = False
  438. new_json = None
  439. if node.item == kconfiglib.MENU or is_menuconfig:
  440. new_json = {"type": "menu",
  441. "title": node.prompt[0],
  442. "depends_on": depends,
  443. "children": [],
  444. }
  445. if is_menuconfig:
  446. sym = node.item
  447. new_json["name"] = sym.name
  448. new_json["help"] = node.help
  449. new_json["is_menuconfig"] = is_menuconfig
  450. greatest_range = None
  451. if len(sym.ranges) > 0:
  452. # Note: Evaluating the condition using kconfiglib's expr_value
  453. # should have one condition which is true
  454. for min_range, max_range, cond_expr in sym.ranges:
  455. if kconfiglib.expr_value(cond_expr):
  456. greatest_range = [min_range, max_range]
  457. new_json["range"] = greatest_range
  458. elif isinstance(node.item, kconfiglib.Symbol):
  459. sym = node.item
  460. greatest_range = None
  461. if len(sym.ranges) > 0:
  462. # Note: Evaluating the condition using kconfiglib's expr_value
  463. # should have one condition which is true
  464. for min_range, max_range, cond_expr in sym.ranges:
  465. if kconfiglib.expr_value(cond_expr):
  466. base = 16 if sym.type == kconfiglib.HEX else 10
  467. greatest_range = [int(min_range.str_value, base), int(max_range.str_value, base)]
  468. break
  469. new_json = {
  470. "type": kconfiglib.TYPE_TO_STR[sym.type],
  471. "name": sym.name,
  472. "title": node.prompt[0] if node.prompt else None,
  473. "depends_on": depends,
  474. "help": node.help,
  475. "range": greatest_range,
  476. "children": [],
  477. }
  478. elif isinstance(node.item, kconfiglib.Choice):
  479. choice = node.item
  480. new_json = {
  481. "type": "choice",
  482. "title": node.prompt[0],
  483. "name": choice.name,
  484. "depends_on": depends,
  485. "help": node.help,
  486. "children": []
  487. }
  488. if new_json:
  489. node_id = get_menu_node_id(node)
  490. if node_id in existing_ids:
  491. raise RuntimeError("Config file contains two items with the same id: %s (%s). " +
  492. "Please rename one of these items to avoid ambiguity." % (node_id, node.prompt[0]))
  493. new_json["id"] = node_id
  494. json_parent.append(new_json)
  495. node_lookup[node] = new_json
  496. for n in config.node_iter():
  497. write_node(n)
  498. with open(filename, "w") as f:
  499. f.write(json.dumps(result, sort_keys=True, indent=4))
  500. def write_docs(deprecated_options, config, filename):
  501. try:
  502. target = os.environ['IDF_TARGET']
  503. except KeyError:
  504. print('IDF_TARGET environment variable must be defined!')
  505. sys.exit(1)
  506. visibility = gen_kconfig_doc.ConfigTargetVisibility(config, target)
  507. gen_kconfig_doc.write_docs(config, visibility, filename)
  508. deprecated_options.append_doc(config, visibility, filename)
  509. def update_if_changed(source, destination):
  510. with open(source, "r") as f:
  511. source_contents = f.read()
  512. if os.path.exists(destination):
  513. with open(destination, "r") as f:
  514. dest_contents = f.read()
  515. if source_contents == dest_contents:
  516. return # nothing to update
  517. with open(destination, "w") as f:
  518. f.write(source_contents)
  519. OUTPUT_FORMATS = {"config": write_config,
  520. "makefile": write_makefile, # only used with make in order to generate auto.conf
  521. "header": write_header,
  522. "cmake": write_cmake,
  523. "docs": write_docs,
  524. "json": write_json,
  525. "json_menus": write_json_menus,
  526. }
  527. class FatalError(RuntimeError):
  528. """
  529. Class for runtime errors (not caused by bugs but by user input).
  530. """
  531. pass
  532. if __name__ == '__main__':
  533. try:
  534. main()
  535. except FatalError as e:
  536. print("A fatal error occurred: %s" % e)
  537. sys.exit(2)