I recently ran into a problem while integrating Apple’s on-demand resources system to bazel. Essentially I needed a way to generate .assetpack
archives via command line without calling into xcodebuild
.
After spending way too much time debugging this, I finally figured out exactly what Apple’s toolchain does to create these files - and more importantly, how to recreate them using standard macOS command-line tools.
The Problem: Asset Packs Look Like Regular Zip Files (But Aren’t)
When you run file
on an .assetpack
, it tells you it’s a zip archive:
$ file my-assets.assetpack
my-assets.assetpack: Zip archive data
So naturally I thought I will create the expected file hierarchy and zip it:
zip -r new-assets.assetpack some-assetpack-folder/
Your asset pack becomes unusable. iOS will reject it, and you’ll get unhelpful errors about not being able to move the file to the NSBundle.
Investigating the Differences
I used zipinfo -v
to examine the internal structure of both Apple’s original asset packs and my re-zipped versions:
Apple’s Original Asset Pack:
- Compression: none (stored)
- zero compression
- File ordering: Very specific sequence starting with META-INF/
- Structure: Flat hierarchy with files at zip root level
- Metadata: No extended attributes or extra fields
- Encoding version: 3.0
My zipped Version:
- Compression: deflated
- standard compression
- File ordering: Alphabetical (zip’s default)
- Structure: Nested bundle directory structure
- Metadata: Full of Unix extended attributes and timestamps
- Encoding version: 2.0
The Critical Requirements
After lots of experimentation, I discovered Apple’s asset packs have five strict requirements:
1. Flat Hierarchy Structure
The contents must be at the zip root level, not nested in a bundle directory. Apple’s structure looks like:
META-INF/
_CodeSignature/
SomeFile
Info.plist
Not like:
com.company.app.bundle-hash/
├── META-INF/
├── _CodeSignature/
├── SomeFile
└── Info.plist
2. Zero Compression
Every single file must use the store
method (no compression). This is critical - any compressed files will cause rejection.
3. Specific File Ordering
The central directory must have entries in this exact order:
1. META-INF/
(directory)
2. META-INF/com.apple.ZipMetadata.plist
(file)
3. _CodeSignature/
(directory)
4. Code signature files
5. Content files
6. Info.plist
4. No Extended Attributes
The zip must be clean of any extended attributes, Unix UID/GID info, or extra metadata fields.
5. The Critical Metadata File
The META-INF/com.apple.ZipMetadata.plist
file must be the second entry in the zip. This file contains metadata that iOS validates.
The Solution: Recreating Asset Packs Correctly
Here’s the call to zip
that packages the assetpack correctly:
# Navigate to the assetpack directory
cd some-assetpack-directory/
# Recreate with proper ordering and settings
(echo "META-INF/"; echo "META-INF/com.apple.ZipMetadata.plist"; find . -mindepth 1 -not -path "./META-INF*") | zip -0 -X recreated.assetpack -@
Let me break down what each part does:
echo "META-INF/"
- Ensures META-INF directory is firstecho "META-INF/com.apple.ZipMetadata.plist"
- Puts the critical metadata file secondfind . -mindepth 1 -not -path "./META-INF*"
- Adds everything else while excluding META-INF (to avoid duplicates)zip -0 -X ... -@
- Creates zip with zero compression (-0
), no extended attributes (-X
), reading file list from stdin (-@
)
The Bottom Line
Apple’s asset packs aren’t just zip files - they’re zip files with very specific structural requirements. iOS validates not just the content, but the exact structure, compression settings, and file ordering of the archive.
With the right zip parameters and file ordering, it is very easy to create them.