GNAT Tools

In chapter we present a brief overview of some of the tools included in the GNAT toolchain.

For further details on how to use these tools, please refer to the GNAT User's Guide.

gnatchop

gnatchop renames files so they match the file structure and naming convention expected by the rest of the GNAT toolchain. The GNAT compiler expects specifications to be stored in .ads files and bodies (implementations) to be stored in .adb files. It also expects file names to correspond to the content of each file. For example, it expects the specification of a package Pkg.Child to be stored in a file named pkg-child.ads.

However, we may not want to use that convention for our project. For example, we may have multiple Ada packages contained in a single file. Consider a file example.ada containing the following:

with Ada.Text_IO; use Ada.Text_IO;

package P is
   procedure Test;
end P;

package body P is
   procedure Test is
   begin
      Put_Line("Test passed.");
   end Test;
end P;

with P; use P;

procedure P_Main is
begin
   P.Test;
end P_Main;

To compile this code, we first pass the file containing our source code to gnatchop before we call gprbuild:

gnatchop example.ada
gprbuild p_main

This generates source files for our project, extracted from example_ada, that conform to the default naming convention and then builds the executable binary p_main from those files. In this example gnatchop created the files p.ads, p.adb, and p_main.adb using the package names in example.ada.

When we use this mechanism, any warnings or errors the compiler displays refers to the files generated by gnatchop. We can, however, instruct gnatchop to instrument the generated files so the compiler refers to the original file (example.ada in our case) when displaying messages. We do this by using the -r switch:

gnatchop -r example.ada
gprbuild p_main

If, for example, we had an unused variable in example.ada, the compiler warning would now refer to the line in the original file, not in one of the generated ones.

For documentation of other switches available for gnatchop, please refer to the gnatchop chapter of the GNAT User's Guide.

gnatprep

We may want to use conditional compilation in some situations. For example, we might need a customized implementation of a package for a specific platform or need to select a specific version of an algorithm depending on the requirements of the target environment. A traditional way to do this uses a source-code preprocessor. However, in many cases where conditional compilation is needed, we can instead use the syntax of the Ada language or the functionality provided by GPRbuild to avoid using a preprocessor in those cases. The conditional compilation section of the GNAT User's Guide discusses how to do this in detail.

Nevertheless, using a preprocessor is often the most straightforward option in complex cases. When we encounter such a case, we can use gnatprep, which provides a syntax that reminds us of the C and C++ preprocessor. However, unlike in C and C++, this syntax is not part of the Ada standard and can only be used with gnatprep. Also, you'll notice some differences in the syntax from that preprocessor, such as shown in the example below:

#if VERSION'Defined and then (VERSION >= 4) then
   --  Implementation for version 4.0 and above...
#else
   --  Standard implementation for older versions...
#end if;

Of course, in this simple case, we could have used the Ada language directly and avoided the preprocessor entirely:

package Config is
   Version : constant Integer := 4;
end Config;

with Config;
procedure Do_Something is
begin
   if Config.Version >= 4 then
      null;
      --  Implementation for version 4.0 and above...
   else
      null;
      --  Standard implementation for older versions...
   end if;
end Do_Something;

But for the sake of illustrating the use of gnatprep, let's use that tool in this simple case. This is the complete procedure, which we place in file do_something.org.adb:

procedure Do_Something is
begin
   #if VERSION'Defined and then (VERSION >= 4) then
   --  Implementation for version 4.0 and above...
   null;
   #else
   --  Standard implementation for older versions...
   null;
   #end if;
end Do_Something;

To preprocess this file and build the application, we call gnatprep followed by GPRbuild:

gnatprep do_something.org.adb do_something.adb
gprbuild do_something

If we look at the resulting file after preprocessing, we see that the #else implementation was selected by gnatprep. To cause it to select the newer "version" of the code, we include the symbol and its value in our call to gnatprep, just like we'd do for C/C++:

