How To: GNAT Pro with Docker
by Léo Germond –
Using GNAT Pro with containerization technologies, such as Docker, is so easy, a whale could do it!
In this article I will show you how to get started setting up a Docker image with the GNAT Pro tools.
But first...
What is Docker?
Maybe a better question:
What isn’t Docker these days… Am I right?
Over the last decade, DevOps methodology has revolutionized software engineering as we know it. A fundamental concept, Infrastructure as Code, has helped us build software that matters in a reliable, repeatable, and performant way, providing an actionable solution to one of the hardest IT problems:
How can I ensure my build process is stable and repeatable?
Docker is one such tool (one we really like here at AdaCore) that allows us to define our IT infrastructure as code in a highly portable way.
It uses an exhaustive approach to configuration using layers, which means it is easier than ever to set up build environments that share dependencies, a common occurrence for build systems.
Furthermore, its decentralized architecture will work independently from any external infrastructure, an argument that has gained weight as fast as a whale recently.
Lastly, a small piece of advice: keep in mind that Docker instances are containers. While powerful, some things aren’t set up out of the box, such as sharing a USB port or a host system directory. In order to do these things you'll need to do some host configuration work.
Rule of thumb: if you need more than a ssh to the tools inside the container, e.g. to run a JTAG debugger through Docker, it will require some leg work.
Getting Started
As a prerequisite, you must have a recent version of Docker, a GNAT Pro release package for Linux x86-64, and a Python 3 install.
The procedure and script works with the current stable native compiler 20.2 version, as well as in-stabilization 21.1 and wavefront 22.0. Older versions or cross toolchains may require some additional work.
First, let’s start with a sample Dockerfile. An example can be found in the AdaCore GitHub's GNAT Docker repository.
The repository contains two directories:
- gnatpro-deps/ Resources for building the GNAT Pro base image.
- gnatpro/ Resources for building the full GNAT Pro toolsuite image.
For technical reasons, the actual build is performed in two steps: we set up a build environment in gnatpro-deps/ then use it to build the GNAT Pro release, that we install in gnatpro/.
All of this is done by the create_image script.
Creating a Docker Image Using create_image
The create_image Python script starts by creating a gnatpro:deps image containing the necessary tooling to build GNAT Pro from a release package. Then using a provided GNAT Pro release archive, it builds a second image with GNAT Pro actually installed. Finally it tags this image as gnatpro:NN.N, with NN.N being the GNAT Pro version number, so images of several GNAT Pro versions can coexist side-by-side.
If we try to run the create_image Python script, we can see that the GNAT Pro release package file must be provided, and an optional GNAT Pro version number.
When provided, the version number will be used to tag the GNAT Pro image. Otherwise, it will be gathered from the filename, when possible.
usage: create_image [-h] [--verbose] [--gnat_version GNAT_VERSION] gnatpro_release
positional arguments:
gnat_release GNAT Pro release package file
optional arguments:
-h, --help show this help message and exit
--verbose, -v Display commands as they are run
--gnat_version GNAT_VERSION
GNAT Pro version number for automatic tagging and archive
search. Leave empty for the script to infer it.
In our example we're using a 20.2 stable GNAT Pro version:
- The --gnat_version argument should therefore be 20.2, and we provide the path to gnatpro-20.2-x86_64-linux-bin.tar.gz which contains the GNAT Pro installer.
- If we set the --verbose flag, we can see in real time the commands as they are called by the script.
From the gnatpro-deps/ directory, it builds the gnatpro:deps image, which takes no arguments.
Then, it builds the full GNAT Pro image. This is done in two steps:
First it copies the release package into the gnatpro/ directory (for technical reasons relative to Docker’s “copy” command).
It then calls docker build. This command does the heavy lifting by running the building steps described in the file gnatpro/Dockerfile.
The format of this file is defined in the Docker builder doc. It accepts arguments, and in our case one is mandatory: gnat_release, which should point to the GNAT Pro release package so the create_image script can use it as an argument to the docker build command.
Once this step is performed, the image is complete, and is tagged as gnatpro:20.2.
./create_image --verbose --gnat_version=20.2 gnatpro/gnatpro-20.2-x86_64-linux-bin.tar.gz
Docker for build dependencies: image gnatpro:deps
> docker build -t gnatpro:deps gnatpro-deps
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM ubuntu:18.04
---> 56def654ec22
Step 2/2 : RUN set -xe && DEBIAN_FRONTEND=noninteractive [...]
Get:1 http://archive.ubuntu.com/ubuntu bionic InRelease [242 kB]
[...]
---> 89afd2c07618
Successfully built 89afd2c07618
Successfully tagged gnatpro:20.2
GNAT Pro image built successfully
You can open a shell on it with the command
docker run --entrypoint bash -it gnatpro:20.2
Do you want to build and run the GNAT example ? [yN]
The script finishes by asking to test the newly built image. Input Y to start the test.
The test builds all the GNAT Pro examples and checks that they compile and run without error.
Do you want to build and run the GNAT example ? [yN] y
> docker run --entrypoint make -t gnatpro:20.2 -C /usr/gnat/share [...]
make: Entering directory '/usr/gnat/share/examples/gnat'
make -C starter
make[1]: Entering directory '/usr/gnat/share/examples/gnat/starter'
gnatmake -g hello
gcc -c -g hello.adb
gnatbind -x hello.ali
gnatlink hello.ali -g
./hello
Hello World. Welcome to GNAT
gnatmake -g demo1
[...]
Bind
[gprbind] use_of_import.bexch
[Ada] use_of_import.ali
Link
[archive] libimport_from_c.a
[index] libimport_from_c.a
[link] use_of_import.adb
import_from_c/use_of_import
I am now in the imported function
make[1]: Leaving directory '/usr/gnat/share/examples/gnat/other_languages'
make: Leaving directory '/usr/gnat/share/examples/gnat'
gcc -c -g demo1.adb
gcc -c -g gen_list.adb
gcc -c -g instr.adb
gnatbind -x demo1.ali
gnatlink demo1.ali -g
Notice how the examples are running on the container. At this point, we have a Docker image complete with GNAT Pro!
Using the Docker CLI, we can see the gnatpro:deps and gnatpro:20.2 images.
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
gnatpro 20.2 89afd2c07618 4 minutes ago 1.19GB
gnatpro deps 9f1bdb3dbef0 15 minutes ago 87.9MB
Only the gnatpro:20.2 image is necessary for using the GNAT Pro toolset. The gnatpro:deps image can be removed if you don't need to build images for other GNAT Pro versions.
How to Use This Example
Using these resources as a template, you'll be able to quickly get a working GNAT Pro toolchain running in a Docker container.
On top of that foundation, it is possible to build on-demand CI matrices, scalable compilation jobs, and on-demand analysis services with tools like CodePeer, GNATcoverage and SPARK Pro.
Your imagination is the limit.