
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.
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.
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.
Figure 3: Working with a Makefile-based project in Xcode.
If you'd like to reproduce this figure, you can use the following recipe.
- Download the Ruby distribution from the Ruby download page.
- If needed, unpack the distribution. If you download it with Safari, it should have been downloaded and unpacked onto your Desktop.
- Open a Terminal window and cd to the Ruby distribution directory. Then run the
configure script.
- Open Xcode and create a new project of the GNU Make type. Save it to your Ruby distribution directory.
- Add the Ruby sources to the project using Project > Add to Project menu.
- 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:
- Mount the filesystem that contains your code to your Mac via NFS or SMB.
- Open Xcode and create a new project of the GNU Make type.
- Add your sources to the project.
- 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.
- Set up SSH keys on both your Mac and the remote system and use a utility like SSH Keychain to manage them.
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.
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
|