How can I write a function so that a value is added only if it's not found in the macro already? - gnu-make

I'd a like a fairly generic way of conditionally adding a variable to macro if it's not already present.
Here's my attempt with gnu Make version 4.1:
$ cat Makefile
define add_obj_dir
ifneq ($(1), $(filter $(1), $(OBJ_DIRS)))
OBJ_DIRS += $(1)
endif
endef
$(eval $(call add_obj_dir, "cat"))
$(eval $(call add_obj_dir, "cat"))
$(eval $(call add_obj_dir, "cat"))
all:
#echo $(OBJ_DIRS)
$
$
$ make
cat cat cat
$
The output I'd like to see is one "cat" (not multiple)

Use the if function, not ifdef. Way simpler.
add_obj_dir = $(if $(filter $1,$(OBJ_DIRS)),,OBJ_DIRS += $1)
$(eval (call add_obj_dir,cat))
$(eval (call add_obj_dir,cat))
$(eval (call add_obj_dir,cat))
all: ; #echo $(OBJ_DIRS)

Related

Pass variable from bash to R with commandArgs

I'm having a terrible go trying to pass some variables from the shell to R. I am hesitant to post this because I can't figure out a reasonable way to make this reproducible, since it involves a tool that has to be downloaded, and really it's more of a general methodology issue that I don't think needs to be reproducible, if you can just suspend your disbelief and bear with me for a quick minute.
I have arguments that are defined in a bash script: $P, $G, and $O.
I have some if/then statements and everything is fine until I get to the $O options.
This is the first part of the $O section and it works fine. It grabs data from $P and passes it to the twoBitToFa utility from UCSC's genome project and outputs the data correctly in a .fa file. Beautiful. (Although I think using 'stdout' and '>' is perhaps redundant?)
if [ "$O" = "fasta" ]
then
awk '{print $0" "$1":"$2"-"$3}' "$P" |
twoBitToFa -bed=stdin -udcDir=. "$twobit" stdout > "${P%.bed}".fa
fi
The next section is where I am stuck. If the $O option is "bed", then I want to invoke the Rscript command and pass my stuff over to R. I am able to pass my $P, $G, and $O variables without issue, but now I also need to pass the output from the twoBitToFa function. I could add a step and make the .fa file and then pick that up in R, but I am trying to skip the .fa file creation step and output a different file type instead (.bed). Here are some things I have tried:
# try saving twoBitToFa output to variable and including it in the variables passed to R:
if [ "$O" = "bed" ]
then
awk '{print $0" "$1":"$2"-"$3}' "$P" |
myvar=$(twoBitToFa -bed=stdin -udcDir=. "$twobit" stdout) \
Rscript \
GetSeq_R.r \
$P \
$G \
$O \
$myvar
fi
To check what variables come through, my GetSeq_R.r script starts with:
args = commandArgs(trailingOnly=TRUE)
print(args)
and with the above code, the output only includes my $P, $G, and $O variables. $myvar doesn't make it. $P is the TAD-1 file, $G is "hg38", and $O is "bed".
[1] "TAD-1_template.bed" "hg38" "bed"
I am not sure if the way I am trying to pass the data in the variable is wrong. From everything I've read, it seems like it should work. I've also tried using tee to see what is in my stdout at that step like so:
if [ "$O" = "bed" ]
then
awk '{print $0" "$1":"$2"-"$3}' "$P" |
twoBitToFa -bed=stdin -udcDir=. "$twobit" stdout | tee \
Rscript \
GetSeq_R.r \
$P \
$G \
$O
fi
And the data I want to pass to R is correctly shown in my console by using tee. I've tried saving stdout and tee to a variable and passing that variable to R, thinking maybe it's something about twoBitToFa that refuses to be put inside a variable, but was unsuccessful. I've spent hours looking up info about tee, stdout, and passing variables from bash to R. I feel like I'm missing something fundamental, or trying to do something impossible, and would really appreciate some other eyes on this.
Here's the whole bash script, in case that's illuminating. Do I need to define a variable in "$#" for what I am trying to pass to R, even though it's not something I want the user to be aware of? Am I capturing the variable with $myvar incorrectly? Can I get the contents of stdout or tee to show up in R?
Thanks in advance.
for arg in "$#"; do
shift
case "$arg" in
"--path") set -- "$#" "-P" ;;
"--genome") set -- "$#" "-G" ;;
"--output") set -- "$#" "-O" ;;
"--help") set -- "$#" "-h" ;;
*) set -- "$#" "$arg"
esac
done
while getopts ":P:G:O:h" OPT
do
case $OPT in
P) P=$OPTARG;;
G) G=$OPTARG;;
O) O=$OPTARG;;
h) help ;;
\?)
echo "Invalid option: -$OPTARG" >&2
usage
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." >&2
usage
exit 1
;;
esac
done
num_col=$(cat "$P" | awk "{print NF; exit}")
if [ "$num_col" = 3 ]
then
echo -e "\n\n3 column bed file detected; no directional considerations for sequences \n\n"
if [ "$G" = "hg38" ]
then
twobit="https://hgdownload.cse.ucsc.edu/goldenpath/hg38/bigZips/hg38.2bit"
fi
if [ "$G" = "hg19" ]
then
twobit="https://hgdownload.cse.ucsc.edu/goldenpath/hg19/bigZips/hg19.2bit"
fi
if [ "$O" = "fasta" ]
then
awk '{print $0" "$1":"$2"-"$3}' "$P" |
twoBitToFa -bed=stdin -udcDir=. "$twobit" stdout > "${P%.bed}".fa
fi
if [ "$O" = "bed" ]
then
awk '{print $0" "$1":"$2"-"$3}' "$P" |
#myvar=$(twoBitToFa -bed=stdin -udcDir=. "$twobit" stdout) \
Rscript \
GetSeq_R.r \
$P \
$G \
$O \
$myvar
fi
fi

