GNAT Tools

In chapter we present a brief overview of some of the tools included in the GNAT Community 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 contain 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 ofen 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 prepocessor 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 package body, 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).

gnatelim

When creating new applications, we may need to reuse existing frameworks containing multiple packages. While this approach allows us to quickly create useful applications, it may result in applications that contain many unused subprograms. For example, our application may use only two procedures of a package P that contains 15 procedures and functions. If so, our resulting application will contain code for 13 unused procedures or functions. This can especially be a problem in platforms with strict memory contraints.

GNAT provides the pragma Eliminate to remove unused code. When we specify this pragma for a subprogram, the compiler doesn't generate code for that subprogram, reducing the overall code size. We can use this pragma manually, but we can simplify our task by using gnatelim, which scans source files and determines which subprograms are unreferenced and can be safely eliminated.

Let's start with a simple example:

package P is
   procedure Test_1;
   procedure Test_2;
end P;

with Ada.Text_IO; use Ada.Text_IO;

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

   procedure Test_2 is
   begin
      Put_Line("Test #2 passed.");
   end Test_2;
end P;
with P; use P;

procedure Main_App is
begin
   P.Test_1;
end Main_App;

To create a list of unused subprograms for Main_App, we call gnatelim, passing the file containing the main application and the file we want to check for unused code. In this example:

gnatelim -main=main_app.adb p.adb

We get the following output:

---------------------------------------------------------
--  List of unused entities to be placed in gnat.adc.  --
---------------------------------------------------------
pragma Eliminate (P, Test_2, Source_Location => "p.ads:3");

We can then place this output in the configuration pragma file of our application, gnat.adc. Let's do that by redirecting the output of gnatelim and then recompiling our application:

gnatelim -main=main_app.adb p.adb > gnat.adc
gnatmake main_app.adb

(Of course, if we already had a gnat.adc file, we would add the pragmas produced by gnatelim to it instead of replacing the entire file as we did above.)

We can also associate gnatelim with project files for GPRbuild.

Please refer to the gnatelim chapter of the GNAT User's Guide for further switches for gnatprep.

gnatxref and gnatfind

We can use the gnatxref and gnatfind tools to retrieve information about individual elements in our source-code, such as variable and subprogram declarations. Both tools are based on the cross-referencing information generated by the compiler and stored in .ali files.

Let's reuse an example from the previous section:

package P is
   procedure Test_1;
   procedure Test_2;
end P;

with Ada.Text_IO; use Ada.Text_IO;

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

   procedure Test_2 is
   begin
      Put_Line("Test #2 passed.");
   end Test_2;
end P;
with P; use P;

procedure Main_App is
begin
   P.Test_1;
end Main_App;

After building the application, we can use gnatxref to display elements from individual files:

gnatxref p.adb

This generates the following report:

Ada                                              package
  Decl:  ada.ads     16:9
  Ref:   p.adb        1:6       1:23
P                                                package
  Decl:  p.ads        1:9
  Body:  p.adb        3:14
Put_Line                                         procedure
  Decl:  a-textio.ads         263:14
  Ref:   p.adb        6:7      11:7
Test_1                                           procedure
  Decl:  p.ads        2:14
  Body:  p.adb        4:14
Test_2                                           procedure
  Decl:  p.ads        3:14
  Body:  p.adb        9:14
Text_IO                                          package
  Decl:  a-textio.ads          49:13
  Ref:   p.adb        1:27

This report contains the locations of the declaration and body of package P and procedures Test_1 and Test_2 as well as of the declaration of Put_Line (from the Ada.Test_IO) package and of its reference in the body of package P.

We can use gnatfind to retrieve all references to a specific pattern. For example, let's search for all instances of Test_1:

gnatfind -r Test_1

This generates the following output:

p.ads:2:14:           (spec) Test_1
p.adb:4:14:           (body) Test_1
main_app.adb:5:6:            Test_1
p.adb:7:8:                   Test_1

The chapter on gnatxref and gnatfind of the GNAT User's Guide contains further examples and describes the usage of more command-line switches for these tools.

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.

gnat2xml

gnat2xml is a tool for generating an XML representation of the source code from a project. You can use this XML representation to analyze your source code. Use the tool by naming the project file when calling it:

gnat2xml -P default.gpr > output.xml

gnat2xml writes the XML representation to the standard output by default, so we usually redirect it to a file, as we did in the example above. In that example, the output.xml file receives an XML representation of the complete source code of our project.

Alternatively, we can generate individual XML files corresponding to each source file from our project. To do that, we use the --output-dir switch:

gnat2xml -P default.gpr --output-dir=out_xml

This command generates multiple XML files and stores them in the out_xml directory.

