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