From eb8afbe72056f3f92af01e9abaae452c93e3b9b9 Mon Sep 17 00:00:00 2001 From: Eric Froemling Date: Sat, 12 Jun 2021 16:24:52 -0500 Subject: [PATCH] consolidating resource makefile generation functionality --- tools/batools/pcommand.py | 6 + tools/batools/resourcesmakefile.py | 337 +++++++++++++++++++++++++++++ tools/batools/updateproject.py | 16 +- tools/pcommand | 2 +- 4 files changed, 354 insertions(+), 7 deletions(-) create mode 100755 tools/batools/resourcesmakefile.py diff --git a/tools/batools/pcommand.py b/tools/batools/pcommand.py index 52867dd0..3f3d3a74 100644 --- a/tools/batools/pcommand.py +++ b/tools/batools/pcommand.py @@ -868,3 +868,9 @@ def android_sdk_utils() -> None: """Wrangle android sdk stuff.""" from batools.androidsdkutils import run run(projroot=str(PROJROOT), args=sys.argv[2:]) + + +def update_resources_makefile() -> None: + """Update the resources Makefile if needed.""" + from batools.resourcesmakefile import update + update(projroot=str(PROJROOT), check='--check' in sys.argv) diff --git a/tools/batools/resourcesmakefile.py b/tools/batools/resourcesmakefile.py new file mode 100755 index 00000000..f267351f --- /dev/null +++ b/tools/batools/resourcesmakefile.py @@ -0,0 +1,337 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Generate our resources Makefile. + +(builds things like icons, banners, images, etc.) +""" + +from __future__ import annotations + +import os +import sys +from typing import TYPE_CHECKING + +from efro.terminal import Clr + +if TYPE_CHECKING: + from typing import Optional, List, Dict + +# These paths need to be relative to the dir we're writing the Makefile to. +TOOLS_DIR = '../tools' +ROOT_DIR = '..' +RES_DIR = '.' +RESIZE_CMD = os.path.join(TOOLS_DIR, 'pcommand resize_image') + + +def _add_windows_icon(targets: List[Dict]) -> None: + + # Windows Icon + sizes = [256, 128, 96, 64, 48, 32, 16] + all_icons = [] + for size in sizes: + dst_base = 'build' + src = os.path.join('src', 'icon', 'icon_clipped.png') + dst = os.path.join(dst_base, 'win_icon_' + str(size) + '_tmp.png') + cmd = ' '.join([ + RESIZE_CMD, + str(size), + str(size), '"' + src + '"', '"' + dst + '"' + ]) + all_icons.append(dst) + targets.append({'src': [src], 'dst': dst, 'cmd': cmd, 'mkdir': True}) + + # Assemble all the bits we just made into .ico files. + for path in [ + ROOT_DIR + '/ballisticacore-windows/Generic/BallisticaCore.ico', + ROOT_DIR + '/ballisticacore-windows/Oculus/BallisticaCore.ico', + ]: + cmd = ('convert ' + ''.join([' "' + f + '"' + for f in all_icons]) + ' "' + path + '"') + targets.append({'src': all_icons, 'dst': path, 'cmd': cmd}) + + +def _add_ios_app_icon(targets: List[Dict]) -> None: + sizes = [(20, 2), (20, 3), (29, 2), (29, 3), (40, 2), (40, 3), (60, 2), + (60, 3), (20, 1), (29, 1), (40, 1), (76, 1), (76, 2), (83.5, 2), + (1024, 1)] + for size in sizes: + res = int(size[0] * size[1]) + src = os.path.join('src', 'icon', 'icon_flat.png') + dst = os.path.join( + ROOT_DIR, 'ballisticacore-xcode', 'BallisticaCore Shared', + 'Assets.xcassets', 'AppIcon iOS.appiconset', + 'icon_' + str(size[0]) + 'x' + str(size[1]) + '.png') + cmd = ' '.join( + [RESIZE_CMD, + str(res), + str(res), '"' + src + '"', '"' + dst + '"']) + targets.append({'src': [src], 'dst': dst, 'cmd': cmd}) + + +def _add_macos_app_icon(targets: List[Dict]) -> None: + sizes = [(16, 1), (16, 2), (32, 1), (32, 2), (128, 1), (128, 2), (256, 1), + (256, 2), (512, 1), (512, 2)] + for size in sizes: + res = int(size[0] * size[1]) + src = os.path.join(RES_DIR, 'src', 'icon', 'icon_clipped.png') + dst = os.path.join( + ROOT_DIR, + 'ballisticacore-xcode', + 'BallisticaCore Shared', + 'Assets.xcassets', + 'AppIcon macOS.appiconset', + 'icon_' + str(size[0]) + 'x' + str(size[1]) + '.png', + ) + cmd = ' '.join( + [RESIZE_CMD, + str(res), + str(res), '"' + src + '"', '"' + dst + '"']) + targets.append({'src': [src], 'dst': dst, 'cmd': cmd}) + + +def _add_android_app_icon(targets: List[Dict], + src_name: str = 'icon_clipped.png', + variant_name: str = 'main') -> None: + sizes = [('mdpi', 48), ('hdpi', 72), ('xhdpi', 96), ('xxhdpi', 144), + ('xxxhdpi', 192)] + for size in sizes: + res = size[1] + src = os.path.join(RES_DIR, 'src', 'icon', src_name) + dst = os.path.join(ROOT_DIR, 'ballisticacore-android', + 'BallisticaCore', 'src', variant_name, 'res', + 'mipmap-' + size[0], 'ic_launcher.png') + cmd = ' '.join( + [RESIZE_CMD, + str(res), + str(res), '"' + src + '"', '"' + dst + '"']) + targets.append({'src': [src], 'dst': dst, 'cmd': cmd, 'mkdir': True}) + + +def _add_android_app_icon_new(targets: List[Dict], + src_fg_name: str = 'icon_android_layered_fg.png', + src_bg_name: str = 'icon_android_layered_bg.png', + variant_name: str = 'main') -> None: + sizes = [('mdpi', 108), ('hdpi', 162), ('xhdpi', 216), ('xxhdpi', 324), + ('xxxhdpi', 432)] + for size in sizes: + res = size[1] + + # Foreground component. + src = os.path.join(RES_DIR, 'src', 'icon', src_fg_name) + dst = os.path.join(ROOT_DIR, 'ballisticacore-android', + 'BallisticaCore', 'src', variant_name, 'res', + 'mipmap-' + size[0], 'ic_launcher_foreground.png') + cmd = ' '.join( + [RESIZE_CMD, + str(res), + str(res), '"' + src + '"', '"' + dst + '"']) + targets.append({'src': [src], 'dst': dst, 'cmd': cmd, 'mkdir': True}) + + # Background component. + src = os.path.join(RES_DIR, 'src', 'icon', src_bg_name) + dst = os.path.join(ROOT_DIR, 'ballisticacore-android', + 'BallisticaCore', 'src', variant_name, 'res', + 'mipmap-' + size[0], 'ic_launcher_background.png') + cmd = ' '.join( + [RESIZE_CMD, + str(res), + str(res), '"' + src + '"', '"' + dst + '"']) + targets.append({'src': [src], 'dst': dst, 'cmd': cmd, 'mkdir': True}) + + +def _add_android_cardboard_app_icon(targets: List[Dict]) -> None: + _add_android_app_icon(targets=targets, + src_name='icon_clipped_vr.png', + variant_name='cardboard') + + +def _add_android_cardboard_app_icon_new(targets: List[Dict]) -> None: + _add_android_app_icon_new(targets=targets, + src_fg_name='icon_android_layered_fg_vr.png', + variant_name='cardboard') + + +def _add_android_tv_banner(targets: List[Dict]) -> None: + res = (320, 180) + src = os.path.join(RES_DIR, 'src', 'banner', 'banner_16x9.png') + dst = os.path.join( + ROOT_DIR, + 'ballisticacore-android', + 'BallisticaCore', + 'src', + 'main', + 'res', + 'drawable-xhdpi', + 'banner.png', + ) + cmd = ' '.join([ + RESIZE_CMD, + str(res[0]), + str(res[1]), '"' + src + '"', '"' + dst + '"' + ]) + targets.append({'src': [src], 'dst': dst, 'cmd': cmd, 'mkdir': True}) + + +def _add_apple_tv_top_shelf(targets: List[Dict]) -> None: + instances = [('24x9', '', '', 1920, 720), + ('29x9', ' Wide', '_wide', 2320, 720)] + for instance in instances: + for scale in [1, 2]: + res = (instance[3] * scale, instance[4] * scale) + src = os.path.join(RES_DIR, 'src', 'banner', + 'banner_' + instance[0] + '.png') + dst = os.path.join( + ROOT_DIR, + 'ballisticacore-xcode', + 'BallisticaCore Shared', + 'Assets.xcassets', + 'tvOS App Icon & Top Shelf Image.brandassets', + 'Top Shelf Image' + instance[1] + '.imageset', + 'shelf' + instance[2] + '_' + str(scale) + 'x.png', + ) + cmd = ' '.join([ + RESIZE_CMD, + str(res[0]), + str(res[1]), '"' + src + '"', '"' + dst + '"' + ]) + targets.append({'src': [src], 'dst': dst, 'cmd': cmd}) + + +def _add_apple_tv_3d_icon(targets: List[Dict]) -> None: + res = (400, 240) + for layer in ['Layer1', 'Layer2', 'Layer3', 'Layer4', 'Layer5']: + for scale in [1, 2]: + src = os.path.join(RES_DIR, 'src', 'icon_appletv', + layer.lower() + '.png') + dst = os.path.join( + ROOT_DIR, + 'ballisticacore-xcode', + 'BallisticaCore Shared', + 'Assets.xcassets', + 'tvOS App Icon & Top Shelf Image.brandassets', + 'App Icon.imagestack', + layer + '.imagestacklayer', + 'Content.imageset', + layer.lower() + '_' + str(scale) + 'x.png', + ) + cmd = ' '.join([ + RESIZE_CMD, + str(res[0] * scale), + str(res[1] * scale), '"' + src + '"', '"' + dst + '"' + ]) + targets.append({'src': [src], 'dst': dst, 'cmd': cmd}) + + +def _add_apple_tv_store_icon(targets: List[Dict]) -> None: + res = (1280, 768) + for layer in ['Layer1', 'Layer2', 'Layer3', 'Layer4', 'Layer5']: + for scale in [1]: + src = os.path.join(RES_DIR, 'src', 'icon_appletv', + layer.lower() + '.png') + dst = os.path.join( + ROOT_DIR, + 'ballisticacore-xcode', + 'BallisticaCore Shared', + 'Assets.xcassets', + 'tvOS App Icon & Top Shelf Image.brandassets', + 'App Icon - App Store.imagestack', + layer + '.imagestacklayer', + 'Content.imageset', + layer.lower() + '_' + str(scale) + 'x.png', + ) + cmd = ' '.join([ + RESIZE_CMD, + str(res[0] * scale), + str(res[1] * scale), '"' + src + '"', '"' + dst + '"' + ]) + targets.append({'src': [src], 'dst': dst, 'cmd': cmd}) + + +def _add_google_vr_icon(targets: List[Dict]) -> None: + res = (512, 512) + for layer in ['vr_icon_background', 'vr_icon']: + src = os.path.join(RES_DIR, 'src', 'icon_googlevr', layer + '.png') + dst = os.path.join( + ROOT_DIR, + 'ballisticacore-android', + 'BallisticaCore', + 'src', + 'cardboard', + 'res', + 'drawable-nodpi', + layer + '.png', + ) + cmd = ' '.join([ + RESIZE_CMD, + str(res[0]), + str(res[1]), '"' + src + '"', '"' + dst + '"' + ]) + targets.append({'src': [src], 'dst': dst, 'cmd': cmd, 'mkdir': True}) + + +def _write_makefile(fname: str, targets: List[Dict], check: bool) -> None: + from efrotools import get_public_license + existing: Optional[str] + try: + with open(fname, 'r') as infile: + existing = infile.read() + except Exception: + existing = None + + ourname = os.path.basename(__file__) + out = (get_public_license('makefile') + + f'\n# Generated by {ourname}; do not hand-edit.\n\n') + all_dsts = set() + for target in targets: + all_dsts.add(target['dst']) + out += 'all: resources\n\nresources: ' + ' \\\n '.join( + dst.replace(' ', '\\ ') for dst in sorted(all_dsts)) + '\n\n' + + out += 'clean:\n\trm -f ' + ' \\\n '.join( + '"' + dst + '"' for dst in sorted(all_dsts)) + '\n\n' + var_num = 1 + for target in targets: + if bool(False) and ' ' in target['dst']: + out += 'TARGET_' + str(var_num) + ' = ' + target['dst'].replace( + ' ', '\\ ') + '\n${TARGET_' + str(var_num) + '}' + var_num += 1 + else: + out += target['dst'].replace(' ', '\\ ') + out += ' : ' + ' '.join(s for s in target['src']) + ( + ('\n\t@mkdir -p "' + + os.path.dirname(target['dst']) + '"') if target.get( + 'mkdir', False) else '') + '\n\t@' + target['cmd'] + '\n\n' + if out == existing: + print('Resources Makefile is up to date.') + else: + if check: + print(Clr.SRED + 'ERROR: file out of date: ' + fname + Clr.RST) + sys.exit(255) + print(Clr.SBLU + 'Generating: ' + fname + Clr.RST) + with open(fname, 'w') as outfile: + outfile.write(out) + + +def update(projroot: str, check: bool) -> None: + """main script entry point""" + + # Operate out of root dist dir for consistency. + os.chdir(projroot) + + targets: List[Dict] = [] + + _add_windows_icon(targets) + _add_ios_app_icon(targets) + _add_macos_app_icon(targets) + _add_android_app_icon(targets) + _add_android_app_icon_new(targets) + _add_android_cardboard_app_icon(targets) + _add_android_cardboard_app_icon_new(targets) + _add_android_tv_banner(targets) + _add_apple_tv_top_shelf(targets) + _add_apple_tv_3d_icon(targets) + _add_apple_tv_store_icon(targets) + _add_google_vr_icon(targets) + + # Write makefile (or print if nothing has changed). + _write_makefile('resources/Makefile', targets, check) diff --git a/tools/batools/updateproject.py b/tools/batools/updateproject.py index 68583a8b..eb947013 100755 --- a/tools/batools/updateproject.py +++ b/tools/batools/updateproject.py @@ -675,12 +675,16 @@ class Updater: sys.exit(255) def _update_resources_makefile(self) -> None: - if os.path.exists('tools/update_resources_makefile'): - if os.system('tools/update_resources_makefile' + - self._checkarg) != 0: - print(f'{Clr.RED}Error checking/updating' - f' resources Makefile.{Clr.RST}') - sys.exit(255) + # FIXME: should support running this in public too. + if not self._public: + try: + subprocess.run( + ['tools/pcommand', 'update_resources_makefile'] + + self._checkarglist, + check=True) + except Exception as exc: + raise CleanError( + 'Error checking/updating resources Makefile.') from exc def _update_python_enums_module(self) -> None: if os.path.exists('tools/update_python_enums_module'): diff --git a/tools/pcommand b/tools/pcommand index e7c52fef..80f35e26 100755 --- a/tools/pcommand +++ b/tools/pcommand @@ -40,7 +40,7 @@ from batools.pcommand import ( update_assets_makefile, update_project, update_cmake_prefab_lib, cmake_prep_dir, gen_binding_code, gen_flat_data_code, wsl_path_to_win, wsl_build_check_win_drive, win_ci_binary_build, genchangelog, - android_sdk_utils) + android_sdk_utils, update_resources_makefile) # pylint: enable=unused-import if TYPE_CHECKING: