I am trying to have gprbuild automatically set some variables' values in my source code - one way or another. In particular I want the outputs of certain commands to be accessible from within the code. In C with Makefiles this is easy:
source:
#include <stdio.h>
int main() { printf("%s\n", COMMAND_OUTPUT); return 0; }
make:
result : source.c
$(CC) -DCOMMAND_OUTPUT=`command -with -options`
However I have no idea how to do such a thing with gprbuild and Ada. (Short of ditching gprbuild and just using make - but I rather like gprbuild)
Ada does not use a preprocessor like C does.You cannot expect Ada compilers to modify strings in your code.
Use of such inline editing can easily become a violation of Ada strong typing, which would be very difficult to diagnose and would be completely invisible to source code static analysis.
I solve that by generating an Ada file from the makefile before building.
An example:
HG_STATE_SOURCE = src/mercurial.ads
HG_MODIFIER = `test $$(hg status | wc -c || echo 0) -gt 0 && echo "plus changes" || echo "as committed"`
HG_REVISION = `hg tip --template '{node}' 2>/dev/null || echo N/A_____________________________________`
[...]
$(HG_STATE_SOURCE): Makefile $(REPOSITORY_CONFIG) $(REPOSITORY_STATE) $(PROJECT_ROOT_SOURCE)
#mkdir -p src
#echo 'package 'Mercurial is' > $(HG_STATE_SOURCE)
#echo ' Revision : constant String (1 .. 53) :=' >> $(HG_STATE_SOURCE)
#echo ' "'$(HG_REVISION)' '$(HG_MODIFIER)'";' >> $(HG_STATE_SOURCE)
#echo 'end 'Mercurial;' >> $(HG_STATE_SOURCE)
Yes, the gnatprep preprocessor allows exactly the same as what you have in your C code:
main.adb:
with Ada.Text_IO; use Ada.Text_IO;
procedure Main is
begin
Put_Line ($Command_Output);
end Main;
simple_gnatprep.gpr:
project simple_gnatprep is
for Create_Missing_Dirs use "True";
Command_Output := external ("Command_Output");
for Source_Dirs use (".");
for Exec_Dir use ".";
for Main use ("main.adb");
for Object_Dir use "obj/" & "CommandOutput_" & Command_Output;
package Compiler is
for Switches ("Ada") use ("-gnateDCommand_Output=""" & Command_Output & """");
end Compiler;
end simple_gnatprep;
Makefile:
COMMAND_OUTPUT=$(shell echo hello there)
all:
gprbuild -d -p -g -XCommand_Output='${COMMAND_OUTPUT}'
clean:
rm -rf obj/ *.exe
Note I have included the command output in the obj/ directory used, which will fail if the command outputs any symbol that cannot appear in a directory name. However, if you omit it then gprbuild will say that your executable is up-to-date when nothing has changed except the output of the command.
Another option is to always remove the object directory before compiling, but when possible it is better to include the value of any preprocessor symbols in the object path so that switching from one configuration (e.g. Debug / Release) to another and back doesn't throw away intermediate results and slow down your development process.
Gnatprep is only included in the GNAT compiler, because there isn't yet any provision for preprocessing in the Ada standard. For other compilers, you will need to run each file through gnatprep separately in the Makefile, and then pass it to the compiler. In this case there is no need to fiddle with object directory names, as the source file will always be new and the compiler will always have to recompile everything.
Related
How to define local variable in Makefile target?
I would like to avoid repeating filename like:
zsh:
FILENAME := "text.txt"
#echo "Copying ${FILENAME}...";
scp "${FILENAME}" "user#host:/home/user/${FILENAME}"
But I am getting an error:
FILENAME := "text.txt"
/bin/sh: FILENAME: command not found
Same with $(FILENAME)
Trying
zsh:
export FILENAME="text.txt"
#echo "Copying ${FILENAME} to $(EC2)";
Gives me an empty value:
Copying ...
You can't define a make variable inside a recipe. Recipes are run in the shell and must use shell syntax.
If you want to define a make variable, define it outside of a recipe, like this:
FILENAME := text.txt
zsh:
#echo "Copying ${FILENAME}...";
scp "${FILENAME}" "user#host:/home/user/${FILENAME}"
Note, it's virtually never correct to add quotes around a value when assigning it to a make variable. Make doesn't care about quotes (in variable values or expansion) and doesn't treat them specially in any way.
The rules for a target are executed by the shell, so you can set a variable using shell syntax:
zsh:
#FILENAME="text.txt"; \
echo "Copying $${FILENAME}..."; \
scp "$${FILENAME}" "user#host:/home/user/$${FILENAME}"
Notice that:
I'm escaping end-of-line using \ so that everything executes in
the same shell
I'm escaping the $ in shell variables by writing $$ (otherwise
make will attempt to interpret them as make variables).
For this rule, which apparently depends on a file named text.txt,
you could alternatively declare text.txt as an explicit dependency and then write:
zsh: text.txt
#echo "Copying $<..."; \
scp "$<" "user#host:/home/user/$<"
I want to save some git information in a textfile, which I want to put in the src folder of my project.
In case branch Master is checked out, I only want the date of the latest commit. If any other branch is checked out I want the date and the name of the branch like so:
date-branchname
This is my code:
src/version2.txt:
DATE=$(shell git log -1 --date=format:"%Y.%m.%d" --format="%ad")
BRANCH=$(shell git rev-parse --abbrev-ref HEAD)
ifeq ($(BRANCH), 'Master')
$(DATE) > $#
else
DATE+='-'
DATE+=$(BRANCH)
$(DATE) > $#
endif
I'm new to GNU make and quite confused with its syntax.
I assume my ifeq/else blocks are working fine, since I checked printing a dummy text to the version.txt file while having the Master or some other branch checked out.
I also saw my commands to get the date or the branch are ok, since I can put them to the textfile like so:
git log -1 --date=format:"%Y.%m.%d" --format="%ad" > $#
Only when I want to use variables, it seems the variable is empty, for example
$(DATE) > $#
seems to print an empty string to the textfile.
Also, I don't know if my way of creating the DATE-BRANCH output is correct at all.
I've spent ages trying and would really appreciate some help.
Thanks
You cannot create GNU make variables inside recipes and use them later in the GNU make file because GNU make first parses whole file and then runs recipes
You cannot use GNU make ifeqs inside recipes
% in command lines may cause problems in Windows, so I've added a workaround for it in the following code (skip it if your Makefile should work only for Unix-like systems)
' should not be used in ifeqs constants (GNU make reads it literaly)
src/version2.txt does not depend on anything and therefore will not be regenerated by GNU make (if it already exists), consider .PHONY for the target
Try this:
BRANCH:=$(shell git rev-parse --abbrev-ref HEAD)
ifeq ($(OS),Windows_NT)
P:=%%
else
P:=%
endif
.PHONY : src/version2.txt
ifeq ($(BRANCH),master)
src/version2.txt :
git log -1 --date=format:"$PY.$Pm.$Pd" --format="$Pad" >$#
else
src/version2.txt :
git log -1 --date=format:"$PY.$Pm.$Pd" --format="$Pad-$(BRANCH)" >$#
endif
Note that BRANCH will be computed each time you run make even if the src/version2.txt should not be regenerated.
Is there some trick with the standard AIX make tool to use dynamic variables?
I wish to make a different directory depending on the underlying hostname:
# makefile
thisHOSTNAME=`hostname`
all: $(thisHOSTNAME) makeMyDir
machine-1:
TMP_DIR=V1
machine-2:
TMP_DIR=V2
makeMyDir:
#echo makeMyDir TMP_DIR=$(TMP_DIR)
# mkdir -p $(TMP_DIR) !! NOT WORKING
On running "make all", it turns out that $TMP_DIR is empty!?
The dependency $(thisHOSTNAME) in the target all is resolved correctly, and the corresponding target is executed, but the variable TMP_DIR does not maintain the assigned value it would seem.
$ make all
TMP_DIR=V2
makeMyDir TMP_DIR=
The backtick syntax is shell syntax, not make syntax. You can use it as the value of variables that are used in commands, but you can't use it for a target name as in
thisHOSTNAME=`hostname`
all: $(thisHOSTNAME) makeMyDir
Understand that this becomes
all: `hostname` makeMyDir
and makes all depend on the target hostname including backticks (which I don't know how to insert here), since dependencies are specified in make syntax.
I'm not sure if AIX make allows to use the GNU make syntax which would permit
thisHOSTNAME = $(shell hostname)
If not, you might ponder converting the AIX makefile to a GNU makefile.
If you don't have the GNU make extensions (namely shell and if*), then it's going to get ugly:
# Makefile
all: makeMyDir
thisbox:
mkdir -vp thisdir
thatbox:
mkdir -vp thatdir
makeMyDir: $(HOSTNAME)
then inject environment variables on invocation:
$ HOSTNAME=$(hostname) make -e
References:
https://www.gnu.org/software/make/manual/html_node/Environment.html
https://www.gnu.org/software/make/manual/html_node/Options-Summary.html
I have a Makefile that I run with multithreading (-j8 specifically).
I want to force a make clean and make all operation if I'm missing a specific marker file identifying the version compiled.
(This file should be introduced when the make is completed after the second make all.)
I can't seem to make this work properly. I either get stuck in loops or it just doesn't happen at all.
(This is part of a huge system so I can't just change any paradigms and I have to work with what I have)
Here's the relevant section I have so far. This wasn't the original plan but I shifted so many things around this is the current situation:
VERSION = 2.8
.DEFAULT_GOAL := all
.PHONY : all
all : {some targets} | marker_file
###########################
.PHONY : marker_file
marker_file : build/$(VERSION).marker
.PHONY : check_marker
check_marker :
ifeq (,$(wildcard build/$(VERSION).marker))
#echo -e "\e[41mYOU ARE ON NEW PREREQUISITES $(VERSION)! FORCING MAKE CLEAN BEFORE REBUILDING\e[0m"
$(MAKE) clean
#mkdir -p build
#touch build/$(VERSION).marker
$(MAKE) $(MAKECMDGOALS)
endif
# if the marker file needs generation, force clean and rebuild
build/$(VERSION).marker : check_marker
Can anyone figure out how to properly plan the rules and dependencies so that I can generate the file on the second time?
You definitely don't want to use order-only prerequisites. That forces the prerequisite to always run, but doesn't use the results in determining whether to run the target. That's almost the exact opposite of what you want.
Also you cannot use make preprocessor constructs like ifeq inside a recipe (indented by a TAB). Recipes are passed to the shell, and the shell is not make and does not understand make constructs like ifeq.
You can use make's auto-re-exec feature: if an included file changes then make will re-exec itself. So:
VERSION = 2.8
.DEFAULT_GOAL := all
.PHONY : all
all : {some targets}
###########################
MARKER_FILE = build/$(VERSION).marker
$(MARKER_FILE) :
#echo -e "\e[41mYOU ARE ON NEW PREREQUISITES $(VERSION)! FORCING MAKE CLEAN BEFORE REBUILDING\e[0m"
$(MAKE) clean MARKER_FILE=
#mkdir -p $(#D)
#touch $#
include $(MARKER_FILE)
I am trying to implement a simple string comparison to get the type of a file (using its extension) like this:
extract_pkg: $(PKG)
$(eval EXT := $(suffix $(PKG)))
#echo $(EXT)
ifeq ($(EXT), .zip)
#echo "is zip file"
else
#echo "is not a zip file"
endif
extract_pkg : PKG = mypkg.zip
However, when I run it it goes into the else branch. My guess is, it has to do with the dot, but I dont find a solution. Thanks for your help !
Edit 1: the essential code would be actually somewhat like the following, and it works as expected:
test_cmp:
ifeq (.zip,.zip)
#echo ".zip==.zip"
endif
ifeq (zip,zip)
#echo "zip==zip"
endif
thus the problem is somewhere else !
One thing to be careful about -- spaces in if constructs are significant. So if you have something like:
ifeq ($(EXT), .zip)
it will only match if $(EXT) expands to exactly ".zip" -- including the space before the period. So your first example will always print is not a zip file, since $(EXT) will never contain the space.
You cannot use ifeq() etc. inside recipes. ifeq() are preprocessor statements: they are interpreted immediately as the makefile is read in. Recipes are not run until much later, after all makefiles are parsed and make decides that this target needs to be updated. So trying to set a variable in a recipe using eval, etc. then test that variable using ifeq() cannot work.
You have to use shell constructs for this; something like:
extract_pkg: $(PKG)
#EXT=$(suffix $<); \
echo $$EXT; \
if [ $$EXT = .zip ]; then \
echo "is zip file"; \
else \
echo "is not a zip file"; \
fi