GNATpolyglot

GNATpolyglot is a multi-language, high-level bindings generator.

The generation of bindings for a given library is done in a two step process:

  1. Generation of the Proxy intermediate representation which describes the content of the library in a language-agnostic way, together with glue code wrapping the original library and adhering to a standard interface over the C ABI.

  2. Generation of a high level interface in a given target language, relying exclusively on the Proxy intermediate representation and glue code.

The key takeaway is that the second step can be done independently of the first one. In fact, it doesn’t even need to be aware of the input language of the original library, and does not require a compiler for the input language.

Moreoever, unlike writing a dedicated tool for each input-output pair of languages, this decoupling through an intermediate representation implies that when a language frontend is added to GNATpolyglot, it immediately allows generating bindings from that language to all supported output languages. Similarly, once a new language backend is added, it immediately allows creating bindings for that language from all the input languages supported by GNATpolyglot.

Generating Proxy IR

Generating Ada Proxy layer

$> gnatpolyglot ada2proxy -P<project> -o<output_path>

The ada2proxy subcommand takes a GPR project file as an input to fetch the list of sources to bind, and generates Ada code that interfaces with the C ABI, as well as a proxy.json file that describes the content of the library.

See the dedicated Ada2Proxy chapter for more information on the subcommand’s usage.

In order to bind these library, 2 GPR projects files are generated:

  • Proxy: Contains all the code that uses the C ABI

  • Proxy aggregate: Aggregate of the bound library, gnatpolyglot runtime and bindings. This project can be compiled as an Encapsulated Standalone Aggregate Library (ESAL) to avoid future dependencies with the Ada runtime, and other transitive dependencies from the bound project.

    Danger

    When building using an ESAL, the resulting library should be the only ada project. Learn more here.

$> gnatpolyglot ada2proxy -Ptest.gpr -o./2proxy
$> find ./2proxy
2proxy/
2proxy/proxy.json
2proxy/test-proxy-agg.gpr
2proxy/test-proxy.gpr
2proxy/src
2proxy/src/*.ad[sb]

Warning

It is necessary to use gprbuild2 to build the generated projects. Refer to the GPR documentation to use the new builder.

Generating Language specific interfaces

From a single proxy.json, GNATpolyglot can generate bindings for any of its supported target languages, independently and without re-running the frontend. A dedicated subcommand drives each backend:

  • proxy2cpp — C++ bindings.

  • proxy2java — Java bindings (via JNI).

  • proxy2rust — Rust bindings (experimental).

Generating C++ interfaces

$> gnatpolyglot proxy2cpp <proxy.json file> -o<output_path>
$> find <output_path>

The proxy2cpp subcommand generates all source files that call the functions described in the json proxy IR, and all the headers that contain functions and type declarations from the bound library. Header files are located in the <output_path>/include directory.

See the dedicated Proxy2Cpp chapter for more information on the subcommand’s usage.

$> gnatpolyglot proxy2cpp 2proxy/proxy.json -o./2cpp
$> find /2cpp
2cpp/
2cpp/*.cpp
2cpp/include
2cpp/include/*.h

Use your preferred build system to build these.

See the dedicated Proxy2Cpp chapter for more information.

Generating Java interfaces

$> gnatpolyglot proxy2java 2proxy/proxy.json -o ./2java

The proxy2java subcommand generates a Maven project containing the Java bindings together with the JNI bridge that connects them to the proxy over the C ABI. Build the Java sources with Maven, then build the JNI layer with gprbuild:

$> mvn package -f 2java/
$> gprbuild 2java/<proxy>_jni.gpr --gpr=2 \
       -XOS=unix \
       -XPROXY_LIB_LOCATION=<proxy-lib-dir> \
       -XPROXY_LIB=<proxy-lib-name>

See the dedicated Proxy2Java chapter for more information.

Generating Rust interfaces

Note

The Rust backend is experimental.

$> gnatpolyglot proxy2rust 2proxy/proxy.json -o ./2rust

The proxy2rust subcommand generates a Cargo crate. Its build.rs links the native proxy library, whose location and name are passed through environment variables:

$> POLYGLOT_PROXY_LIB_DIR=<proxy-lib-dir> \
   POLYGLOT_PROXY_LIB_NAME=<proxy-lib-name> \
   cargo build --manifest-path 2rust/Cargo.toml

See the dedicated Proxy2Rust chapter for more information.

Example: Generating Ada to C++ bindings

-- input/src/test.ads
package Test is

   procedure Hello;

   function Do_Double (I: Integer) return Integer is (I * 2);

begin
// cpp/main.cpp
#include <iostream>

#include "test.h"

int main() {
    test::hello();
    std::cout << test::do_double(10) << "\n";
}
$> find .
.
./input
./input/test.gpr
./input/src
./input/src/test.ads
./input/src/test.adb
./cpp
./cpp/main.cpp
$> gnatpolyglot ada2proxy -P input/test.gpr -o 2proxy
$> gnatpolyglot proxy2cpp 2proxy/proxy.json -o 2cpp
// 2cpp/include/test.h

namespace test {

void hello();

int do_double(int i);

} // namespace test
$> gprbuild 2proxy/test-proxy-agg.gpr -f -ggdb --gpr=2
$> gprbuild 2cpp/runtimes/ada/gnatpolyglot_ada2cpp.gpr -f -ggdb --gpr=2
$> g++ main.cpp \
       2cpp/*.cpp \
       -o main \
       -I2cpp/include \
       -Wall -Wextra \
       -L2cpp/runtimes/ada/lib2cpp/static/dev/ -lgnatpolyglotada2cpp \
       -L2proxy/lib_agg/static/dev/ -lfoo_proxy_agg \
       -ldl -lpthread # May be necessary on Linux systems
$> ./main
Hello!
20