Custom Incremental Builder

The GPR2 build infrastructure provides a complete framework for implementing incremental builders on top of the project model. It is used by gprbuild but is fully available to third-party tools. The key packages are:

  • GPR2.Build.Tree_Db - persistent build database and action DAG.

  • GPR2.Build.Actions - abstract base type for a single build step.

  • GPR2.Build.Artifacts - abstract base type for the inputs and outputs that connect actions to each other.

  • GPR2.Build.Actions_Population - populates the action graph from a project tree using the standard GPR2 build actions.

  • GPR2.Build.Actions_Scheduler - parallel action execution engine.

Overview

A build proceeds in four stages:

  1. Load the project tree (Tree.Load).

  2. Populate sources (Tree.Update_Sources).

  3. Populate the action graph - either via Actions_Population.Populate_Actions for standard GPR builds, or by inserting custom actions directly into Tree.Artifacts_Database.

  4. Execute the graph (Tree.Artifacts_Database.Execute).

The build database

Tree.Artifacts_Database returns an access to the GPR2.Build.Tree_Db.Object for the tree. The database is created automatically when the tree is loaded.

Db : constant GPR2.Build.Tree_Db.Object_Access :=
       Tree.Artifacts_Database;

The database holds a directed acyclic graph of actions connected by artifacts. Each action’s output artifacts become input artifacts of downstream actions, establishing the dependency order for execution. Signature checksums persist on disk so that unchanged actions are skipped on the next run.

Populating the standard action graph

For standard GPR builds (compile, bind, link), use GPR2.Build.Actions_Population.Populate_Actions:

with GPR2.Build.Actions_Population;
with GPR2.Build.Options;

Build_Opts : GPR2.Build.Options.Build_Options;
--  Build_Opts.Mains may be set to restrict to specific mains;
--  leave empty to build all mains from the root project.

if not GPR2.Build.Actions_Population.Populate_Actions
  (Tree    => Tree,
   Options => Build_Opts)
then
   return;
end if;

if not Db.Propagate_Actions then
   return;
end if;

Populate_Actions inserts compile, bind, and link actions for every source in the tree. Propagate_Actions then calls On_Tree_Propagation on each action to resolve cross-action dependencies (e.g. the Ada binder closure).

Executing the graph

Pass a scheduler and options to Db.Execute:

with GPR2.Build.Actions_Scheduler;

Scheduler  : GPR2.Build.Actions_Scheduler.Object;
Sched_Opts : GPR2.Build.Actions_Scheduler.Options;

Sched_Opts.Jobs         := 0;    --  0 = auto-detect CPU count
Sched_Opts.Stop_On_Fail := True;

case Db.Execute (Scheduler, Sched_Opts) is
   when GPR2.Build.Actions_Scheduler.Success => null;
   when GPR2.Build.Actions_Scheduler.Errors  =>
      --  some actions reported errors
      return;
   when GPR2.Build.Actions_Scheduler.Failed  =>
      --  some actions failed to launch
      return;
end case;

Key Options fields:

Jobs

Parallel job count; 0 auto-detects the number of CPUs.

Force

Re-execute all actions regardless of signature validity.

Stop_On_Fail

Abort on first failure (default True).

Keep_Temp_Files

Preserve temporary files after execution (useful for debugging).

Script_File

If defined, records all executed commands to this file.

Show_Progress

Emit progress counters as actions are dispatched.

No_Warnings_Replay

When set, warnings from skipped (up-to-date) actions are suppressed rather than replayed.

Force_Jobserver

When set, abort if no Make jobserver protocol is available.

Actions

GPR2.Build.Actions.Object is the abstract base type for a build step. Each action owns a view (its context for looking up attributes and directories) and a signature (checksums of all its inputs and outputs).

Built-in concrete actions provided by the library:

GPR2.Build.Actions.Compile

Compiles one source file for any language.

GPR2.Build.Actions.Compile.Ada

Ada-specific compilation (extends Compile).

GPR2.Build.Actions.Ada_Bind

Runs the Ada binder (gnatbind) for one main.

GPR2.Build.Actions.Post_Bind

Compiles the binder-generated body.

GPR2.Build.Actions.Link

