Exporting recursive makefile variables to submakes - recursion

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

Related

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

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

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)

Collapse directories in zsh prompt in a unique way

Is there a way to collapse the current working directory in the zsh prompt in a unique way, so that I could copy and paste it to another terminal, hit TAB and get the original path?
Let's say we have following directories:
/adam/devl
/alice/devl
/alice/docs
/bob/docs
If the prompt is programmed to show the first characters, and I'm in /b/d, then it is unique. On the other hand, /a/d is not unique, so I would need /ad/d, /al/de and /al/do. And even /ali/… as soon as the user alex appears.
Is it possible to hack this directly in zsh or do I need to write a script, that finds the shortest unique beginning of each parent directory?
Thank you for your ideas!
I'm not aware that zsh has a built-in function of this sort, but it should be pretty easy to script without resorting to a single subshell or slow pipe:
#!/bin/zsh
paths=(${(s:/:)PWD})
cur_path='/'
cur_short_path='/'
for directory in ${paths[#]}
do
cur_dir=''
for (( i=0; i<${#directory}; i++ )); do
cur_dir+="${directory:$i:1}"
matching=("$cur_path"/"$cur_dir"*/)
if [[ ${#matching[#]} -eq 1 ]]; then
break
fi
done
cur_short_path+="$cur_dir/"
cur_path+="$directory/"
done
printf %q "${cur_short_path: : -1}"
echo
This script will output the shortest path needed for auto-completion to work.
You can throw it in your .zshrc as a function and then run it from any directory.
function spwd {
paths=(${(s:/:)PWD})
cur_path='/'
cur_short_path='/'
for directory in ${paths[#]}
do
cur_dir=''
for (( i=0; i<${#directory}; i++ )); do
cur_dir+="${directory:$i:1}"
matching=("$cur_path"/"$cur_dir"*/)
if [[ ${#matching[#]} -eq 1 ]]; then
break
fi
done
cur_short_path+="$cur_dir/"
cur_path+="$directory/"
done
printf %q "${cur_short_path: : -1}"
echo
}
Here's a video of it in action:
https://asciinema.org/a/0TyL8foqvQ8ec5ZHS3c1mn5LH
Or if you prefer, some sample output:
~/t $ ls
adam alice bob getshortcwd.zsh
~/t $ ls adam
devl
~/t $ ls alice
devl docs
~/t $ spwd
/h/v/t
~/t $ cd adam/devl
~/t/adam/devl $ spwd
/h/v/t/ad/d
~/t/adam/devl $ cd ../../alice/devl
~/t/alice/devl $ spwd
/h/v/t/al/de
~/t/alice/devl $ cd ../docs
~/t/alice/docs $ spwd
/h/v/t/al/do
~/t/alice/docs $ `spwd` [TAB]
~/t/alice/docs $ /h/v/t/al/do [TAB]
~/t/alice/docs $ /home/vsimonian/t/alice/docs
Yes it is possible to collapse the directories to a first-unique-letter path and have the Z Shell expand that path upon pressing [Tab]. I simply used compinstall (zsh utility script installed with Zsh) to generate the following code. The important part to take note of for expanding path elements is on the sixth zstyle command, near the end, where the bracket of characters to separate completion points with includes the /, which is, of course, the directory separator. With this, the unique paths you suggested will fully fill out with just one [Tab] press as if the * were at the end of each path-name unique letters.
# The following lines were added by compinstall
zstyle ':completion:*' add-space true
zstyle ':completion:*' completer _list _expand _complete _ignored _match _correct _approximate _prefix
zstyle ':completion:*' completions 1
zstyle ':completion:*' list-colors ${(s.:.)LS_COLORS}
zstyle ':completion:*' matcher-list 'm:{[:lower:]}={[:upper:]} r:|[._-]=* r:|=*' 'm:{[:lower:]}={[:upper:]} m:{[:lower:][:upper:]}={[:upper:][:lower:]} r:|[._-]=* r:|=*' 'r:|[._-/]=* r:|=*' 'l:|=* r:|=*'
zstyle ':completion:*' match-original both
zstyle :compinstall filename '/home/micah/.zsh/.zshrc'
autoload -Uz compinit
compinit
# End of lines added by compinstall
As for creating the unique path in the first place and inserting it into the prompt, it is possible with a zsh script or function, and therefore should be possible for the completer or line-editor also, but just sticking to the prompt, you would add a function to the $precmd_functions array that modifies or adds to the PS1 variable. This special array is a list of function names that are run right before each prompt.
function precmd_unique_pwd {
local pwd_string="$(upwd)"
PS1="%B%n#%m $pwd_string => %b"
}
precmd_functions+=( precmd_unique_pwd )
For getting of the current PWD in shortened form, I think this function is clear and easy to follow, though not necessarily optimized for low resource usage.
#!/bin/zsh
function upwd {
emulate -LR zsh -o nullglob
local dir Path
local -a newpwd tmp stack
local -i length=1 Flag=0
newpwd=( ${(s./.)PWD} )
foreach dir ( $newpwd )
(( length=0, Flag=0 ))
repeat $#dir
do
(( length += 1 ))
tmp=( ${(j.*/.)~stack}/$dir[1,$length]*(/) )
if
(( $#tmp == 1 ))
then
Path=$Path/$dir[1,$length]
stack+=( /$dir )
(( Flag=1 ))
break
fi
done
if
(( Flag ))
then
continue
else
Path=$Path/$dir
fi
end
print -- $Path
}
upwd
Notice that it finds unique paths with directory names because of the Zsh feature (/) at the end of the globbing. On the last directory (current) this means you may match something else if there is a file with the same name plus an extension.

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 $#

Resources