Let’s Make Honey! Version 0.13 – You’re a Tool for Packaging Apps


Let’s Make Honey!


Today we’re going to turn the app packaging process into a tool.



No, no, it’s only going to be a small tool. We’re making a lot of assumptions, and it won’t be very versatile, but it will work for now for packaging games made with Honey.


Before we forget, let’s set the version to 0_13 in both Engine’s SConstruct and BearsLoveHoney’s SConstruct files, just in case. We won’t be building a new version of Honey today, but if we do, we don’t want it to clobber the last one.


Instead, we’re adding one file to the Tools directory, which has sat empty until now, and we’re wrapping up BearsLoveHoney into an app.


So. What are the simplifying assumptions our simple tool is making? We’re going to have to copy frameworks, and we only know about the ones from the SDL, so our tool won’t work with programs that need to copy other frameworks besides these. Also, we’re letting folks specify a series of additional material (folders like Art and Sound and Fonts, and files like config.txt) but we’re copying it all to one place, so our tool won’t work with programs that store their content somewhere other than next to the program. And we’re taking a stab at automatic icon generation, but we’re starting from a 512×512 png and making a minimal set of icons, so our tool won’t work with programs that start with a different base size or need retina icons. And heck, we’re assuming the SDL frameworks are in /Library/Frameworks, so our tool won’t work for programs where those are somewhere else.


But our tool will work with programs that use the Honey library (and its dependency on SDL2, SDL2_image, SDL2_mixer, and SDL2_ttf), have a 512×512 png file ready for icon making, and have dependent files in folders at the same level as the program (ie, next to ABearCs are Art, Sound, etc). In fact, these will be the parameters the tool asks for.


To start, let’s prepare BearsLoveHoney for packaging. Make a directory called BearsLoveHoney/Release, and inside that, BearsLoveHoney/Release/BearsLoveHoney. Grab a copy of this file (here or from github) and add it to BearsLoveHoney/Release with the name bearslovehoney.png:





Your folder structure should look like this now:



Add a new script file named app to the Tools directory, and make it executable by running chmod +x app inside Tools.


Our app script is just uses python to make some system calls, following roughly the plan we layed out a few posts ago when we did this manually for ABearCs. Super low tech. Add this to app:



import argparse
import os

parser = argparse.ArgumentParser()

parser.add_argument("--name", action="store", required=True)
parser.add_argument("--program", action="store", required=True)
parser.add_argument("--extra_files", action="store", required=True)
parser.add_argument("--icon", action="store", required=True)
parser.add_argument("--output_path", action="store", required=True)

args = parser.parse_args()
name = args.name
program = args.program
extra_files = args.extra_files.split(",")
icon = args.icon
output_path = args.output_path


print "Making directories"

def place(item):
  return os.path.join(output_path, "%s.app" % name, item)

os.system("mkdir -p %s" % place(""))
os.system("mkdir -p %s" % place("Contents/MacOS"))
os.system("mkdir -p %s" % place("Contents/Resources"))
os.system("mkdir -p %s" % place("Contents/Frameworks"))


print "Writing Info.plist and launch file"

info = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">

with open(place("Contents/Info.plist"), "w") as infofile:
  infofile.write(info.replace("_name_", name))

launch = """#!/bin/bash
cd "${0%/*}"

with open(place("Contents/MacOS/launch"), "w") as launchfile:
  launchfile.write(launch.replace("_program_", os.path.basename(program)))

os.system("chmod +x %s" % place("Contents/MacOS/launch"))


print "Copying program files"

os.system("cp -R %s %s" % (program, place("Contents/MacOS")))
for path in extra_files:
  os.system("cp -R %s %s" % (path, place("Contents/MacOS")))


print "Making icons"

os.system("mkdir %s" % place("Contents/Resources/%s.iconset" % name))
big_icon_path = place("Contents/Resources/%s.iconset/icon_512x512.png" % name)
os.system("cp %s %s" % (icon, big_icon_path))
for size in [16, 32, 128, 256]:
  little_icon_path = place("Contents/Resources/%s.iconset/icon_%dx%d.png" % (name, size, size))
  os.system("sips -z %d %d %s --out %s" % (size, size, big_icon_path, little_icon_path))

os.system("iconutil -c icns -o %s %s" % (place("Contents/Resources/%s.icns" % name), place("Contents/Resources/%s.iconset" % name)))
os.system("rm -rf %s" % place("Contents/Resources/%s.iconset" % name))


print "Copying and relinking frameworks"

for framework in ["SDL2", "SDL2_image", "SDL2_mixer", "SDL2_ttf"]:
  os.system("cp -R /Library/Frameworks/%s.framework %s" % (framework, place("Contents/Frameworks")))
  os.system("install_name_tool -change @rpath/%s.framework/Versions/A/%s @executable_path/../Frameworks/%s.framework/Versions/A/%s %s" % (framework, framework, framework, framework, place("Contents/MacOS/%s" % os.path.basename(program))))


print "Done! Your app is an app."


We’re going to run this with the command:


./app --name BearsLoveHoney --program ../BearsLoveHoney/BearsLoveHoney --extra_files ../BearsLoveHoney/config.txt,../BearsLoveHoney/Sound,../BearsLoveHoney/Art,../BearsLoveHoney/Fonts --icon ../BearsLoveHoney/Release/bearslovehoney.png --output_path ../BearsLoveHoney/Release/BearsLoveHoney/


So we supply the name BearsLoveHoney, the location of the program file in the BearsLoveHoney folder, a comma separated list with each of config.txt, Sound, Art, and Fonts, the new bearslovehoney.png as the icon file, and the output path Release/BearsLoveHoney.


In lines 6-19, we parses these arguments into variables. No validation, just parses ’em and go. Very simple for now.


In lines 25-33, we makes directories, first the .app container directory, then Contents, then MacOS, Frameworks, and Resources.


In lines 37-70, we defines a string with the contents of the Info.plist file, replaces the name with the name of our new program, and writes that file to .app/Contents/Info.plist.


Similarly in lines 73-81, we defines a string with the contents of the program launcher, replaces the program field with the path to our program, and writes the launcher to .app/Contents/MacOS/launcher, which we then makes executable with chmod +x.


In lines 87-91, we copies the program and all associated files to .app/Contents/MacOS.


In lines 97-107, we copies the icon png file to a special location as icon_512x512.png, then uses the sips tool to make shrunken copies at 256×256, 128×128, 32×32 and 16×16. Then we uses iconutil to wrap these all up into a single icns file, which we places in .app/Contents/MacOS/Resources, and we cleans up our temporary files.


Finally, in lines 113-117, we copies our frameworks from /Library/Frameworks to .app/Contents/Frameworks and uses install_name_tool to make the program point at the frameworks in that location.


So, try running it:


./app --name BearsLoveHoney --program ../BearsLoveHoney/BearsLoveHoney --extra_files ../BearsLoveHoney/config.txt,../BearsLoveHoney/Sound,../BearsLoveHoney/Art,../BearsLoveHoney/Fonts --icon ../BearsLoveHoney/Release/bearslovehoney.png --output_path ../BearsLoveHoney/Release/BearsLoveHoney/


You should get a shiny new app file in BearsLoveHoney/Release/BearsLoveHoney, and you should double click it, and it should run, and in an ideal world, which I’m just going to assume is what you’ve got, our work here is done:



Leave a Reply

Your email address will not be published. Required fields are marked *