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

232 lines
10 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 json
import struct
from waflib import Node, Task, TaskGen
from waflib.TaskGen import before_method, feature
from resources.types.resource_definition import ResourceDefinition
from resources.types.resource_object import ResourceObject
from sdk_helpers import validate_resource_not_larger_than
class layouts_json(Task.Task):
"""
Task class for generating a layouts JSON file with the timeline/glance resource id mapping for
publishedMedia items
"""
def run(self):
"""
This method executes when the layouts JSON task runs
:return: N/A
"""
published_media_dict = {m['id']: m['name'] for m in self.published_media}
timeline_entries = [{'id': media_id, 'name': media_name} for media_id, media_name in
published_media_dict.iteritems()]
image_uris = {
'resources': {'app://images/' + r['name']: r['id'] for r in timeline_entries}
}
# Write a dictionary (created from map output) to a json file in the build directory
with open(self.outputs[0].abspath(), 'w') as f:
json.dump(image_uris, f, indent=8)
def _collect_lib_published_media(ctx):
"""
Collects all lib-defined publishedMedia objects and provides a list for comparison with app
aliases
:param ctx: the current Context object
:return: a list of all defined publishedMedia items from included packages
"""
published_media = []
for lib in ctx.env.LIB_JSON:
if 'pebble' not in lib or 'resources' not in lib['pebble']:
continue
if 'publishedMedia' not in lib['pebble']['resources']:
continue
published_media.extend(lib['pebble']['resources']['publishedMedia'])
return published_media
class timeline_reso(Task.Task):
"""
Task class for generating a timeline lookup table for publishedMedia items, which is then
packed and packaged as a ResourceObject for later inclusion in a ResourceBall and PBPack
"""
def run(self):
"""
This method executes when the timeline reso task runs
:return: N/A
"""
bld = self.generator.bld
resource_id_mapping = self.env.RESOURCE_ID_MAPPING
TIMELINE_RESOURCE_TABLE_ENTRY_FMT = '<III'
TLUT_SIGNATURE = 'TLUT'
timeline_resources = []
published_media_from_libs = _collect_lib_published_media(self.generator)
# Create a sparse table to represent a c-style array
for item in self.published_media:
timeline_id = item.get('id', None)
published_media_name = item.get('name', None) # string representation of published_id
build_type = self.env.BUILD_TYPE
timeline_tiny_exists = 'timeline' in item and 'tiny' in item['timeline']
if 'glance' in item:
# Alias ['timeline']['tiny'] to ['glance'] if missing, or validate
# ['timeline']['tiny'] == ['glance'] if both exist
if not timeline_tiny_exists:
timeline = item.pop('timeline', {})
timeline.update({'tiny': item['glance']})
item['timeline'] = timeline
elif item['glance'] != item['timeline']['tiny']:
bld.fatal("Resource {} in publishedMedia specifies different values {} and {}"
"for ['glance'] and ['timeline']['tiny'] attributes, respectively. "
"Differing values for these fields are not supported.".
format(item['name'], item['glance'], item['timeline']['tiny']))
else:
if not timeline_tiny_exists:
if 'alias' in item and build_type != 'lib':
# Substitute package-defined publishedMedia item for objects with `alias`
# defined
for definition in published_media_from_libs:
if definition['name'] == item['alias']:
del item['alias']
del definition['name']
item.update(definition)
break
else:
bld.fatal("No resource for alias '{}' exists in installed packages".
format(item['alias']))
else:
bld.fatal("Resource {} in publishedMedia is missing values for ['glance'] "
"and ['timeline']['tiny'].".format(published_media_name))
# Extend table if needed
if timeline_id >= len(timeline_resources):
timeline_resources.extend({'tiny': 0, 'small': 0, 'large': 0} for x in
range(len(timeline_resources), timeline_id + 1))
# Set the resource IDs for this timeline item
for size, res_id in item['timeline'].iteritems():
if res_id not in resource_id_mapping:
bld.fatal("Invalid resource ID {} specified in publishedMedia".format(res_id))
timeline_resources[timeline_id][size] = resource_id_mapping[res_id]
# Serialize the table
table = TLUT_SIGNATURE
for r in timeline_resources:
table += struct.pack(TIMELINE_RESOURCE_TABLE_ENTRY_FMT,
r['tiny'],
r['small'],
r['large'])
r = ResourceObject(ResourceDefinition('raw', 'TIMELINE_LUT', ''), table)
r.dump(self.outputs[0])
def _get_resource_file(ctx, mapping, resource_id, resources_node=None):
try:
resource = mapping[resource_id]
except KeyError:
ctx.bld.fatal("No resource '{}' found for publishedMedia use.".format(resource_id))
if isinstance(resource, Node.Node):
return resource.abspath()
elif resources_node:
return resources_node.find_node(str(resource)).abspath()
else:
return ctx.path.find_node('resources').find_node(str(resource)).abspath()
@feature('process_timeline_resources')
@before_method('generate_resource_ball')
def process_timeline_resources(task_gen):
"""
Process all of the resources listed in the publishedMedia object in project JSON files.
As applicable, generate a layouts.json file for mobile apps to do resource id lookups,
and generate a timeline lookup table for FW to do resource id lookups.
Keyword arguments:
published_media -- A JSON object containing all of the resources defined as publishedMedia in a
project's JSON file
timeline_resource_table -- The name of the file to be used to store the timeline lookup table
layouts_json -- The name of the file to be used to store the JSON timeline/glance resource id
mapping
:param task_gen: the task generator instance
:return: N/A
"""
bld = task_gen.bld
build_type = task_gen.env.BUILD_TYPE
published_media = task_gen.published_media
timeline_resource_table = task_gen.timeline_reso
layouts_json = task_gen.layouts_json
mapping = task_gen.resource_mapping
MAX_SIZES = {
'glance': (25, 25),
'tiny': (25, 25),
'small': (50, 50),
'large': (80, 80)
}
used_ids = []
for item in published_media:
if 'id' not in item:
# Pebble Package builds omit the ID
if build_type == 'lib':
continue
else:
bld.fatal("Missing 'id' attribute for publishedMedia item '{}'".
format(item['name']))
# Check for duplicate IDs
if item['id'] in used_ids:
task_gen.bld.fatal("Cannot specify multiple resources with the same publishedMedia ID. "
"Please modify your publishedMedia items to only use the ID {} once".
format(item['id']))
else:
used_ids.append(item['id'])
# Check for valid resource dimensions
if 'glance' in item:
res_file = _get_resource_file(task_gen, mapping, item['glance'])
if not validate_resource_not_larger_than(task_gen.bld, res_file, MAX_SIZES['glance']):
bld.fatal("publishedMedia item '{}' specifies a resource '{}' for attribute "
"'glance' that exceeds the maximum allowed dimensions of {} x {} for "
"that attribute.".
format(item['name'], mapping[item['glance']], MAX_SIZES['glance'][0],
MAX_SIZES['glance'][1]))
if 'timeline' in item:
for size in ('tiny', 'small', 'large'):
if size in item['timeline']:
res_file = _get_resource_file(task_gen, mapping, item['timeline'][size])
if not validate_resource_not_larger_than(task_gen.bld, res_file,
MAX_SIZES[size]):
bld.fatal("publishedMedia item '{}' specifies a resource '{}' for size '{}'"
" that exceeds the maximum allowed dimensions of {} x {} for "
" that size.".
format(item['name'], mapping[item['timeline'][size]], size,
MAX_SIZES[size][0], MAX_SIZES[size][1]))
timeline_reso_task = task_gen.create_task('timeline_reso',
src=None, tgt=timeline_resource_table)
timeline_reso_task.published_media = published_media
layouts_json_task = task_gen.create_task('layouts_json', src=None, tgt=layouts_json)
layouts_json_task.published_media = published_media