Generating GNU Makefile Rules

So in my project I have a src directory and an obj directory. I'm recursively finding the .c and .cpp files in my src directory, and then its corresponding .o file gets put right in the obj directory. So for example if I have a .cpp file: src/dir1/dir2/file.cpp, its corresponding .o file would be obj/file.o. Then I'm generating the rule to get the .o file from the .cpp file using a make foreach function using this code:
rwildcard=$(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2)$(filter $(subst *,%,$2),$d))
src = $(call rwildcard,src/,*.cpp *.c)
obj = $(patsubst %,obj/%.o,$(basename $(notdir $(src))))
$(info src: [$(src)])
$(info obj: [$(obj)])
game.exe: $(obj)
g++ $^ -o $#
define objFromSrc
$(1): $(2)
$(info $(1) $(2))
g++ -c $(2) -o $(1)
endef
$(foreach t,$(src),$(call objFromSrc,$(patsubst %,obj/%.o,$(basename $(notdir $(t)))),$(t)))
Here is the output for some example files:
src: [src/dir/main.cpp src/dir/dir2/other3.cpp src/dir/other2.cpp src/other.c]
obj: [obj/main.o obj/other3.o obj/other2.o obj/other.o]
obj/main.o src/dir/main.cpp
obj/other3.o src/dir/dir2/other3.cpp
obj/other2.o src/dir/other2.cpp
obj/other.o src/other.c
makefile:20: *** multiple target patterns. Stop.
You can see the obj variable correctly holds the corresponding .o file names. And the objFromSrc function generates a rule where the target and dependency are correct, but yet I get a multiple target patterns error.
Why am I getting this error and how can I fix it?
You are missing the $(eval) to parse the generated makefile code:
$(eval $(foreach t,$(src),...))
I would also suggest to add an empty line at the end of the multi-line define. Leaving this out is usually calling for trouble when $(eval)uating dynamically generated makefile code.
define objFromSrc
$(1): $(2)
$(info $(1) $(2))
g++ -c $(2) -o $(1)
endef
$(info eval $(foreach t,$(src),...))
BONUS CODE: your recipe is a constant so there is no need to re-generate it for every rule. Use a static pattern rule for $(obj) instead:
.DEFAULT_GOAL := game.exe
obj :=
define objFromSrc
$(1): $(2)
obj += $(1)
endef
$(eval $(foreach t,$(src),...))
$(info obj: [$(obj)])
$(obj): %.o:
g++ -o $# -c $<
game.exe: $(obj)
g++ $^ -o $#
Why am I getting this error and how can I fix it?
All these define and $(call ...) in make produce simple strings. You have to eval it to make the make do what you've ordered (i.e. to create the rule $1 : $2):
$(foreach t,$(src),$(eval $(call objFromSrc,$(patsubst %,obj/%.o,$(basename $(notdir $(t)))),$(t))))

Difference between "$(shell ...)" and "$$(...)" in make

I am puzzled by the difference between the "shell" MAKE function and "$$". In the documentation I find:
The shell function accepts a single argument that is expanded (like all arguments) and passed to a subshell for execution. The standard output of the command is then read and returned as the value of the function.
I believed this was exactly what "$$" was doing as well, however in this small example:
a = $(shell find . -maxdepth 1 -type f -name "Makefile")
b = $$(find . -maxdepth 1 -type f -name "Makefile")
.PHONY: all A B
all: A B
A: $(a)
#echo "Target: $(#)"
#echo "Prereq: $(<)"
#echo "Var a: $(a)"
#echo "Var b: $(b)"
B: $(b)
#echo "Target: $(#)"
#echo "Prereq: $(<)"
#echo "Var a: $(a)"
#echo "Var b: $(b)"
the output is the following:
Target: A
Prereq: Makefile
Var a: ./Makefile
Var b: ./Makefile
make: *** No rule to make target '$(find)', needed by 'B'. Stop.
Note here that it says "No rule to make target '$(find)'", as if the argument has not yet been expanded. (I also tried to make the variable simply expanded, "b:=$$(...)", but this changed nothing).
I hope that someone has the knowledge to elaborate more on this, which to me seems like a subtle difference, but probably is much more profound than I can comprehend at this time.
$(shell ...) is a Make text function. Make will expand this, so in your example, a will expand, when substituted, to the result of the find command. (If you made it a simply-expanded variable, the shell command would be evaluated only once, of course).
$$ is just expanded to $, so in your example, b will substitute as the value $(find . -maxdepth 1 -type f -name "Makefile"). This will be the same whether b is defined with = or :=.
When you use $(b) in a command such as echo $(b), the shell running that command will see this as command substitution. In other words, you have echo $(find ...) as a shell command.
Using $(b) in a Make target or dependency will, as you have seen, perform no further evaluation.
Here's another example Makefile, which I hope demonstrates what's going on. We use single-quotes to show literally what the shell is given:
a = $$(echo true)
b = $(shell echo true)
print:
echo '$$a: $a' = "$a"
echo '$$b: $b' = "$b"
.PHONY: print
This gives me
echo '$a: $(echo true)' = "$(echo true)"
$a: $(echo true) = true
echo '$b: true' = "true"
$b: true = true
showing that in the first case, the shell is given $(echo true) in its command, whereas in the second case, Make evaluates echo true and simply substitutes the result true into the command.

