Project Extension

Project extension lets one project inherit sources and settings from another and selectively override them. A common use case is patching a third-party library: the extending project replaces only the files that need to change while reusing everything else unchanged.

Basic syntax

project Patched extends "original/original.gpr" is
   --  Source files here shadow those in original.gpr.
   --  All other sources and settings are inherited.
end Patched;

The path in extends is relative to the extending project file.

What is inherited

An extending project inherits:

  • Sources - all source files from the base are implicitly present. A source file in the extending project whose base name matches a base source shadows it.

  • Attribute values - project-level attributes not declared in the extending project are inherited from the base.

  • Packages - any package not declared in the extending project is inherited in full from the base.

Exception: Linker'Linker_Options is never inherited.

Object_Dir and Library_Dir must always be overridden in the extending project; the two projects cannot share the same output directories.

project Patched extends "original/original.gpr" is
   for Object_Dir  use "obj";
   for Library_Dir use "lib";  -- only for library projects
end Patched;

Overriding sources

Place a source file with the same base name in the extending project’s source directory. The extending project’s version is used and the base version is ignored entirely.

To remove an inherited source without providing a replacement, list it in Excluded_Source_Files:

project Patched extends "original/original.gpr" is
   for Object_Dir use "obj";
   for Excluded_Source_Files use ("obsolete.adb");
end Patched;

Overriding attributes and packages

Attribute values are overridden by simply re-declaring them. Package declarations, however, replace the inherited package entirely - no per-attribute inheritance occurs within a package:

project Patched extends "original/original.gpr" is
   for Object_Dir use "obj";

   --  This replaces Original's entire Compiler package:
   package Compiler is
      for Switches ("Ada") use ("-O2");
   end Compiler;
end Patched;

To amend a package rather than replace it, use package X extends Base.X:

project Patched extends "original/original.gpr" is
   for Object_Dir use "obj";

   --  Inherit Original's Compiler switches and add one more:
   package Compiler extends Original.Compiler is
      for Switches ("Ada") use Original.Compiler'Switches ("Ada") & ("-gnatwa");
   end Compiler;
end Patched;

Abstract base projects

A common pattern is to declare common settings in an abstract base project and extend it for each concrete build variant. The concrete project still needs to declare its own project-specific attributes (such as Object_Dir) for its own purposes:

--  base.gpr
abstract project Base is
   type Build_Type is ("debug", "release");
   Build : Build_Type := external ("BUILD", "debug");

   package Compiler is
      case Build is
         when "debug"   => for Switches ("Ada") use ("-g", "-gnatwa");
         when "release" => for Switches ("Ada") use ("-O2");
      end case;
   end Compiler;
end Base;
--  my_app.gpr
project My_App extends "base.gpr" is
   for Source_Dirs use ("src");
   for Object_Dir  use "obj/" & Base.Build;
   for Exec_Dir    use "bin/" & Base.Build;
   for Main        use ("main.adb");
end My_App;

Import redirection

When a project D extends A, and D also imports C which extends B, and A imports B - then within D’s project tree, A’s import of B is automatically redirected to C. The build system connects these automatically when both extensions are present in the tree.

For example, suppose B defines a variable that A references:

--  b.gpr
library project B is
   Build_Suffix : constant String := "debug";
   for Source_Dirs  use ("src");
   for Object_Dir   use "obj";
   for Library_Name use "b";
   for Library_Dir  use "lib";
end B;
--  a.gpr
with "b.gpr";
project A is
   for Source_Dirs use ("src");
   for Object_Dir  use "obj-" & B.Build_Suffix;  --  "obj-debug"
end A;

C extends B and overrides the variable:

--  c.gpr
library project C extends "b.gpr" is
   Build_Suffix : constant String := "release";
   for Object_Dir  use "obj";
   for Library_Dir use "lib";
end C;

D extends A and imports C. When D is loaded, A’s import of B is redirected to C, so A’s Object_Dir becomes "obj-release":

--  d.gpr
with "c.gpr";
project D extends "a.gpr" is
   for Object_Dir use "obj";
end D;

Since a project tree may not depend on both an extending and an extended project, this mechanism can only be used in simple cases. That is where extends all solves the issue for more complex structures.

extends all

extends all implicitly creates an extending project for every project in the import closure of the named base and applies import redirection throughout the entire subtree in one step. This makes it practical to amend any constituent of a large project hierarchy without manually extending every project along the import path.

project Full_Override extends all "original/original.gpr" is
   for Object_Dir use "obj";
end Full_Override;

The same inheritance rules apply as with a plain extends: each constituent inherits its base’s sources, attributes, and packages with the same semantics described above. A source file placed in Full_Override’s source directory shadows the matching file wherever it originates in the tree.

To replace a specific constituent - for example to substitute instrumented sources for one particular library - import an explicit extending project for that constituent from within the extends all project. The explicit extension takes the place of the corresponding implicit one, and import redirection propagates it consistently across the whole subtree:

with "instrumented_lib.gpr";  --  extends "libs/my_lib/my_lib.gpr"

project Full_Override extends all "original/original.gpr" is
   for Object_Dir use "obj";
end Full_Override;