Zipping files is easy, right?

I’ve been learning about writing Firefox extensions and naturally sought to automate the .xpi assembly process. Packaged Firefox extensions (.xpi) and the jar files nested within them are “ordinary zip files”. A simple extension’s .xpi file might contain a structure like this:

  • helloworld.xpi
    • install.rdf
    • chrome/
      • helloworld.jar
        • content/
          • helloworld/
            • contents.rdf
            • .. (more files) ..

(Where bold means “is a zip file containing…”)

First, I wrote a little Python script that assembled the .jar and then assembled the .xpi. Initially it was generating corrupt a nested .jar file because of a bug in my code. When I tried to install the resulting .xpi, Firefox gracelessly failed to display anything and enigmatically reported “This extension will be uninstalled when you restart Firefox.” in the extension list about my extension. It was not uninstalled when I restarted Firefox. I had to manually remove it from C:\Documents and Settings\Ken\Application Data\Mozilla\Firefox\Profiles\...\extensions\Extensions.rdf and delete the {uuid} directory for it in the same location.

I eventually figured out my .jar file was corrupt, fixed the build script, and verified Windows Explorer’s compressed file support could read both the .xpi and the internal .jar correctly (when they were renamed to .zip).

When I tried to install the new, fixed .xpi, Firefox displayed an error message saying it couldn’t find “blahblahblah/helloworld.jar!/content/helloworld/”. It handled the .xpi but was choking on my .jar file. This time it recognized a problem and automatically uninstalled the broken extension so I didn’t have to manually edit anything to restore Firefox’s state.

I suspected Python’s zipfile module of making something sufficiently nonstandard to throw off Firefox but that the Explorer .zip file support knew how to deal with. I rewrote my build.py script to use 7-Zip (one of the recommended tools for assembling .xpis). The extension tutorials I found all used WinZip but I didn’t want to hassle with installing and registering WinZip. The script was still simple and fast. And wrong. The new .xpi file failed in exactly the same way.

More troubleshooting and reading .xpi docs and scrutinizing my install and contents.rdfs. Still broken.

At wit’s end, I tried using 7-Zip’s command line util (the same one called from the Python script) from the command line to assemble the .jar and .xpi “by hand.”

That .xpi worked.

Further scrutiny revealed 7z was behaving differently when called “by hand” from the command prompt than when called from inside the build.py script. At this point build.py could have been a batch file but I was still hoping to go back to the pure Python version because I eventually wanted the build script to do more than just call a zip utility.

The relevent difference were these lines:

Called from build.py:

Creating archive helloworld.jar
Compressing  .\content\helloworld\about.xul
Compressing  .\content\helloworld\contents.rdf
Compressing  .\content\helloworld\helloworldOverlay.js
Compressing  .\content\helloworld\helloworldOverlay.xul
Compressing  .\skin\classic\helloworld\contents.rdf
Compressing  .\skin\classic\helloworld\helloworld.png
Compressing  .\skin\classic\helloworld\helloworldb.png

Called “by hand” from the command line: Creating archive helloworld.jar Compressing content\helloworld Compressing content\helloworld\about.xul Compressing content\helloworld\contents.rdf Compressing content\helloworld\helloworldOverlay.js Compressing content\helloworld\helloworldOverlay.xul Compressing skin\classic Compressing skin\classic\helloworld Compressing skin\classic\helloworld\contents.rdf Compressing skin\classic\helloworld\helloworld.png Compressing skin\classic\helloworld\helloworldb.png

Note how the “by hand” call output lines for the directories. When I listed the contents of the .jar file with “7z l”, I saw that the “by hand” .jar file had 0-length “directory” entries for each of those directories and the “automated” .jar file did not. “7z x” (and windows explorer) unpacked these two .jar files into the same directory tree — so in some sense both were correctly made zip files. But only the one with “directory” entries worked with Firefox.

Armed with this knowledge, I modified the pure Python version of build.py to emit directory entries as it built the .jar file. Failed.

After further debugging and testing, I finally throw up my hands in disgust at having spent this much time on a distraction and use ant to build my xpi. The ant-generated .xpi works. I’ve learned something about zip file interop but very little about writing Firefox extensions.

I was trying to streamline development by automating .xpi creation. I derailed it instead. Next — getting back to work on the actual extension.