Apple Developer Connection
Advanced Search
Member Login Log In | Not a Member? Contact ADC

Developing Cross-Platform UNIX Applications with Mac OS X

The ability to do cross-platform development is a strength of Mac OS X, as the UNIX underpinnings make it easy to develop applications on your Mac and port them to other flavors of UNIX for deployment. This article enumerates some of the tools that you can use to develop code on Mac OS X so that you can deploy that code on other UNIX-based platforms, and some things that you'll want to make sure to note before you get started.

Overview

It would be nice to live in a world where there's only one platform to worry about. One platform to code on, the same platform to test on, and the very same platform to deploy on. While some developers can make the decision to do this, many can't. If you are a developer who lives in a world where you deploy your applications on a rack of Linux or Solaris-based servers, choosing a Mac for your development is a great idea, as you have a lot of options for cross-platform development.

Let's start with some of the things that make Mac OS X a great development platform: the UNIX command line (see Figure 1, showing top and a typical man page, familiar to any UNIX programmer); the power and interoperability of open source software; powerful tools provided by Apple; and the freedom to configure and hack your system to bend it to your will. The combination of GCC, Perl, Python, Ruby, and Java covers almost every development base that you can think of. The depth and maturity of the UNIX foundation of Mac OS X, as well as the large number of tools that are shared across many other platforms like Linux and Solaris (and which are even available on Windows), means that you've got a rich toolbox for creating cross platform applications.

The Terminal and UNIX command line on the Mac Desktop

Figure 1: The Terminal and UNIX command line on the familiar Mac Desktop.

Cross-platform development is being done every day on the Mac. Many of the open source projects that you depend on, and which are shipped with Mac OS X, Linux, and Solaris, are written by developers using PowerBooks and iBooks to do their work. For example, many of the developers working on Perl, including the upcoming Perl 6, use a Mac. And if you ever find yourself at the O'Reilly Open Source Convention—a bastion of hard-core UNIX hackers if there ever was one—you'll see PowerBooks everywhere.

The reason why you see this trend is pretty clear. Not only is Mac OS X a great development platform, but it also runs mainstream tools that are in use by the rest of the corporate world. Programs like Microsoft Office and the Adobe Creative Suite, Quark XPress and QuickBooks Pro. By using Mac OS X, many developers have found that they no longer need to dual-boot between Windows and Linux systems, or maintain two different systems for their desktop use.

So, how does one go about developing software that runs equally well on a multitude of platforms? Well, it's not that difficult if you start your project out with a bit of forethought. We're going to take a look at three different ways to do it. First, we'll take a look at three dynamic languages: Perl, Python, and Ruby. Then we'll look at development with Java. Finally, we'll cover how to set up C and C++ projects for cross-platform development.

Using Perl, Python, or Ruby

From their rather humble beginnings, these three scripting languages have become very powerful application development platforms. No longer are they the domain only of system administrators who use them to whip up quick task-specific scripts; they are used to build some serious applications—especially on the server side. For example, the Moveable Type Publishing Platform is written in Perl and the Mailman Mailing List Manager is written in Python.

The best part: Any program written in one of these languages will work equally well on Linux or Mac OS X. All you have to do is pick your platform, check out your code, twiddle the configuration variables for your project if needed, and get running. The only things to be aware of are possible differences in the location of the interpreters for the various languages and the directories used for locally installed modules.

To take care of any path differences in the location of an interpreter (such as /usr/bin/python or /usr/local/bin/python), you should use env to find and launch the interpreter you want in a script's "shebang" line, as shown here:

#!/usr/bin/env python

print "Hello!"

Perl Modules

By far, the easiest way to add modules to your Perl installation is to use CPAN, the Comprehensive Perl Archive Network. It takes all of the muss and fuss out of keeping your Perl modules installed and up to date. To use CPAN, just use the cpan command line tool in Terminal:

$ cpan
cpan shell -- CPAN exploration and modules installation (v1.76)
cpan> 

If, for some reason, the module you need isn't in CPAN, you just need to make sure that it ends up on Perl's include path. To see the include path for the version of Perl currently installed on your platform, you can use the following command at the shell:

