Next: , Previous: Library Projects, Up: GNAT Project Manager


5.6 Project Extension

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 built every night for the whole team, in some shared directory. A developer usually needs 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 be used only 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 implicitly inherits 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 directories 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 values for these attributes will be used that is to say the current directory (where project Work is placed). We can compile that project with

    gprbuild -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 in 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, `gprbuild' 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).