Let's consider this simple example:

procedure Main is

   function Init_2 return Integer is (2);

   I : Integer;

begin
   I := Init_2;
end Main;

For this example, gnat2xml generates the following XML representation of the program:

<?xml version="1.0" encoding="UTF-8" ?>
<compilation_unit unit_kind="A_Procedure_Body" unit_class="A_Public_Declaration_And_Body" unit_origin="An_Application_Unit" unit_full_name="Main" def_name="Main" source_file="main.adb">
   <sloc line="2" col="1" endline="10" endcol="9"/>
   <context_clause_elements_ql>
   </context_clause_elements_ql>
   <unit_declaration_q>
      <procedure_body_declaration>
         <sloc line="2" col="1" endline="10" endcol="9"/>
         <is_overriding_declaration_q>
            <not_an_element>
               <sloc line="1" col="1" endline="0" endcol="0"/>
            </not_an_element>
         </is_overriding_declaration_q>
         <is_not_overriding_declaration_q>
            <not_an_element>
               <sloc line="1" col="1" endline="0" endcol="0"/>
            </not_an_element>
         </is_not_overriding_declaration_q>
         <names_ql>
            <defining_identifier def_name="Main" def="ada://procedure_body/Main+2:11" type="null">
               <sloc line="2" col="11" endline="2" endcol="14"/>
            </defining_identifier>
         </names_ql>
         <parameter_profile_ql>
         </parameter_profile_ql>
         <aspect_specifications_ql>
         </aspect_specifications_ql>
         <body_declarative_items_ql>
            <expression_function_declaration>
               <sloc line="4" col="4" endline="4" endcol="41"/>
               <is_overriding_declaration_q>
                  <not_an_element>
                     <sloc line="1" col="1" endline="0" endcol="0"/>
                  </not_an_element>
               </is_overriding_declaration_q>
               <is_not_overriding_declaration_q>
                  <not_an_element>
                     <sloc line="1" col="1" endline="0" endcol="0"/>
                  </not_an_element>
               </is_not_overriding_declaration_q>
               <names_ql>
                  <defining_identifier def_name="Init_2" def="ada://expression_function/Main+2:11/Init_2+4:13" type="null">
                     <sloc line="4" col="13" endline="4" endcol="18"/>
                  </defining_identifier>
               </names_ql>
               <parameter_profile_ql>
               </parameter_profile_ql>
               <is_not_null_return_q>
                  <not_an_element>
                     <sloc line="1" col="1" endline="0" endcol="0"/>
                  </not_an_element>
               </is_not_null_return_q>
               <result_profile_q>
                  <identifier ref_name="Integer" ref="ada://ordinary_type/Standard-1:1/Integer-1:1" type="null">
                     <sloc line="4" col="27" endline="4" endcol="33"/>
                  </identifier>
               </result_profile_q>
               <result_expression_q>
                  <parenthesized_expression type="ada://ordinary_type/Standard-1:1/Integer-1:1">
                     <sloc line="4" col="38" endline="4" endcol="40"/>
                     <expression_parenthesized_q>
                        <integer_literal type="universal integer" lit_val="2">
                           <sloc line="4" col="39" endline="4" endcol="39"/>
                        </integer_literal>
                     </expression_parenthesized_q>
                  </parenthesized_expression>
               </result_expression_q>
               <aspect_specifications_ql>
               </aspect_specifications_ql>
            </expression_function_declaration>
            <variable_declaration>
               <sloc line="6" col="4" endline="6" endcol="15"/>
               <names_ql>
                  <defining_identifier def_name="I" def="ada://variable/Main+2:11/I+6:4" type="ada://ordinary_type/Standard-1:1/Integer-1:1">
                     <sloc line="6" col="4" endline="6" endcol="4"/>
                  </defining_identifier>
               </names_ql>
               <has_aliased_q>
                  <not_an_element>
                     <sloc line="1" col="1" endline="0" endcol="0"/>
                  </not_an_element>
               </has_aliased_q>
               <object_declaration_view_q>
                  <subtype_indication>
                     <sloc line="6" col="8" endline="6" endcol="14"/>
                     <has_aliased_q>
                        <not_an_element>
                           <sloc line="1" col="1" endline="0" endcol="0"/>
                        </not_an_element>
                     </has_aliased_q>
                     <has_null_exclusion_q>
                        <not_an_element>
                           <sloc line="1" col="1" endline="0" endcol="0"/>
                        </not_an_element>
                     </has_null_exclusion_q>
                     <subtype_mark_q>
                        <identifier ref_name="Integer" ref="ada://ordinary_type/Standard-1:1/Integer-1:1" type="null">
                           <sloc line="6" col="8" endline="6" endcol="14"/>
                        </identifier>
                     </subtype_mark_q>
                     <subtype_constraint_q>
                        <not_an_element>
                           <sloc line="1" col="1" endline="0" endcol="0"/>
                        </not_an_element>
                     </subtype_constraint_q>
                  </subtype_indication>
               </object_declaration_view_q>
               <initialization_expression_q>
                  <not_an_element>
                     <sloc line="1" col="1" endline="0" endcol="0"/>
                  </not_an_element>
               </initialization_expression_q>
               <aspect_specifications_ql>
               </aspect_specifications_ql>
            </variable_declaration>
         </body_declarative_items_ql>
         <body_statements_ql>
            <assignment_statement>
               <sloc line="9" col="4" endline="9" endcol="15"/>
               <label_names_ql>
               </label_names_ql>
               <assignment_variable_name_q>
                  <identifier ref_name="I" ref="ada://variable/Main+2:11/I+6:4" type="ada://ordinary_type/Standard-1:1/Integer-1:1">
                     <sloc line="9" col="4" endline="9" endcol="4"/>
                  </identifier>
               </assignment_variable_name_q>
               <assignment_expression_q>
                  <function_call type="ada://ordinary_type/Standard-1:1/Integer-1:1">
                     <sloc line="9" col="9" endline="9" endcol="14"/>
                     <prefix_q>
                        <identifier ref_name="Init_2" ref="ada://expression_function/Main+2:11/Init_2+4:13" type="null">
                           <sloc line="9" col="9" endline="9" endcol="14"/>
                        </identifier>
                     </prefix_q>
                     <function_call_parameters_ql>
                     </function_call_parameters_ql>
                     <is_prefix_call_q>
                        <is_prefix_call>
                           <sloc line="1" col="1" endline="0" endcol="0"/>
                        </is_prefix_call>
                     </is_prefix_call_q>
                     <is_prefix_notation_q>
                        <not_an_element>
                           <sloc line="1" col="1" endline="0" endcol="0"/>
                        </not_an_element>
                     </is_prefix_notation_q>
                  </function_call>
               </assignment_expression_q>
            </assignment_statement>
         </body_statements_ql>
         <body_exception_handlers_ql>
         </body_exception_handlers_ql>
      </procedure_body_declaration>
   </unit_declaration_q>
   <pragmas_after_ql>
   </pragmas_after_ql>
