GNAT GPL User's Guide: Conditional Compilation

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

# E. Conditional Compilation

It is often necessary to arrange for a single source program to serve multiple purposes, where it is compiled in different ways to achieve these different goals. Some examples of the need for this feature are

- Adapting a program to a different hardware environment
- Adapting a program to a different target architecture
- Turning debugging features on and off
- Arranging for a program to compile with different compilers

In C, or C++, the typical approach would be to use the preprocessor that is defined as part of the language. The Ada language does not contain such a feature. This is not an oversight, but rather a very deliberate design decision, based on the experience that overuse of the preprocessing features in C and C++ can result in programs that are extremely difficult to maintain. For example, if we have ten switches that can be on or off, this means that there are a thousand separate programs, any one of which might not even be syntactically correct, and even if syntactically correct, the resulting program might not work correctly. Testing all combinations can quickly become impossible.

Nevertheless, the need to tailor programs certainly exists, and in this Appendix we will discuss how this can be achieved using Ada in general, and GNAT in particular.

> E.1 Use of Boolean Constants |  |
> E.2 Debugging - A Special Case |  |
> E.3 Conditionalizing Declarations |  |
> E.4 Use of Alternative Implementations |  |
> E.5 Preprocessing |  |

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

## E.1 Use of Boolean Constants

In the case where the difference is simply which code sequence is executed, the cleanest solution is to use Boolean constants to control which code is executed.

    FP_Initialize_Required : constant Boolean := True;
    ...
    if FP_Initialize_Required then
    ...
    end if;

Not only will the code inside the `if` statement not be executed if the constant Boolean is `False`, but it will also be completely deleted from the program. However, the code is only deleted after the `if` statement has been checked for syntactic and semantic correctness. (In contrast, with preprocessors the code is deleted before the compiler ever gets to see it, so it is not checked until the switch is turned on.)

Typically the Boolean constants will be in a separate package, something like:

    package Config is
       FP_Initialize_Required : constant Boolean := True;
       Reset_Available        : constant Boolean := False;
       ...
    end Config;

The `Config` package exists in multiple forms for the various targets, with an appropriate script selecting the version of `Config` needed. Then any other unit requiring conditional compilation can do a `with` of `Config` to make the constants visible.

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

## E.2 Debugging - A Special Case

A common use of conditional code is to execute statements (for example dynamic checks, or output of intermediate results) under control of a debug switch, so that the debugging behavior can be turned on and off. This can be done using a Boolean constant to control whether the code is active:

    if Debugging then
       Put_Line ("got to the first stage!");
    end if;

or

    if Debugging and then Temperature > 999.0 then
       raise Temperature_Crazy;
    end if;

Since this is a common case, there are special features to deal with this in a convenient manner. For the case of tests, Ada 2005 has added a pragma `Assert` that can be used for such tests. This pragma is modeled on the `Assert` pragma that has always been available in GNAT, so this feature may be used with GNAT even if you are not using Ada 2005 features. The use of pragma `Assert` is described in section `Pragma Assert' in GNAT Reference Manual, but as an example, the last test could be written:

    pragma Assert (Temperature <= 999.0, "Temperature Crazy");

or simply

    pragma Assert (Temperature <= 999.0);

In both cases, if assertions are active and the temperature is excessive, the exception `Assert_Failure` will be raised, with the given string in the first case or a string indicating the location of the pragma in the second case used as the exception message.

You can turn assertions on and off by using the `Assertion_Policy` pragma. This is an Ada 2005 pragma which is implemented in all modes by GNAT, but only in the latest versions of GNAT which include Ada 2005 capability. Alternatively, you can use the ``-gnata'` switch to enable assertions from the command line (this is recognized by all versions of GNAT).

For the example above with the `Put_Line`, the GNAT-specific pragma `Debug` can be used:

    pragma Debug (Put_Line ("got to the first stage!"));

If debug pragmas are enabled, the argument, which must be of the form of a procedure call, is executed (in this case, `Put_Line` will be called). Only one call can be present, but of course a special debugging procedure containing any code you like can be included in the program and then called in a pragma `Debug` argument as needed.

One advantage of pragma `Debug` over the `if Debugging then` construct is that pragma `Debug` can appear in declarative contexts, such as at the very beginning of a procedure, before local declarations have been elaborated.

Debug pragmas are enabled using either the ``-gnata'` switch that also controls assertions, or with a separate Debug_Policy pragma. The latter pragma is new in the Ada 2005 versions of GNAT (but it can be used in Ada 95 and Ada 83 programs as well), and is analogous to pragma `Assertion_Policy` to control assertions.

