How is Ada and GPR supposed to handle conditional compilation? [duplicate] - ada

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?

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)

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

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.

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.

Related

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?
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)
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
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.
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.

Ada gprbuild hangs on initialization

I have no idea why suddenly my gprbuild is getting stuck. Here's a pretty straightforward way to replicate what I'm seeing:
mkdir test
mkdir build
printf 'with Ada.Text_IO; use Ada.Text_IO;\nprocedure Main is begin Put_Line ("Hello, World!"); end Main;' > test/main.adb
printf 'project Test is\n for Source_Dirs use ("test");\n for Object_Dir use "build";\n for Exec_Dir use ".";\n for Main use ("main.adb");\n package Builder is\n for Executable ("main.adb") use "host";\n end Builder;\nend Test;' > test.gpr
gprbuild -Ptest # This hangs forever
Contents of files after executing:
test/main.adb
with Ada.Text_IO; use Ada.Text_IO;
procedure Main is begin Put_Line ("Hello, World!"); end Main;
test.gpr
project Test is
for Source_Dirs use ("test");
for Object_Dir use "build";
for Exec_Dir use ".";
for Main use ("main.adb");
package Builder is
for Executable ("main.adb") use "host";
end Builder;
end Test;
The result is no output whatsoever and the process has to be killed with ^C.
I ran it again with the -v flag and found that it hanged on gprconfig --batch -o /some/directory/src/build/GNAT-TEMP-000001.TMP --target=x86_64-linux --config=ada,,. The results of env are:
HADOOP_LOG_DIR=/tmp/hadoop/log
LANG=en_CA.utf8
DISPLAY=:0
COLORTERM=truecolor
LCLIMPORTDIR=/usr/share/splint/imports
MOZ_PLUGIN_PATH=/usr/lib/mozilla/plugins
HADOOP_SLAVES=/etc/hadoop/slaves
TERMINAL=xfce4-terminal
XDG_VTNR=1
XDG_SESSION_ID=c1
USER=user
GRADLE_HOME=/usr/share/java/gradle
PWD=/home/user
HOME=/home/user
HADOOP_CONF_DIR=/etc/hadoop
SPARK_HOME=/opt/apache-spark
arch=x86_64
MAIL=/var/spool/mail/user
VISUAL=gvim
WINDOWPATH=1
TERM=xterm-termite
SHELL=/bin/bash
VTE_VERSION=4803
HADOOP_PID_DIR=/tmp/hadoop/run
XDG_SEAT=seat0
SHLVL=3
WINDOWID=71303171
LOGNAME=user
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
XDG_RUNTIME_DIR=/run/user/1000
XAUTHORITY=/home/user/.Xauthority
ANT_HOME=/usr/share/apache-ant
LARCH_PATH=/usr/share/splint/lib
PATH=/home/user/bin:/usr/lib/hardening-wrapper/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/opt/android-sdk/platform-tools:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/home/user/.gem/ruby/2.4.0/bin
_=/usr/bin/env
I'm wondering if something is wrong with my GPR install? or perhaps my environment variables.
It turns out the problem was having too much in my PATH. gprbuild was looking through all the directories and trying some long running commands to see if they were the correct compilers.
Thanks to Jean-François Fabre for suggesting it.

How to create deb with custom layout

I have a Play server application.
Currently, I have 20-line bash script that creates this deb:
/srv
/foo
/conf
<unmanaged resources>
/staged
<jars>
I'd like to use sbt native packager to generate this.
Currently, sbt debian:package-bin gives me
etc/
default/
foo
foo
init/
foo.conf
usr/
bin/
foo
share/
foo/
bin/
foo
conf/
<unmanaged resources>
lib/
<jars>
share/
doc/
api/
<docs>
logs
README
var/
log/
foo/
How do I do I get my desired layout? Do I need to implement an archetype?
I'm using SBT 0.13.7 and SBT native packager 1.0.0-M1.
If your layout is close to the one already generated, you could use settings like defaultLinuxInstallLocation and defaultLinuxConfigLocation.
Or modify linuxPackageSymlinks and linuxPackageMappings directly, something like
linuxPackageSymlinks := Seq(),
linuxPackageMappings := {
val libPath = "/srv/foo/staged"
val libs = scriptClasspathOrdering.value.map { case (file, classpath) =>
file -> classpath.replaceFirst("^lib", Matcher.quoteReplacement(libPath))
}
Seq(LinuxPackageMapping(libs))
// plus configuration
},
If you have lots of binaries to archive (i.e. you have lots of dependencies), debian:packageBin is pretty slow. For debugging, consider using debianExplodedPackage.
Also, know that whatever is in the directory debianExplodedPackage will get included in the archive, so if there's extra stuff in the .deb at the end, you may need to delete that directory.

Import GLFW function to Ada

How to import GLFW function to Ada?
Using GNAT GPS as compiler and have the following folder structure:
test.adb
test.gpr
bin
obj
libglfw3.a
lib
libglfw3.a
test.gpr
project test is
for Source_Dirs use ("");
for Main use ("test.adb");
for Object_Dir use "obj";
for Library_Dir use "lib";
for Exec_Dir use "bin";
end test;
test.adb
with Interfaces.C;
with Ada.Text_IO;
procedure test is
pragma Linker_Options("-lglfw3");
pragma Linker_Options("-lgdi32");
pragma Linker_Options("-lopengl32");
function Init return Interfaces.C.int;
pragma Import(C,Init,"glfwInit");
success : Interfaces.C.int;
begin
success := Init;
Ada.Text_IO.Put_Line(success'Img);
end test;
Compiling with gprbuild -P test.gpr and outputs:
p:/gnat/2014/bin/../libexec/gcc/i686-pc-mingw32/4.7.4/ld.exe: cannot find -lglfw3
So I was thinking when gdi32, winmm, opengl32 is found is because they are already installed on my PC. I don't know if they are needed, but just to be on the safe side. The glfw3 lib is not found so I don't know if I'm using the Linker_Option the right way or where to put the libglfw3.a file. Is there a good way to link to c libraries?
pragma Linker_Options("-lglfw3dll");
Is a bad method, It should be like below:
pragma Linker_Options("-lglfw3");
To tell the linker to use library file libglfw3.a
To check libglfw3.a is in the object path used by GNAT, type the command
gnatls -v
Complete Ada binding to both GLFW2 and GLFW3 with GPR project files are available at OpenGLAda currently at Github.

Specifying rootProject in build.sbt

In my Build.scala, I have:
override def rootProject = Some(frontendProject)
I'm trying to convert to the newer build.sbt format, but don't know the equivalent of this line. How do I set the project for sbt to load by default when using build.sbt?
I'm still not sure that I understood you right, but you said about multi-project build, so I assume that you want to define a root project which aggregates subprojects. Here is how you can do that (in your root build.sbt):
lazy val root = project.in( file(".") ).aggregate(subProject1, subProject2)
lazy val subProject1 = project in file("subProject1")
lazy val subProject2 = project in file("subProject2")
See sbt documentation about multi-projects.
Then if you want to set the default project to load on sbt startup to a sub-project, in addition to your answer to this SO question, I can suggest
run sbt with sbt "project XXX" shell command
or adding this line to your build.sbt:
onLoad in Global := { Command.process("project XXX", _: State) } compose (onLoad in Global).value
In both cases sbt first loads the root project and then the subproject.
I've found the following script to be helpful:
#!/usr/bin/env bash
exec "sbt" "project mysubproject" "shell"
exit $?

Resources