$ perl -le 'print for @INC'
/System/Library/Perl/5.8.1/darwin-thread-multi-2level
/System/Library/Perl/5.8.1
/Library/Perl/5.8.1/darwin-thread-multi-2level
/Library/Perl/5.8.1
/Library/Perl
/Network/Library/Perl/5.8.1/darwin-thread-multi-2level
/Network/Library/Perl/5.8.1
/Network/Library/Perl
.

Note that this output occurs when running the command on Mac OS X 10.3. You can place your modules into any of these directories. In most cases, however, you should probably use /Library/Perl/5.8.1.

You can use the command on any platform to discover its library path. For example, if it is run on a FreeBSD system, you might see the following:

$ perl -le 'print for @INC'
/usr/local/lib/perl5/5.8.3/i386-freebsd
/usr/local/lib/perl5/5.8.3
/usr/local/lib/perl5/site_perl/5.8.3/i386-freebsd
/usr/local/lib/perl5/site_perl/5.8.3
/usr/local/lib/perl5/site_perl/5.8.0/i386-freebsd
/usr/local/lib/perl5/site_perl/5.8.0
/usr/local/lib/perl5/site_perl/5.6.1
/usr/local/lib/perl5/site_perl/5.005
/usr/local/lib/perl5/site_perl
.

Python Modules

The installation of Python on Mac OS X is set up a bit differently than Perl. There isn't a directory in the /Library hierarchy that is set up for Python modules. Instead, the default Python module path points at the installed version of Python in the /System/Library/Frameworks/Python.framework/Versions/2.3 directory. To see the default Python module path for an installation, open up the Python shell and then print the value of sys.path, as shown here:

