process_sdk_resources.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  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 copy
  15. from waflib import Node
  16. from resources.find_resource_filename import find_most_specific_filename
  17. from resources.types.resource_definition import ResourceDefinition
  18. from resources.types.resource_object import ResourceObject
  19. from resources.resource_map import resource_generator
  20. import resources.resource_map.resource_generator_bitmap
  21. import resources.resource_map.resource_generator_font
  22. import resources.resource_map.resource_generator_js
  23. import resources.resource_map.resource_generator_pbi
  24. import resources.resource_map.resource_generator_png
  25. import resources.resource_map.resource_generator_raw
  26. from sdk_helpers import is_sdk_2x, validate_resource_not_larger_than
  27. def _preprocess_resource_ids(bld, resources_list, has_published_media=False):
  28. """
  29. This method reads all of the defined resources for the project and assigns resource IDs to
  30. them prior to the start of resource processing. This preprocessing step is necessary in order
  31. for the timeline lookup table to contain accurate resource IDs, while still allowing us the
  32. prepend the TLUT as a resource in the resource ball.
  33. :param bld: the BuildContext object
  34. :param resources_list: the list of resources defined for this project
  35. :param has_published_media: boolean for whether publishedMedia exists for the project
  36. :return: None
  37. """
  38. resource_id_mapping = {}
  39. next_id = 1
  40. if has_published_media:
  41. # The timeline lookup table must be the first resource if one exists
  42. resource_id_mapping['TIMELINE_LUT'] = next_id
  43. next_id += 1
  44. for res_id, res in enumerate(resources_list, start=next_id):
  45. if isinstance(res, Node.Node):
  46. if res.name == 'timeline_resource_table.reso':
  47. continue
  48. res_name = ResourceObject.load(res.abspath()).definition.name
  49. resource_id_mapping[res_name] = res_id
  50. else:
  51. resource_id_mapping[res.name] = res_id
  52. bld.env.RESOURCE_ID_MAPPING = resource_id_mapping
  53. def generate_resources(bld, resource_source_path):
  54. """
  55. This method creates all of the task generators necessary to handle every possible resource
  56. allowed by the SDK.
  57. :param bld: the BuildContext object
  58. :param resource_source_path: the path from which to retrieve resource files
  59. :return: N/A
  60. """
  61. resources_json = getattr(bld.env, 'RESOURCES_JSON', [])
  62. published_media_json = getattr(bld.env, 'PUBLISHED_MEDIA_JSON', [])
  63. if resource_source_path:
  64. resources_node = bld.path.find_node(resource_source_path)
  65. else:
  66. resources_node = bld.path.find_node('resources')
  67. resource_file_mapping = {}
  68. for resource in resources_json:
  69. resource_file_mapping[resource['name']] = (
  70. find_most_specific_filename(bld, bld.env, resources_node, resource['file']))
  71. # Load the waftools that handle creating resource objects, a resource pack and the resource
  72. # ID header
  73. bld.load('generate_pbpack generate_resource_ball generate_resource_id_header')
  74. bld.load('process_timeline_resources')
  75. # Iterate over the resource definitions and do some processing to remove resources that
  76. # aren't relevant to the platform we're building for and to apply various backwards
  77. # compatibility adjustments
  78. resource_definitions = []
  79. max_menu_icon_dimensions = (25, 25)
  80. for r in resources_json:
  81. if 'menuIcon' in r and r['menuIcon']:
  82. res_file = (
  83. resources_node.find_node(find_most_specific_filename(bld, bld.env,
  84. resources_node,
  85. str(r['file'])))).abspath()
  86. if not validate_resource_not_larger_than(bld, res_file,
  87. dimensions=max_menu_icon_dimensions):
  88. bld.fatal("menuIcon resource '{}' exceeds the maximum allowed dimensions of {}".
  89. format(r['name'], max_menu_icon_dimensions))
  90. defs = resource_generator.definitions_from_dict(bld, r, resource_source_path)
  91. for d in defs:
  92. if not d.is_in_target_platform(bld):
  93. continue
  94. if d.type == 'png-trans':
  95. # SDK hack for SDK compatibility
  96. # One entry in the media list with the type png-trans actually represents two
  97. # resources, one for the black mask and one for the white mask. They each have
  98. # their own resource ids, so we need two entries in our definitions list.
  99. for suffix in ('WHITE', 'BLACK'):
  100. new_definition = copy.deepcopy(d)
  101. new_definition.name = '%s_%s' % (d.name, suffix)
  102. resource_definitions.append(new_definition)
  103. continue
  104. if d.type == 'png' and is_sdk_2x(bld.env.SDK_VERSION_MAJOR, bld.env.SDK_VERSION_MINOR):
  105. # We don't have png support in the 2.x sdk, instead process these into a pbi
  106. d.type = 'pbi'
  107. resource_definitions.append(d)
  108. bld_dir = bld.path.get_bld().make_node(bld.env.BUILD_DIR)
  109. lib_resources = []
  110. for lib in bld.env.LIB_JSON:
  111. # Skip resource handling if not a Pebble library or if no resources are specified
  112. if 'pebble' not in lib or 'resources' not in lib['pebble']:
  113. continue
  114. if 'media' not in lib['pebble']['resources'] or not lib['pebble']['resources']['media']:
  115. continue
  116. lib_path = bld.path.find_node(lib['path'])
  117. try:
  118. resources_path = lib_path.find_node('resources').find_node(bld.env.PLATFORM_NAME)
  119. except AttributeError:
  120. bld.fatal("Library {} is missing resources".format(lib['name']))
  121. else:
  122. if resources_path is None:
  123. bld.fatal("Library {} is missing resources for the {} platform".
  124. format(lib['name'], bld.env.PLATFORM_NAME))
  125. for lib_resource in bld.env.LIB_RESOURCES_JSON.get(lib['name'], []):
  126. # Skip resources that specify targetPlatforms other than this one
  127. if 'targetPlatforms' in lib_resource:
  128. if bld.env.PLATFORM_NAME not in lib_resource['targetPlatforms']:
  129. continue
  130. reso_file = '{}.{}.reso'.format(lib_resource['file'], lib_resource['name'])
  131. resource_node = resources_path.find_node(reso_file)
  132. if resource_node is None:
  133. bld.fatal("Library {} is missing the {} resource for the {} platform".
  134. format(lib['name'], lib_resource['name'], bld.env.PLATFORM_NAME))
  135. if lib_resource['name'] in resource_file_mapping:
  136. bld.fatal("Duplicate resource IDs are not permitted. Package resource {} uses the "
  137. "same resource ID as another resource already in this project.".
  138. format(lib_resource['name']))
  139. resource_file_mapping[lib_resource['name']] = resource_node
  140. lib_resources.append(resource_node)
  141. resources_list = []
  142. if resource_definitions:
  143. resources_list.extend(resource_definitions)
  144. if lib_resources:
  145. resources_list.extend(lib_resources)
  146. build_type = getattr(bld.env, 'BUILD_TYPE', 'app')
  147. resource_ball = bld_dir.make_node('system_resources.resball')
  148. # If this is a library, generate a resource ball containing only resources provided in this
  149. # project (not additional dependencies)
  150. project_resource_ball = None
  151. if build_type == 'lib':
  152. project_resource_ball = bld_dir.make_node('project_resources.resball')
  153. bld.env.PROJECT_RESBALL = project_resource_ball
  154. if published_media_json:
  155. # Only create TLUT for non-packages
  156. if build_type != 'lib':
  157. timeline_resource_table = bld_dir.make_node('timeline_resource_table.reso')
  158. resources_list.append(timeline_resource_table)
  159. _preprocess_resource_ids(bld, resources_list, True)
  160. bld(features='process_timeline_resources',
  161. published_media=published_media_json,
  162. timeline_reso=timeline_resource_table,
  163. layouts_json=bld_dir.make_node('layouts.json'),
  164. resource_mapping=resource_file_mapping,
  165. vars=['RESOURCE_ID_MAPPING', 'PUBLISHED_MEDIA_JSON'])
  166. # Create resource objects from a set of resource definitions and package them in a resource ball
  167. bld(features='generate_resource_ball',
  168. resources=resources_list,
  169. resource_ball=resource_ball,
  170. project_resource_ball=project_resource_ball,
  171. vars=['RESOURCES_JSON', 'LIB_RESOURCES_JSON', 'RESOURCE_ID_MAPPING'])
  172. # Create a resource ID header for use during the linking step of the build
  173. # FIXME PBL-36458: Since pebble.h requires this file through a #include, this file must be
  174. # present for every project, regardless of whether or not resources exist for the project. At
  175. # this time, this means the `generate_resource_id_header` task generator must run for every
  176. # project. Since the input of the `generate_resource_id_header` task generator is the
  177. # resource ball created by the `generate_resource_ball` task generator, the
  178. # `generate_resource_ball` task generator must also run for every project.
  179. resource_id_header = bld_dir.make_node('src/resource_ids.auto.h')
  180. bld.env.RESOURCE_ID_HEADER = resource_id_header.abspath()
  181. bld(features='generate_resource_id_header',
  182. resource_ball=resource_ball,
  183. resource_id_header_target=resource_id_header,
  184. use_extern=build_type == 'lib',
  185. use_define=build_type == 'app',
  186. published_media=published_media_json)
  187. resource_id_definitions = bld_dir.make_node('src/resource_ids.auto.c')
  188. bld.env.RESOURCE_ID_DEFINITIONS = resource_id_definitions.abspath()
  189. bld(features='generate_resource_id_definitions',
  190. resource_ball=resource_ball,
  191. resource_id_definitions_target=resource_id_definitions,
  192. published_media=published_media_json)
  193. if not bld.env.BUILD_TYPE or bld.env.BUILD_TYPE in ('app', 'rocky'):
  194. # Create a resource pack for distribution with an application binary
  195. pbpack = bld_dir.make_node('app_resources.pbpack')
  196. bld(features='generate_pbpack',
  197. resource_ball=resource_ball,
  198. pbpack_target=pbpack,
  199. is_system=False)