'Ada Gnat project which includes differently-named files for different build configurations

I have a Gnat/Gprbuild project with several build configurations. I have a main source file and an secondary ads file which the main source file includes:

with Secondary_File; use Secondary_File;

The problem is that in each configuration, the secondary file has a different name. For example, it may be called Secondary_File_1.ads for one config and Secondary_File_2.ads for another. This makes it impossible to use the above with statement.

In C, I would do something like this:

#ifdef BUILD_CFG_1
#include "secondary_file_1.h"
#else
#include "secondary_file_2.h"
#endif

Is there a clever way to do something like this in ADA, using the Gprbuild system?



Solution 1:[1]

Many purists reject the idea of preprocessing, but it’s possible using GNAT.

You can include this in a GPR-based build environment by writing your source, e.g. main.adb, like so:

with Secondary_File_$NUMBER;
procedure Main is
begin
   null;
end Main;

(observe the $NUMBER) and the project file like so:

project Prj is

   for Main use ("main.adb");

   --  Configurations
   type Config_Type is ("config_1", "config_2");
   --  Which one? (default is "config_1")
   Config : Config_Type := external ("CONFIG", "config_1");

   package Compiler is
      case Config is
         when "config_1" =>
            for Switches ("main.adb") use ("-gnateDNUMBER=1");
         when "config_2" =>
            for Switches ("main.adb") use ("-gnateDNUMBER=2");
      end case;
   end Compiler;

end Prj;

Compiling gives

$ gprbuild -Pprj
Compile
   [Ada]          main.adb
main.adb:1:06: error: file "secondary_file_1.ads" not found
gprbuild: *** compilation phase failed

(the compilation looked for secondary_file_1.ads)

$ gprbuild -Pprj -XCONFIG=config_2
Compile
   [Ada]          main.adb
main.adb:1:06: error: file "secondary_file_2.ads" not found
gprbuild: *** compilation phase failed

(the compilation looked for secondary_file_2.ads)

Solution 2:[2]

A small variation on the answer of Simon Wright is one that uses a Naming package and Spec attributes in the GPRbuild file. This variation is useful when file names are different, but package names are the same: Secondary_File. So, not sure if this works in your case.

main.adb

with Ada.Text_IO;
with Secondary_File;

procedure Main is
begin
   Ada.Text_IO.Put_Line (Secondary_File.Foo);
end Main;

secondary_file_1.ads

package Secondary_File is
   Foo : constant String := "Package 1";
end Secondary_File;

secondary_file_2.ads

package Secondary_File is
   Foo : constant String := "Package 2";
end Secondary_File;

prj.gpr

project Prj is

   for Main use ("main.adb");
   for Source_Dirs use ("src/**");
   for Object_Dir use "obj";

   type Config_Type is ("config_1", "config_2");
   Config : Config_Type := external ("CONFIG", "config_1");

   package Naming is      
      case Config is         
         when "config_1" =>
            for Spec ("Secondary_File") use "secondary_file_1.ads";            
         when "config_2" =>
            for Spec ("Secondary_File") use "secondary_file_2.ads";            
      end case;
   end Naming;

end Prj;

output (compiler)

$ gprbuild -Pprj -XCONFIG=config_1 && ./obj/main
Compile
   [Ada]          main.adb
   [Ada]          secondary_file_1.ads
Bind
   [gprbind]      main.bexch
   [Ada]          main.ali
Link
   [link]         main.adb
Package 1
$ gprbuild -Pprj -XCONFIG=config_2 && ./obj/main
Compile
   [Ada]          main.adb
   [Ada]          secondary_file_2.ads
Bind
   [gprbind]      main.bexch
   [Ada]          main.ali
Link
   [link]         main.adb
Package 2

Solution 3:[3]

Bouncing on Simon's answer

Many purists reject the idea of preprocessing

And a purist's answer would be: use GPR project files, they offer the "scenario variables" feature that should do exactly what you want, without having to rename files or rely on some preprocessing step.

I guess Secondary_File.ads is unique (interface/contract), so you put each Secondary_File.adb in its own folder (distinct implementations).

Then its easy to adapt the GPR source_dir/source_files list according to a scenario variable. The variable can be set in the GnatStudio IDE, in an env var, and in a command line flag.

So you could have this folder tree:

src
|-- main.adb
|-- Secondary_File.ads
|-- implA
    |-- Secondary_File.adb
|-- implB
    |-- Secondary_File.adb
|-- implC
    |-- Secondary_File.adb

Then use this GPR file my_project.gpr:

project my_project is
    -- enum value shall match folders
    type Secondary_Impl is ("implA", "implB", "implC");
    the_secondary_impl_val : Secondary_Impl := external("secondary_impl_env_var", "implA"); -- gprbuild will look for env var if any, otherwise defaults to implA
    for Source_Dirs use ("src", "src/" & the_secondary_impl_val );
    -- other useful settings : obj dir, compiler/linker switches etc.
    -- ...
end my_project;

All you have to do is then build using gpr build:

# build with impl A
gprbuild -Pmy_project.gpr -Xsecondary_impl_env_var=implA

# build with impl C
gprbuild -Pmy_project.gpr -Xsecondary_impl_env_var=implC

or even :

# bash commands, syntax to set env var depends on the OS/shell
secondary_impl_env_var=implB
gprbuild -Pmy_project.gpr

Depending on your specific software, you may also consider object oriented design patterns that can help achieve a similar result in some cases.

Solution 4:[4]

If some of the files are different then why not have two different .gpr configuration files? You can separate the different code into different directories and have a third directory for common code and then specify in the .gpr in a block like this

for Source_Dirs use ("main_directory",
                     "common_code_directory");

and this

for Source_Dirs use ("secondary_directory",
                     "common_code_directory");

Now you have different configurations in the same project and can build either one. You can even go further and create common ada spec files (.1.ada) that contain shared functions between the two configuration but have two different ada body files (.2.ada) that behave differently.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Simon Wright
Solution 2 DeeDee
Solution 3 LoneWanderer
Solution 4 slick