$ python
Python 2.3 (#1, Sep 13 2003, 00:49:11) 
[GCC 3.3 20030304 (Apple Computer, Inc. build 1495)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> print sys.path
['', '/System/Library/Frameworks/Python.framework/Versions/2.3/lib/python23.zip',
'/System/Library/Frameworks/Python.framework/Versions/2.3/lib/python2.3',
'/System/Library/Frameworks/Python.framework/Versions/2.3/lib/python2.3/plat-darwin',
'/System/Library/Frameworks/Python.framework/Versions/2.3/lib/python2.3/plat-mac',
'/System/Library/Frameworks/Python.framework/Versions/2.3/lib/python2.3/plat-mac/lib-scriptpackages',
'/System/Library/Frameworks/Python.framework/Versions/2.3/lib/python2.3/lib-tk',
'/System/Library/Frameworks/Python.framework/Versions/2.3/lib/python2.3/lib-dynload',
'/System/Library/Frameworks/Python.framework/Versions/2.3/lib/python2.3/site-packages']

This presents a bit of a problem as you typically don't want to modify anything in the /System directory. A better way to go is to use the PYTHONPATH environment variable to place all of your modules into a /Library/Python/2.3/site-packages directory that you create. Just make sure to set the PYTHONPATH environment variable. For example, you could add the following line to your .bash_profile:

export PYTHONPATH=/Library/Python2.3/site-packages

To discover the Python module path on a different environment, you can execute the same commands. Here's an example from a FreeBSD system:

$ python
Python 2.3.3 (#1, Apr 13 2004, 07:09:05) 
[GCC 2.95.4 20020320 [FreeBSD]] on freebsd4
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> print sys.path
['', 
'/usr/local/lib/python23.zip', 
'/usr/local/lib/python2.3', 
'/usr/local/lib/python2.3/plat-freebsd4', 
'/usr/local/lib/python2.3/lib-tk', 
'/usr/local/lib/python2.3/lib-dynload', 
'/usr/local/lib/python2.3/site-packages']

Ruby Modules

Ruby's module story is somewhere in between Perl's and Python's. The module path can easily be discovered by using the following command in the Terminal:

$ ruby -e 'puts $LOAD_PATH'
/usr/local/lib/ruby/site_ruby/1.6
/usr/local/lib/ruby/site_ruby/1.6/powerpc-darwin7.0
/usr/local/lib/ruby/site_ruby
/usr/lib/ruby/1.6
/usr/lib/ruby/1.6/powerpc-darwin7.0
.

For the most part, you should put your modules into the /usr/local/lib/ruby/site_ruby directory. This directory doesn't exist on the system by default so you'll have to make it yourself. Of course, you can run the same command on any platform with Ruby installed. We won't repeat the example again.

There is also a project underway to bring CPAN-like functionality to Ruby called RubyGems. You can find out more at the RubyGems website.

Keep On the Cutting Edge

The versions of Perl, Python, and Ruby installed on Mac OS X are relatively recent and typically get updated during major updates to the OS. However, if you need a feature that's only available in the latest version—or if you just like living dangerously—you can always build your own version. All three of these languages use the typical configure; make; make install commands that Unix-hands are familiar with.

A good approach is to build them into the /usr/local directory so that a software update from Apple doesn't clobber your installation. To do this, set the --prefix option when configuring, as shown here:

$ ./configure --prefix=/usr/local

Using Xcode

It may not be immediately obvious, but Xcode understands Perl, Ruby, and Python files and will highlight keywords and strings in color as well as provide indentation support and pop-up symbol navigation. All you need to do is open a file written in one of these languages in Xcode, as shown in Figure 2.

Editing a Python file in Xcode

Figure 2: Editing a Python file in Xcode.

You can also create an empty project in Xcode and add a group of Perl, Python, or Ruby files to it so that you can easily flip between them at will.

Using Java

Java has become the workhorse language for enterprise-level applications in companies near and far. The Write Once, Run Anywhere mantra certainly applies to the Mac OS X implementation of Java. The main difference between Java on Mac OS X and other systems is where the various parts of the Java runtime are located, and that every Mac OS X system comes with a full install of the JDK.

The familiar Java directory layout that developers are used to seeing is located in the /Library/Java/Home folder. Many Java-based tools and utilities need to know where this directory is. To let them know, you can set the JAVA_HOME environment variable. To make this setting permanent, you could set it in your .bash_rc file:

export JAVA_HOME=/Library/Java/Home

There's one last thing to point out. In previous versions of Mac OS X, there could only be one Java VM on the system at a time. Starting with the Java 1.4.1 release, it is possible to have two different versions of Java on one machine—but by using the above home directory you can be assured that you are always using the latest and greatest available on the system. You can find out more about this in the Java 1.4.1 Release Notes.

Extensions and JNI libraries

When you want to add JNI libraries or JARs to your Java installation, you have a few options. First, Apple recommends placing them into the /Library/Java/Extensions directory. This will enable them for every user on the system. If you just want to make a JNI library or JAR available for use by only a specific user, place it into the ~/Library/Java/Extensions directory of that user's home directory.

Otherwise, you don't have to download, install, or configure anything—it just works. Plus, Java applications developed on Mac OS X can take advantage of automatic support of multiprocessor hardware, native support for the Java Accessibility API, and the Aqua look and feel, as well as the object-oriented Cocoa framework. This means that Java applications can look and perform like native applications on Mac OS X.

Using C or C++

Building an application in C or C++ requires a bit more care. Because of the nature of compiling a C and C++ application together with the libraries available on the system, it's not a plug and play setup like using Java, Perl, Python or Ruby. But it's still manageable. In fact, just by virtue of Mac OS X using the GCC (the GNU Compiler Collection), you're already most of the way there.

Autoconf

The big task that falls onto your shoulders is to make sure your source tree is set up in a way to be built on multiple platforms. In the past, this required the creation of a lot of custom tools. Over the last few years, however, a set of tools has matured which takes a lot of work out of creating cross-platform code. These are autoconf, automake, and libtool tools, and they are what give many projects their customary configure; make; make install behavior.

In a nutshell, autoconf is a tool that tests a system to discover various characteristics that your source code needs to adapt to. The automake tool generates Makefiles that conform to a number of standards used in the UNIX cross-platform development world and simplifies the process of describing a software package. The libtool tool is an interface to the GCC toolchain that enables you to portably generate static and shared libraries.

One of the principle files that is output by configure is a config.h file. This file contains a set of #define preprocessor directives which can be used by your code to adapt to the differences between platforms. Your source files can then import config.h. Here's an example taken from a file in the Neon WebDAV library:

#include <config.h>
...
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
...
void ne_sock_exit(void)
{
#ifdef WIN32
    WSACleanup();
#endif
    init_result = 0;
}

If you want to see a full example, you should pull down the Open Source project of your choosing, run configure and then look at the config.h file that is generated as well as the source code that uses it.

To get the full scoop on how to use these tools, you should read the GNU Autoconf, Automake, and Libtool book, available online or in print form.

Use Explicit Data Types

When you're only writing for one platform, it's tempting to assume that sizeof(int) == sizeof(void *) == 4 bytes and that bitfields are always little- or big-endian. Don't do this. Keep pointers as pointers, use explicit sizes for any important integers, and macros for any externally-stored bitfields.

Pathnames

Mac OS X uses a few non-traditional idioms, like putting some system libraries in /System/Library/Frameworks instead of /usr/lib. While GCC handles most of this automatically, hardcoded pathnames could easily break. Use Makefile macros or symlinks to avoid those kinds of dependencies.

Also, you'll want to watch out for whitespace in pathnames. Other UNIX-based systems don't make a habit of including spaces in paths, but Mac OS X does. As well, you should watch out for HFS+ vs. UFS issues. HFS+ doesn't allow you to have foo.c, Foo.C, and FoO.c in the same directory. UFS does. If this kind of case-sensitivity is important to you, you can create a UFS partition or disk image and use that for your sources.

Using Xcode

Using autoconf, automake, and libtool certainly doesn't mean that you have to give up using Xcode. In order to use Xcode with your project, just create an Empty Project with a GNU Make target type using the New Project Assistant. Once you've created the project, you can add your sources to the project. When you click the Build button, Xcode will build your project using the project's Makefile. For example, Figure 3 shows a project that was set up to build Ruby.

Working with a Makefile-based project in Xcode

Figure 3: Working with a Makefile-based project in Xcode.

If you'd like to reproduce this figure, you can use the following recipe.

  1. Download the Ruby distribution from the Ruby download page.
  2. If needed, unpack the distribution. If you download it with Safari, it should have been downloaded and unpacked onto your Desktop.
  3. Open a Terminal window and cd to the Ruby distribution directory. Then run the configure script.
  4. Open Xcode and create a new project of the GNU Make type. Save it to your Ruby distribution directory.
  5. Add the Ruby sources to the project using Project > Add to Project menu.
  6. Build using the Build > Build menu or Command-B.

Using Xcode with Remote Sources

There are some cases in which you won't be able to build a project natively on Mac OS X but you still want to be able to use your PowerBook and Xcode. As unlikely as it might seem, if the machine that you need to build your project is on the network with a network-accessible filesystem and SSH, then you can do it. Here's how:

  1. Mount the filesystem that contains your code to your Mac via NFS or SMB.
  2. Open Xcode and create a new project of the GNU Make type.
  3. Add your sources to the project.
  4. Edit the Makefile target to use SSH to execute the make command, as shown in Figure 4. The trick here is to set up the arguments to ssh in the following pattern: user@hostname make $(ACTION) -C sourcedir. The -C argument tells make to change into the specified directory before executing.
  5. Set up SSH keys on both your Mac and the remote system and use a utility like SSH Keychain to manage them.
Setting up a remote build

Figure 4: Setting up a remote build.

Once you've set this up, when you build your project, Xcode will dutifully execute ssh and your build will run. The results will appear in the Build Results window and any build errors and warnings will be reported as you would expect, as shown in Figure 5.

The results of a remote build

Figure 5: The results of a remote build.

Conclusion

Even though we can't live in a world where there's only one platform to worry about, there are lots of languages and tools to help us when we do need to deploy code onto other platforms. And the Mac helps you by providing access to all of the mainstream languages and development tools to do so.

For More Information

Posted: 2004-08-30