# Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import copy from waflib import Node from resources.find_resource_filename import find_most_specific_filename from resources.types.resource_definition import ResourceDefinition from resources.types.resource_object import ResourceObject from resources.resource_map import resource_generator import resources.resource_map.resource_generator_bitmap import resources.resource_map.resource_generator_font import resources.resource_map.resource_generator_js import resources.resource_map.resource_generator_pbi import resources.resource_map.resource_generator_png import resources.resource_map.resource_generator_raw from sdk_helpers import is_sdk_2x, validate_resource_not_larger_than def _preprocess_resource_ids(bld, resources_list, has_published_media=False): """ This method reads all of the defined resources for the project and assigns resource IDs to them prior to the start of resource processing. This preprocessing step is necessary in order for the timeline lookup table to contain accurate resource IDs, while still allowing us the prepend the TLUT as a resource in the resource ball. :param bld: the BuildContext object :param resources_list: the list of resources defined for this project :param has_published_media: boolean for whether publishedMedia exists for the project :return: None """ resource_id_mapping = {} next_id = 1 if has_published_media: # The timeline lookup table must be the first resource if one exists resource_id_mapping['TIMELINE_LUT'] = next_id next_id += 1 for res_id, res in enumerate(resources_list, start=next_id): if isinstance(res, Node.Node): if res.name == 'timeline_resource_table.reso': continue res_name = ResourceObject.load(res.abspath()).definition.name resource_id_mapping[res_name] = res_id else: resource_id_mapping[res.name] = res_id bld.env.RESOURCE_ID_MAPPING = resource_id_mapping def generate_resources(bld, resource_source_path): """ This method creates all of the task generators necessary to handle every possible resource allowed by the SDK. :param bld: the BuildContext object :param resource_source_path: the path from which to retrieve resource files :return: N/A """ resources_json = getattr(bld.env, 'RESOURCES_JSON', []) published_media_json = getattr(bld.env, 'PUBLISHED_MEDIA_JSON', []) if resource_source_path: resources_node = bld.path.find_node(resource_source_path) else: resources_node = bld.path.find_node('resources') resource_file_mapping = {} for resource in resources_json: resource_file_mapping[resource['name']] = ( find_most_specific_filename(bld, bld.env, resources_node, resource['file'])) # Load the waftools that handle creating resource objects, a resource pack and the resource # ID header bld.load('generate_pbpack generate_resource_ball generate_resource_id_header') bld.load('process_timeline_resources') # Iterate over the resource definitions and do some processing to remove resources that # aren't relevant to the platform we're building for and to apply various backwards # compatibility adjustments resource_definitions = [] max_menu_icon_dimensions = (25, 25) for r in resources_json: if 'menuIcon' in r and r['menuIcon']: res_file = ( resources_node.find_node(find_most_specific_filename(bld, bld.env, resources_node, str(r['file'])))).abspath() if not validate_resource_not_larger_than(bld, res_file, dimensions=max_menu_icon_dimensions): bld.fatal("menuIcon resource '{}' exceeds the maximum allowed dimensions of {}". format(r['name'], max_menu_icon_dimensions)) defs = resource_generator.definitions_from_dict(bld, r, resource_source_path) for d in defs: if not d.is_in_target_platform(bld): continue if d.type == 'png-trans': # SDK hack for SDK compatibility # One entry in the media list with the type png-trans actually represents two # resources, one for the black mask and one for the white mask. They each have # their own resource ids, so we need two entries in our definitions list. for suffix in ('WHITE', 'BLACK'): new_definition = copy.deepcopy(d) new_definition.name = '%s_%s' % (d.name, suffix) resource_definitions.append(new_definition) continue if d.type == 'png' and is_sdk_2x(bld.env.SDK_VERSION_MAJOR, bld.env.SDK_VERSION_MINOR): # We don't have png support in the 2.x sdk, instead process these into a pbi d.type = 'pbi' resource_definitions.append(d) bld_dir = bld.path.get_bld().make_node(bld.env.BUILD_DIR) lib_resources = [] for lib in bld.env.LIB_JSON: # Skip resource handling if not a Pebble library or if no resources are specified if 'pebble' not in lib or 'resources' not in lib['pebble']: continue if 'media' not in lib['pebble']['resources'] or not lib['pebble']['resources']['media']: continue lib_path = bld.path.find_node(lib['path']) try: resources_path = lib_path.find_node('resources').find_node(bld.env.PLATFORM_NAME) except AttributeError: bld.fatal("Library {} is missing resources".format(lib['name'])) else: if resources_path is None: bld.fatal("Library {} is missing resources for the {} platform". format(lib['name'], bld.env.PLATFORM_NAME)) for lib_resource in bld.env.LIB_RESOURCES_JSON.get(lib['name'], []): # Skip resources that specify targetPlatforms other than this one if 'targetPlatforms' in lib_resource: if bld.env.PLATFORM_NAME not in lib_resource['targetPlatforms']: continue reso_file = '{}.{}.reso'.format(lib_resource['file'], lib_resource['name']) resource_node = resources_path.find_node(reso_file) if resource_node is None: bld.fatal("Library {} is missing the {} resource for the {} platform". format(lib['name'], lib_resource['name'], bld.env.PLATFORM_NAME)) if lib_resource['name'] in resource_file_mapping: bld.fatal("Duplicate resource IDs are not permitted. Package resource {} uses the " "same resource ID as another resource already in this project.". format(lib_resource['name'])) resource_file_mapping[lib_resource['name']] = resource_node lib_resources.append(resource_node) resources_list = [] if resource_definitions: resources_list.extend(resource_definitions) if lib_resources: resources_list.extend(lib_resources) build_type = getattr(bld.env, 'BUILD_TYPE', 'app') resource_ball = bld_dir.make_node('system_resources.resball') # If this is a library, generate a resource ball containing only resources provided in this # project (not additional dependencies) project_resource_ball = None if build_type == 'lib': project_resource_ball = bld_dir.make_node('project_resources.resball') bld.env.PROJECT_RESBALL = project_resource_ball if published_media_json: # Only create TLUT for non-packages if build_type != 'lib': timeline_resource_table = bld_dir.make_node('timeline_resource_table.reso') resources_list.append(timeline_resource_table) _preprocess_resource_ids(bld, resources_list, True) bld(features='process_timeline_resources', published_media=published_media_json, timeline_reso=timeline_resource_table, layouts_json=bld_dir.make_node('layouts.json'), resource_mapping=resource_file_mapping, vars=['RESOURCE_ID_MAPPING', 'PUBLISHED_MEDIA_JSON']) # Create resource objects from a set of resource definitions and package them in a resource ball bld(features='generate_resource_ball', resources=resources_list, resource_ball=resource_ball, project_resource_ball=project_resource_ball, vars=['RESOURCES_JSON', 'LIB_RESOURCES_JSON', 'RESOURCE_ID_MAPPING']) # Create a resource ID header for use during the linking step of the build # FIXME PBL-36458: Since pebble.h requires this file through a #include, this file must be # present for every project, regardless of whether or not resources exist for the project. At # this time, this means the `generate_resource_id_header` task generator must run for every # project. Since the input of the `generate_resource_id_header` task generator is the # resource ball created by the `generate_resource_ball` task generator, the # `generate_resource_ball` task generator must also run for every project. resource_id_header = bld_dir.make_node('src/resource_ids.auto.h') bld.env.RESOURCE_ID_HEADER = resource_id_header.abspath() bld(features='generate_resource_id_header', resource_ball=resource_ball, resource_id_header_target=resource_id_header, use_extern=build_type == 'lib', use_define=build_type == 'app', published_media=published_media_json) resource_id_definitions = bld_dir.make_node('src/resource_ids.auto.c') bld.env.RESOURCE_ID_DEFINITIONS = resource_id_definitions.abspath() bld(features='generate_resource_id_definitions', resource_ball=resource_ball, resource_id_definitions_target=resource_id_definitions, published_media=published_media_json) if not bld.env.BUILD_TYPE or bld.env.BUILD_TYPE in ('app', 'rocky'): # Create a resource pack for distribution with an application binary pbpack = bld_dir.make_node('app_resources.pbpack') bld(features='generate_pbpack', resource_ball=resource_ball, pbpack_target=pbpack, is_system=False)