Next: Project File Reference, Previous: Project Extension, Up: GNAT Project Manager [Contents][Index]
Aggregate projects are an extension of the project paradigm, and are meant to solve a few specific use cases that cannot be solved directly using standard projects. This section will go over a few of these use cases to try and explain what you can use aggregate projects for.
Most often, an application is organized into modules and submodules,
which are very conveniently represented as a project tree or graph
(the root project A with
s the projects for each modules (say B and C),
which in turn with
projects for submodules.
Very often, modules will build their own executables (for testing purposes for instance), or libraries (for easier reuse in various contexts).
However, if you build your project through gnatmake or gprbuild, using a syntax similar to
gprbuild -PA.gpr
this will only rebuild the main units of project A, not those of the imported projects B and C. Therefore you have to spawn several gnatmake commands, one per project, to build all executables. This is a little inconvenient, but more importantly is inefficient (since gnatmake needs to do duplicate work to ensure that sources are up-to-date, and cannot easily compile things in parallel when using the -j switch).
Also libraries are always rebuild when building a project.
You could therefore define an aggregate project Agg that groups A, B and C. Then, when you build with
gprbuild -PAgg.gpr
this will build all main units from A, B and C.
aggregate project Agg is for Project_Files use ("a.gpr", "b.gpr", "c.gpr"); end Agg;
If B or C do not define any main unit (through their Main attribute), all their sources are build. When you do not group them in the aggregate project, only those sources that are needed by A will be build.
If you add a main unit to a project P not already explicitly referenced in the aggregate project, you will need to add "p.gpr" in the list of project files for the aggregate project, or the main unit will not be built when building the aggregate project.
One other case is when you have multiple applications and libraries that are build independently from each other (but they can be build in parallel). For instance, you have a project tree rooted at A, and another one (which might share some subprojects) rooted at B.
Using only gprbuild, you could do
gprbuild -PA.gpr gprbuild -PB.gpr
to build both. But again, gprbuild has to do some duplicate work for those files that are shared between the two, and cannot truly build things in parallel efficiently.
If the two projects are really independent, share no sources other than through a common subproject, and have no source files with a common basename, you could create a project C that imports A and B. But these restrictions are often too strong, and one has to build them independently. An aggregate project does not have these limitations, and can aggregate two project trees that have common sources.
Aggregate projects can group projects with duplicate file names
This scenario is particularly useful in environment like VxWork 653 where the applications running in the multiple partitions can be build in parallel through a single gprbuild command. This also works nicely with Annex E.
Aggregate projects can be used to build multiple partitions
The environment variables at the time you launch gprbuild or gprbuild will influence the view these tools have of the project (PATH to find the compiler, ADA_PROJECT_PATH or GPR_PROJECT_PATH to find the projects, environment variables that are referenced in project files through the "external" statement,...). Several command line switches can be used to override those (-X or -aP), but on some systems and with some projects, this might make the command line too long, and on all systems often make it hard to read.
An aggregate project can be used to set the environment for all projects build through that aggregate. One of the nice aspects is that you can put the aggregate project under configuration management, and make sure all your user have a consistent environment when building. The syntax looks like
aggregate project Agg is for Project_Files use ("A.gpr", "B.gpr"); for Project_Path use ("../dir1", "../dir1/dir2"); for External ("BUILD") use "PRODUCTION"; package Builder is for Switches ("Ada") use ("-q"); end Builder; end Agg;
One of the often requested features in projects is to be able to
reference external variables in with
statements, as in
with external("SETUP") & "path/prj.gpr"; -- ILLEGAL project MyProject is ... end MyProject;
For various reasons, this isn’t authorized. But using aggregate projects provide an elegant solution. For instance, you could use a project file like:
aggregate project Agg is for Project_Path use (external("SETUP") % "path"); for Project_Files use ("myproject.gpr"); end Agg; with "prj.gpr"; -- searched on Agg'Project_Path project MyProject is ... end MyProject;
The loading of aggregate projects is optimized in gprbuild and gnatmake, so that all files are searched for only once on the disk (thus reducing the number of system calls and contributing to faster compilation times especially on systems with sources on remote servers). As part of the loading, gprbuild and gnatmake compute how and where a source file should be compiled, and even if it is found several times in the aggregated projects it will be compiled only once.
Since there is no ambiguity as to which switches should be used, files can be compiled in parallel (through the usual -j switch) and this can be done while maximizing the use of CPUs (compared to launching multiple gprbuild and gnatmake commands in parallel).
An aggregate project follows the general syntax of project files. The
recommended extension is still .gpr. However, a special
aggregate
qualifier must be put before the keyword
project
.
An aggregate project cannot with
any other project (standard or
aggregate), except an abstract project which can be used to share
attribute values. Building other aggregate projects from an aggregate
project is done through the Project_Files attribute (see below).
An aggregate project does not have any source files directly (only through other standard projects). Therefore a number of the standard attributes and packages are forbidden in an aggregate project. Here is the (non exhaustive) list:
The only package that is authorized (albeit optional) is Builder. Other packages (in particular Compiler, Binder and Linker) are forbidden. It is an error to have any of these (and such an error prevents the proper loading of the aggregate project).
Three new attributes have been created, which can only be used in the context of aggregate projects:
This attribute is compulsory (or else we are not aggregating any project, and thus not doing anything). It specifies a list of .gpr files that are grouped in the aggregate. The list may be empty. The project files can be either other aggregate projects, or standard projects. When grouping standard projects, you can have both the root of a project tree (and you do not need to specify all its imported projects), and any project within the tree.
Basically, the idea is to specify all those projects that have
main units you want to build and link, or libraries you want to
build. You can even specify projects that do not use the Main
attribute nor the Library_*
attributes, and the result will be to
build all their source files (not just the ones needed by other
projects).
The file can include paths (absolute or relative). Paths are relative to the location of the aggregate project file itself (if you use a base name, we expect to find the .gpr file in the same directory as the aggregate project file). The extension .gpr is mandatory, since this attribute contains file names, not project names.
Paths can also include the "*"
and "**"
globbing patterns. The
latter indicates that any subdirectory (recursively) will be
searched for matching files. The latter ("**"
) can only occur at the
last position in the directory part (ie "a/**/*.gpr"
is supported, but
not "**/a/*.gpr"
). Starting the pattern with "**"
is equivalent
to starting with "./**"
.
For now, the pattern "*"
is only allowed in the filename part, not
in the directory part. This is mostly for efficiency reasons to limit the
number of system calls that are needed.
Here are a few valid examples:
for Project_Files use ("a.gpr", "subdir/b.gpr"); -- two specific projects relative to the directory of agg.gpr for Project_Files use ("**/*.gpr"); -- all projects recursively
This attribute can be used to specify a list of directories in
which to look for project files in with
statements.
When you specify a project in Project_Files
say "x/y/a.gpr"
), and this projects imports a project "b.gpr", only
b.gpr is searched in the project path. a.gpr must be exactly at
<dir of the aggregate>/x/y/a.gpr.
This attribute, however, does not affect the search for the aggregated
project files specified with Project_Files
.
Each aggregate project has its own (that is if agg1.gpr includes agg2.gpr, they can potentially both have a different project path). This project path is defined as the concatenation, in that order, of the current directory, followed by the command line -aP switches, then the directories from the Project_Path attribute, then the directories from the GPR_PROJECT_PATH and ADA_PROJECT_PATH env. variables, and finally the predefined directories.
In the example above, agg2.gpr’s project path is not influenced by the attribute agg1’Project_Path, nor is agg1 influenced by agg2’Project_Path.
This can potentially lead to errors. In the following example:
+---------------+ +----------------+ | Agg1.gpr |-=--includes--=-->| Agg2.gpr | | 'project_path| | 'project_path | | | | | +---------------+ +----------------+ : : includes includes : : v v +-------+ +---------+ | P.gpr |<---------- withs --------| Q.gpr | +-------+---------\ +---------+ | | withs | | | v v +-------+ +---------+ | R.gpr | | R'.gpr | +-------+ +---------+
When looking for p.gpr, both aggregates find the same physical file on the disk. However, it might happen that with their different project paths, both aggregate projects would in fact find a different r.gpr. Since we have a common project (p.gpr) "with"ing two different r.gpr, this will be reported as an error by the builder.
Directories are relative to the location of the aggregate project file.
Here are a few valid examples:
for Project_Path use ("/usr/local/gpr", "gpr/");
This attribute can be used to set the value of environment
variables as retrieved through the external
statement
in projects. It does not affect the environment variables
themselves (so for instance you cannot use it to change the value
of your PATH as seen from the spawned compiler).
This attribute affects the external values as seen in the rest of the aggreate projects, and in the aggregated projects.
The exact value of external a variable comes from one of three sources (each level overrides the previous levels):
for External ("BUILD_MODE") use "DEBUG"
;
These override the value given by the attribute, so that users can override the value set in the (presumably shared with others in his team) aggregate project.
This always takes precedence.
This attribute is only taken into account in the main aggregate project (i.e. the one specified on the command line to gprbuild or natmake), and ignored in other aggregate projects. It is invalid in standard projects. The goal is to have a consistent value in all projects that are build through the aggregate, which would not be the case in the diamond case: A groups the aggregate projects B and C, which both (either directly or indirectly) build the project P. If B and C could set different values for the environment variables, we would have two different views of P, which in particular might impact the list of source files in P.
As we mentioned before, only the package Builder can be specified in an aggregate project. In this package, only the following attributes are valid:
This attribute gives the list of switches to use for the builder (gprbuild or gnatmake), depending on the language of the main file. For instance,
for Switches ("Ada") use ("-d", "-p"); for Switches ("C") use ("-p");
These switches are only read from the main aggregate project (the one passed on the command line), and ignored in all other aggregate projects or projects.
It can only contain builder switches, not compiler switches.
This attribute gives the list of compiler switches for the various languages. For instance,
for Global_Compilation_Switches ("Ada") use ("-O1", "-g"); for Global_Compilation_Switches ("C") use ("-O2");
This attribute is only taken into account in the aggregate project specified on the command line, not in other aggregate projects.
In the projects grouped by that aggregate, the attribute Builder.Global_Compilation_Switches is also ignored. However, the attribute Compiler.Default_Switches will be taken into account (but that of the aggregate have higher priority). The attribute Compiler.Switches is also taken into account and can be used to override the switches for a specific file. As a result, it always has priority.
The rules are meant to avoid ambiguities when compiling. For instance, aggregate project Agg groups the projects A and B, that both depend on C. Here is an extra for all of these projects:
aggregate project Agg is for Project_Files use ("a.gpr", "b.gpr"); package Builder is for Global_Compilation_Switches ("Ada") use ("-O2"); end Builder; end Agg; with "c.gpr"; project A is package Builder is for Global_Compilation_Switches ("Ada") use ("-O1"); -- ignored end Builder; package Compiler is for Default_Switches ("Ada") use ("-O1", "-g"); for Switches ("a_file1.adb") use ("-O0"); end Compiler; end A; with "c.gpr"; project B is package Compiler is for Default_Switches ("Ada") use ("-O0"); end Compiler; end B; project C is package Compiler is for Default_Switches ("Ada") use ("-O3, "-gnatn"); for Switches ("c_file1.adb") use ("-O0", "-g"); end Compiler; end C;
then the following switches are used:
Even though C is seen through two paths (through A and through B), the switches used by the compiler are unambiguous.
This attribute can be used to specify a file containing configuration pragmas, to be passed to the compiler. Since we ignore the package Builder in other aggregate projects and projects, only those pragmas defined in the main aggregate project will be taken into account.
Projects can locally add to those by using the
Compiler.Local_Configuration_Pragmas
attribute if they need.
For projects that are build through the aggregate, the package Builder is ignored, except for the Executable attribute which specifies the name of the executables resulting from the link of the main units, and for the Executable_Suffix.