Friday, October 26, 2012

Dealing with exit code 5 when running Ghostscript under MAMP


I had been using Ghostscript for a project, calling it from a PHP script running in a MAMP environment on Snow Leopard. Several months later, on a different computer, running a newer version of MAMP (2.1.1), on Lion, I kept getting some weird errors.

When GS was called through passthru, like so:

passthru('gs -version', $retval);

I wouldn't get the expected output, indicating the version of GS, instead the command would be completely silent and  $retval would be set to 5, indicating an error (on success it should have been 0).

Running the same command in a regular terminal window would produce the expected result:

$ gs -v
GPL Ghostscript 9.06 (2012-08-08)
Copyright (C) 2012 Artifex Software, Inc.  All rights reserved.

My intuition told me that it may be a problem of the environment used by PHP interpreter running under MAMP. Taking a look it shows this:


Next step was to reproduce the same environment on the terminal, and surely the result confirmed my guess:

$ ./gs -version            
dyld: Symbol not found: _iconv
  Referenced from: /usr/lib/libcups.2.dylib
  Expected in: /Applications/MAMP/Library/lib/libiconv.2.dylib
 in /usr/lib/libcups.2.dylib
Trace/BPT trap: 5

Later I discovered that the same error also shows up in the Apache error log under MAMP, albeit without explicitly showing the error 5.


The problem seems to be caused by the dylibs under /Applications/MAMP/Library/lib/. MAMP seems to be prompted to look there by the DYLD_LIBRARY_PATH environment variable that comes with the default config (set by /Applications/MAMP/Library/bin/envvars).

It seems that the dylibs used by MAMP are different than those used by a default terminal session. A regular shell session, ran from terminal, loads dylibs from /usr/lib. If I do this:


$ nm /usr/lib/libiconv.2.dylib | grep _iconv
00000000000f1af0 S ___iconv_2VersionNumber
00000000000f1b90 S ___iconv_2VersionString
000000000000a1e1 T _iconv
000000000000a5a0 T _iconv_canonicalize
0000000000013164 T _iconv_close
0000000000013171 T _iconv_open
000000000000a72c T _iconvctl
000000000000a20f T _iconvlist


it shows the _iconv symbol present in that version of the lib.

However, if I look in the version used by MAMP, it's missing:

$ nm /Applications/MAMP/Library/lib/libiconv.2.dylib | grep _iconv
0000000000029c50 T _iconv_canonicalize

Instead those symbols seem to have an extra "lib" prefix:

$ nm /Applications/MAMP/Library/lib/libiconv.2.dylib | grep iconv
000000000010d748 D __libiconv_version
0000000000029c50 T _iconv_canonicalize
0000000000028a90 T _libiconv
0000000000028b40 T _libiconv_close
00000000000280c0 T _libiconv_open
0000000000028b80 T _libiconv_open_into
000000000002a7c0 t _libiconv_relocate
000000000002a310 T _libiconv_set_relocation_prefix
0000000000029500 T _libiconvctl
0000000000029870 T _libiconvlist

It seems that MAMP loads libcups.2.dylib from /usr/lib/ and its libiconv.2.dylib dependency from /Applications/MAMP/Library/lib. That's, I believe, the root of the problem. Those 2 libraries have incompatible versions. That's confirmed by:

$ otool -L /usr/lib/libcups.2.dylib/usr/lib/libcups.2.dylib:
/usr/lib/libcups.2.dylib (compatibility version 2.0.0, current version 2.9.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 159.1.0)
/System/Library/Frameworks/Kerberos.framework/Versions/A/Kerberos (compatibility version 5.0.0, current version 6.0.0)
/System/Library/Frameworks/GSS.framework/Versions/A/GSS (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 635.21.0)
/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/SystemConfiguration (compatibility version 1.0.0, current version 395.11.0)
/usr/lib/libresolv.9.dylib (compatibility version 1.0.0, current version 46.1.0)
/System/Library/Frameworks/Security.framework/Versions/A/Security (compatibility version 1.0.0, current version 55148.1.0)
/usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)
/usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.5)

and:

$ otool -L /Applications/MAMP/Library/lib/libiconv.2.dylib
/Applications/MAMP/Library/lib/libiconv.2.dylib:
/Applications/MAMP/Library/lib/libiconv.2.dylib (compatibility version 8.0.0, current version 8.1.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 125.2.11)

A workaround may be as easy as easy as re-building libcups.2.dylib from sources, and configure it to pick the libs from /Applications/MAMP/Library/lib/.


$ cd cups-1.6.1
$ ./configure CC=clang CXX=clang++ LDFLAGS=-L/Applications/MAMP/Library/lib 
$ make
$ cp ./cups/libcups.2.dylib /Applications/MAMP/Library/lib/

Seems to fix the problem with my script, so I can move on with my work. The question is how sane this rebuilt libcups.2.dylib is.

Doing

$ otool -L ./cups/libcups.2.dylib

./cups/libcups.2.dylib:
/usr/lib/libcups.2.dylib (compatibility version 2.0.0, current version 2.10.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 159.1.0)
/System/Library/Frameworks/Kerberos.framework/Versions/A/Kerberos (compatibility version 5.0.0, current version 6.0.0)
/System/Library/Frameworks/GSS.framework/Versions/A/GSS (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 635.21.0)
/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/SystemConfiguration (compatibility version 1.0.0, current version 395.11.0)
/usr/lib/libresolv.9.dylib (compatibility version 1.0.0, current version 46.1.0)
/System/Library/Frameworks/Security.framework/Versions/A/Security (compatibility version 1.0.0, current version 55148.1.0)
/usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.5)


shows the dependency on /usr/lib/libiconv.2.dylib gone. A bit surprising.


A quick sanity check done like this:
$ make check 


has most of the tests pass, except internationalization API tests, which is not that surprising, since I suppose libiconv.2.dylib would play an important role there.

All in all, that's how I was able to get around this problem with MAMP. I posted this issue on their forum, but almost 24 hr later it hasn't even cleared the moderator, let alone gotten a better solution.




Saturday, October 06, 2012

Caveats of calling subviews in a UIScrollView, and how to customize the scrollbars.

In a recent project I had a scroll view defined in a storyboard, together with 2 subviews. I was looking for a way to access them in the layoutSubviews method of the scroll view.

The first approach I took was to create 2 outlets in the scroll view, pointing to the to the said subviews. Even though I had it working fine, I had to twist Xcode's arm in doing that, since the normal drag-to-create-outlet maneouvre in the Assistant editor seems to work only when the target of the drag is the code pane of a view controller, not a view.

So, as an alternative, I figured, hey, we have this subviews property available, why not fast-iterate through it and call it a day. When testing that approach weird things started to happen. A little debugging showed this:

(lldb) po [self subviews]
(id) $1 = 0x06b805b0 <__NSArrayM 0x6b805b0>(
<SomePageView: 0x68968b0; frame = (0 0; 320 452.507); transform = [0.450704, 0, 0, 0.450704, 0, 0]; opaque = NO; autoresize = W+H; layer = <CALayer: 0x68965f0>>,
<AnotherPageView: 0x6896e40; frame = (0 0; 320 460); autoresize = W+H; layer = <CATiledLayer: 0x6896e90>>,
<UIImageView: 0x6897330; frame = (313 453; 7 7); alpha = 0; opaque = NO; autoresize = LM; userInteractionEnabled = NO; layer = <CALayer: 0x68973a0>>,
<UIImageView: 0x6897290; frame = (313 453; 7 7); alpha = 0; opaque = NO; autoresize = TM; userInteractionEnabled = NO; layer = <CALayer: 0x6897300>>
)

What ? I thought I had 2 subviews - this shows 4. Looking at the frames, autoresize mask and the fact that they ignore touches it's a strong indication that those 2 extra subviews may be the scrollbars - LOL.

Inspecting the UIScrollView confirms that. See the _horizontalScrollIndicator and _verticalScrollIndicator ivars:



This has one clear implication - if you're nuts enough you can customize the appearance of the scrollbars in a UIScrollView. They seem to be simple UIImageViews. However rummaging through UIKit internals like this is not condoned by Apple, since future updates may break your assumptions.

Incidentally, another approach I could have taken to access my own subviews would have been to tag them in the storyboard and use the viewWithTag method to get to them.