`Assertion_Policy` and `Debug_Policy` are configuration pragmas, and thus they can appear in ``gnat.adc'` if you are not using a project file, or in the file designated to contain configuration pragmas in a project file. They then apply to all subsequent compilations. In practice the use of the ``-gnata'` switch is often the most convenient method of controlling the status of these pragmas.

Note that a pragma is not a statement, so in contexts where a statement sequence is required, you can't just write a pragma on its own. You have to add a `null` statement.

    if ... then
       ... -- some statements
    else
       pragma Assert (Num_Cases < 10);
       null;
    end if;

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

## E.3 Conditionalizing Declarations

In some cases, it may be necessary to conditionalize declarations to meet different requirements. For example we might want a bit string whose length is set to meet some hardware message requirement.

In some cases, it may be possible to do this using declare blocks controlled by conditional constants:

    if Small_Machine then
       declare
          X : Bit_String (1 .. 10);
       begin
          ...
       end;
    else
       declare
          X : Large_Bit_String (1 .. 1000);
       begin
          ...
       end;
    end if;

Note that in this approach, both declarations are analyzed by the compiler so this can only be used where both declarations are legal, even though one of them will not be used.

Another approach is to define integer constants, e.g. `Bits_Per_Word`, or Boolean constants, e.g. `Little_Endian`, and then write declarations that are parameterized by these constants. For example

    for Rec use
      Field1 at 0 range Boolean'Pos (Little_Endian) * 10 .. Bits_Per_Word;
    end record;

If `Bits_Per_Word` is set to 32, this generates either

    for Rec use
      Field1 at 0 range 0 .. 32;
    end record;

for the big endian case, or

    for Rec use record
      Field1 at 0 range 10 .. 32;
    end record;

for the little endian case. Since a powerful subset of Ada expression notation is usable for creating static constants, clever use of this feature can often solve quite difficult problems in conditionalizing compilation (note incidentally that in Ada 95, the little endian constant was introduced as `System.Default_Bit_Order`, so you do not need to define this one yourself).

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

## E.4 Use of Alternative Implementations

In some cases, none of the approaches described above are adequate. This can occur for example if the set of declarations required is radically different for two different configurations.

In this situation, the official Ada way of dealing with conditionalizing such code is to write separate units for the different cases. As long as this does not result in excessive duplication of code, this can be done without creating maintenance problems. The approach is to share common code as far as possible, and then isolate the code and declarations that are different. Subunits are often a convenient method for breaking out a piece of a unit that is to be conditionalized, with separate files for different versions of the subunit for different targets, where the build script selects the right one to give to the compiler.

As an example, consider a situation where a new feature in Ada 2005 allows something to be done in a really nice way. But your code must be able to compile with an Ada 95 compiler. Conceptually you want to say:

    if Ada_2005 then
       ... neat Ada 2005 code
    else
       ... not quite as neat Ada 95 code
    end if;

where `Ada_2005` is a Boolean constant.

But this won't work when `Ada_2005` is set to `False`, since the `then` clause will be illegal for an Ada 95 compiler. (Recall that although such unreachable code would eventually be deleted by the compiler, it still needs to be legal. If it uses features introduced in Ada 2005, it will be illegal in Ada 95.)

So instead we write

    procedure Insert is separate;

Then we have two files for the subunit `Insert`, with the two sets of code. If the package containing this is called `File_Queries`, then we might have two files

- ``file_queries-insert-2005.adb'`
- ``file_queries-insert-95.adb'`

and the build script renames the appropriate file to

    file_queries-insert.adb

and then carries out the compilation.

This can also be done with project files' naming schemes. For example:

    For Body ("File_Queries.Insert") use "file_queries-insert-2005.ada";

Note also that with project files it is desirable to use a different extension than ``ads'` / ``adb'` for alternative versions. Otherwise a naming conflict may arise through another commonly used feature: to declare as part of the project a set of directories containing all the sources obeying the default naming scheme.

The use of alternative units is certainly feasible in all situations, and for example the Ada part of the GNAT run-time is conditionalized based on the target architecture using this approach. As a specific example, consider the implementation of the AST feature in VMS. There is one spec:

    s-asthan.ads

which is the same for all architectures, and three bodies:

``s-asthan.adb'`
  used for all non-VMS operating systems
``s-asthan-vms-alpha.adb'`
  used for VMS on the Alpha
``s-asthan-vms-ia64.adb'`
  used for VMS on the ia64

The dummy version ``s-asthan.adb'` simply raises exceptions noting that this operating system feature is not available, and the two remaining versions interface with the corresponding versions of VMS to provide VMS-compatible AST handling. The GNAT build script knows the architecture and operating system, and automatically selects the right version, renaming it if necessary to ``s-asthan.adb'` before the run-time build.

Another style for arranging alternative implementations is through Ada's access-to-subprogram facility. In case some functionality is to be conditionally included, you can declare an access-to-procedure variable `Ref` that is initialized to designate a "do nothing" procedure, and then invoke `Ref.all` when appropriate. In some library package, set `Ref` to `Proc'Access` for some procedure `Proc` that performs the relevant processing. The initialization only occurs if the library package is included in the program. The same idea can also be implemented using tagged types and dispatching calls.

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

## E.5 Preprocessing

Although it is quite possible to conditionalize code without the use of C-style preprocessing, as described earlier in this section, it is nevertheless convenient in some cases to use the C approach. Moreover, older Ada compilers have often provided some preprocessing capability, so legacy code may depend on this approach, even though it is not standard.

To accommodate such use, GNAT provides a preprocessor (modeled to a large extent on the various preprocessors that have been used with legacy code on other compilers, to enable easier transition).

The preprocessor may be used in two separate modes. It can be used quite separately from the compiler, to generate a separate output source file that is then fed to the compiler as a separate step. This is the `gnatprep` utility, whose use is fully described in 18. Preprocessing with `gnatprep`.

The preprocessing language allows such constructs as

    #if DEBUG or else (PRIORITY > 4) then
       bunch of declarations
    #else
       completely different bunch of declarations
    #end if;

The values of the symbols `DEBUG` and `PRIORITY` can be defined either on the command line or in a separate file.

The other way of running the preprocessor is even closer to the C style and often more convenient. In this approach the preprocessing is integrated into the compilation process. The compiler is fed the preprocessor input which includes `#if` lines etc, and then the compiler carries out the preprocessing internally and processes the resulting output. For more details on this approach, see 3.2.17 Integrated Preprocessing.

[ << ] | [ >> ] |  |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

This document was generated by GNAT Mailserver on April, 17 2014 using texi2html