Wand Of Rust

A while back, I started a project to wrap the ImageMagick MagickWand API in a layer of rustic (tm) abstractions. By a while, I mean way back in June of 2013. I made a few commits to the project, then got distracted by my career (as tends to happen) and the project languished. It’s now been brought to my attention that the library no longer compiles and the binding tool that I used won’t even generate the base bindings any more!

In this post, I will provide some notes on how to setup a rust installation and generate bindings for C libraries. Hopefully this will help me be more responsive to Wand of Rust requests in the future!

Step One, Enter the World of Rust

The first step to getting things rolling is to get the most recent version of the rust language built and installed on your system. These instructions are for Linux, though they should work for other UNIX-like operating systems. If you are using windows as your development platform, go away.

The official Rust site has documentation on the process of getting started, which I will somewhat parrot here.

  1. Head over to the rust git repository and clone the master branch.
  2. Make sure your system has the following prerequisites:
    • g++ 4.4 or clang++ 3.x (on aptitude as clang)
    • python 2.6 or later (apt package python2.7)
    • perl 5.0 or later (probably already on your system)
    • gnu make 3.81 or later (likely already installed - see build-essential package)
    • curl (come on… I know you have curl installed)
  3. Once the rust repository has finished synchronizing, cd into it and do the usual dance:
  4. ./configure (this can take several minutes)
  5. make && make install (this can easily exceed an hour)

Hopefully everything went smoothly with the build steps above. This process can take some time, as the compiler and standard library are non-trivial pieces of software.

After the installation finishes, you should now have a rustc, rustdoc, and rustpkg command on your path.

Step Two, Fetch the ImageMagick library

Since we are working with bleeding edge everything, why stop at the wrapped library? :-)

The version that I found already installed on my system was 6.6.9-7, already nearly 18 months old.

The latest version of ImageMagick at the time of this writing is 6.8.8-1, so head over to the build from source page and follow along with the directions. This component built easily for me; hopefully you have an equally pleasant experience. Be sure to take note of the ldconfig step, as you may have an older version of the libraries already present in the library cache.

Verify that everything installed properly by compiling the example program listed on the MagickWand C API page, listed here for convenience:

#include <stdio.h>
#include <stdlib.h>
#include <wand/MagickWand.h>

int main(int argc,char **argv)
{
#define ThrowWandException(wand) \
  { \
    char \
    *description; \
    \
    ExceptionType \
    severity; \
    \
    description=MagickGetException(wand,&severity); \
    (void) fprintf(stderr,"%s %s %lu %s\n",GetMagickModule(),description); \
    description=(char *) MagickRelinquishMemory(description); \
    exit(-1); \
  }

  MagickBooleanType
    status;

  MagickWand
    *magick_wand;

  if (argc != 3)
  {
    (void) fprintf(stdout,"Usage: %s image thumbnail\n",argv[0]);
    exit(0);
  }
  /*
     Read an image.
   */
  MagickWandGenesis();
  magick_wand=NewMagickWand();
  status=MagickReadImage(magick_wand,argv[1]);
  if (status == MagickFalse)
    ThrowWandException(magick_wand);
  /*
     Turn the images into a thumbnail sequence.
   */
  MagickResetIterator(magick_wand);
  while (MagickNextImage(magick_wand) != MagickFalse)
    MagickResizeImage(magick_wand,106,80,LanczosFilter,1.0);
  /*
     Write the image then destroy it.
   */
  status=MagickWriteImages(magick_wand,argv[2],MagickTrue);
  if (status == MagickFalse)
    ThrowWandException(magick_wand);
  magick_wand=DestroyMagickWand(magick_wand);
  MagickWandTerminus();
  return(0);
}

Please note that the build instructions on the ImageMagick website did not work for me. The instructions listed there used pkg-config to generate the appropriate c flags, but this left undefined references to the MagickWand function calls. Instead, you will want to build the test program above with the following:

    cc `MagickWand-config --cflags --cppflags` wand.c `MagickWand-config --ldflags --libs`

If the program successfully builds and prints usage without a coredump, things are good!

Step Three, Fetch the rust-bindgen Repository

Head over to the rust-bindgen github project page and clone the repository.

Unfortunately, the version of libclang that is packaged in the ubuntu repositories is too old to build rust-bindgen against. You will know this problem if attempting to build yields this error.

The outdated version of Clang means that we must venture into frightful territory…

Step Three and a Half, Build libclang From Source

God help us…

To begin with, head on over to the official Clang Getting Started Guide. Follow these directions precisely. I chose to install the optional Clang Tools repository as well.

I also hit this problem when running configure on the llvm project:

rdahlgren@charon: pts/2: 0 files 8.0Kb$ ../llvm/configure 
checking for clang... clang
checking for C compiler default output file name... a.out
checking whether the C compiler works... yes
checking whether we are cross compiling... no
checking for suffix of executables... 
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether clang accepts -g... yes
checking for clang option to accept ISO C89... none needed
checking for clang++... clang++
checking whether we are using the GNU C++ compiler... yes
checking whether clang++ accepts -g... yes
checking how to run the C preprocessor... clang -E
checking whether clang works... no
configure: error: Selected compiler could not find or parse C++ standard library headers.  Rerun with CC=c-compiler CXX=c++-compiler ./configure ...

For some reason, the configure script didn’t know to look in /usr/bin for g++. Go figure.

Anyway, to fix this problem, just explicitly set the C++ compiler:

$ CXX=/usr/bin/g++ ../llvm/configure

With this in place, things should configure correctly. Continue following the directions from the Getting Started Guide. While the instructions don’t mention it explicitly, be sure to make install after the build finishes. This installs static libraries in a system-wide location (/usr/local/lib/libclang*.a), but does not place the shared libraries anywhere. There is probably a make command or a configure flag to fix this, but I chose to just copy the .so from the build directory:

$ cd llvm-build-dir/
$ cp ./Debug+Asserts/lib/libclang.so /usr/lib/libclang.so
$ ldconfig

Note that this is probably poor practice.

Now that we have a modern version of libclang.so on our system, we should be able to build rust-bindgen without incident!

$ cd $WHEREVER_YOU_CLONED_RUST-BINDGEN
$ rustc bindgen.rs

Step Four, Generate the Bindings!

We’ve come a long way dearest reader, and now the time for celebration is upon us! Let’s generate the base bindings for the MagickWand library.

First, head over to wherever you placed the bindgen binary generated above. The bindgen utility takes a number of flags, nearly all of which we will be using.

For the impatient, here’s the final command we use:

    $ ./bindgen `MagickWand-config  --libs` /usr/local/include/ImageMagick-6/wand/MagickWand.h `MagickWand-config --cflags` -I/usr/local/lib/clang/3.5/include/ -allow-bitfields > bindings.rs

Now for the curious, a bit of explanation as to what all this means. The first thing we use is the MagickWand-config program to generate the apropriate library commands as well as the include paths. This gets around a platform portability issue in which ImageMagick libraries have different naming conventions. See this issue for more details on that. Next, we specifically tell clang where its libraries are. By default, clang knows to search in the appropriate location, but since we are using MagickWand-config to specify alternative include paths we need to provide it ourselves. You’ll want to update this if your clang installation is non-standard.

Finally, we specify the header file we would like to generate the bindings for (the MagickWand API in this case) and indicate that bitfields are allowed.

With that, the bindings have been generated!