Friday, 14 February 2014

C libraries and the runpath

This was prompted by a posting on comp.lang.ada; under Mac OS X, if you write a binding to a C library that's in a non-standard place, it's tricky to link against the binding so that the C library is found at run time.

The first thing is to create a simple C library libbar. The function bar() doubles its parameter:

   int bar(int x) {
     return x * 2;

and the library's built and installed with the makefile

   libbar.dylib: bar.c
           gcc -fPIC -c bar.c
           gcc                                     \
             -dynamiclib                           \
             -flat_namespace                       \
             -undefined suppress                   \
             bar.o                                 \
             -o libbar.dylib                       \

   install: libbar.dylib
           cp $< $(HOME)/local/lib

The line in red makes libbar.dylib accessible via the runtime path (see the dyld(1) man page, last few paragraphs, for more detail).

Next, create the Ada binding. In this case, it's a single Ada spec:

   package Foo is
      function Bar (X : Integer) return Integer;
      pragma Import (C, Bar, "bar");
   end Foo;

which is built using a GNAT project file (using gprbuild, not gnatmake; and remember to use the -p switch so that the necessary directories are created for you if needed):

   project Build_Libfoo is
      for Library_Name use "foo";
      for Library_Kind use "relocatable";
      for Library_Dir use "lib";
      for Library_Src_Dir use "include";
      for Library_Interface use ("foo");
      for Source_Files use ("");
      for Object_Dir use ".build";
   end Build_Libfoo;

You supply a different project file for users of the binding:

   library project Libfoo is
      for Library_Name use "foo";
      for Library_Kind use "relocatable";
      for Library_Dir use "lib";
      for Source_Dirs use ("include");
      for Externally_Built use "true";
      package Linker is
         for Linker_Options use
            external ("HOME") & "/local/lib",
            "-Wl,-rpath," & external("HOME") & "/local/lib"
      end Linker;
   end Libfoo;

in which Linker'Linker_Options is to be used when a main program is linked against libfoo. Again, there's an rpath option, but this time it tells the linker to include the named path in the runtime search path.

Now to build a program that uses the binding:

   with Ada.Text_Io; use Ada.Text_Io;
   with Foo;
   procedure Foo_Test is
      Put_Line ("Foo.Bar (2) => " & Foo.Bar (2)'Img);
   end Foo_Test;

using a project file:

   with "libfoo/libfoo";
   project Foo_Test is
      for Main use ("foo_test.adb");
      for Source_Files use ("foo_test.adb");
      for Exec_Dir use ".";
      for Object_Dir use ".build";
   end Foo_Test;
(normally libfoo.gpr would be on $ADA_SEARCH_PATH, but in this case the subdirectory is named explicitly).

Source code is available at Dropbox.

Edit 6.iii.14: corrected path in install target in libbar/Makefile

No comments:

Post a Comment