Shared

Building and Using Shared Ada Libraries on Mac OS X

Download a gzip'd tar file containing the code here.

Here is an example Makefile to build and use shared Ada libraries on Mac OS X.

Building

The make target named shared contains the steps to build a shared library. After deleting any leftover .ali file, the library gets compiled with the -c option, which skips the bind and link phases. Then Apple's libtool creates a dynamic library using the .o file. It also adds the names of any other dynamic libraries needed to resolve references at runtime. Note how the -install_name option allows for a relative path using the @executable_path symbol. After removing the unneeded .o file, the .ali file is marked read-only. This causes subsequent invocations of gnatmake to treat it as a library. Without this step, gnatmake would insist on compiling & binding the file each time, defeating the purpose of the shared library. Finally, the full pathname of each required library is set with install_name_tool. This precludes having to set the environment variable DYLD_LIBRARY_PATH, discussed further below.

Using

Using the shared library is more familiar. Focusing on the target main, the essential step is passing the shared library name among the -largs. Note that -dead_strip is used to strip the object file. It is instructive to compare the stripped executable with the statically linked debug target: 17K v. 584K on ppc; 15K v. 328K on x86. Again, install_name_tool is used to edit the path for both the new shared library, as well as the GNAT run-time shared libraries. The list of required run-time libraries, stored in the variable SHARED may be extended to include other libraries stored in the ADALIB path.

Testing

The test target puts it all together by cleaning, building and running the example. It uses nm to ensure that the shared routines didn't get statically linked by accident, and then uses otool to verify the shared library paths.

Shared run-time

The -shared option of gnatbind may be used to link against the shared GNAT run time libraries, libgnat & libgnarl. The `gnatls -v` command shows the corresponding default object search path, but the shared libraries (libgnat & libgnarl) don't have the correct installed names. The usual approach is to specify the DYLD_LIBRARY_PATH:

$ export ADA_LIB = /usr/local/ada-4.3/lib

$ export DYLD_LIBRARY_PATH = ${ADA_LIB}/gcc/i686-apple-darwin9/4.3.4/adalib

Alternatively, one may change the installed names to match the default path provided automatically in the gnatlink phase of gnatmake:

cd /usr/local/ada-4.3/lib/gcc/i686-apple-darwin9/4.3.4/adalib

install_name_tool -id $(pwd)/libgnat-4.3.dylib libgnat-4.3.dylib

install_name_tool -id $(pwd)/libgnarl-4.3.dylib libgnarl-4.3.dylib

install_name_tool -change libgnat-4.3.dylib $(pwd)/libgnat-4.3.dylib libgnarl.dylib


$ otool -L libgnat-4.3.dylib libgnarl-4.3.dylib | awk '{ print $1 }'

libgnat-4.3.dylib:

/usr/local/ada-4.3/lib/gcc/i686-apple-darwin9/4.3.4/adalib/libgnat-4.3.dylib

/usr/local/ada-4.3/lib/libgcc_s.1.dylib

/usr/lib/libSystem.B.dylib

libgnarl-4.3.dylib:

/usr/local/ada-4.3/lib/gcc/i686-apple-darwin9/4.3.4/adalib/libgnarl-4.3.dylib

/usr/local/ada-4.3/lib/gcc/i686-apple-darwin9/4.3.4/adalib/libgnat-4.3.dylib

/usr/local/ada-4.3/lib/libgcc_s.1.dylib

/usr/lib/libSystem.B.dylib

It may be convenient to link to the shared run-time libraries

cd /usr/local/ada-4.3/lib

ln -s gcc/i686-apple-darwin9/4.3.4/adalib/libgnat-4.3.dylib libgnat-4.3.dylib

ln -s gcc/i686-apple-darwin9/4.3.4/adalib/libgnarl-4.3.dylib libgnarl-4.3.dylib

Finally, one may build a private Framework to be embedded in an application bundle, as discussed here.

Makefile

Here's what the Makefile looks like:

# Example Makefile for building and using shared libraries

#

# Author: John B. Matthews

# Created: 15-Oct-2007

# Modified:

#   JBM 16-Oct-2007 Factor main & library source names

#   JBM 16-Oct-2007 Handle multiple SHARED libs

#   JBM 12-Apr-2009 Revised for x86

# Distribution: GPL


# The target and library to be built

TARGET = main

LIBSRC = greetings

LIBNAME = lib$(LIBSRC).dylib

MYLIB = $(shell pwd)


# A list of other shared libraries that may be needed

SHARED = libgnat-4.3.dylib libgnarl-4.3.dylib

ADALIB = $(ADA_LIB)


CHMOD = chmod

GNATMAKE = gnatmake

CARGS = -cargs -O1 -gnatwu -dynamic

BARGS = -bargs -shared

LARGS = -largs -v -l$(LIBSRC) -dead_strip

LIBTOOL = libtool

RENAME = install_name_tool

.PHONY: shared debug test clean cleaner tar


all:

  @echo ""

  @echo "Build targets:"

  @echo ""

  @echo "    main       Make a program that uses a shared library."

  @echo "    shared     Make a shared Ada library."

  @echo "    debug      Enable debugging."

  @echo "    test       Clean, build & check the results."

  @echo ""

  @echo "Support targets:"

  @echo ""

  @echo "    clean      Remove *.ali *.o b~.*"

  @echo "    cleaner    Remove target & dylib, too."

  @echo "    tar        Archive."

  @echo ""


$(TARGET): $(TARGET).adb shared

  @echo "# Building program $(TARGET)"

  ${GNATMAKE} $(TARGET) $(CARGS) $(BARGS) $(LARGS)

  ${RENAME} -change $(LIBNAME) $(MYLIB)/$(LIBNAME) $(TARGET)

  for lib in $(SHARED); do \

        ${RENAME} -change $$lib $(ADALIB)/$$lib $(TARGET); done


shared:  SHAREDLIBS := $(foreach lib,$(SHARED),$(ADALIB)/$(lib))

shared: $(LIBSRC).ad[sb]

  @echo "# Building shared library $(LIBNAME)"

  ${RM} $(LIBSRC).ali

  ${GNATMAKE} -c $(LIBSRC).adb $(CARGS)

  ${LIBTOOL} -dynamic \

        -install_name @executable_path/$(LIBNAME) \

        -o $(LIBNAME) $(LIBSRC).o $(SHAREDLIBS)

  ${RM} $(LIBSRC).o

  ${CHMOD} a-w $(LIBSRC).ali

  for lib in $(SHARED); do \

        ${RENAME} -change $$lib $(ADALIB)/$$lib $(LIBNAME); done


debug: cleaner *.ad[sb]

  ${GNATMAKE} -g $(TARGET)


test: cleaner $(TARGET)

  @echo "# Running program $(TARGET)"

  @./$(TARGET)

  @echo "# These library routines should be U (undefined):"

  @nm $(TARGET) | grep $(LIBSRC) | awk '{ print $$1 " " $$2 }'

  @echo "# These are the shared libraries used:"

  @otool -L $(TARGET) | awk '{ print $$1 }'

  @otool -L $(LIBNAME) | awk '{ print $$1 }'


clean:

  ${RM} *.o *.ali b~*


cleaner: clean

  ${RM} $(TARGET) *.dylib


tar: cleaner

  (export COPYFILE_DISABLE=true; \

  tar -zcvf ~/Sites/HomePage/misc/shared.tgz ../shared)

Code

Here's what the sample code looks like (gnatchop format):

with Greetings;

with Text_IO;


procedure Main is

begin

  Text_IO.Put_Line(Greetings.Hello);

  Text_IO.Put_Line(Greetings.Goodbye);

end Main;


package Greetings is

  function Hello return String;

  function Goodbye return String;

end Greetings;


package body Greetings is


function Hello return String is

begin

  return "Hello, world!";

end Hello;


function Goodbye return String is

begin

  return "Goodbye, world!";

end Goodbye;


end Greetings;

Copyright 2007, 2009 John B. Matthews

Distributed without warranty under the terms of the GNU Public License.

Last updated 10-Nov-2009