How to create an ada lib.a and link to C
Asked Answered
G

1

9

I am trying to create an ada library and have tried a few different things. I have tried compiling the project using makefiles and trying to create a library from all the .o files This seemed to not work as expected. I then asked adacore support and they pointed me in the direction of using .gpr files for both the ada and c projects and withing in the ada.gpr that should create a library. This almost worked but when it tries to compile the ada I get undefined references.

What I have tried: Command line:

ar rc libmy_lib.a *.o

and when I try to read what is in the lib

ld libmy_lib.a

I get this error ld: warning: cannot find entry symbol _start; not setting start address

Project Files: My ada project file prj.gpr

project Prj is
for Source_Dirs use ("source1/", "source2", ....);
for Object_Dir use ".";

for Languages use ("Ada");
for Library_Name use "test";
for Library_Dir use "lib";
for Library_Interface use (
 --All my ada packages
        );

package Naming is
      for Spec_Suffix ("ada") use ".1.ada";
      for Body_Suffix ("ada") use ".2.ada";
      for Separate_Suffix use ".2.ada";
      for Dot_Replacement use ".";
   end Naming;

   package Compiler is
      for Default_Switches ("ada") use ("-v", "-g", "-gnato", "-gnatwa", "-gnatQ", "-gnat05");
   end Compiler;

   package Builder is
      for Global_Compilation_Switches ("Ada") use ("-gnat95");
   end Builder;

   package Ide is
  end Ide;

end Prj;

My c project file c_main.gpr

with "prj.gpr";
project C_Main is
for Source_Dirs use ("source_c_1/", "source_c_2/");
for Languages use ("C");
for Main use ("source_c_1/main.c");
end C_Main;

When i run the command gprbuild c_main.gpr

I got 2 different errors: First being undefined references to some packages that are part of my ada code and come that at gnat .adb files I didnt know existed. So broken library I figure. Second being that fields of certain packages cant be found/dont exist even though the code compiles fine and runs. It gives me errors stating that the fields dont exist in the ada code.

TLDR: I have an ada project in 3 different directories and I want to create a library from them. Then link to a C testing program. Ultimately I just have to deliver the library file. Command line would be best. I do not want to deal with project files.

Gorgonzola answered 7/11, 2014 at 16:18 Comment(4)
Updated my question, sorry I didnt think my past attempts would help. I did not think it was really an error. More a lack of direction or what to do in the first place on my part.Gorgonzola
You could confirm that it’s a library project by starting it library project prj is ...Elkins
I just deleted my answer to this, because it totally failed when the library needed elaboration (initialization). Will come back when/if I find an answer that does work.Elkins
Reinstated the answer. TL;DR: use dynamic libraries to avoid user-visible problems with linking the Ada RTS libraries and with elaboration.Elkins
E
12

There are big problems with creating a static library libtest.a.

First, the Ada code is extremely likely to call in the Ada runtime system (RTS). If you create a static library, you (or your users) will need to call in the Ada RTS explicitly, whether or not you use gprbuild. So neither

gcc main_c.c -ltest

nor

gprbuild -P c_main

will be enough; you'll get failures like this (and worse):

$ gcc main.c -Lada/lib -ltest
Undefined symbols for architecture x86_64:
  "_ada__calendar__delays__delay_for", referenced from:
      _Hello in libtest.a(hello.o)
ld: symbol(s) not found for architecture x86_64
collect2: error: ld returned 1 exit status