gnatprep -DVERSION=5 do_something.org.adb do_something.adb

However, a cleaner approach is to create a symbol definition file containing all symbols we use in our implementation. Let's create the file and name it prep.def:

VERSION := 5

Now we just need to pass it to gnatprep:

gnatprep do_something.org.adb do_something.adb prep.def
gprbuild do_something

When we use gnatprep in that way, the line numbers of the output file differ from those of the input file. To preserve line numbers, we can use one of these command-line switches:

  • -b: replace stripped-out code by blank lines

  • -c: comment-out the stripped-out code

For example:

gnatprep -b do_something.org.adb do_something.adb prep.def
gnatprep -c do_something.org.adb do_something.adb prep.def

When we use one of these options, gnatprep ensures that the output file do_something.adb has the same line numbering as the original file (do_something.org.adb).

The gnatprep chapter of the GNAT User's Guide contains further details about this tool, such as how to integrate gnatprep with project files for GPRbuild and how to replace symbols without using preprocessing directives (using the $symbol syntax).

gnatmem

Memory allocation errors involving mismatches between allocations and deallocations are a common source of memory leaks. To test an application for memory allocation issues, we can use gnatmem. This tool monitors all memory allocations in our application. We use this tool by linking our application to a special version of the memory allocation library (libgmem.a).

Let's consider this simple example:

procedure Simple_Mem is
   I_Ptr : access Integer := new Integer;
begin
   null;
end Simple_Mem;

To generate a memory report for this code, we need to:

  • Build the application, linking it to libgmem.a;

  • Run the application, which generates an output file (gmem.out);

  • Run gnatmem to generate a report from gmem.out.

For our example above, we do the following:

# Build application using gmem
gnatmake -g simple_mem.adb -largs -lgmem

# Run the application and generate gmem.out
./simple_mem

# Call gnatmem to display the memory report based on gmem.out
gnatmem simple_mem

For this example, gnatmem produces the following output:

Global information
------------------
   Total number of allocations        :   1
   Total number of deallocations      :   0
   Final Water Mark (non freed mem)   : 4 Bytes
   High Water Mark                    : 4 Bytes

Allocation Root # 1
-------------------
 Number of non freed allocations    :   1
 Final Water Mark (non freed mem)   : 4 Bytes
 High Water Mark                    : 4 Bytes
 Backtrace                          :
   simple_mem.adb:2 simple_mem

This shows all the memory we allocated and tells us that we didn't deallocate any of it.

Please refer to the chapter on gnatmem of the GNAT User's Guide for a more detailed discussion of gnatmem.

gnatmetric

We can use the GNAT metric tool (gnatmetric) to compute various programming metrics, either for individual files or for our complete project.

For example, we can compute the metrics of the body of package P above by running gnatmetric as follows:

gnatmetric p.adb

This produces the following output:

Line metrics summed over 1 units
  all lines            : 13
  code lines           : 11
  comment lines        : 0
  end-of-line comments : 0
  comment percentage   : 0.00
  blank lines          : 2

Average lines in body: 4.00

Element metrics summed over 1 units
  all statements      : 2
  all declarations    : 3
  logical SLOC        : 5

 2 subprogram bodies in 1 units

Average cyclomatic complexity: 1.00

Please refer to the section on gnatmetric of the GNAT User's Guide for the many switches available for gnatmetric, including the ability to generate reports in XML format.

gnatdoc

Use GNATdoc to generate HTML documentation for your project. It scans the source files in the project and extracts information from package, subprogram, and type declarations.

The simplest way to use it is to provide the name of the project or to invoke GNATdoc from a directory containing a project file:

gnatdoc -P some_directory/default.gpr

# Alternatively, when the :file:`default.gpr` file is in the same directory

gnatdoc

Just using this command is sufficient if your goal is to generate a list of the packages and a list of subprograms in each. However, to create more meaningful documentation, you can annotate your source code to add a description of each subprogram, parameter, and field. For example:

