Second expansion and substitution on makefile targets - makefile

I am trying to do second expansion in a makefile with substitution.
A sample makefile:
# We have src{0..3}.md documents. Generate them with
#
# for i in src{0..3}.md; do echo "Hello in $i" > $i; done
#
# doc0.txt and doc2.txt are capitalized and doc1.txt and doc3b.txt are
# lowercased.
CAP_DOCS := doc0.txt doc2.txt
LOW_DOCS := doc1.txt doc3.txt
DOCS := $(CAP_DOCS) $(LOW_DOCS)
all: $(DOCS)
.SECONDEXPANSION:
$(CAP_DOCS): $$(#:doc%.txt=src%.md)
tr '[a-z]' '[A-Z]' < $< > $#
.SECONDEXPANSION:
$(LOW_DOCS): $$(#:doc%.txt=src%.md)
tr '[A-Z]' '[a-z]' < $< > $#
And the error I get is
$ make
Makefile:15: *** target pattern contains no `%'. Stop.
I also tried setting perc=% and replacing the % characters with $(perc) and even $$(perc) as I figured it was trying to expand them before the second expansion and failed. It didn't help.

I also couldn't get hiding the % from make with variables to work though that was also my first thought.
I was able to get $(CAP_DOCS): $$(patsubst doc%.txt,src%.md,$$#) and $(LOW_DOCS): $$(patsubst doc%.txt,src%.md,$$# working though.
I haven't tried it, but I wonder if you could just hide the substitution ref in a define or something and achieve the same effect.

Related

How do computed variables passed to included make files' functions get expanded?

To reduce the amount of repeated boiler plate in a top level makefile, I created an included make file that uses computed variable names. Where I'm having difficulty is in the excerpt from the included makefile: ftp-files.mk:
...
$($(FNMPFX)_FTP_CFG): $(CFG_MAKE_FILE) | $($(FNMPFX)_FTP_CFG_DIR)
$(call ftp_helper, $#, $($(FNMPFX)_FTP_DIR), $($(FNMPFX)_CACHE_DIR), $($(FNMPFX)_FTP_NAME))
...
In the main makefile, I was hoping to do something akin to:
CFG_MAKE_FILE := Makefile
define ftp_helper
echo "quote USER anonymous" > $(1)
echo "quote PASS" >> $(1)
echo "cd $(2)" >> $(1)
echo "lcd $(3)" >> $(1)
echo "binary" >> $(1)
echo "get $(4)" >> $(1)
echo "quit" >> $(1)
endef
FNMPFX := FILE_A
include ftp-files.mk
...
FNMPFX := FILE_Z
include ftp-files.mk
...
The trouble is that the order only prerequisite (also tried it as a normal prerequisite) expands to the last ... instanciation (?) of the included file.
What appears to be happening is a first expansion of the two targets that behave as though they were written:
# Point of confusion V
# |
$(FILE_A_FTP_CFG): $(CFG_MAKE_FILE) | $(FILE_Z_FTP_CFG_DIR)
$(call ftp_helper, $#, $(FILE_A_FTP_DIR), $(FILE_A_CACHE_DIR), $(FILE_A_FTP_NAME))
$(FILE_Z_FTP_CFG): $(CFG_MAKE_FILE) | $(FILE_Z_FTP_CFG_DIR)
$(call ftp_helper, $#, $(FILE_Z_FTP_DIR), $(FILE_Z_CACHE_DIR), $(FILE_Z_FTP_NAME))
Is this possible?
My work around was to include that simple target rule in the main Makefile (ftp-file.mk is reasonably wordy at 100 lines), so having those two lines (repeated) throughout the main Makefile isn't too burdensome.
Can somebody suggest a working alternative?
In general you can't read a whole Makefile like a shell script. It's read in multiple phases, and certain things are done with the entire contents of the Makefile before proceeding to the next step. The rules are rather complex, but suffice it to say that it looks like the last assigned value is used:
$ cat Makefile
variable := original
first:
echo $(variable)
variable := other
second:
echo $(variable)
$ make first
echo other
other
$ make second
echo other
other

evaluation of a command in variable for prerequisite of makefile rule

I'm writing a Makefile and I encounter some problems. I have a command which returns a string on stdout and I want to store this string in a variable. This program returns the list of dependencies of a file and I want to inject this list into the prerequisite line.
For now, I have this:
FILE= $(eval cut -d '.' -f1 $#)
FILEDEP= ./listfiledepend $(FILE).bar
IMAGEDEP= ./listimgdepend $(FILE).bar
%.foo: %.bar $(eval $(FILEDEP)
Some code to do
%.pdf: %.foo $(eval $(IMAGEDEP))
Some code to do
But it doesn't work and I don't know why.
I don't know how to do that

Redirection of output in shell script lines in Makefiles

I'm trying to figure out what one specific line in a makefile is doing:
foo: smth_foo_depends_on
...
#echo $< | bar >> $#
...
In particular I'd like to know:
why do we write # before echo;
what does $< mean;
why do we output something in $# which, as far as I know, is the list of arguments given to the script? Why do we modify it?
Thanks!
Why do we write # before echo?
Source: It is done to suppress the echoing for one specific line in the recipe.
What does $< mean?
Source: $< is the name of the first prerequisite (i.e. smth_foo_depends_on)
Why do we output something in $# which, as far as I know, is the list of arguments given to the script? Why do we modify it?
Source: In makefile language, $# is the name of the current target (i.e. foo). Do not confuse it with the shell list of positional parameters. The recipe line echo $< | bar >> $# is expanded to:
echo smth_foo_depends_on | bar >> foo

Insert a new-line in a Makefile $(foreach ) loop

I'm compiling a large list of files in a Makefile.
my.list : ${deps}
rm -f $#
$(foreach F,$^,echo "${F}" >> $#;)
but ${deps} can be large and the generated command-line could be too large for one SHELL call. Is it possible to replace ';' by a newline '\n' ?
As already mentioned in Jonathan Wakely's answer, the straightforward answer is
define newline
endef
Interestingly enough, for all chars except newline there is an easier way to get non-printable characters into variables with some help from the shell, e.g.:
tab := $(shell printf '\011')
It won't work here, though, because the builtin shell function replaces all newlines by spaces.
The version above is still a bit fragile though, in particular when combining with automake which will silently remove the second consecutive newline from the input file, turning newline into an empty variable. To force it to keep the newline, we can extend the snippet:
blank :=
define newline
$(blank)
endef
Finally, to actually get a separate shell command for each dependency, you need to run the generated string through eval:
define generate-rule =
my.list : $(1)
rm -f $$#
$(foreach F,$$^,$(newline)$(tab)echo "${F}" >> $#)
endef
$(eval $(call generate-rule,$(deps)))
You can define a variable that expands to a newline like so:
define NEWLINE
endef
Now you can use $(NEWLINE) to expand to a newline.
This won't change anything though, the foreach will still generate a single command with embedded newlines between the statements, rather than a single command with ; between the statements.
Possibly the most straight-forward answer is to use:
#printf "\n"
You can also use this, as I commonly do, to show a description when each new make target executes. An example target:
.PHONY: clean-version
clean-version: ## Reset version back to committed version number
#printf "\n## Reset version to last committed version number ##\n\n"; \
$(GIT) checkout $(VERSION_FILE);
Replacing ';' by a carriage-return will produce a string of the same size, subject to the same problem.
"foreach" is simply a string expansion. If you want to execute a separate command for each item, you can use a for loop.
my.list : ${deps}
rm -f $#
(for F in $^; do echo $$F >> $# ; done)
Edit -- after some revisions, it looks like the only problem with my original was not due to the whitespaces, but with MAKE_O. I've fixed it in my version, but I'll mostly be removing them below.
By the way the original post was written, I'm not sure if my solution will be relevant. However, I found myself in the middle of a define already inside a foreach, and couldn't figure out how to insert a newline using any of the other answers as given.
Solution:
define BR
$(1)
endef
define MAKE_O
$(1): $(wildcard $(1:obj/%.o=src/%.cpp)); \
$(CXX) ... -c $$< \$(call BR)-o $1 ... \
\$(call BR)&& touch $$#
endef
$(foreach N,main.o,$(eval $(call MAKE_O,$(N))))
Desired output (compilation is truncated from auto-dependency generation, hence the touch):
> make obj/main.o
g++ ... -c src/main.cpp \
-o obj/main.o ... \
&& touch obj/main.o
I changed BR to perform the indentation but leave the end of the line up to you:
define BR
$(1)
$(1:%= ) #<remove this comment
endef
define MAKE_O
$(1): $(wildcard $(1:obj/%.o=src/%.cpp)); \
$(CXX) ... -c $$< $(call BR,\)-o $1 ... \
$(call BR,\)&& touch $$#
endef
$(foreach N,main.o,$(eval $(call MAKE_O,$(N))))
The markup won't help to show this, but line 2 of BR is $(1:%=_space_)_tab_ (the comment itself is not allowed.) Result:
> make -n obj/main.o
g++ obj/main.o -c \
-o obj/main.o && \
echo statement on new line
I used $(call BR,\) so that the newline was not parsed as an escape of the new line, and $(1:%=space)tab so that the tab is forced (many similar rules have been defined, like SPACE:=$(SPACE) $(SPACE) without a prior value.) The variable left of the whitespace must evaluate to something. To be clear, removing the whitespace before and after the call: ...lastword$(call BR,\)firstword... yields ...lastword\n\tfirstword..., or written out,
[^]...lastword \[$]
[^]$(call BR,\)firstword...[$]
...yields...
[^]...lastword \[$]
[^] firstword...[$]
to achieve the same (using ^,$ to denote the beginning and end of the line. Someone else will probably know how to format/annotate this better.)
My syntax highlighter is decidedly unimpressed with the 'escaped' parentheses and trailing whitespace, but the result is decent.

makefile backreference to matched text

Suppose, I have 2 files, dependent upon each other:
./pictures/1_data.tex
|
V
./data/1.pl
So, 1_data.tex is generated from the Perl file. To do it I have the following rule in the makefile:
./pictures/1_data.tex: ./data/1.pl
perl given.pl 1 > $#
If I have multiple files with this pattern:
./data/1.pl
./data/2.pl
...
./data/n.pl
I'd like to use wildcards to process them. I tried this:
./pictures/*_data.tex: ./data/*.pl
perl given.pl $* > $#
But it generates incorrect command:
perl given.pl pictures/1_data > pictures/1_data.tex
Is it possible to have a backreference only to 1, and not to the whole target? As $* does.
Use pattern rules:
all: $(patsubst ./data/%.pl,./pictures/%_data.tex,$(wildcard ./data/*.pl))
./pictures/%_data.tex : ./data/%.pl
perl given.pl $* > $#

Resources