Satisfy MacPorts Dependencies Locally

Introduction

Like many Unix geeks, I have software installed that I’ve built manually from source. A good example is my post on compiling django; a number of the relevant dependencies were built in /usr/local/src/ and installed in /usr/local/. I also like using package managers, because if I’m not doing any customization (and the package is common and not hard-to-find), I want to just get the latest version and slap it in the right place. The conflict between the two methodologies is when a managed package depends on software that is already installed on your system, either part of the default configuration (OS X ships with a fair bit of Unixy software included, especially if you install the Dev Tools, although not always a “standard” or particularly recent version) or custom-built.

I recently dumped Fink for MacPorts; while I’ve used Fink for a long time, since an early version was available for Mac OS 10.2 Jaguar in fact, it’s just gotten in a messy state maintenance-wise. I’ve been familiar with apt since using Debian-based systems at the SCCS, but the mish-mash of binary and source items, the preponderance of out-of-date packages, and the apparent need to install 70 metric boatloads of GNOME just to satisfy a few dependencies was frustrating. Of course, MacPorts has its own weaknesses, as do almost all package managers; in particular, none of them seem to be able to track whether a package was installed explicitly by the user or merely to satisfy a dependency. My opinion is that the latter should get uninstalled when all of its dependents are uninstalled, but no package manager seems to agree with me on that. A rant on that probably merits a separate post.

Below the cut is a rough step-by-step guide to creating a local portindex and creating portfiles for your manual dependencies. Note that most MacPorts users would tell you this is a terrible idea, and you should just install all the port dependencies, but I already put the effort into these custom from-source builds and I just want to use them without duplicates getting dropped all over my hard drive.

Create Local Portfile Repository

These instructions are based on Section 4.6 of the MacPorts Guide.

  1. Create a directory that will serve as your portfile repository; I used /usr/local/ports/
  2. Add this directory to your MacPorts configuration. For me, that meant editing /opt/local/etc/macports/sources.conf to add the line file:///usr/local/ports/ before the standard rsync line (for priority)
  3. Update the port index. For me, I needed to run sudo portindex in /usr/local/ports/ because of the way I have permissions set.

Now, if you use any port command, it will look at your local source first.

Create a Placeholder Portfile

For this example, I’m focusing on installing the port phpmyadmin, but satisfying its runtime dependencies on MySQL and PHP using local builds.

  1. Check the dependencies of your target port: port deps phpmyadmin
    phpmyadmin has runtime dependencies on:
    	mysql5
    	php5
  2. For each dependency, look it up using MacPorts search, and then click on the link to view the checked-in portfile. In general this link will have the form http://trac.macports.org/browser/trunk/dports/CATEGORY/PORT/Portfile, where CATEGORY and PORT are the relevant names of the package you’re trying to replace
  3. Now you need to create a local placeholder portfile for each of these packages; in the port repository you created above, you’ll need to create a subdirectory for the CATEGORY and then open a file named PORT in that directory in your favorite text editor. For example:
    • cd /usr/local/ports/
    • sudo mkdir -p databases/mysql5
    • sudo touch databases/mysql5/Portfile
    • sudo emacs databases/mysql5/Portfile
  4. We only need a few variables in the portfile to trick MacPorts into accepting our dependency, some of which can be copied from the real portfile. Note that I added a mention of “placeholder” to the description, changed the version to match what I built locally (which is a newer version than what’s available in MacPorts), and added some information about the build. We also need to make MacPorts do a no-op for all port phases. Here’s what mine looks like:
    PortSystem              1.0
    name                    mysql5
    version                 5.1.30
    homepage                http://www.mysql.com/
    categories              databases
    platforms               darwin
    distname                mysql-${version}
    
    description \
        Placeholder for multithreaded SQL database server
    
    long_description \
        MySQL is an open-source, multi-threaded SQL database \
        with a command syntax very similar to mSQL. \
        \
        This is a placeholder built from source in /usr/local/src/mysql-5.1.30/ \
        on 2009.01.27 using the options:\
        \
        ./configure --prefix=/usr/local/mysql --with-extra-charsets=complex \
        --enable-thread-safe-client --enable-local-infile --disable-shared \
        --with-unix-socket-path=/usr/local/mysql/run/mysql_socket \
        --with-mysqld-user=_mysql --disable-dependency-tracking
    
    fetch {}
    checksum {}
    extract {}
    patch {}
    configure {}
    build {}
    test {}
    destroot {}
    install {}
    activate {}
  5. After creating and editing the relevant portfiles(s), you need to regenerate the portindex as above.
  6. If you run port search mysql5, you’ll see both the real port and your placeholder listed with the short description, like this:
    mysql5 @5.0.83 (databases)
        Multithreaded SQL database server
    
    mysql5 @5.1.30 (databases)
        Placeholder for multithreaded SQL database server
  7. If you run port info mysql5, you’ll get a warning about multiple definitions, but see the long description of your custom placeholder:
    Warning: Found 2 port mysql5 definitions, displaying first one.
    mysql5 @5.1.30 (databases)
    Variants:    universal
    
    MySQL is an open-source, multi-threaded SQL database with a command syntax very
    similar to mSQL. This is a placeholder built from source in
    /usr/local/src/mysql-5.1.30/ on 2009.01.27 using the options: ./configure
    --prefix=/usr/local/mysql --with-extra-charsets=complex
    --enable-thread-safe-client --enable-local-infile --disable-shared
    --with-unix-socket-path=/usr/local/mysql/run/mysql_socket
    --with-mysqld-user=_mysql --disable-dependency-tracking
    Homepage:    http://www.mysql.com/
    
    Platforms:            darwin

Undoing the Mess

Pretty simple. Just uninstall the placeholder you created, remove its file from your local ports, rebuild the portindex, and reinstall the “real” package (or just leave it uninstalled, depending on what you’re trying to do). Alternatively, uninstall all the placeholders you created, and remove the offending line from your sources.conf before continuing.

Conclusion

I’ve only tested this with runtime dependencies so far, not build dependencies. I think the general idea is sound, but as I said, it’s probably a terrible idea. I’m curious if you can extend this process to get any other ports to install using system or local dependencies. I’d also welcome any suggestions for a better way to achieve or similar result. Feel free to just call me misguided and mad, as well!

Tagged with: , , , , ,
One comment on “Satisfy MacPorts Dependencies Locally
  1. stepleton says:

    For me, all software compiled from source that isn’t part of a distribution goes into ~/usr. It might be different if I were installing server software like django, or if my computer had any other users besides myself, but neither is the case. With this strategy, I don’t have to type sudo over and over, and make install can’t accidentally do something really dumb (e.g. put stuff in /usr/bin).

    I also recently switched from Fink to MacPorts. It was a bit disenchanting to watch it compile Perl as a dependency to some other port (why can’t you just use the OSX one?), but the up-to-date packages are worth it.

Nurd Up!