Wednesday, May 14, 2014

Gotcha of the Day: Adobe AIR adt fails with "Unable to run aapt"

This morning I installed the Adobe AIR SDK on one my laptops that had never been used for Air development. I kicked off adt.bat from within Cygwin and was greeted with the following:

$ adt.bat -package ...
unexpected failure: Unable to run aapt
java.io.IOException: Unable to run aapt
        at com.adobe.air.apk.APKOutputStream.generateResourcesAndManifest(APKOutputStream.java:842)
        at com.adobe.air.apk.APKOutputStream.addApplicationDescriptor(APKOutputStream.java:298)
        at com.adobe.air.ApplicationPackager.addSpecialFiles(ApplicationPackager.java:301)
        at com.adobe.air.ApplicationPackager.createPackage(ApplicationPackager.java:66)
        at com.adobe.air.ADT.parseArgsAndGo(ADT.java:590)
        at com.adobe.air.ADT.run(ADT.java:435)
        at com.adobe.air.ADT.main(ADT.java:485)

What the heck?!

I tried a number of obvious'ish things: I tried an older version of the SDK as well as an x86 version of the Java JRE. Nothing.

Even Google failed me, offering no useful hits for this exception. It was time to get creative.

Step one, I decided I'd take a peek at the source code for APKOutputStream. I downloaded a very slick Java Decompiler (back in the day, jad was an aboslutely essential tool for Java development) and was trivially able to find the function in question:

private void generateResourcesAndManifest(String packageName, ApplicationDescriptor descriptor, int appVersionCode)
    throws IOException
  {
    String pathToApptTool = getAapt().getPath();
    ...
    try
    {
      ProcessBuilder pb = new ProcessBuilder(aaptCommand);
      Process p = pb.start();
      ByteArrayOutputStream aaptOutput = new ByteArrayOutputStream();
      new Utils.OutputEater(p.getErrorStream(), aaptOutput).start();
      new Utils.OutputEater(p.getInputStream()).start();
      p.waitFor();
      if (p.exitValue() != 0) {
        aaptOutputString = new String(aaptOutput.toByteArray(), "UTF-8");
      }
    }
    catch (Exception e)
    {
      throw new IOException("Unable to run aapt");
    }

The source gave me a couple of leads, including mention of the following command line option:

  aaptCommand.add("--target-sdk-version");
  aaptCommand.add("17");

I checked and realized I didn't have version 17 of the Android SDK installed. However, installing that package still didn't help.

Next up, I decided I dig even deeper and run adb through JDB. This turned out to be pretty straightforward. I kicked off jdb and entered the following commands:

$ jdb
stop in com.adobe.air.apk.APKOutputStream.getAapt
stop at com.adobe.air.apk.APKOutputStream:845
run  -jar c:\tools\flex4\lib\adt.jar -package ...

While on line 845, I inspected the variable pathToApptTool and learned that adb was trying to execute:

 c:\tools\flex4\lib\android\bin\aapt.exe

From within Cygwin I tried to manually execute that command. Thankfully, I got a permission denied error. Finally, a problem I knew how to fix! I ran:

 $ cd /cygdrive/c/tools/flex4
 $ find -name '*.exe' -exec grep chmod a+rx {} \;

And sure enough, the adt command finally ran without fault.

In the end, this issue had nothing to do with Adobe AIR and everything to do with Cygwin and Windows permissions. Still, check out the exception handling in the above code; it's atrocious. The only code that would have been worse would have been to swallow the exception. Still, to catch a detailed exception and not report it is a rookie mistake. It would have cost them no additional effort to output the root cause of the exception as well as the full command line that failed.

Seriously, Adobe, you can do better. At least, I certainly hope you can..

No comments:

Post a Comment