How can I evaluate a variable created inside a rule? (with eval) - makefile

I'm new to makefiles, and I'm stuck in this problem: how to create symbolic links to all the files in a directory found, all this inside a rule:
I want to know if a call to "find" retrieves any result. If I assign the result to a variable with "eval" (what is the only way I know to set a variable inside a rule), "ifdef" evaluation doesn't work.
I have also tried comparing to "" with "ifneq" But doesn't work.
Probably I'm following a wrong direction, so if someone has an alternative approach, I'll be very thankful.
Here the whole Makefile: (interesting line marked with ######)
Thank you a lot!
DEFAULT_YCS_ROOT := /opt/yujin
all:
ifdef YCS_ROOT
#echo Looking for Yujin maps in ${YCS_ROOT}
else
$(eval YCS_ROOT := ${DEFAULT_YCS_ROOT})
#echo WARNING: YCS_ROOT is undefined. Looking for Yujin maps in default path ${YCS_ROOT}
endif
$(eval MAPS_DIR := $(shell find ${YCS_ROOT} -name yujin_maps))
#echo ${MAPS_DIR} "${MAPS_DIR}"
ifdef MAPS_DIR ######
###### misguided alternative.... ifneq (${MAPS_DIR}, " ")
$(eval DEST_DIR := $(shell pwd)/resources/yaml/stage/maps)
#echo Creating symbolic links on ${DEST_DIR} for every PGM map on ${MAPS_DIR}/maps
rm ${DEST_DIR}/*.pgm
for f in ${MAPS_DIR}/maps/*.pgm; do ln -s $$f ${DEST_DIR}/; done
else
#echo ERROR: Yujin maps package not found in ${YCS_ROOT}. Define YCS_ROOT variable properly
endif

First, assuming you are using GNUMake, there is an easier way to handle YCS_ROOT and DEFAULT_YCS_ROOT:
DEFAULT_YCS_ROOT := /opt/yujin
YCS_ROOT ?= DEFAULT_YCS_ROOT
This will assign the value of DEFAULT_YCS_ROOT to YCS_ROOT if and only if YCS_ROOT is undefined.
For the variables MAPS_DIR and DEST_DIR, you are mixing Make syntax with shell syntax. I think the easiest approach is to define both variables outside the rule:
MAPS_DIR := $(shell find ${YCS_ROOT} -name yujin_maps)
DEST_DIR := $(shell pwd)/resources/yaml/stage/maps
You should test all of this to make certain that the variables have the values you expect. It seems that you expect only one directory in MAPS_DIR; I will follow that convention, but it is unsafe. Once the makefile so far is working, you can add the rule. You can use a shell loop:
all:
rm ${DEST_DIR}/*.pgm
for f in ${MAPS_DIR}/maps/*.pgm; do echo ln -s $$f ${DEST_DIR}/; done
or use Make syntax (which gives some advantages):
MAPS := $(wildcard $(MAPS_DIR)/maps/*.pgm)
DEST_MAPS := $(patsubst $(MAPS_DIR)/maps/%.pgm, ${DEST_DIR}/%.pgm, $(MAPS))
all: $(DEST_MAPS)
${DEST_DIR}/%.pgm: $(MAPS_DIR)/maps/%.pgm
rm $#
ln -s $< ${DEST_DIR}/

Related

How can I build two interactive web-sites from one GNU makefile and mostly the same source?

I'm using GNU Make to build a dynamic web site but I need to build two versions. As a net result currently I run my makefile using two command line incantations. This is inefficient and can result in errors (I don't always remember to run both versions or make a typing error and they fail) thus I want to make it one build.
The Command Line incantations are:
sudo make release build=test
sudo make release build=release
The two incantations activate ifeq blocks that set the path and modify some files.
Part of the much simplified (to help readability) top level makefile:
subs = today tomorrow
files = index.php foot.php
ifeq ($(build),test)
export path = /var/www/html/cgi-test
$(shell cp -f head-test.php head.php)
$(shell sed -i '/"DB"/c\ define("DB", "db_test");' subs.php)
else ifeq ($(build),release)
export path = /var/www/html/cgi-new
$(shell cp -f head-release.php head.php)
$(shell sed -i '/"DB"/c\ define("DB", "db_new");' subs.php)
endif
.PHONY: all go
all:
$(MAKE) go
#for ALL in $(subs);\
do $(MAKE) -C $$ALL all || exit $$?;\
done;
go:
cp $(files) $(path)/.
The sub-makefiles have a very similar structure but don't need the ifeq blocks because the files and paths have already been setup.
I think I can simply move the shell commands into the .PHONY rules but I can't do that with the exports because I get errors "export: : bad variable name".
I could do it with a batch file and call the makefile twice but that sidesteps the problem rather than cures it and I wish to learn from the process.
So can anybody show me the way to do this in a makefile?
Thanks to Tripleee here is the answer that finally worked back ported to match my starting post. The one major change is that I have gone back to 'all' as the rule I expect to start the build habits die hard! - Thanks
.PHONY: all both rel-test rel-release
cgi-test := cgi-test
db-test := db_test
cgi-release := cgi-new
db-release := db_new
subs = today tomorrow
files = index.php foot.php
all: both
both: rel-test rel-release
rel-test rel-release: rel-%:
cp -f head-$*.php head.php
sed -i '/"DB"/c\ define("DB", "$(db-$*)");' subs.php
$(MAKE) go path=/var/www/html/strutts/$(cgi-$*)
#for ALL in $(subs);\
do $(MAKE) build=$* path=/var/www/html/strutts/$(cgi-$*) -C $$ALL all || exit $$?;\
done;
Something like this?
.PHONY: both rel-test rel-release
both: rel-test rel-release
cgi-test := cgi-test
db-test := db_test
cgi-release := cgi-new
db-release := db_new
rel-%:
cp -f head-$*.php head.php
sed -i '/"DB"/c\ define("DB", "$(db-$*)")' subs.php
$(MAKE) release build=$* path=/var/www/html/$(cgi-$*)
The reason the export can't be moved into a recipe is that you are using the export command of make itself, not the shell's command with the same name.
You absolutely should not use sudo unless you specifically require the output files to be owned and only writable by root. Even then, running as much as possible as a regular user would be proper hygiene; maybe add a sudo command inside the Makefile to copy the files to their final location.

GNU Make - Set a flag if multiple files exist

In my Makefile, I check if a file exists, and execute different code depending on the result of that check.
Previously, there was only one relevant file, OUTLIB_CACHE.
OUTLIB_CACHE := filename
OUTLIB_CACHE_EXITS := $(shell if [ -f $(OUTLIB_CACHE) ]; then echo "1"; fi;)
ifneq ($(OUTLIB_CACHE_EXISTS),1)
# do something
endif
Now I am introducing a second relevant file, storing both of the file names in a list:
OUTLIB_CACHE_LIST := filename1 filename2
I want to set the OUTLIB_CACHE_EXISTS flag to true if and only if all the files in OUTLIB_CACHE_LIST exist. What would be the best way to do this?
You could substitute each element of OUTLIB_CACHE_LIST with a command, then execute the resulting commands in a shell:
OUTLIB_CACHE_MISSING := $(shell $(patsubst %,test -e "%" || echo missing;,$(OUTLIB_CACHE_LIST)))
If all the members exist, then the output of the shell command will be empty, else it will contain one word for each missing file. You can test for emptiness:
ifneq ($(OUTLIB_CACHE_MISSING),)
# do something
endif
If you want to know which files are missing, you can't just replace ! with %, because patsubst only replaces the first % it finds. Instead, you could use foreach:
OUTLIB_CACHE_MISSING := $(shell $(foreach f,$(OUTLIB_CACHE_LIST),test -e "$f" || echo "$f";))
Putting this all together in a testable example:
OUTLIB_CACHE_LIST := /etc /bin
#OUTLIB_CACHE_LIST += /nonexistent
OUTLIB_CACHE_MISSING := $(shell $(foreach f,$(OUTLIB_CACHE_LIST),test -e "$f" || echo "$f";))
.PHONY: all
all:
ifneq ($(OUTLIB_CACHE_MISSING),)
false "$(OUTLIB_CACHE_MISSING)"
else
true "No missing files"
endif
Uncomment the second line to select the first branch of the if.
N.B. You wanted to know whether the files exist, so I've used test -e rather than -f or -r. You'll know which test is actually appropriate for your case...
There is no need to use shell functions to test for the existence of multiple files in make. I use the following construct to make make output decent error messages for a very convoluted makefile. I presume you can adopt it to your needs if need be. The original idea (probably) comes from https://stackoverflow.com/a/20566812/1905491.
$(foreach p,$(ALL_INCLUDES),$(if $(wildcard $(p)),,$(info $(p) does not exist!) $(eval err:=yes)))
$(if $(err),$(error Aborting),)
Simply define OUTLIB_CACHE_EXISTS as follows
OUTLIB_CACHE_EXISTS := $(shell if ls $(OUTLIB_CACHE_LIST) >/dev/null 2>&1; then echo "1"; fi)
This should work
ifeq ($(OUTLIB_CACHE_LIST),$(wildcard $(OUTLIB_CACHE_LIST)))
# do something
endif

$(eval ) in makefile causing recipe commences before first target error

CFormat:
define Format_File :=
#echo Formatting
ifneq ("$(wildcard $(1))","")
#echo if1
# The default extensions for intermediate files are not used,
# to avoid collisions with backup files open in the editor, etc.
# Save the original source file with an '_X' suffix on the extension.
ifneq("$(wildcard $(1)_X)","")
#echo if2
else
#echo else2
endif
#ren $(1) $(1)_X
# C-Comment is invoked separately, due to pathing limitations
# The redirection is a means to suppress blabbering.
#echo Formatting $(1) . . .
$(CFORMAT_PATH)\Cformat -h$(CFORMAT_PATH) $(1)_X -o$[Base, $(1)].tmp -ino >temp.tmp;
$(CFORMAT_PATH)\Ccomment -h$(CFORMAT_PATH) $[Base, $(1)].tmp -o$(1) >temp.tmp;
else
#echo else1
endif
endef
FormatAll: CFormat
$(foreach loopFile,$(ALL_S_SOURCES),$(eval $(call Format_File,$(loopFile))))
.PHONY: FormatAll
When I replaced eval with info it printed out the function call correctly but every time I try to actually eval the formatter it gives me the error in the title.
Edit: This question was plagued with syntax errors everywhere but following the advice of #MadScientist I was eventually able to get it to work using shell loops.
The shortest answer is, you can't do it that way. A single function like $(foreach ...), or a single variable expansion, can never expand to multiple logical lines in a makefile. That's just not how the make parser works.
Further, $(eval ...) can be used to construct a complete rule but you can't use it to build just part of a rule: before make starts to parse the output of the evaluation it will "close" any rule that's currently being defined (just like you can't put the introduction of a rule in one file and the recipe in another file and use include to include the recipe).
You haven't really explained why you want to do things in just this way. One simple answer is to use shell loops, not makefile loops, like this:
FormatAll: CFormat
#for f in $(ALL_S_SOURCES); do \
echo Formatting; \
if [ -f $$f ]; then \
echo if1; \
if [ -f $${f}_X ]; then \
echo if2; \
else \
echo else2; \
fi; \
ren $$f $${f}_X; \
echo Formatting $$f . . .; \
$(CFORMAT_PATH)\Cformat -h$(CFORMAT_PATH) $F{f}_X -o$[Base, $$f].tmp -ino >temp.tmp; \
$(CFORMAT_PATH)\Ccomment -h$(CFORMAT_PATH) $[Base, $$f].tmp -o$$f >temp.tmp; \
else \
echo else1; \
fi; \
done
I agree with Etan that the $[Base ...] syntax is weird and certainly isn't right.
If you want more details about eval and debugging, you might check out this post and the earlier ones in the series.
The error message is pretty clear: the foreach loop spits out recipe commands before/outside of a target recipe definition.
You can try something like:
all:
$(foreach loopFile,$(ALL_S_SOURCES),$(eval $(call Format_File,$(loopFile))))
.PHONY: all

Directory wildcard in Makefile pattern rule

I'm trying to create a Makefile that will compile terminfo files residing in a directory via tic. tic also copies the termcap files it creates automatically to a system- or user-specific destination folder. For a regular user if the terminfo file is e.g. screen-256color-bce-s.terminfo, it will be compiled and copied to ~/.terminfo/s/screen-256color-bce-s. So it will look something like this:
terminfo/screen-256color-bce-s.terminfo => /home/user/.terminfo/s/screen-256color-bce-s
terminfo/screen-256color-s.terminfo => /home/user/.terminfo/s/screen-256color-s
If I put something like this into my Makefile:
TISRC = $(wildcard terminfo/*.terminfo)
TIDST = $(foreach x, $(TISRC), $(HOME)/.terminfo/$(shell basename $x|cut -c 1)/$(shell basename $x .terminfo))
$(HOME)/.terminfo/s/%: terminfo/%.terminfo
#echo "$< => $#"
#tic $<
install: $(TIDST)
it works. However, I'd like to make it general, and use a wildcard in the target, i.e.:
$(HOME)/.terminfo/**/%: terminfo/%.terminfo
#echo "$< => $#"
#tic $<
to be able to add terminfo files to my local repository. The above, however, does not work. How can I specify a wildcard directory in a pattern rule?
You can do that with GNU Make Secondary Expansion feature:
all : ${HOME}/.terminfo/x/a
all : ${HOME}/.terminfo/y/b
.SECONDEXPANSION:
${HOME}/.terminfo/%: terminfo/$$(notdir $$*).terminfo
#echo "$< ---> $#"
Output:
[~/tmp] $ make
terminfo/a.terminfo ---> /home/max/.terminfo/x/a
terminfo/b.terminfo ---> /home/max/.terminfo/y/b
As a side note, make provides some path manipulation functions, so that you don't really need to invoke the shell for that.
I don't think you can use wildcards the way you're trying to, but if you don't mind using eval trickery, you can get the effect you're shooting for without having to spell out all the directory paths explicitly:
TISRC = $(wildcard terminfo/*.terminfo)
BASENAMES = $(notdir $(basename ${TISRC}))
MKDST = ${HOME}/.terminfo/$(shell echo $1 | cut -c 1)/$1
TIDST := $(foreach s,${BASENAMES},$(call MKDST,$s))
DIRLTRS = $(notdir $(patsubst %/,%,$(sort $(dir ${TIDST}))))
install: ${TIDST}
# $1 - Directory Name
# $2 - File name
define T
${HOME}/.terminfo/$1/$2 : terminfo/$2.terminfo
#echo "$$< => $$#"
tic $$<
endef
# This is the tricky part: use template T to make the rules you need.
$(foreach d,${DIRLTRS},$(foreach f,${BASENAMES},$(eval $(call T,$d,$f))))

Makefile: recursive macro call running indefinitely

I'm trying to create a Makefile which creates a custom filesystem and my problem is to copy some packages inside it. I need to process a directory containing all the packages that I need to include, but each package may or may not contain a file associated describing its dependencies. Here is an excerpt of what I've tried to do:
PKGS_DIR := $(shell pwd)/packages
PKGS := $(wildcard $(PKGS_DIR)/*.pkg)
.PHONY: $(PKGS)
define getdeps
#if [ ! -f $(2)/$(1) ]; then \
echo $(1) >> $(BASE_DIR)/deplist.tmp; \
if [ -f $(PKGS_DIR)/$(1).dep ]; then \
depapps=`cat $(PKGS_DIR)/$(1).dep`; \
$(foreach depapp,$$depapps,\
$(call getdeps,$(depapp),$(BASE_DIR)/optional); \
) \
fi \
fi
endef
$(PKGS):
#echo processing $(notdir $#)
$(call getdeps,$(notdir $#),$(BASE_DIR)/optional)
With this code, my makefile crashes because it enters in an indefinitely loop and I'm not understanding why.
My question is: Is it possible to make a recursive call in a macro definition? Is there a better way to implement this? Perhaps it would be better using a shell script to implement this and just call it inside my makefile.
Any suggestion will be really appreciated.
Thanks in advance

Resources