</compilation_unit>

Although we started from a source that was very small, the corresponding XML representation is quite verbose because gnat2xml classifies each syntactical element in the source and produces a detailed description of each.

Please refer to the section on gnat2xml of the GNAT User's Guide for a detailed discussion on gnat2xml.

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.

gnattest

gnattest creates unit tests based on AUnit, the Ada Unit Testing Framework. In principle, we could create unit tests based on AUnit by coding everything by hand. However, gnattest saves us time by generating all the boilerplate code required to connect our project's package specification to the AUnit framework.

In this section, we'll reuse the package specification of the Aux package from the previous example:

package Aux is

   function Add_One (V : Integer) return Integer;

   procedure Reset (V : in out Integer);

end Aux;

We start by calling gnattest for our project:

gnattest -P default.gpr -r

We specify the -r switch to request that gnattest recursively create unit tests for all package specifications in our project.

It will put the complete generated code in obj/gnattest. We can build it by calling GPRbuild for the test_driver project that gnattest generated:

gprbuild ./obj/gnattest/harness/test_driver.gpr

Finally, we can now run the generated application:

./obj/gnattest/harness/test_runner

If we implemented the actual unit tests, we'd see the test results at this point. However, we haven't implemented them yet. The stub code generated by gnattest reminds us of this when running the test_runner application:

aux.ads:3:4: error: corresponding test FAILED: Test not implemented. (aux-test_data-tests.adb:44)
aux.ads:5:4: error: corresponding test FAILED: Test not implemented. (aux-test_data-tests.adb:65)
2 tests run: 0 passed; 2 failed; 0 crashed.

Our next step is to implement the actual unit tests, which go into the obj/gnattest/tests/aux-test_data-tests.adb file. To see what the behavior would look like when the test is implemented, we can simply replace the calls to Assert in that file by null. If we do that and rebuild the application, we get:

aux.ads:3:4: info: corresponding test PASSED
aux.ads:5:4: info: corresponding test PASSED
2 tests run: 2 passed; 0 failed; 0 crashed.

Of course, this latest change created fake tests. We would now implement the actual tests where those calls to Assert were originally located. You can find more details on how to do that in the AUnit Cookbook. gnattest offers many options to configure the test generation and integrate it to project files. You can find more information in the section on gnattest in the GNAT User's Guide.