Mac OS X includes many open source projects that contribute to the stability and robustness of the system. While Apple provides working versions of these for both PowerPC and Intel architectures, sometimes you want to build your own to tune performance or enable custom features. Plus, distributing a single binary is often preferable to keeping track of separate, architecture-specific binaries. These objectives can be accomplished by building the project as a Universal Binary, a file that contains code for both the PowerPC and Intel architectures.
OpenSSL is one such open source project. It provides an implementation of the Secure Sockets Layer (v2 and v3) and Transport Layer Security (v1), and has been included in Darwin since Mac OS X v10.0. This article shows how to use Xcode (we are using Xcode 2.2.1) to construct a make-based project that builds OpenSSL as a Universal Binary. We look at what needs to be done in order to get both the libraries and test applications to build and run properly. This should give you the information you need to start porting your own or other open source applications to Mac OS X.
You can download the Xcode project .DMG file (100KB), created for this article.
The OpenSSL Project
OpenSSL is an open source version of the Secure Sockets Layer library. You've seen the lock icon that appears in the corner of a web browser window when you connect to an
The OpenSSL project is moderately complex, but as you will see in our discussion it requires no source code changes to generate the core libraries. We will focus on how to build the project for both the PowerPC and Intel architectures using Xcode. We build the Universal Binary on a PowerPC Macintosh, but test on both PowerPC and Intel-based Macintoshes. You can also do it the other way around if you have an Intel-based Mac available. If you are new to the idea of multiple architectures, or open source projects in general, this may be a good way to get started.
The OpenSSL Project Structure
The first step is to download and unpack the OpenSSL source code archive. Double-click on the
Figure 1: OpenSSL Project Folder Hierarchy
Most open source projects use a makefile to control the build process. The makefile sets flags that instruct the compiler and linker which source files to include, what build options to use, and the type of the final binary. A discussion of makefiles is included in several ADC articles and documents that are referenced at the end of this article.
If you open the top-level makefile, you will see that it does not include platform-specific settings. In order to fill-in some of those details, you need to run the
Listing 1 contains a portion of the generated makefile from the root folder of the OpenSSL project. The comment at the very top let's us know that
Listing 1: Top-Level Makefile
### Generated automatically from Makefile.org by Configure. ## ## Makefile for OpenSSL ## VERSION=0.9.8a MAJOR=0 MINOR=9.8 PLATFORM=darwin-ppc-cc OPTIONS= no-gmp no-krb5 no-mdc2 no-rc5 no-shared no-zlib no-zlib-dynamic CONFIGURE_ARGS=darwin-ppc-cc SHLIB_TARGET=darwin-shared CC= cc CFLAG= -DOPENSSL_THREADS -D_REENTRANT -O3 -DB_ENDIAN AR=ar $(ARFLAGS) r RANLIB= /usr/bin/ranlib DIRS= crypto ssl engines apps test tools LIBS= libcrypto.a libssl.a BUILD_CMD= if [ -d "$$dir" ]; then \ ( cd $$dir && echo "making $$target in $$dir..." && \ $(CLEARENV) && $(MAKE) -e $(BUILDENV) TOP=.. DIR=$$dir $$target \ ) || exit 1; \ fi build_all: build_libs build_apps build_tests build_tools build_libs: build_crypto build_ssl build_engines build_crypto: @dir=crypto; target=all; $(BUILD_CMD) build_ssl: @dir=ssl; target=all; $(BUILD_CMD) build_engines: @dir=engines; target=all; $(BUILD_CMD)
What interests us in this makefile are the targets, specifically
Summary of the Build Sequence
The overall build sequence is this: build a PowerPC library, build an Intel library, then combine them into one library. OpenSSL also includes some test applications that we build in the final step. Below are the detailed steps. Note that we build each library (crypto and ssl) for each architecture.
This general plan may be used with other open source projects. We will use shell scripts to perform the build steps.
Creating The Xcode Project
Now that we have taken a cursory glance at the OpenSSL archive structure and the desired set of build steps, we can start the Xcode project process. If you prefer to download the finished Xcode project, use the link provided in the "For More Information" section at the end of this article. Our first step is to create a new project. In Xcode, select File > New Project.... Then, in the Assistant dialog, choose project type "Empty Project", as shown in Figure 2.
Figure 2: Create A New Xcode Project
Name the project "OpenSSL", and create it inside the OpenSSL folder, as shown in Figure 3. Note that the default Xcode behavior is to create a subfolder named after the target. We do not want that behavior here, because the build scripts are intended to run at the top level folder of the project.
Figure 3: Create The Project Directly In The OpenSSL Folder
The next step is to add targets that perform the build steps. Right-click the "Targets" group of the project, then Add > New Target... as shown in Figure 4.
Figure 4: Add A Target
Select the "Aggregate" target type, as shown in Figure 5.
Figure 5: Select A Target Type
Figure 6 shows the addition of a target named "Build PPC".
Figure 6: Name The New Target
Next, add a build phase to the Build PPC target. This phase will execute a script to setup the build environment, and create the PowerPC libraries. Right-click on "Build PPC", and select Add > New Build Phase > New Run Script Build Phase.
Figure 7: Add A Run Script Build Phase To The Target
If you double-click on Targets > Build PPC > Run Script, Xcode will display a dialog where you can enter the script shown in Figure 8 (see Listings 2 and 3 under Build Scripts below for the actual code).
Figure 8: The Build PPC Run Script Dialog
The project needs several additional targets, for building the i386 libraries, the Universal libraries, and the test applications. See Figure 9. Each of these targets is of type "Aggregate". An aggregate target has dependencies on other targets. The order in which these dependencies are listed determines the build sequence. Our OpenSSL project uses aggregate targets to divide the build process into discrete, ordered steps.
Figure 9: All Targets
Figure 10: Dependencies In The Build Universal Target
Adding the Build Scripts
Listings 2 and 3 show the scripts for building the PowerPC and i386 libraries. These scripts are included in the project download that accompanies this article. Each script configures the build environment to match the desired architecture, builds the libraries, and moves the libraries into the architecture-specific build folder.
Listing 2: The Build PPC Script
# shell script goes here BUILD_ARCH=build/ppc ./config make build_libs mkdir -p $BUILD_ARCH mv *.a $BUILD_ARCH echo "Done" exit 0
Listing 3: The Build i386 Script
# shell script goes here ARCH_DIR=build/i386 make clean ./Configure 386 darwin-i386-cc make build_libs "CC=cc -arch i386" mkdir -p $ARCH_DIR ls *.a > libnames.tmp mv *.a $ARCH_DIR exit 0
There are some minor differences between these scripts. For example,
Using lipo to Create the Universal Binaries
Listing 4: The Run lipo Script
# shell script goes here for lib in `cat libnames.tmp`; do lipo -create build/*/$lib -output $lib done exit 0
You can check the libraries after running this script. The
Listing 5: Get Info Using the file and lipo Commands
Mertz:/Volumes/Tiger/Users/asd/openssl-0.9.8a asd$ file * libcrypto.a: Mach-O fat file with 2 architectures libcrypto.a (for architecture ppc): current ar archive libcrypto.a (for architecture i386): current ar archive random library libssl.a: Mach-O fat file with 2 architectures libssl.a (for architecture ppc): current ar archive libssl.a (for architecture i386): current ar archive random library Mertz:/Volumes/Tiger/Users/asd/openssl-0.9.8a asd$ lipo -info * Architectures in the fat file: libcrypto.a are: ppc i386 Architectures in the fat file: libssl.a are: ppc i386
Now, how do we know the libraries work? To answer that question we need to build some test apps.
Testing The Libraries
OpenSSL includes a number of test files, usually just a
Listing 5: The Build Tests Script
ranlib *.a ./config make build_tests
Listing 6 shows the result of running the test named
Listing 6: Running the Cast Test Application
Mertz:/Volumes/Tiger/Users/asd/OpenSSL/test/castTest; exit In main() checkpoint 0 In main() checkpoint 0.5 In main() checkpoint 1 In main() checkpoint 2 In main() checkpoint 4 In main() checkpoint 0.5 In main() checkpoint 1 In main() checkpoint 2 In main() checkpoint 4 In main() checkpoint 0.5 In main() checkpoint 1 In main() checkpoint 2 In main() checkpoint 4 ecb cast5 ok In main() checkpoint 2 This test will take some time....123456789ABCDEF ok
The test applications provide an easy path to getting familiar with the libraries and how to use them, in preparation for incorporating OpenSSL into your own application.
You may need to change the values of
Listing 7: Change References To The Libraries in /test/Makefile
# Change these lines: LIBCRYPTO= -L.. -lcrypto LIBSSL= -L.. -lssl # to LIBCRYPTO= ../libcrypto.a LIBSSL= ../liblssl.a
Testing On Intel
If you have access to an Intel-based Mac, perhaps a Mac mini with a KVM (keyboard/video/monitor) switch, you can copy the entire project, including the built libraries, over for this part of the discussion. Otherwise, you may have to get creative, perhaps by convincing a friend or colleague to assist you with the testing. This is certainly in the spirit of the open source community!
Figure 11 shows the "About This Mac" box from an Intel-based Mac that will serve as a test platform.
Figure 11: About This Intel-Based Mac
On the Intel-based Mac, we build and run the same cast test application, except this time on the Intel-based Mac. The output in Listing 8 looks the same as when we ran it on PowerPC. Excellent!
Listing 8: Running the Cast Test on Intel
Twenty-Something:~ asd$ /Users/asd/OpenSSL/test/castTest; exit In main() checkpoint 0 In main() checkpoint 0.5 In main() checkpoint 1 In main() checkpoint 2 In main() checkpoint 4 In CAST_decrypt() In main() checkpoint 0.5 In main() checkpoint 1 In main() checkpoint 2 In main() checkpoint 4 In CAST_decrypt() In main() checkpoint 0.5 In main() checkpoint 1 In main() checkpoint 2 In main() checkpoint 4 In CAST_decrypt() ecb cast5 ok In main() checkpoint 2 This test will take some time....123456789ABCDEF ok
For More Information
Using the techniques discussed in this article, you can migrate other open source projects to the Universal Binary architecture. The sources below will help you find more specific information.