How integrate gnatmake/gnatbind/gnatlink in CMake files for C/Ada code?
Asked Answered
F

1

5

I wrote a code in a few languages (C, C++, Fortran77, Fortran90) and I can compile it without any sort of problem by using CMake. It works out perfectly.

Now, I would like to add in the main(), which is written in C, some Ada function and I want to compile it by CMake. Given that I am not able to link my Ada function to the main one by using CMake, I get

main.c:(.text.startup+0x16a): undefined reference to adainit
main.c:(.text.startup+0x179): undefined reference to adafunction
main.c:(.text.startup+0x190): undefined reference to adafinal

I did another simplified test by using the main function (written in C) calling the only Ada function, which I coded, and I compiled it by using

gcc -c main.c
gnatmake -c lib_ada.ali
gnatbind -n lib_ada.ali
gnatlink lib_ada.ali main.o -o exe

and it works out. Do you know how I can integrate this approach in a CMakeList.txt?

Note: I think (maybe I mistake) I cannot use the only gnatlink because I need to link all other functions I already have.

Here is reported a minimal reproducible example.

--- main.c ---

#include <stdio.h>

extern int adainit();
extern int adafinal();
extern int Add(int,int);

int main()
{
    adainit();
    printf ("Sum of 3 and 4 is: %d\n", Add (3,4));
    adafinal();

    return 0;
}

--- lib_test.adb ---

package body Lib_Test is
    function Ada_Add (A, B : Integer) return Integer is
    begin
        return A + B;
    end Ada_Add;
end Lib_Test;

--- lib_test.ads ---

package Lib_Test is
   function Ada_Add (A, B : Integer) return Integer;
   pragma Export (C, Ada_Add, "Add");
end Lib_Test;

1° test: if you compile by using the following commands:

gcc -c main.c
gnatmake -c lib_test.adb
gnatbind -n lib_test.ali
gnatlink lib_test.ali main.o -o exe

and run ./exe you get Sum of 3 and 4 is: 7.

2° test: I tried to use the following CMake file (CMakeLists.txt) linking the *.a

cmake_minimum_required(VERSION 2.6)
project(Ada2C)

enable_language(C)

set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -m64")

find_library(TEST_lib lib_test.a PATHS ${CMAKE_CURRENT_SOURCE_DIR})
message(STATUS "Finding library: ${TEST_lib}")
add_executable(TEST_release ${CMAKE_CURRENT_SOURCE_DIR}/main.c)
target_link_libraries(TEST_release ${TEST_lib})

I generate library lib_test.a for the Ada function

gnatmake lib_test.adb
ar rc lib_test.a

I run the cmake and make and I get

main.c:(.text.startup+0x16a): undefined reference to adainit
main.c:(.text.startup+0x179): undefined reference to adafunction
main.c:(.text.startup+0x190): undefined reference to adafinal
Financial answered 23/4, 2020 at 14:0 Comment(9)
I don't think CMake supports Ada directly, so you'll have to run these commands manually, or use some third-party software that offers CMake Ada support like this.Cheerleader
You need to add an option to the link command, to link the object that contains adainit and friends. This is auto-generated Ada source, by gnatbind. with a name like b~<something>.ads/adb and you can inspect it in any text editor. You need to convince Cmake to compile it along with your other Ada sources, and link the resulting object. I've never used Cmake so I can't help with that. Alternatively you can use gnatlink to link .o files from Fortran etc into your executable.Oleander
@squareskittles I already tried that support and it's really good. I tested it by using only ADA code and it works well, however, I didn't get the same good result when I extend it in my "mix-language" code. Probably, it was my mistake but I was not able to link ADA function in C main function with that support)Financial
@BrianDrummond Unfortunately, I don't know how to convince CMake to do that. Regardless of your second point, your are saying to use gnatlink command in CMake to link every .o files? If you mean to generate .o files and then use gnatlink externally from CMake to link them, it will be my still rescue choice. However, I would like to do automatically everything in Cmake.Financial
It would be great if you could call it by its proper name, Ada, not ADA, please (I’m afraid I can’t offer any insights into CMake, sorry)Frigidarium
@SimonWright you are right, sorry for misspelling Ada. I will pay more attention to use the correct name. Thanks for your clarification! I will edit my comment with Ada.Financial
I get Can you create a minimal reproducible example? Would you please post all the steps that are needed so that others (like me) can "get" the same errors as you? Please post the cmake script sources? How do you "link" your "Ada function"? in the main(), which is written in C, - then you can't/shouldn't compile with C++, or was this just a test?Snorter
@Snorter I posted a minimal example as answer to my question because it was a little bit long.Financial
Instead of find_library you could add_custom_target+add_custom_command and build the library in cmake. I don't think ar rc lib_test.a will work without much work, this post seems to exaplin it. I think the simplest is to just change linker to gnatlink in cmake like set(CMAKE_C_LINK_EXECUTABLE gnatlink) before the call project(). I think you could make it work - but the creation of a static C library from Ada takes more work.Snorter
N
6

More of a comment than an answer, but too long for a comment, so here goes:

Compiling Ada code into your binary means that your binary needs access to the GNAT runtime. This is one thing gnatlink does when you use it to link the final executable. The other thing is the b~<something>.ad{s,b} source gnatbind generates which you need to compile and link against as others mentioned.

The cleanest way to embed Ada in C I've seen so far is to create an encapsulated library. This probably does not make sense if your actual problem is with only one Ada function, but it does with larger chunks of Ada. The encapsulated library will be a shared library that has GNAT's runtime baked in. Being a shared library enables it to implicitly handle initialization during library loading so you don't need adainit() / adafinal() anymore.

The easiest way to create an encapsulated library is to use a ada_code.gpr file:

project ada_code is
   for Library_Name use "mylib";
   for Library_Dir use "lib";
   for Library_Kind use "relocatable";
   for Library_Standalone use "encapsulated";
   for Library_Auto_Init use "true";
   for Library_Interface use ("All", "Packages", "In.Your", "Ada.Code");
   for Source_Dirs use ("adasrc");
end ada_code;

In CMake, you can then do:

# tell CMake how to call `gprbuild` on the `.gpr` file.
# you may need to replace `gprbuild` with the absolute path to it
# or write code that finds it on your system.
add_custom_target(compile_mylib
        COMMAND gprbuild -P ada_code.gpr)

# copy the library file generated by gprbuild to CMake's build tree
# (you may skip this and just link against the file in the source tree)
add_custom_command(
        OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/mylib.so
        DEPENDS compile_mylib
        COMMAND ${CMAKE_COMMAND} -E copy
                ${CMAKE_SOURCE_DIR}/lib/mylib.so
                ${CMAKE_CURRENT_BINARY_DIR}/mylib.so)

# ... snip ...

# link to the copied library
# I am not 100% sure this adds the correct dependency to the custom command.
# You may need to experiment a bit yourself
target_link_libraries(TEST_release ${CMAKE_CURRENT_BINARY_DIR}/mylib.so)

In your C file, you can then delete everything related to adainit() and adafinal().

Nonesuch answered 24/4, 2020 at 9:22 Comment(1)
I really want to thank you! After several tests and experimenting a bit, I solved my problem by using the approach you posted.Financial

© 2022 - 2024 — McMap. All rights reserved.