Universal builds are new with Tiger. 64bit builds are new with Leopard (though there is a limited 64bit capability in Tiger).
By default, up thru Leopard, GCC builds for the architecture you are building on, and always 32bit, even on 64bit processors. Starting with Snow Leopard, GCC will build 64bit by default on 64bit processors.
The basic method of telling the compiler and linker to build for a different architecture is the -arch flag. The 4 architectures you can build for are ppc, i386, ppc64 and x86_64. The first two are 32bit, and last two are 64bit.
GCC accepts multiple -arch flags, and it will build a "fat" binary of all the architectures specified. This will build a quad-arch binary that will run on any Mac and system (within limits, as noted later):
-arch ppc -arch i386 -arch ppc64 -arch x86_64
One additional flag that may be needed is the -isysroot flag. You specify a SDK path. This basically changes the whole "root" of paths where all libraries and header files are found. There are SDKs for each system version. This flag is required when building ON a PPC architecture FOR an Intel architecture and the system is Tiger, because the system is not universal on PPC OSX Tiger. So, in this case this is needed:
-isysroot /Developer/SDKs/MacOSX10.4u.sdk
Another case where you would need it is when building on Leopard, but you want a 32bit binary that is compatible with Tiger, etc.
It doesn't hurt to use the SDKs, but in Tiger (Xcode 2.4 and earlier) there is a bit of a problem – since the SDK effectively changes the root path, "/" translates to "/Developer/SDKs/MacOSX10.4u.sdk/", anything not in the SDK will not be found during compilation and linking. /usr/local is not in the SDKs. /Library/Frameworks is not in the SDKs. To fix this, you need to create symlinks in the SDKs to these paths, and any others you may use to install software (ie I use /Users/Shared/unix for static libraries).
ie, for the 10.4u SDK (substitute 10.3.9 for that SDK):
sudo ln -sf /usr/local /Developer/SDKs/MacOSX10.4u.sdk/usr/local sudo mkdir -p /Developer/SDKs/MacOSX10.4u.sdk/Library sudo ln -s /Library/Frameworks /Developer/SDKs/MacOSX10.4u.sdk/Library/Frameworks
Apple mostly fixed this in the Leopard Xcode 3.0, and in Xcode 2.5. /usr/local/lib and /Library/Frameworks are in the SDKs. But, /usr/local/include is not, so this must be symlinked into the SDKs or some compilation may fail. Same for Snow.
sudo ln -sf /usr/local/include /Developer/SDKs/MacOSX10.4u.sdk/usr/local/include sudo ln -sf /usr/local/include /Developer/SDKs/MacOSX10.5.sdk/usr/local/include sudo ln -sf /usr/local/include /Developer/SDKs/MacOSX10.5.sdk/usr/local/include
Most of the time, with configure-based packages, these flags can easily be added using environment variables common to autotools-based configure scripts, and most others as well. First, and possibly the only one needed, is CFLAGS. If there is C++ source, CXXFLAGS may be needed, but often CFLAGS is used for C++ source. Sometimes a separate LDFLAGS may be needed, but usually CFLAGS is also reused here.
Before running configure, just set these in the Terminal:
export CFLAGS="-arch ppc -arch i386 -isysroot /Developer/SDKs/MacOSX10.4u.sdk"
or:
export CFLAGS="-arch ppc -arch i386 -arch ppc64 -arch x86_64 -isysroot /Developer/SDKs/MacOSX10.5.sdk"
An important configure flag is the –disable-dependency-tracking flag. Always use this if it is available – those "dependency-tracking" flags can't handle multiple archs.
A potential problem In Tiger with Xcode 2.4 and earlier is that LD doesn't accept multiple -isysroot flags. In autotools-based software, CFLAGS and LDFLAGS are often both added to link commands, so testing in configure fails (and compilation would fail also). This is not a problem in Xcode 2.5.
Configured builds often have a configured endianess, instead of using runtime endian detection. For a single-arch build this is not a problem. But with multi-arch builds this is a problem, because configure detects the architecture you are building on, so the opposite arch will build with the wrong endianess. This can be handled by editing the resulting config.h (or whatever it is named) and conditionalizing the endian setting on __BIG_ENDIAN__ or __LITTLE_ENDIAN__ – these are compiler-defined depending on the arch flag used, and it works because headers and source files are processed once per architecture. WORDS_BIGENDIAN is the usual definition used, but it may be something else. This is what the new setting would look like (I use __BIG_ENDIAN__ because Macs started on big-endian processors):
#ifdef __BIG_ENDIAN__ #define WORDS_BIGENDIAN 1 #else #undef WORDS_BIGENDIAN #endif
You can also conditionalize other configured definitions this way that change depending on the PPC or Intel processor used. Though it would be more proper to use the processor definitions (the same arch names encased in double-undersores, ie __i386__).
Some sources are ancient and assume that OSX = PPC.
There is only minimal support for 64bit builds in Tiger – only libSystem is 64bit-enabled, so only basic CLI programs can run in 64bit mode on Tiger. It's not worth the hassle, so I ignore it.
64bits has an effect on a few basic data types, so must be considered. These are also commonly configured items, and can be handled in a 32+64bit build in the same way as endianess. The compiler definition to use here is __LP64__. Affected data types include long, size_t, ssize_t, register_t, vm_offset_t and vm_size_t. In all cases, they are 4 bytes on 32bit architectures and 8 bytes on 64bit archs.
For example, long (and its partner unsigned long) might be handled like so:
#ifdef __LP64__ #define SIZEOF_LONG 8 #define SIZEOF_UNSIGNED_LONG 8 #else #define SIZEOF_LONG 4 #define SIZEOF_UNSIGNED_LONG 4 #endif
In many cases data type sizes are not handled by configure (and sometimes by both), but directly in the source. These must be found and dealt with. ie a variable definition that must be 32bits, regardless of the processor, is currently defined using long: instead, either use int, or conditionalize the definition.
… Does not exist. Carbon is deprecated in Leopard. This is understandable, since Carbon is really a transition API from Mac OS 9. But Apple seems to have a mixed definition of "deprecated" in this case – instead of (roughly) "existing and operational, but discouraged from use, and going away in the future", here it's "the normal deprecated, for 32bits, but non-existent in 64bits because it never existed in the first place". Apple should have deprecated it in Tiger.
Any software that uses Carbon will not build 64bits in Leopard until the developers update to use Cocoa or POSIX APIs. This includes Qt (used in Qgis and OSSIM), Python and Perl. As of v4.6, Qt is fully 64bit. For Python and Perl, on Leopard, actually the core libraries are 64bit, but the executables are 32bit because the GUI extensions depend on Carbon. You could build just the executables 64bit and run 64bit python scripts, as long as they don't use any Carbonized extensions, or skip it and stay 32bit since there is no ready way to run them 64bit anyways. Snow Leopard has an almost completely 64bit Python 2.6, only wxPython is still 32bit. Python 2.5 on Snow is still the mixed 64bit lib + 32bit exe.
Most packages use GCC for linking the final executable or library. It takes care of passing on the linking to LD, and splitting multiple architectures to link and rejoining them.
LD can't handle multiple architectures, that is, it can't create a fat binary. There are rare packages that use LD directly for linking (ie one componenet of MySQL) or intermediate object files (ie Postgres). There are two possibilities.
libtool – if LD is used only for linking an library, libtool can be substituted for LD. This is /usr/bin/libtool, not the libtool commonly included with source packages. The basic usage is:
/usr/bin/libtool --dynamic|static -o [output.lib] [object files...]
Choose dynamic or static depending on the library being created.
lipo – any other case must use lipo. This means building binaries for each arch (either with copies of the source or with out-of-source builds), and running lipo on each binary created to merge them together:
lipo -create [arch1 binary] [arch2 binary] [...] -output [merged binary]
64bit builds are not backwards compatible with earlier OSX versions (the limited 64bit support in Tiger doesn't count), so there is no need to worry about that case. But the system will attempt to run a 64bit binary on Tiger if there is a 64bit processor (ALL PPC processors, Intel Core 2 Duo or Xeon).
For 32bits, the main control of backward compatibility is the SDK used. The simplest way to make a build compatible with an earlier OSX version is to use that SDK version.
It is possible to use the current SDK to make something backward compatible, but it's more of a hassle for little or no gain.
There is also a companion environment setting for compatibility – MACOSX_DEPLOYMENT_TARGET. This affects header preprocessing. You generally set this to the lowest system version you want to support. In Tiger and Panther, it's set to 10.3 (I think, maybe an even earlier version). Starting with Leopard it defaults to the same as the SDK version. Even so, I like to make sure and always set it in the shell before configuring and compiling (match the minor version you want for x):
export MACOSX_DEPLOYMENT_TARGET=10.x
It's also good to set if you have scripts that need it. The default is set only as a header macro, and is not available in the shell environment.
If you are aiming for maximum features and performance for each OSX version, your best bet is to build separate binaries for each version.
With the way Python 2.5 is built on Snow, anything distutils-based can't be compiled to be compatible with Leopard's Python 2.5. The main problem is that it refuses to configure. There is no technical problem otherwise with compiling extensions that will work on Leopard, ie you can manually compile sources into an extension with no problem.
Normally, GCC compiles and links everything sequentially. Like anything that run sequentially, this only uses a single processor on the Mac, so large projects and quad-arch builds can take a long time.
Apple's GCC 4.0, used for Tiger and Leopard, has a built-in limited form of parallel compilation. When multiple arch's are specified, it will split off processes for each arch in the background and join them together when all are ready. It's a transparent process, you get a fat binary in the end.
But, Apple dropped that feature in their GCC 4.2, used in Snow Leopard
. GCC now compiles each arch sequentially, using a single process. The only other alternative is to use make's jobs option, -j n. This will tell make to split targets into multiple processes. Individual items are still processed sequentially internally (multiple archs), but more can be processed at once, utilizing more processors. A few things to be aware of:
– It's only for make. Python compilation thru distutils won't be affected, since it doesn't use make. cmake should be fine, since cmake is just for generating the makefiles, and make still does the work.
– It's a manual setting – you must specify the number of jobs to split into. Generally, use the number of processors/cores on your Mac. Hyperthreading on the new i* processors may seem like more processors, but may cause trouble, I need to test more. Another gauge to use that might work better is the number of archs you are compiling.
– Makefiles need to be parallel-aware. They need to say what is safe to split into parallel jobs, and what absolutely requires that some other dependency is met first, that may have not completed yet in another job. If you get make errors on something that normally works, reduce or eliminate the number of jobs. I will identify which packages I've had success with parallel compilation.
The parallel option is only needed in the compilation stage, and shouldn't be used for install targets.
Note that you could still use make's parallel option in Tiger and Leopard, especially if you are compiling for a single arch.