package P is
--  Collection of auxiliary subprograms

   function Add_One
     (V : Integer
      --  Coefficient to be incremented
     ) return Integer;
   --  @return Coefficient incremented by one

end P;
package body P is

   function Add_One (V : Integer) return Integer is
   begin
      return V + 1;
   end Add_One;

end P;
with P; use P;

procedure Main is

   I : Integer;

begin
   I := Add_One (0);
end Main;

When we run this example, GNATdoc will extract the documentation from the specification of package P and add the description of each element, which we provided as a comment in the line below the actual declaration. It will also extract the package description, which we wrote as a comment in the line right after package P is. Finally, it will extract the documentation of function Add_One (both the description of the V parameter and the return value).

In addition to the approach we've just seen, GNATdoc also supports the tagged format that's commonly found in tools such as Javadoc and uses the @ syntax. We could rewrite the documentation for package P as follows:

package P is
-- @summary Collection of auxiliary subprograms

   function Add_One
     (V : Integer
     ) return Integer;
   -- @param V Coefficient to be incremented
   -- @return Coefficient incremented by one

end P;

You can control what parts of the source-code GNATdoc parses to extract the documentation. For example, you can specify the -b switch to request that the package body be parsed for additional documentation and you can use the -p switch to request GNATdoc to parse the private part of package specifications. For a complete list of switches, please refer to the GNATdoc User's Guide.

gnatpp

The term 'pretty-printing' refers to the process of formatting source code according to a pre-defined convention. gnatpp is used for the pretty-printing of Ada source-code files.

Let's look at this example, which contains very messy formatting:

PrOcEDuRE Main
                  IS

   FUNCtioN
                         Init_2
                  RETurn
     inteGER                iS
                                    (2);

                I : INTeger;




     BeGiN
            I              :=        Init_2;
                 ENd;

We can request gnatpp to clean up this file by using the command:

gnatpp main.adb

gnatpp reformats the file in place. After this command, main.adb looks like this:

procedure Main is

   function Init_2 return Integer is (2);

   I : Integer;

begin
   I := Init_2;
end Main;

We can also process all source code files from a project at once by specifying a project file. For example:

gnatpp -P default.gpr

gnatpp has an extensive list of options, which allow for specifying the formatting of many aspects of the source and implementing many coding styles. These are extensively discussed in the section on gnatpp of the GNAT User's Guide.

gnatstub

Suppose you've created a complex specification of an Ada package. You can create the corresponding package body by copying and adapting the content of the package specification. But you can also have gnatstub do much of that job for you. For example, let's consider the following package specification:

package Aux is

   function Add_One (V : Integer) return Integer;

   procedure Reset (V : in out Integer);

end Aux;

We call gnatstub, passing the file containing the package specification:

gnatstub aux.ads

This generates the file aux.adb with the following contents:

pragma Ada_2012;
package body Aux is

   -------------
   -- Add_One --
   -------------

   function Add_One (V : Integer) return Integer is
   begin
      --  Generated stub: replace with real body!
      pragma Compile_Time_Warning (Standard.True, "Add_One unimplemented");
      return raise Program_Error with "Unimplemented function Add_One";
   end Add_One;

   -----------
   -- Reset --
   -----------

   procedure Reset (V : in out Integer) is
   begin
      --  Generated stub: replace with real body!
      pragma Compile_Time_Warning (Standard.True, "Reset unimplemented");
      raise Program_Error with "Unimplemented procedure Reset";
   end Reset;

end Aux;

As we can see in this example, not only has gnatstub created a package body from all the elements in the package specification, but it also created:

  • Headers for each subprogram (as comments);

  • Pragmas and exceptions that prevent us from using the unimplemented subprograms in our application.

This is a good starting point for the implementation of the body. Please refer to the section on gnatstub of the GNAT User's Guide for a detailed discussion of gnatstub and its options.