Thursday, January 26, 2006

The OS X Intel Transition: Pascal library

I'm solely responsible for transitioning a fairly large, old, OS X C++ Carbon application to the new world of XCode, and beyond that to an Intel/PowerPC universal binary. Most of this is fairly standard stuff, and as before I recommend attending a Universal Binary Workshop if one comes to a town near you, or at a minimum watching all the videos on Apple's transition resource page.

But of course, every transition is different and has one snag that takes up a disproportionate amount of time, and in my case that was a medium sized library written in Pascal which had not been compiled since Metrowerks briefly shipped a Pascal compiler capable of generating a Carbon compliant CFM binary.

I had a bunch of Pascal source code written in an archaic dialect (Mac Pascal?, MPW Pascal? Think Pascal?) with obsolete extensions and run time libraries (StringOf, PLStringOfSet, etc.), no Apple supplied compiler, and I have to generate a universal binary. So I googled around and found GNU Pascal for Mac OS X, which promised XCode integration and universal binary generation. So, I downloaded the binaries and got to work. I decided I would just use the command line for compilation, as there were complications with XCode integration, so I started by issuing the following command:

glenn% gpc -c --automake -funit-path=/Developer/Pascal/GPCPInterfaces/ MySource.p

Which of course spat out hundreds of warnings and errors, and I was forced to walk though each of the dozens of Pascal source files included by the automake directive and get rid of the errors and some of the more scary warnings, replaced the function StringOf with the procedure WriteStr, change almost all the file handing routines, etc. The old code had relied on being passed old fashioned Mac paths with colon separation, GNU Pascal likes / separators. The old code assumed integers were 16 bit, GNU Pascal assumes they are 32 bit. The old code would blithely add an integer to a set, GNU Pascal would like to be assured you don't really want a set 4GBits in size. And since I was making a universal binary, I made sure to make use of the big endian specific file routines to make sure I didn't mangle my input files. It was a lot of work, but eventually it compiled. Yeah.

Then it was time to package a universal binary.

glenn% rm *.o
glenn% rm *.a
glenn% rm *.gpi
glenn% gpc-i386 -c --automake -funit-path=/Developer/Pascal/GPCPInterfaces/ MySource.p
glenn% libtool -static *.o -o MyLib386.a
glenn% rm *.o
glenn% rm *.gpi
glenn% gpc -c --automake -funit-path=/Developer/Pascal/GPCPInterfaces/ MySource.p
glenn% libtool -static *.o -o MyLibPPC.a
glenn% lipo *.a -create -output MyLib.a

I included MyLib.a into my application, and of course it couldn't link because I needed a universal version of the Pascal runtime libraries:

glenn% lipo /Developer/Pascal/gpc345u2/lib/gcc/i386-apple-darwin8/3.4.5/libgpc.a /Developer/Pascal/gpc345u2/lib/gcc/powerpc-apple-darwin8/3.4.5/libgpc.a -create -output libgpcUniversal.a

Now everything linked into the C++ application but calls into the library quickly crashed. Subscribed to the GNU mailing list, had GNU Pascal guru Adriaan van Os gently tell me to read the README. Oops had to initialize the Pascal runtime:

extern "C" void _p_initialize (int argc, char **argv, char **envp, int options);
extern "C" void _p_finalize ();

int main (int argc, char **argv, char **envp)
_p_initialize (argc, argv, envp, 0);
_p_finalize ();

It still crashed. I needed to debug in situ. Therefore I added the -g flag to my compilation above:

glenn% gpc -c -g --automake -funit-path=/Developer/Pascal/GPCPInterfaces/ MySource.p

I could now step into my source code. The Assign runtime method was crashing. I had to make the runtime debuggable, which meant compiling gpc from scratch. This is amazingly easy, just a matter of downloading the source package, putting it in a directory whose path has no spaces and commanding:

glenn% sudo ./build-on-powerpc-10-4.command

And repeat my packaging of gpc:

glenn% lipo /Developer/Pascal/gpc345u2/lib/gcc/i386-apple-darwin8/3.4.5/libgpc.a /Developer/Pascal/gpc345u2/lib/gcc/powerpc-apple-darwin8/3.4.5/libgpc.a -create -output libgpcUniversal.a

Now I could step into the runtime, where I soon figured out I had neglected to allocate a pointer or some such silliness. So now I have a working library that needs a little debugging, but is no longer stopping my transition to XCode from CodeWarrior and no longer stopping me from testing on Intel hardware.