Building Icons with GNU Make

Desktop icon formats are a mess. No platforms share formats - Windows uses .ico (and for various reasons so does the web), Mac OS X uses .icns, and desktop GNU/Linux doesn't even have an icon format, you just throw PNGs into the air and pray.

But let's say you are foolishly trying to write a cross-platform application, yet intelligently want to automate creating most of these from source data.

A reasonable "source data" for icons is a directory full of PNGs.1 Apple uses a format called .iconset for this, with filenames like example.iconset/icon_64x64.png. Since it's just a directory of images, it'll serve for making Windows/website icons as well, and GNU/Linux build scripts can copy files out of it directly.

To convert this to the desired formats, you'll need GraphicsMagick, and either build on a Mac OS X system or install libicns.

Now for the Makefile:

ICONUTIL := $(word 1, $(shell command -v iconutil icnsutil) iconutil)
%.icns: %.iconset $$(wildcard $$(@D)/$$*.iconset/icon_*.png)
        $(ICONUTIL) -c icns -o $@ $<

%.ico: %.iconset $$(wildcard $$(@D)/$$*.iconset/icon_*[0-9].png)
        convert -background transparent -colors 256 $(filter-out $<,$^) $@

(You'll need to replace those spaces with tabs. Sorry. Complain here.)

This uses a couple unusual tricks.

First, the ICONUTIL assignment figures out whether you've got the genuine iconutil from Mac OS X, or the compatible icnsutil from libicns. If you don't have either, it picks iconutil so you get a sensible error.

Second, it uses GNU Make's secondary expansion feature. This specifies a wildcard dependent on the pattern and target name when generating the list of source names.

Third, it depends on the source directory. This is uncommon in Makefiles because directories update when contained files are added or deleted, not when files are modified. The modification case is covered with the .SECONDEXPANSION trick but we also need to handle the case where an icon size was removed, because the target files need to be rebuilt without the removed file.

Finally, please note the slightly different glob patterns. .icns files support high-DPI ("Retina") icons using the format icon_WxH@2.png - so icon_16x16@2.png is actually 32 pixels on each side. .ico files, as far as I can determine, have no such feature; they will use the 32x32 icon instead. So @2x files should not be included when building the .ico.

Assigning a .ico to a .exe

I've not found a good cross-platform tool for doing this.

GitHub's Atom project has produced a Windows tool called rcedit (compiled binary here) which does nothing but poke icons and version numbers into Windows executables. Because it is so minimal it runs perfectly in Wine, and it has an MIT-style license.

wine rcedit.exe --set-icon example.ico example.exe

Assigning a .icns to a .app

Mac OS X .app files are directories. The usual place to put an icon would be Contents/Resources/Icon.icns, and you may simply need to copy it over the file already there.

If you're building the package from scratch, you'll need to create the Info.plist file referencing the icon in the first place. This can be done on any platform with Python's plistlib.

  1. Another reasonable source format is one or multiple SVGs. Turning an SVG into a directory of PNGs is left as an exercise for the reader.