Freesurfer CMake Port

This page is mostly for documenting the process of converting the automake framework to cmake. General documentation for using cmake with freesurfer can be found here. configure.in is a massive file, with years worth of additions, so of course, this isn't a 100% conversion, but it satisfies all the requirements for building and installing a full (default) freesurfer distribution on centos and OSX.

Overview of the framework

The top-level CMakeLists.txt file is the main cmake configuration script (which replaces setup_configure, configure.in, and Makefile.am), and all subdirectories are added from here with the add_subdirectory() function. This main script is split into three parts: locating third-party packages, configuring compilation settings, and configuring freesurfer libs and programs.

third-party packages

Most of the packages required by freesurfer are located via custom "find-modules" stored in the cmake subdirectory. These find-modules expect each package to be installed under a common path defined by FS_PACKAGES_DIR. On Martinos machines, this variable automatically defaults to /usr/pubsw/packages, but external developers must provide this path manually:

cmake . -DFS_PACKAGES_DIR="/path/to/packages"

If a package is not found under FS_PACKAGES_DIR, cmake will continue to look through the default search paths. Alternative paths to package installs can also be specified with the <PACKAGE>_DIR variables. For example, to use a non-default ITK version:

cmake . -DITK_DIR="/usr/pubsw/packages/itk/3.16.0"

find modules

In CMakeLists.txt, packages are located by using the find_package() function. Some common, modern projects, like Qt, VTK, ITK, Boost, etc..., distribute their own cmake config files, so locating the package's include directory and libraries is a fairly straightforward, automatic process:

find_package(ITK HINTS ${ITK_DIR} REQUIRED)

In this example, if ITK is found by cmake, then ITK_FOUND is set to true, and ITK_INCLUDE_DIR and ITK_LIBRARIES are set accordingly. This syntax and the variables generated by find_package() all follow the same general pattern across packages.

Unfortunately, most freesurfer dependencies don't ship with cmake configuration files, so we have to create our own find-modules... fortunately, this isn't too difficult. Most find-modules look something like this, where we're only searching for an include directory and one or two libraries.

external developers

In general, the goal is to distance ourselves from distributing the pre-built package tarballs since they are difficult to maintain across multiple platforms. The packages/build_packages.py script is a potential alternative to the pre-built archives - it's a utility script to help external developers build freesurfer dependencies on their own.

The packages configured within build_packages.py are built using the tarballs and buildscripts stored in the packages/source dir and will get installed to a destination directory specified on the command-line:

build_packages.py "/path/to/install/destination"

This script loops through each package in the pkgs list variable, extracts the associated tarball to destination/package-name/version/src, and runs the package build script. The individual build scripts (like this one always expect a single commandline argument that points to the desired install directory. After each successful package build, the checksum of the tarball is saved to the src dir - this way, the package is only rebuilt when the source code has been modified. Once the dependencies are compiled and installed, developers can then point FS_PACKAGES_DIR to their local install directory.

For now, we should definitely still offer the prebuilt packages since it's just easier for most developers and we need them for the travis builds anyway.

removing packages from source

The non-FS libraries (like jpeg, glut, xml2, expat, minc, netcdf, etc...) that used to be built within freesurfer no longer get built by cmake - these packages are now expected in FS_PACKAGES_DIR (/usr/packages/pubsw). THis has a few advantages: we can easily test/swap out different versions of packages, and we can test out different compilers/settings without having to worry about modifying third-party source code and compiling packages differently than the way their developers intended to. Doing this, we can build successfully with gcc5 (at least on mac), and hopefully this helps with Bevin's current push to compile all c-code with g++ (since the external packages are a big roadblock).

Validation

On my machine (topaz), the cmake configuration only takes a few seconds, and the multithreaded buildtime is about half:

automake

cmake

configuration

1:20

0:15

make -j8

10:20

4:48

install

2:15

2:00

recons

I built the same version of dev with automake and cmake and ran buckner40 recons (on sulc) with both distributions. Both tests produced the exact same results for each subject, and the average cmake-distro runtime was actually about 50 minutes faster (still working on figuring out why - could just be a coincidence since there was a lot of background processing happening on sulc).

compiling

As I ported the makefile configurations, I manually compared each gcc/g++ command between automake and cmake builds using a script that sorted and matched the cmdline flags/arguments. Of course, this isn't a completely error-free way of testing for differences, but I think it was a fairly meticulous/safe way to go about it

libraries

I wrote a quick script to traverse through each binary in the automake and cmake installs and compare differences between the linked libraries. All binaries are the same with the exception of a few:

TODO

Questions