Links an executable or shared library.

GPR2.Build.Actions.Link.Partial

Partial link step used for standalone libraries.

Action lifecycle hooks

Each action participates in the build graph via the following hooks, called in this order:

On_Tree_Insertion

Called when the action is added to the database. The action declares its output artifacts and may insert follow-up actions (e.g. a bind action inserts the post-bind compile action here).

On_Tree_Propagation

Called after initial population. Used to expand dependencies dynamically (e.g. the binder walks the Ada closure to pull in all required compilation units). Default implementation does nothing.

Compute_Command

Builds the command line just before execution. Also called when the signature is valid (to include the command line in the signature check) with Signature_Only => True.

Pre_Command

Called immediately before the process is spawned. Not called when the action is skipped. Default returns True.

Post_Command

Called after the process completes, is skipped, or fails. Default returns True.

On_Static_Completion

Replaces Pre_Command/Post_Command when actions are populated but not executed (e.g. gprinstall). Must not modify artifacts. Default returns True.

Artifacts

GPR2.Build.Artifacts.Object is the interface that connects actions. An action’s outputs become inputs to downstream actions, establishing the DAG edges. Concrete artifact types:

Artifacts.Files.Object

A filesystem file (source, object, library, …).

Artifacts.Object_File.Object

A compiled object file.

Artifacts.Library.Object

A static or shared library.

Artifacts.Key_Value.Object

An abstract key/value pair; used for ordering actions that do not produce a file (e.g. the UID artifact that establishes execution order without file dependencies).

Artifacts.Source_Files.Object

A source file as a build artifact.

Wiring actions to artifacts is done via the database:

--  Register an output artifact for an action
if not Db.Add_Output (Action.UID, My_Object_File) then
   --  artifact already owned by another action
end if;

--  Register an input dependency
Db.Add_Input
  (Action   => Downstream_Action.UID,
   Artifact => My_Object_File,
   Explicit => True);

Implementing a custom action

Extend GPR2.Build.Actions.Object, implement Action_Id, and override the mandatory primitives:

with GPR2.Build.Actions;
with GPR2.Build.Tree_Db;
with GPR2.Build.Command_Line;

type My_Action_Id is new GPR2.Build.Actions.Action_Id with record
   View  : GPR2.Project.View.Object;
   Input : GPR2.Path_Name.Object;
end record;

overriding function View
  (Self : My_Action_Id) return GPR2.Project.View.Object
is (Self.View);

overriding function Action_Class
  (Self : My_Action_Id) return Value_Type
is (+"MyAction");

overriding function Language
  (Self : My_Action_Id) return Language_Id
is (No_Language);

overriding function Action_Parameter
  (Self : My_Action_Id) return Value_Type
is (Value_Type (Self.Input.Simple_Name));

type My_Action is new GPR2.Build.Actions.Object with record
   Id    : My_Action_Id;
   Input : GPR2.Path_Name.Object;
end record;

overriding function UID
  (Self : My_Action) return GPR2.Build.Actions.Action_Id'Class
is (Self.Id);

overriding function Working_Directory
  (Self : My_Action) return GPR2.Path_Name.Object
is (Self.View.Object_Directory);

overriding function On_Tree_Insertion
  (Self : My_Action;
   Db   : in out GPR2.Build.Tree_Db.Object) return Boolean
is
begin
   --  Register output artifacts here
   return True;
end On_Tree_Insertion;

overriding procedure Compute_Command
  (Self           : in out My_Action;
   Slot           :        Positive;
   Cmd_Line       : in out GPR2.Build.Command_Line.Object;
   Signature_Only :        Boolean)
is
begin
   Cmd_Line.Add_Argument ("my-tool");
   Cmd_Line.Add_Argument (String (Self.Input.Value));
end Compute_Command;

overriding procedure Compute_Signature
  (Self            : in out My_Action;
   Check_Checksums :        Boolean)
is
begin
   --  Register inputs/outputs in Self.Signature for change detection
   null;
end Compute_Signature;

Once implemented, insert the action into the database before calling Execute:

Action : My_Action := ...;
if not Db.Add_Action (Action) then
   --  action already present
end if;