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):
gnatmake -Pbuild.gpr -Xmode=debug
or
gnatmake -Pbuild.gpr -Xmode=release
`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).