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


5.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, while the second will focus on improving code optimization).

Let's enhance our example to support debug and 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' in release mode. We will also set up 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 two 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 the 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':
When the external value does not come from the command line, it can come from the value of environment variables of the appropriate name. In our case, if an environment variable called "mode" exists, 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 set up 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 ("Ada")
                    use ("-g");
             when "release" =>
                for Switches ("Ada")
                    use ("-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 cannot 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).