pebble/sdk/waftools/process_sdk_resources.py
2025-01-27 11:38:16 -08:00

231 lines
11 KiB
Python

# 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)