During development of a large system, it is sometimes necessary to use modified versions of some of the source files, without changing the original sources. This can be achieved through the project extension facility.
Suppose for instance that our example Build
project is build every night
for the whole team, in some shared directory. A developer usually need to work
on a small part of the system, and might not want to have a copy of all the
sources and all the object files (mostly because that would require too much
disk space, time to recompile everything). He prefers to be able to override
some of the source files in his directory, while taking advantage of all the
object files generated at night.
Another example can be taken from large software systems, where it is common to have multiple implementations of a common interface; in Ada terms, multiple versions of a package body for the same spec. For example, one implementation might be safe for use in tasking programs, while another might only be used in sequential applications. This can be modeled in GNAT using the concept of project extension. If one project (the “child”) extends another project (the “parent”) then by default all source files of the parent project are inherited by the child, but the child project can override any of the parent's source files with new versions, and can also add new files or remove unnecessary ones. This facility is the project analog of a type extension in object-oriented programming. Project hierarchies are permitted (an extending project may itself be extended), and a project that extends a project can also import other projects.
A third example is that of using project extensions to provide different
versions of the same system. For instance, assume that a Common
project is used by two development branches. One of the branches has now
been frozen, and no further change can be done to it or to Common
.
However, the other development branch still needs evolution of Common
.
Project extensions provide a flexible solution to create a new version
of a subsystem while sharing and reusing as much as possible from the original
one.
A project extension inherits implicitly all the sources and objects from the
project it extends. It is possible to create a new version of some of the
sources in one of the additional source dirs of the extending project. Those new
versions hide the original versions. Adding new sources or removing existing
ones is also possible. Here is an example on how to extend the project
Build
from previous examples:
project Work extends "../bld/build.gpr" is end Work;
The project after extends is the one being extended. As usual, it can be
specified using an absolute path, or a path relative to any of the directories
in the project path (see Project Dependencies). This project does not
specify source or object directories, so the default value for these attribute
will be used that is to say the current directory (where project Work
is
placed). We can already compile that project with
gnatmake -Pwork
If no sources have been placed in the current directory, this command
won't do anything, since this project does not change the
sources it inherited from Build
, therefore all the object files
in Build
and its dependencies are still valid and are reused
automatically.
Suppose we now want to supply an alternate version of pack.adb
but use the existing versions of pack.ads and proc.adb.
We can create the new file Work's current directory (likely
by copying the one from the Build
project and making changes to
it. If new packages are needed at the same time, we simply create
new files in the source directory of the extending project.
When we recompile, gnatmake will now automatically recompile this file (thus creating pack.o in the current directory) and any file that depends on it (thus creating proc.o). Finally, the executable is also linked locally.
Note that we could have obtained the desired behavior using project import
rather than project inheritance. A base
project would contain the
sources for pack.ads and proc.adb, and Work
would
import base
and add pack.adb. In this scenario, base
cannot contain the original version of pack.adb otherwise there would be
2 versions of the same unit in the closure of the project and this is not
allowed. Generally speaking, it is not recommended to put the spec and the
body of a unit in different projects since this affects their autonomy and
reusability.
In a project file that extends another project, it is possible to indicate that an inherited source is not part of the sources of the extending project. This is necessary sometimes when a package spec has been overridden and no longer requires a body: in this case, it is necessary to indicate that the inherited body is not part of the sources of the project, otherwise there will be a compilation error when compiling the spec.
For that purpose, the attribute Excluded_Source_Files is used.
Its value is a list of file names.
It is also possible to use attribute Excluded_Source_List_File
.
Its value is the path of a text file containing one file name per
line.
project Work extends "../bld/build.gpr" is for Source_Files use ("pack.ads"); -- New spec of Pkg does not need a completion for Excluded_Source_Files use ("pack.adb"); end Work;
All packages that are not declared in the extending project are inherited from
the project being extended, with their attributes, with the exception of
Linker'Linker_Options
which is never inherited. In particular, an
extending project retains all the switches specified in the project being
extended.
At the project level, if they are not declared in the extending project, some
attributes are inherited from the project being extended. They are:
Languages
, Main
(for a root non library project) and
Library_Name
(for a project extending a library project)