Next: , Previous: Organizing Projects into Subsystems, Up: GNAT Project Manager


11.4 Scenarios in Projects

Various aspects of the projects can be modified based on scenarios. These are user-defined modes that change the behavior of a project. Typical examples are the setup of platform-specific compiler options, or the use of a debug and a release mode (the former would activate the generation of debug information, when the second will focus on improving code optimization).

Let's enhance our example to support a debug and a release modes.The issue is to let the user choose what kind of system he is building: use -g as compiler switches in debug mode and ^-O2^-O2^ in release mode. We will also setup the projects so that we do not share the same object directory in both modes, otherwise switching from one to the other might trigger more recompilations than needed or mix objects from the 2 modes.

One naive approach is to create two different project files, say build_debug.gpr and build_release.gpr, that set the appropriate attributes as explained in previous sections. This solution does not scale well, because in presence of multiple projects depending on each other, you will also have to duplicate the complete hierarchy and adapt the project files to point to the right copies.

Instead, project files support the notion of scenarios controlled by external values. Such values can come from several sources (in decreasing order of priority):

Command line:
When launching gnatmake or gprbuild, the user can pass extra -X switches to define the external value. In our case, the command line might look like
                 gnatmake -Pbuild.gpr -Xmode=debug
             or  gnatmake -Pbuild.gpr -Xmode=release

^Environment variables^Logical names^:
When the external value does not come from the command line, it can come from the value of ^environment variables^logical names^ of the appropriate name. In our case, if ^an environment variable^a logical name^ called "mode" exist, its value will be taken into account.
External function second parameter

We now need to get that value in the project. The general form is to use the predefined function external which returns the current value of the external. For instance, we could setup the object directory to point to either obj/debug or obj/release by changing our project to

        project Build is
            for Object_Dir use "obj/" & external ("mode", "debug");
            ... --  as before
        end Build;

The second parameter to external is optional, and is the default value to use if "mode" is not set from the command line or the environment.

In order to set the switches according to the different scenarios, other constructs have to be introduced such as typed variables and case constructions.

A typed variable is a variable that can take only a limited number of values, similar to an enumeration in Ada. Such a variable can then be used in a case construction and create conditional sections in the project. The following example shows how this can be done:

        project Build is
           type Mode_Type is ("debug", "release");  --  all possible values
           Mode : Mode_Type := external ("mode", "debug"); -- a typed variable
     
           package Compiler is
              case Mode is
                 when "debug" =>
                    for ^Switches^Switches^ ("Ada")
                        use ("-g");
                 when "release" =>
                    for ^Switches^Switches^ ("Ada")
                        use ("^-O2^-O2^");
              end case;
           end Compiler;
        end Build;

The project has suddenly grown in size, but has become much more flexible. Mode_Type defines the only valid values for the mode variable. If any other value is read from the environment, an error is reported and the project is considered as invalid.

The Mode variable is initialized with an external value defaulting to "debug". This default could be omitted and that would force the user to define the value. Finally, we can use a case construction to set the switches depending on the scenario the user has chosen.

Most aspects of the projects can depend on scenarios. The notable exception are project dependencies (with clauses), which may not depend on a scenario.

Scenarios work the same way with project hierarchies: you can either duplicate a variable similar to Mode in each of the project (as long as the first argument to external is always the same and the type is the same), or simply set the variable in the shared.gpr project (see Sharing Between Projects).