How to manipulate string in GNUmakefile for loop

I am new to GNUmakefile and I am just not sure how to handle the strings in the for loop below. I can print out each file using the echo command below. My questions are:
1. How to assign the $$f to a variable?
2. How to print out the content of the new variable?
For example, I did assign the content of $$f to "abc" but echo ${abc}; prints out blank.
Where did I miss? Thanks
DIR := MyDir
CFILES := $(wildcard $(DIR:=/*.c))
.PHONY: all
all:
for f in $(CFILES); \
echo $$f; \
abc=$$f; \
echo ${abc}; \
done
Your for loop is missing a do?
Inside the tabbed block you have scripting.
So you are setting shell vars not make vars.
You must escape the $s in shell block so printing $$abc or $${abc} would work. You can use make var as you have done $(CFILES).
What do you expect/want in abc ?
Do you wish to manipulate a shell or make var?
echo CFILES=$(CFILES)
for f in $(CFILES); do\
echo ff $$f;\
abc=$$f; \
echo $${abc};\
bn=$$(basename $$f);\
b=$${bn%%.*};\
echo you want this b= stripped down file tag $$b basename of file $$bn bash style;\
done
As ever there are more than one ways of doing things.
Make style maybe you could use pattern rules and automatic vars to do what you want?
http://www.gnu.org/software/make/manual/make.html#Automatic-Variables
http://www.gnu.org/software/make/manual/make.html#Pattern-Examples
e.g.
%.o : %.c
echo make var $< matches file.c
echo make var $# matches file.o
echo make var $* matches stem file
echo $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $#

Exporting recursive makefile variables to submakes

I need to export a user-defined GNU make function (i.e. a recursively expanded makefile variable) to a sub-make. However, make seems to expand all such variables into simply expanded variables, which makes them useless.
Here is an example:
Contents of "parent.make":
export myfunc = my arg is $1
$(info parent testing myfunc: $(call myfunc,parent))
all: ; $(MAKE) -f child.make
Contents of "child.make":
$(info child testing myfunc: $(call myfunc,child))
nullrule: ; #true
Running "make -f parent.make" produces the following output:
parent testing myfunc: my arg is parent
make -f child.make
child testing myfunc: my arg is
Parent exports myfunc as a simple expanded variable containing "my arg is" to the sub-make, making it useless as a function.
Is there a reason why common variables and functions can't be put into a separate makefile included by both the parent and the child makefiles?
I'm not sure if this will satisfy your requirements, but here goes:
parent.make:
export myfunc1 = my arg is $1
export myfunc2 = $(value myfunc1)
$(info parent testing myfunc1: $(call myfunc1,parent))
all: ; $(MAKE) -f child.make
child.make:
$(info child testing myfunc2: $(call myfunc2,child))
nullrule: ; #true
EDIT:
All right, how about this:
child.make:
myfunc1=$(myfunc2)
$(info child testing myfunc1: $(call myfunc1,child))
nullrule: ; #true
EDIT:
Not so fast. Take a look at this:
parent.make:
myfunc_base = my arg is $1
export myfunc = $(value myfunc_base)
$(info parent testing myfunc: $(call myfunc_base,parent))
all: ; #$(MAKE) -f child.make
child.make:
$(info child testing myfunc: $(call myfunc,child))
nullrule: ; #$(MAKE) -f grandchild.make
grandchild.make:
$(info grandchild testing myfunc: $(call myfunc,grandchild))
nullrule: ; #true
This works with no modification at all to child.make or grandchild.make. It does require that parent.make call myfunc_base rather than myfunc; if that's a problem there's a way around it, maybe more than one.
One possibility, we move the definition up into whatever calls parent, where it won't actually be called at all:
grandparent.make:
myfunc_base = my arg is $1
export myfunc = $(value myfunc_base)
all: ; #$(MAKE) -f child.make
parent.make:
$(info parent testing myfunc: $(call myfunc,parent))
all: ; #$(MAKE) -f child.make
I realize this might not be workable, since the definition of myfunc might require other things defined in parent.make. See if this will suit your situation; there may be another way...

Resources