'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]
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 |