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.