Wednesday, February 17, 2010

distcc, the Macintosh, the Linux box

Today I decided to get distcc working between Jess' computer and mine. There went my day...

I think my situation is fairly common. I have a Linux box that's pretty fast and Jess has a Mac laptop that's not quite as fast. I sometimes have to build Audacity on Jess' computer, and it would be nice if my computer could help with the compiling. Unfortunately this is not as easy as it should be. Here are some of the problems:

1. Apple's version of distcc isn't compatible with the normal version (Apple added some stuff to the protocol). It also has client-side restrictions on the names of compilers you can use, which are configurable, but require the compiler to be in the same location on the client and server. So you should build a normal version of distcc from the source package to install at /usr/local.

2. distcc 3.1 won't build if you just ./configure && make && make install because of a tricky 3-way incompatibility involving GCC, Python, and universal binaries.

3. Xcode likes to invoke gcc with the -x option to specify language. distcc refuses to distribute tasks using -x (Apple's version accepts it, but since it uses a different protocol we can't use it).

And how I did this.

First you need to get a couple of cross-compiling tools build on the Linux side. The good news is that you don't have to have everything for distcc, just as, gcc, and g++. For as you have to find an odcctools package somewhere. There's one here that should work. Then you need Apple's GCC package, matching the one on your Mac. Use gcc --version to get the release and build numbers and find the package on Apple's site.

To build GCC and as instructions #0-4 on this page should work (adjusting for correct target, version numbers, and sources — opendarwin.org no longer exists). It lists cc1plusobj at one point instead of cc1objplus — other than that, fine. Jess has an Intel Mac with MacOS 10.5.8 and XCode 3.1.4, for which the target is i686-apple-darwin9, GCC version number 4.0.1, and GCC build number 5493. So I had with binary names like i686-apple-darwin9-gcc-4.0.1; there is a compiler with the same name on the Mac in the PATH (this must be the case for distcc to work). Heed the directions! You will probably get compile errors, and they won't matter.

Next you need to build a normal version of distcc 3.1 for Mac. To get around the universal binary weirdness you need a non-universal install of Python. Grab a Python 2.6.x source package and the standard ./configure --prefix=$HOME/foo && make && make install cycle will give you a non-universal. $HOME/foo/python2.6. So you can cd back to the distcc source and do PYTHON=$HOME/foo/python2.6 ./configure --prefix=/usr/local && make && make install. It should build and install correctly. Once that's done you don't need that extra Python binary, so you can nuke $HOME/foo.

OK. Now time to make some little connections. First, on the Linux side, you need distcc to be able to find these new compilers. On Deb/Ubuntu you can set the search path by setting PATH in /etc/default/distcc. You should also set up which network interfaces to listen on in that file.

On the Mac side, as our reference notes in step #5, it's nice to have little shell scripts to run distcc. He puts them in /usr/local/bin, you can put them anywhere... the important thing is that his scripts don't work for me; I need $@ instead of @!. Maybe he has csh and I have bash or something. Anyway, for the g++, if your /bin/sh works like mine, you'll want:
#!/bin/sh
/usr/local/bin/distcc <your $TARGET>-g++-<your GCC version> -msse2 "$@"

Then, to account for XCode's use of gcc -x c++ instead of g++, you'll need to get a little fancy in the gcc script (this obviously won't cover all cases but it's been good enough for me so far):

#!/bin/sh
if [ "$1" = "-x" ] && [ "$2" = "c++" ]
then
shift 2
/usr/local/bin/g++ "$@" # this should point to your g++ script, wherever it is
exit
fi

/usr/local/bin/distcc <your $TARGET>-gcc-<your GCC version> -msse2 "$@"

Now you're almost ready. On the Mac, put localhost your_server in ~/.distcc/hosts (reverse the order if you want to go to the server first) and give it a test run (run your gcc script on something simple). tail -f /var/log/distccd.log on the Linux box to make sure the jobs are making it over. For distcc to distribute you need to use the -c flag (i.e. /usr/local/bin/gcc -c hello.c); otherwise, because there's a linking step included in the compile, it must be done locally. You can do /usr/local/bin/gcc -c hello.c && gcc -o hello hello.o && ./hello for a nice test.

Then in your XCode projects you can set the CC variable to the location of your gcc script, and CXX to the location of your g++ script. There might be a better way to do this, I'm far from an XCode expert. Since this bypasses XCode's normal way of working with distcc, it won't know to add extra parallel build steps. Fix this with: defaults write com.apple.Xcode PBXNumberOfParallelBuildSubtasks 6. Or whatever number works best for you there.

I'm pretty sure this is everything essential I did (I went down lots of dead-ends, including trying to use Apple's distcc). And now distcc works!

To be honest, some of the ways Apple modifies Free tools rub me the wrong way. I appreciate that it's a lot easier to interoperate with their tools than, say, Microsoft's, but Microsoft actually wrote their own compilers; they can do it however they want. Apple used GCC and distcc in such a way that interop takes way more work than it ought to. I guess that's why the Linux box is mine, and the Mac is not.