process_message_keys.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. # Copyright 2024 Google LLC
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import json
  15. from re import findall
  16. from waflib.TaskGen import before_method, feature
  17. from waflib import Logs, Task
  18. from sdk_helpers import get_node_from_abspath
  19. header = (
  20. """#pragma once
  21. #include <stdint.h>
  22. //
  23. // AUTOGENERATED BY BUILD
  24. // DO NOT MODIFY - CHANGES WILL BE OVERWRITTEN
  25. //
  26. """)
  27. definitions_file = (
  28. """
  29. #include <stdint.h>
  30. //
  31. // AUTOGENERATED BY BUILD
  32. // DO NOT MODIFY - CHANGES WILL BE OVERWRITTEN
  33. //
  34. """)
  35. def configure(conf):
  36. """
  37. Configure the build by collecting all of the project's appKeys, as well the appKeys of any
  38. dependencies, and writing them out to a header file and a JSON file for use in the project
  39. :param conf: the ConfigureContext
  40. :return: N/A
  41. """
  42. if conf.env.BUILD_TYPE != 'lib':
  43. if not dict(conf.env.PROJECT_INFO).get('enableMultiJS', False):
  44. Logs.pprint("CYAN",
  45. "WARNING: enableMultiJS is not enabled for this project. message_keys.json "
  46. "will not be included in your project unless you add it to your "
  47. "pebble-js-app.js file.")
  48. keys = conf.env.PROJECT_INFO.get('messageKeys', conf.env.PROJECT_INFO.get('appKeys', []))
  49. if conf.env.BUILD_TYPE == 'rocky':
  50. if keys:
  51. conf.fatal("Custom messageKeys are not supported for Rocky.js projects. Please "
  52. "remove any messageKeys listed in your package.json file.")
  53. else:
  54. keys = {
  55. "ControlKeyResetRequest": 1,
  56. "ControlKeyResetComplete": 2,
  57. "ControlKeyChunk": 3,
  58. "ControlKeyUnsupportedError": 4,
  59. }
  60. key_list = []
  61. key_dict = {}
  62. block_message_keys = []
  63. if keys:
  64. if isinstance(keys, list):
  65. key_list = keys
  66. elif isinstance(keys, dict):
  67. if conf.env.BUILD_TYPE == 'lib':
  68. conf.fatal("Libraries can only specify an array of messageKeys; other object types "
  69. "are not supported.")
  70. key_dict = keys
  71. else:
  72. conf.fatal("You have specified an invalid messageKeys object in your project JSON "
  73. "file.")
  74. combined_key_list = key_list + key_dict.keys()
  75. for lib in conf.env.LIB_JSON:
  76. if not 'pebble' in lib or not 'messageKeys' in lib['pebble']:
  77. continue
  78. lib_keys = lib['pebble']['messageKeys']
  79. if isinstance(lib_keys, list):
  80. for key in lib_keys:
  81. if key in combined_key_list:
  82. conf.fatal("The messageKey '{}' has already been used and cannot be re-used by "
  83. "the {} library.".format(key, lib['name']))
  84. combined_key_list.append(key)
  85. key_list.extend(lib_keys)
  86. else:
  87. conf.fatal("'{}' has an invalid messageKeys object. "
  88. "Libraries can only specify an messageKeys array.".format(lib['name']))
  89. if key_list:
  90. next_key = 10000
  91. multi_keys = [key for key in key_list if ']' in key]
  92. single_keys = [key for key in key_list if ']' not in key]
  93. for key in multi_keys:
  94. try:
  95. key_name, num_keys = findall(r"([\w]+)\[(\d+)\]$", key)[0]
  96. except IndexError:
  97. suggested_key_name = key.split('[', 1)[0]
  98. conf.fatal("An invalid message key of `{}` was specified. Verify that a valid "
  99. "length is specified if you are trying to allocate an array of keys "
  100. "with a single identifier. For example, try `{}[2]`.".
  101. format(key, suggested_key_name))
  102. else:
  103. key_dict.update({key_name: next_key})
  104. next_key += int(num_keys)
  105. block_message_keys.append(key_name)
  106. key_dict.update({value: key for key, value in enumerate(single_keys, start=next_key)})
  107. conf.env.PROJECT_INFO['messageKeys'] = key_dict
  108. conf.env.PROJECT_INFO['appKeys'] = key_dict # Support legacy appinfo.json generation
  109. conf.env.MESSAGE_KEYS = key_dict
  110. conf.env.BLOCK_MESSAGE_KEYS = block_message_keys
  111. bld_dir = conf.path.get_bld()
  112. conf.env.MESSAGE_KEYS_HEADER = bld_dir.make_node('include/message_keys.auto.h').abspath()
  113. if key_dict:
  114. conf.env.MESSAGE_KEYS_DEFINITION = bld_dir.make_node('src/message_keys.auto.c').abspath()
  115. conf.env.MESSAGE_KEYS_JSON = bld_dir.make_node('js/message_keys.json').abspath()
  116. @feature('message_keys')
  117. @before_method('cprogram', 'process_js', 'process_headers')
  118. def process_message_keys(task_gen):
  119. """
  120. Create the appropriate message key output files for the type of build, a header for a library,
  121. and a header + JSON file for a library
  122. :param task_gen: the task generator instance
  123. :return: None
  124. """
  125. message_keys = task_gen.env['MESSAGE_KEYS']
  126. bld = task_gen.bld
  127. # Create a header file that is included during lib/app builds
  128. header_task = (
  129. task_gen.create_task('message_key_header',
  130. tgt=get_node_from_abspath(task_gen.bld,
  131. getattr(task_gen.env,
  132. 'MESSAGE_KEYS_HEADER'))))
  133. header_task.message_keys = message_keys
  134. header_task.dep_vars = message_keys
  135. if bld.env.BUILD_TYPE == 'lib' or not message_keys:
  136. return
  137. # Create a C file to satisfy any extern header files
  138. definitions_task = (
  139. task_gen.create_task('message_key_definitions',
  140. tgt=get_node_from_abspath(task_gen.bld,
  141. getattr(task_gen.env,
  142. 'MESSAGE_KEYS_DEFINITION'))))
  143. definitions_task.message_keys = message_keys
  144. definitions_task.dep_vars = message_keys
  145. # Create a JSON file for apps to require
  146. bld.path.get_bld().make_node('js').mkdir()
  147. json_task = (
  148. task_gen.create_task('message_key_json',
  149. tgt=get_node_from_abspath(task_gen.bld,
  150. getattr(task_gen.env, 'MESSAGE_KEYS_JSON'))))
  151. json_task.message_keys = message_keys
  152. json_task.dep_vars = message_keys
  153. @Task.update_outputs
  154. class message_key_header(Task.Task):
  155. """
  156. Task class for creating a header file with the message key definitions for the project
  157. """
  158. def run(self):
  159. """
  160. This method executes when the message key header task runs
  161. :return: N/A
  162. """
  163. self.outputs[0].parent.mkdir()
  164. with open(self.outputs[0].abspath(), 'w') as f:
  165. f.write(header)
  166. for k, v in sorted(self.message_keys.items(), key=lambda x: x[0]):
  167. f.write("extern uint32_t MESSAGE_KEY_{};\n".format(k))
  168. @Task.update_outputs
  169. class message_key_definitions(Task.Task):
  170. """
  171. Task class for creating a C definitions file with the message key definitions for the project
  172. """
  173. def run(self):
  174. """
  175. This method executes when the message key definitions task runs
  176. :return: N/A
  177. """
  178. self.outputs[0].parent.mkdir()
  179. with open(self.outputs[0].abspath(), 'w') as f:
  180. f.write(definitions_file)
  181. for k, v in sorted(self.message_keys.items(), key=lambda x: x[0]):
  182. f.write("uint32_t MESSAGE_KEY_{} = {};\n".format(k, v))
  183. @Task.update_outputs
  184. class message_key_json(Task.Task):
  185. """
  186. Task class for creating a JSON file with the message key definitions for the project
  187. """
  188. def run(self):
  189. """
  190. This method executes when the message key header task runs
  191. :return: N/A
  192. """
  193. self.outputs[0].parent.mkdir()
  194. with open(self.outputs[0].abspath(), 'w') as f:
  195. json.dump(self.message_keys, f, sort_keys=True, indent=4, separators=(',', ': '))