confserver.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. #!/usr/bin/env python
  2. #
  3. # Long-running server process uses stdin & stdout to communicate JSON
  4. # with a caller
  5. #
  6. from __future__ import print_function
  7. import argparse
  8. import json
  9. import kconfiglib
  10. import os
  11. import sys
  12. import confgen
  13. from confgen import FatalError, __version__
  14. def main():
  15. parser = argparse.ArgumentParser(description='confserver.py v%s - Config Generation Tool' % __version__, prog=os.path.basename(sys.argv[0]))
  16. parser.add_argument('--config',
  17. help='Project configuration settings',
  18. required=True)
  19. parser.add_argument('--kconfig',
  20. help='KConfig file with config item definitions',
  21. required=True)
  22. parser.add_argument('--env', action='append', default=[],
  23. help='Environment to set when evaluating the config file', metavar='NAME=VAL')
  24. args = parser.parse_args()
  25. try:
  26. args.env = [(name,value) for (name,value) in (e.split("=",1) for e in args.env)]
  27. except ValueError:
  28. print("--env arguments must each contain =. To unset an environment variable, use 'ENV='")
  29. sys.exit(1)
  30. for name, value in args.env:
  31. os.environ[name] = value
  32. print("Server running, waiting for requests on stdin...", file=sys.stderr)
  33. run_server(args.kconfig, args.config)
  34. def run_server(kconfig, sdkconfig):
  35. config = kconfiglib.Kconfig(kconfig)
  36. config.load_config(sdkconfig)
  37. config_dict = confgen.get_json_values(config)
  38. ranges_dict = get_ranges(config)
  39. json.dump({"version": 1, "values": config_dict, "ranges": ranges_dict}, sys.stdout)
  40. print("\n")
  41. while True:
  42. line = sys.stdin.readline()
  43. if not line:
  44. break
  45. req = json.loads(line)
  46. before = confgen.get_json_values(config)
  47. before_ranges = get_ranges(config)
  48. if "load" in req: # if we're loading a different sdkconfig, response should have all items in it
  49. before = {}
  50. before_ranges = {}
  51. # if no new filename is supplied, use existing sdkconfig path, otherwise update the path
  52. if req["load"] is None:
  53. req["load"] = sdkconfig
  54. else:
  55. sdkconfig = req["load"]
  56. if "save" in req:
  57. if req["save"] is None:
  58. req["save"] = sdkconfig
  59. else:
  60. sdkconfig = req["save"]
  61. error = handle_request(config, req)
  62. after = confgen.get_json_values(config)
  63. after_ranges = get_ranges(config)
  64. values_diff = diff(before, after)
  65. ranges_diff = diff(before_ranges, after_ranges)
  66. response = {"version": 1, "values": values_diff, "ranges": ranges_diff}
  67. if error:
  68. for e in error:
  69. print("Error: %s" % e, file=sys.stderr)
  70. response["error"] = error
  71. json.dump(response, sys.stdout)
  72. print("\n")
  73. def handle_request(config, req):
  74. if "version" not in req:
  75. return ["All requests must have a 'version'"]
  76. if int(req["version"]) != 1:
  77. return ["Only version 1 requests supported"]
  78. error = []
  79. if "load" in req:
  80. print("Loading config from %s..." % req["load"], file=sys.stderr)
  81. try:
  82. config.load_config(req["load"])
  83. except Exception as e:
  84. error += ["Failed to load from %s: %s" % (req["load"], e)]
  85. if "set" in req:
  86. handle_set(config, error, req["set"])
  87. if "save" in req:
  88. try:
  89. print("Saving config to %s..." % req["save"], file=sys.stderr)
  90. confgen.write_config(config, req["save"])
  91. except Exception as e:
  92. error += ["Failed to save to %s: %s" % (req["save"], e)]
  93. return error
  94. def handle_set(config, error, to_set):
  95. missing = [k for k in to_set if k not in config.syms]
  96. if missing:
  97. error.append("The following config symbol(s) were not found: %s" % (", ".join(missing)))
  98. # replace name keys with the full config symbol for each key:
  99. to_set = dict((config.syms[k],v) for (k,v) in to_set.items() if k not in missing)
  100. # Work through the list of values to set, noting that
  101. # some may not be immediately applicable (maybe they depend
  102. # on another value which is being set). Therefore, defer
  103. # knowing if any value is unsettable until then end
  104. while len(to_set):
  105. set_pass = [(k,v) for (k,v) in to_set.items() if k.visibility]
  106. if not set_pass:
  107. break # no visible keys left
  108. for (sym,val) in set_pass:
  109. if sym.type in (kconfiglib.BOOL, kconfiglib.TRISTATE):
  110. if val is True:
  111. sym.set_value(2)
  112. elif val is False:
  113. sym.set_value(0)
  114. else:
  115. error.append("Boolean symbol %s only accepts true/false values" % sym.name)
  116. else:
  117. sym.set_value(str(val))
  118. print("Set %s" % sym.name)
  119. del to_set[sym]
  120. if len(to_set):
  121. error.append("The following config symbol(s) were not visible so were not updated: %s" % (", ".join(s.name for s in to_set)))
  122. def diff(before, after):
  123. """
  124. Return a dictionary with the difference between 'before' and 'after' (either with the new value if changed,
  125. or None as the value if a key in 'before' is missing in 'after'
  126. """
  127. diff = dict((k,v) for (k,v) in after.items() if before.get(k, None) != v)
  128. hidden = dict((k,None) for k in before if k not in after)
  129. diff.update(hidden)
  130. return diff
  131. def get_ranges(config):
  132. ranges_dict = {}
  133. def handle_node(node):
  134. sym = node.item
  135. if not isinstance(sym, kconfiglib.Symbol):
  136. return
  137. active_range = sym.active_range
  138. if active_range[0] is not None:
  139. ranges_dict[sym.name] = active_range
  140. config.walk_menu(handle_node)
  141. return ranges_dict
  142. if __name__ == '__main__':
  143. try:
  144. main()
  145. except FatalError as e:
  146. print("A fatal error occurred: %s" % e, file=sys.stderr)
  147. sys.exit(2)