Secondly, Ada code might (will!) require elaboration, done at program start. When gprbuild creates the library it adds functions testinit(), which your C code must call before calling any interface of the library, and testfinal() to be called after all uses of the library (most people don't bother).

A way round the first problem is to create a dynamic library (.dll on Windows, .so on Linux and other Unix systems, .dylib on Mac OS X). To do this, you say for Library_Kind use "dynamic";. (Note, although the dynamic library knows what other libraries it needs, it may not know where to find them, so you'll have to arrange for them to be on the loader's library search path).

A way round the second problem is to create what AdaCore call a standalone dynamic library, and to get it to initialize itself automatically.

To do this you need to add two attributes:

  • for Library_Interface use (...); specifies a list of the names of the units you wish to be visible outside the library. The effect is to include only the named units' source and .ali files in the library; if the only callers are to be from C you probably only need to name one.
  • for Library_Auto_Init use "true"; - I think this is actually the default.

I set up a little example (on Mac OS X, GNAT GPL 2014).

Subdirectory ada

The project file,

library project Prj is
   for Languages use ("ada");
   for Library_Name use "test";
   for Library_Kind use "dynamic";
   for Library_Interface use ("hello");
   for Library_Auto_Init use "true";
   for Library_Src_Dir use "include";
   for Library_Dir use "lib";
   for Source_Dirs use (".");
   for Object_Dir use ".build";
end Prj;

hello.ads,

function Hello return Integer;
pragma Export (C, Hello, "Hello");

hello.adb,

with Number;
function Hello return Integer is 
begin
   delay 0.001;            -- so the tasking runtime gets called in
   return Number.Value;
end Hello;

number.ads,

package Number is
   pragma Elaborate_Body;
   Value : Integer := 0;   -- before elaboration
end Number;

and number.adb

package body Number is
begin
   Value := 42;            -- after elaboration
end Number;

Parent directory

The project file,

with "ada/prj";
project C_Main is
   for Source_Dirs use (".");
   for Languages use ("c");
   for Main use ("main.c");
   for Exec_Dir use ".";
   for Object_Dir use ".build";
end C_Main;

and main.c

#include <stdio.h>

extern int Hello(void);

int main() {
  int hello = Hello();
  printf("Hello returned %d.\n", hello);
  return 0;
}

The build

$ gprbuild -p -P c_main
gcc -c main.c
gcc -c -fPIC number.adb
gcc -c -fPIC hello.adb
gprlib test.lexch
gnatbind -n -o b__test.adb -Ltest -a /Users/simon/tmp/crychair/ada/.build/number.ali ...
gcc -c -x ada -gnatA -gnatws b__test.adb -o b__test.o ...
gcc -dynamiclib -shared-libgcc -o /Users/simon/tmp/crychair/ada/lib/libtest.dylib ... /Users/simon/tmp/crychair/ada/.build/number.o ...
ar cr libc_main.a ...
ranlib -c libc_main.a
gcc main.o -o main

and the execution:

$ ./main
Hello returned 42.

To distribute your library to C users on another computer, without the Ada runtime already installed, you’d need to package up libtest.so (or .dylib, or .dll) and the Ada shared libraries required.

On a Unix system, you’d find this out using ldd libtest.so You’re looking for libgnat*.so and libgnarl*.so. You should find these in the compiler’s object search path (usually the last line of the Object Search Path section of the output of gnatls -v). Typically there will be symbolic links:

libgnat.so       ->      libgnat.1.so
libgnat.1.so     ->      libgnat.1.0.0.so
libgnat.1.0.0.so         (the real thing)

Put the shared libraries and symlinks in a directory with libtest.so, say product/, then your users should be able to link with

gcc main.c -o main -Lproduct -ltest

or maybe

gcc main.c -o main -Lproduct -ltest -lgnat -lgnarl

Depending on your OS, the resulting executable may not be able to find the shared libraries at runtime.

One way round this is to put the libraries where the loader already looks, for example /usr/local/lib (in which case you wouldn’t need the -Lproduct).

Another way is to tell the loader where to look by setting an environment variable (LD_LIBRARY_PATH on Linux, DYLD_LIBRARY_PATH on Mac OS X).

A third way is to tell the linker to save the path in the executable:

gcc main.c -o main -Lproduct -ltest -lgnat -lgnarl -Wl,-rpath,$PWD/product

works on Mac OS X, probably will on Linux.

Elkins answered 8/11, 2014 at 19:33 Comment(9)
Thank you so much this helped a lot. Also a few things to add. For some reason when you build this it is CASE SENSITIVE for files names. For my project I had test.1.ada and then a subprogram test.helloWorld.2.ada. If the W was capitalized it couldnt find the file. It was just crazy that ada isnt a case sensitive language, but apparently gprbuild is written in c, because it is case sensitive.Gorgonzola
I realzie this now makes the libtest.so. How would I link to that. Meaning if I hand off that library to someone who has none of the ada code. How would they link to it.Gorgonzola
Re: case sensitivity - if you are making libtest.so then you are probably on a Linux or BSD system, and their file systems are case sensitive; so test.helloWorld.2.ada won’t see test.helloworld.2.ada. Windows and Mac OS X are case-preserving but not case-sensitive.Elkins
Re: linking - added a final section. I can’t easily try it here.Elkins
This runs with no errors: gcc main.c mainReport.c -o main -Lproject -ltest -lgnat -lgnarl , but I get this error when i try to run ./main : ./main: error while loading shared libraries: libtest.so: cannot open shared object file: No such file or directory. and yes it is in the project folder with libgnarl.so and libgnat.soGorgonzola
Apologies for not actually trying this before posting. Extended the final section, hope it does the trick for you.Elkins
I was going to bed last night and I was like hmm, does a -L need the full path? Didnt think of the -Wl thats smart.Thanks so much for the help.Gorgonzola
Sorry one more add on to the question. How would I make the C executable standalone. Meaning pull in what it needs from those libraries and then be able to move just the executable to another machine and run it there.Gorgonzola
I think that’s going to have to be a new question!Elkins

© 2022 - 2024 — McMap. All rights reserved.