Gprbuild and link options

I’ve been grappling with link options automatically generated when building with gprbuild. All of this occurs using a cross-toolchain for Linux AArch64 targets, so gnatlink, default.gpr and default.cgpr are invariant.

I first noticed a problem when I tried to cross-compile an Ada program calling subprograms from libnng.so on a Debian 12 development machine, using the native gprbuild. It failed to link. The final link command, which was helpfully included in the error output, failed because it included a -static link option. A lot of third party Linux libraries fail to link statically, so no surprise there. The problem is that my cross-compiled Ada programs are always supposed to be linked dynamically.

I instrumented the build with strace and it seems that the erroneous -static link option is being generated by gnatbind. Which is weird because:

My main build server is still Debian 11. The cross-toolchain packages are bit-for-bit exactly the same on all of my various Debian 11 and Debian 12 machines. I tried compiling the same program on the Debian 11 machine, using the Debian 11 native gprbuild (now with -v to see what is going on), and the same cross-toolchain, .gpr and .cgpr files, and the build succeeded. I did notice -static-libgcc (which should be -shared-libgcc) in the link options.

So then I uninstalled the Debian 12 native gprbuild and (painfully) copied the Debian 11 native gprbuild and its dependencies to the Debian 12 machine and tried again. The result was -static-libgcc, which indicates that the different outcomes depend on gprbuild rather than gnatbind, which is part of the cross-toolchain and invariant across all this testing.

Next I tried using Alire gprbuild versions 22.0.1 and 25.0.1. The four different versions of gprbuild I tried on the same Debian 12 build machine using the same cross-toolchain produced wildly varying sets of link options (-static, -static-libgcc, -shared-libgcc or nothing at all, as well as dynamic -lgnat/-lgnarl vs static libgnat.a/libgnarl.a). What I want is -shared-libgcc, libgnat.a, and libgnarl.a. Only one combination of gprbuild and Debian produces what I want: Alire gprbuild 22.0.1 on Debian 12.

What I need to figure out is how to coerce what I want from the other combinations, especially since Alire gprbuild version 22.0.1 is not available for the 64-bit Raspberry Pi. Can somebody explain how the link options are generated and aggregated when building with gprbuild?

GPRbuild is the “command runner”, gnatbind and company just do what they are told and what they are passed as options.

Since you want the libraries to be dynamically linked, I assume that you are cross-compiling to another OS and that you are not doing embedded systems programming. GPRbuild, afaik, will link staticly unless told otherwise when doing cross-compilations for other arches that are not x86, but I may be wrong there. The toolchain may also be adding the -static flag if the toolchain is for embedded systems (aka gcc for barebones aarch64 and not gcc for linux on aarch64).

If it is a library, you need to write the correct library type in your projects .gpr file as per 2. GNAT Project Manager — GPR Tools User's Guide 26.0w documentation or use the correct -largs as indicated in 2. GNAT Project Manager — GPR Tools User's Guide 26.0w documentation

Taking a look at Alire’s toolchains, there does not seem to be an x86* to aarch64 linux cross-compiler… only a native aarch64 compiler in linux… Release gnat-15.1.0-2 · alire-project/GNAT-FSF-builds · GitHub

I hope this helps,
Fer

Link options are controlled by the project file. Specifically if you have a library project file like:

library project Whatever is
   ...
   for Library_Kind use "dynamic";
   ...
end Whatever;

This will create a dynamically linked library (*.so, *.dll etc). Other values of this attribute are “static”, “static-pic.”

Link options are controlled by in a bit complex manner, e.g. when you build a library you can specify some options to the time when the application is linked, e.g.

for Library_Options use ("-lsqlite3");

The documentation on GPR is here.

P.S. I would strongly recommend to learn GPR before trying to build anything complex. I would also never use any generated project files. You must manually write and understand each line of any gpr-file you use.

And finally, before mastering it, if you target a 64-bit Raspberry Pi, then you can start with a native tool chain. Yes RPI 4 is painfully slow, but it is fast enough to compile and build middle-sized projects. The main problem there is memory, but again, if you are not compiling large stuff (many generics instantiated etc) under the -j0 switch (use all cores in parallel) then RPI is quite enough.

Do not forget to replace the SD card with an SSD. Compilation corrupts SD in a few days.

When you start to feel comfortable with gprbuild you can switch to cross compilation.

