Saturday, January 5, 2008

Switching Finks

One of the open-source projects I contribute to is Fink, a package manager for OS X; if you've used apt-get or yum on Linux, it provides a similar facility, allowing you to install, say, GnuPG by running fink install gnupg. It installs things into its own directory tree, rooted at /sw by default, to avoid interfering with things shipped by Apple (/, /usr) or manually installed by the user (/usr/local.) That is, if you have Fink installed, your system will have /sw/bin, /sw/lib, /sw/etc, /sw/share/man, &c.

So that you can run things installed in these nonstandard locations, Fink provides some shell commands in /sw/bin/init.sh which edit environment variables like PATH and MANPATH to include the /sw/* directories. Most Fink users have . /sw/bin/init.sh in their ~/.profile, so these commands will be invoked when their shell starts.

Having my shell automatically pull in Fink at startup doesn't work for me, though. It's important to me to have a clean environment available. For instance, when I'm contributing to non-Fink open-source projects, trying to help someone who doesn't have Fink installed troubleshoot something, or submitting a bug report for a program that interacts with other programs where I have the Fink version installed, but Apple ships a different version with the system. (Note that this is only an issue if program A interacts with program B by invoking it as a standalone process without using an absolute path.)

Also, as a Fink developer, I actually have multiple Fink installations at different paths, and I only want one loaded at a time; I don't want to activate /Volumes/SandBox/fink/dev-sw in an environment where /Volumes/SandBox/fink/sw has already been pulled in!

It's much easier to pull Fink stuff in later when I need it than to undo the changes that /sw/bin/init.sh makes to my environment. My solution for making it easy to activate a particular Fink installation was to add the following to ~/.bashrc:

if [ -n "$SW" ]
    then export CFLAGS="-I$SW/include"
    export LDFLAGS="-L$SW/lib"
    export CXXFLAGS="$CFLAGS"
    export CPPFLAGS="$CXXFLAGS"
    export ACLOCAL_FLAGS="-I \"$SW/share/aclocal\""
    export PKG_CONFIG_PATH="$SW/lib/pkgconfig"
    export PS1="[$SW_DISPNAME \\W@$(hostname -s)]\\\$ "
    . "$SW/bin/init.sh"
    export PATH=~/bin:"$PATH"
fi

What this does is arranges it so that if I start a new shell with SW and SW_DISPNAME set, it'll pull in the Fink installation rooted at the directory $SW and put $SW_DISPNAME in my shell prompt so that I can see which environment I'm using. The extra environment variables before . $SW/bin/init.sh set things up so that if I compile things by hand, they'll find and link against Fink-installed libraries; the PATH setting at the end is because init.sh places the Fink bin directory at the front of the PATH, and I want my personal bin directory to come before it.

I run the following script (saved as ~/bin/finkinit) when I want to pull in Fink:

#!/bin/bash

FINK=${1:-main}

case "$FINK" in
    main)
        SW=/Volumes/SandBox/fink/sw
        SW_DISPNAME="fink"
        ;;
    dev)
        SW=/Volumes/SandBox/fink/dev-sw
        SW_DISPNAME="fink-dev"
        ;;
    *) echo "Unknown fink install '$FINK'" >&2 ; exit 1
esac

export SW SW_DISPNAME
exec /bin/bash

This gives me a subshell with Fink turned on, which I can exit out of when I want to return to a clean environment. If I run it as finkinit, I get my main Fink installation, or I can run finkinit dev to get an alternate Fink.