I have always had a love/hate relationship with gprbuild (currently trending toward hate…). It is an evil whose necessity I am not yet convinced of. The problem is not that I don’t know how to write project files–I do. I wrote both .gpr and .cgpr files for cross-compiling Linux targeting Raspberry Pi and other 64-bit Linux microcomputers.

The problem is that gprbuild and its friends and relations are not fully documented. For my current example, linker options, the project file reference (https://docs.adacore.com/gprbuild-docs/html/gprbuild_ug/project_file_reference.html) mentions the Linker package but provides absolutely no information about what can go into the Linker package. Since Ada is for embedded systems, like I am trying to build for, one would expect that the Linker package would allow complete control over the link process. Such is not the case.

Here is the example I am currently fighting with:

My .cgpr for cross-compiling Linux for AArch64 targets contains the following:

   package Linker is
...
      for Required_Switches use
                Linker'Required_Switches
                & ("-shared-libgcc");
...
   end Linker;

I would therefore expect a dynamically linked executable that will require libgcc_s.so.1 on the target computer. Instead, the link command I get with the Debian 12 native gprbuild is:

aarch64-muntsos-linux-gnu-gcc wioe5_ham1_nng_publisher.o
b__wioe5_ham1_nng_publisher.o
/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/aarch64-muntsos-linux-gnu/libc/usr/lib/libsimpleio.lib/liblibsimpleio.a
/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/aarch64-muntsos-linux-gnu/libc/usr/lib/libaws.lib/liblibaws.a 
-L/home/pmunts/src/muntsos/examples/ada/programs/wioe5/obj/ 
-L/home/pmunts/src/muntsos/examples/ada/programs/wioe5/obj/ 
-L/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/aarch64-muntsos-linux-gnu/libc/usr/lib/libsimpleio.lib/
-L/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/aarch64-muntsos-linux-gnu/libc/usr/lib/libaws.lib/
-L/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/lib/gcc/aarch64-muntsos-linux-gnu/14.2.0/adalib/
-static -lnng -lsimpleio -lsimpleio
/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/lib/gcc/aarch64-muntsos-linux-gnu/14.2.0/adalib/libgnarl.a /usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/lib/gcc/aarch64-muntsos-linux-gnu/14.2.0/adalib/libgnat.a
-lrt -lpthread -ldl -shared-libgcc
-Wl,-z,origin,-rpath,$ORIGIN//obj:/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/aarch64-muntsos-linux-gnu/libc/usr/lib/libsimpleio.lib:/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/aarch64-muntsos-linux-gnu/libc/usr/lib/libaws.lib:/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/lib/gcc/aarch64-muntsos-linux-gnu/14.2.0/adalib
-o /home/pmunts/src/muntsos/examples/ada/programs/wioe5//wioe5_ham1_nng_publisher

The link command contains both -static and -shared-libgcc which is invalid. Worse yet, four different versions of gprbuild produce wildly varying results from the exact same toolchain binaries and project files.

Next I try to add the following the the project .gpr file and try again:

  PACKAGE Linker IS
    FOR Default_Switches ("Ada") USE ("-static-libgcc", "-L/tmp/BOGUS");
  END Linker;

Now the link command is:

aarch64-muntsos-linux-gnu-gcc wioe5_ham1_nng_publisher.o b__wioe5_ham1_nng_publisher.o
/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/aarch64-muntsos-linux-gnu/libc/usr/lib/libsimpleio.lib/liblibsimpleio.a
/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/aarch64-muntsos-linux-gnu/libc/usr/lib/libaws.lib/liblibaws.a
-L/tmp/BOGUS -L/home/pmunts/src/muntsos/examples/ada/programs/wioe5/obj/ -L/home/pmunts/src/muntsos/examples/ada/programs/wioe5/obj/
-L/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/aarch64-muntsos-linux-gnu/libc/usr/lib/libsimpleio.lib/
-L/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/aarch64-muntsos-linux-gnu/libc/usr/lib/libaws.lib/
-L/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/lib/gcc/aarch64-muntsos-linux-gnu/14.2.0/adalib/
-static -lnng -lsimpleio -lsimpleio /usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/lib/gcc/aarch64-muntsos-linux-gnu/14.2.0/adalib/libgnarl.a
/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/lib/gcc/aarch64-muntsos-linux-gnu/14.2.0/adalib/libgnat.a
-lrt -lpthread -ldl -shared-libgcc
-Wl,-z,origin,-rpath,/tmp/BOGUS:$ORIGIN//obj:/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/aarch64-muntsos-linux-gnu/libc/usr/lib/libsimpleio.lib:/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/aarch64-muntsos-linux-gnu/libc/usr/lib/libaws.lib:/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/lib/gcc/aarch64-muntsos-linux-gnu/14.2.0/adalib
-o /home/pmunts/src/muntsos/examples/ada/programs/wioe5//wioe5_ham1_nng_publisher

The -static-libgcc is silently discarded, but -L/tmp/BOGUS is included, along with the totally unwanted -static and ignored -shared-libgcc options. My beef is that whatever entity auto-generates the linker options is crap software: It selectively uses or discards options specified in the project files. And the behavior is different for every one of four different gprbuild versions I’ve tried.

What you describe has nothing to do with gprbuild. You instruct it to pass incompatible switches to the linker. It obeys. The GNU linker does a mess out of it. Who is to blame?

You have not even reached the abyss yet. The GNU linker’s behaviour depends under circumstances on the order in which libraries are specified. Welcome to the circus!

The good news is that gprbuild lets you overcome almost any quirks of the GNU linker. But you need to understand what are you doing.

-shared-libgcc is the linker’s default. You do not need to specify it. See.

*.cgpr is an autogenerated mess. You should remove all instances of *.cgpr files.

I doubt it very much. You need to sort out your gpr-files. Prune anything you do not know what it is supposed to do from all files. Make sure that the project search path does not contain garbage. When you use scenarios like -static, you must understand what they do. So I would refrain from using them before you master gprbuild.

Yes, gprbuild is ill documented but relatively simple to use. Again, the main rule is write all gpr-files manually.

I must respectfully disagree. At least for cross-compiled embedded software, everything architecture specific needs to be in the toolchain .cgpr file. I want project files like:

PROJECT Default IS
  FOR Source_Dirs USE (".");

  FOR Object_Dir  USE "./obj";

  FOR Exec_Dir    USE ".";
END Default;

that work for both 64-bit Raspberry Pi targets and 32-Bit BeagleBone targets, with all of the architecture stuff encapsulated in each cross-toolchain’s .cgpr file. My cross-toolchain .cgpr files are carefully hand edited for the embedded system target OS.

My problem is that something in the gprbuild process chain is auto-generating a ton of link options that ignore some project file link options and contradict others. Also gprbuild generates a duplicate -libsimpleio for every Ada package that calls a subprogram in libsimpleio.so. I don’t think the problem is gnatbind, because building with gnatmake instead of gprbuild is flawless and I get exactly what I want. Plus, like I said before, four different versions of gprbuild, using the exact same cross-toolchains including gnatbind, produce different sets of link options.

No. Cross-compilation changes nothing. The target OS is selected by a scenario variable in the master gpr-file. You can use it to select whatever directories. These are typlically inherited from that file. E.g. an example of a project file from a large project with embedded cross-compiled targets:

library project Build is

   case Switches.Development is
      when "release" =>
         for Library_Name use "elabethercat";
      when "debug" =>
         for Library_Name use "elabethercatd";
   end case;

   case Switches.Platform is
      when "x86_64_vxworks" =>
         for Target use "i586-wrs-vxworks";
      when "armhf_linux" =>
         for Target use "armhf-linux";
      when "x86_windows" =>
         for Languages use ("Ada", "winres");
      when "x86_64_linux" =>
         null;
   end case;

Two targets VxWorks and armhf are cross-compiled. Windows target incorporates resources.

You must check the Ada source code too. Because it is possible to manipulate library options through the GNAT compilation pragma:

pragma Linker_Options

For each external library I would recommended to have a project file like:

library procject Simple_IO is
   for Library_Name     use "simpleio";
   for Externally_Built use "true";
   for Library_Kind     use "dynamic";
end Simple_IO;

That is because you use it incorrectly. I can only repeat you must be in full control of the gpr-files involved and know exactly what are you doing.

Adding -shared-libgcc to the link options in the cross-toolchain configuration project (.cgpr) files has greatly reduced the variations in linker options generated by various versions of gprbuild. I have captured the link command revealed by gprbuild -v for each of the four versions of gprbuild I have tried on my Debian 12 x86-64 development machine. In order to make comparisons possible, I parsed the link options from each link command to a list of one item per line. For reference, this is the list of link options from the Debian 12 native gprbuild (the other lists are so similar now that I won’t post them):

aarch64-muntsos-linux-gnu-gcc
wioe5_ham1_nng_publisher.o
b__wioe5_ham1_nng_publisher.o
/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/aarch64-muntsos-linux-gnu/libc/usr/lib/libsimpleio.lib/liblibsimpleio.a
/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/aarch64-muntsos-linux-gnu/libc/usr/lib/libaws.lib/liblibaws.a
-L/home/pmunts/src/muntsos/examples/ada/programs/wioe5/obj/
-L/home/pmunts/src/muntsos/examples/ada/programs/wioe5/obj/
-L/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/aarch64-muntsos-linux-gnu/libc/usr/lib/libsimpleio.lib/
-L/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/aarch64-muntsos-linux-gnu/libc/usr/lib/libaws.lib/
-L/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/lib/gcc/aarch64-muntsos-linux-gnu/14.2.0/adalib/
-static
-lnng
-lsimpleio
-lsimpleio
/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/lib/gcc/aarch64-muntsos-linux-gnu/14.2.0/adalib/libgnarl.a
/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/lib/gcc/aarch64-muntsos-linux-gnu/14.2.0/adalib/libgnat.a
-lrt
-lpthread
-ldl
-shared-libgcc
-Wl,-z,origin,-rpath,$ORIGIN//obj:/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/aarch64-muntsos-linux-gnu/libc/usr/lib/libsimpleio.lib:/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/aarch64-muntsos-linux-gnu/libc/usr/lib/libaws.lib:/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/lib/gcc/aarch64-muntsos-linux-gnu/14.2.0/adalib
-o
/home/pmunts/src/muntsos/examples/ada/programs/wioe5//wioe5_ham1_nng_publisher

Here is the rather cryptic output of diff3 for Debian 12 native, Alire 22.0.1, and Alire 25.0.1 link option lists:

====3
1:1c
2:1c
  aarch64-muntsos-linux-gnu-gcc
3:1c
  /usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/bin/aarch64-muntsos-linux-gnu-gcc
====2
1:11c
3:11c
  -static
2:10a

The first difference is cosmetic. For some reason Alire 25.0.1 displays the full path for the gcc command. The second shows that Debian 12 native and Alire 25.0.1 both insert the spurious -static link option and that Alire 22.0.1 does not.

Here are the more wordy but easier to understand results of grep’ing the link option lists for items of interest:

pmunts@gadara:~$ egrep 'static|libgcc|libgna' *.folded
gprbuild-alire-22.0.1.folded:/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/lib/gcc/aarch64-muntsos-linux-gnu/14.2.0/adalib/libgnarl.a
gprbuild-alire-22.0.1.folded:/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/lib/gcc/aarch64-muntsos-linux-gnu/14.2.0/adalib/libgnat.a
gprbuild-alire-22.0.1.folded:-shared-libgcc
gprbuild-alire-25.0.1.folded:-static
gprbuild-alire-25.0.1.folded:/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/lib/gcc/aarch64-muntsos-linux-gnu/14.2.0/adalib/libgnarl.a
gprbuild-alire-25.0.1.folded:/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/lib/gcc/aarch64-muntsos-linux-gnu/14.2.0/adalib/libgnat.a
gprbuild-alire-25.0.1.folded:-shared-libgcc
gprbuild-debian11-native.folded:/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/lib/gcc/aarch64-muntsos-linux-gnu/14.2.0/adalib/libgnarl.a
gprbuild-debian11-native.folded:/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/lib/gcc/aarch64-muntsos-linux-gnu/14.2.0/adalib/libgnat.a
gprbuild-debian11-native.folded:-shared-libgcc
gprbuild-debian12-native.folded:-static
gprbuild-debian12-native.folded:/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/lib/gcc/aarch64-muntsos-linux-gnu/14.2.0/adalib/libgnarl.a
gprbuild-debian12-native.folded:/usr/local/gcc-aarch64-muntsos-linux-gnu-ctng/lib/gcc/aarch64-muntsos-linux-gnu/14.2.0/adalib/libgnat.a
gprbuild-debian12-native.folded:-shared-libgcc

After adding -shared-libgcc to the cross-toolchain configuration project file, the libgnat, libgnarl, and libgcc options are now the same with all four versions of gprbuild. Debian 12 native and Alire 25.0.1 gprbuild both still insert that spurious -static link option.

I love to learn what to put into either the project file or the configuration project file to eliminated the spurious -static link option. Nothing I’ve tried has worked.