My current project contains a makefile which has, among its rules (simplified without dependencies):
SOURCES : = "file1.txt file2.txt file3.txt" # and so on
assembled.txt:
myprog -I $(SOURCES)
THe issue is that myprog doesn't want a single value, but instead, it would expect:
myprog -I file1.txt -I file2.txt ...
How can I handle this in a Makefile?
Quick and dirty:
SOURCES := file1.txt file2.txt file3.txt
SOURCES := $(addprefix -I , $(SOURCES))
assembled.txt:
myprog -I $(SOURCES)
Slightly more elegant:
SOURCES := file1.txt file2.txt file3.txt
assembled.txt:
myprog $(foreach S, $(SOURCES),-I $(S))
#Beta,
Your first example (using addprefix) was a little off. Should've looked like
SOURCES := file1.txt file2.txt file3.txt
assembled.txt:
myprog $(addprefix -I , $(SOURCES))
which I think reads a little nicer than your foreach version.
Related
I'd like to create a set of output files from multiple input files where the output format is specified as a target. E.g., for a single format:
svg: svg/file1.svg svg/file2.svg
svg/file1.svg svg/file2.svg: %.svg: %.dot
#dot -T svg -o $# $<
Now how can I add more formats without duplicating rules and prerequisites? My approach was something like this:
input := file1 file2
formats := svg png
output := $(foreach format, ${formats}, $(foreach name, ${input}, ${format}/${name}.${format}))
# output := svg/file1.svg svg/file2.svg png/file1.png png/file2.png
all: ${formats}
${formats}: [somehow filter ${output} based on chosen format?]
${output}: [somehow filter out just the file name?]: %.dot
#dot -T svg -o $# $<
I haven't been able to figure this out with variables and patterns. I did find a way using a variable for the format; e.g., make format=svg, but I'd rather use target names.
Using GNU Make 4.3.
You're going to want to use a macro/eval for this -- but beware, by doing so, you may cross that line where your makefile can be easily understood/maintained by others.,,
input := file1 file2
formats := svg png
output := $(foreach format, ${formats}, $(foreach name, ${input}, ${name}.${format}))
all: $(foreach file,$(input),$(foreach format,$(foramts),$(file).$(format)))
define dot_rules
# $$1 is the basenames,
# $$2 is the input extension,
# $$3 is the target extension
$(1:=.$3) : %.$3 : %.$2
#echo dot -T $3 -o $3/$$# $$<
endef
# just to be suure you got it right:
$(info $(foreach format,$(formats),\
$(call dot_rules,$(input),dot,$(format))))
# expand the calls into the makefile
$(eval $(foreach format,$(formats),\
$(call dot_rules,$(input),dot,$(format))))
There are a couple of kind of similar issues but I could not fit any of the proposed concepts to my case.
Just to give a little bit of context: I have a set of Julia files which create plots as PDFs which are part of a make procedure to create scientific papers, something like:
plots = $(shell find $(PLOT_PATH)/*.jl | sed 's/\.jl/\.pdf/g')
$(PLOT_PATH)/%.pdf: $(PLOT_PATH)/%.jl $(JULIA_SYSIMAGE)
$(JL) --project $< -o $(PLOT_PATH)
$(DOCUMENT_FILENAME).pdf: FORCE $(plots) $(figures)
latexmk $(DOCUMENT_FILENAME).tex
In the current setup, each XYZ.jl file is creating a XYZ.pdf file and it works absolutely fine.
Now I am dealing with cases where it would be much easier to create multiple plots from single Julia files, so a script like this:
#!/usr/bin/env julia
using PGFPlotsX
...
...
pgfsave("whatever.pdf")
pgfsave("another.pdf")
pgfsave("yetanother.pdf")
so that one could do a grep pgfsave SCRIPT | awk... to figure out the targets. However, I could not figure out how to generate dynamic targets (plots) based on the contents of the dependency file (Julia script).
An MWE for my problem is the following: I have a couple of files (dependencies) which are generating a bunch of targets, which are defined inside those files (and can be access via awk/grep/sed/whatever). For now, let's say that these are simply *.txt files and each line is a target.
file: a.txt
foo
bar
baz
file: b.txt
naarf
fjoord
A very basic (non-working) manual Makefile to demonstrate the goal would be something like this (it does not work as it cannot figure out how to make foo etc. but it shows the pattern for *.txt which needs to be repeated):
file: Makefile
all_products := $(shell find *.txt | xargs cat)
final_product: $(all_products)
echo $< > $#
(foo bar baz): a.txt
touch $(shell cat $<)
(narf fjoord): b.txt
touch $(shell cat $<)
so in principle, I need something to "process" the dependency (*.txt) to create a list of the targets, like
$(shell cat $%): %.txt
echo $< > $#
but I cannot manage to get a reference to the dependency on the target side ($% does not work).
Any ideas? Maybe the whole approach is just a bad idea ;)
A combination of GNU make foreach, eval and call functions is probably what you need. With your example:
TXT := $(wildcard *.txt)
.PHONY: all
.DEFAULT_GOAL := all
define MY_MACRO
$(1)-targets := $$(shell cat $(1))
$$($(1)-targets): $(1)
echo $$< > $$#
all: $$($(1)-targets)
endef
$(foreach t,$(TXT),$(eval $(call MY_MACRO,$(t))))
(pay attention to the $$ in the macro definition, they are needed). And then:
$ make
make
echo a.txt > foo
echo a.txt > bar
echo a.txt > baz
echo b.txt > naarf
echo b.txt > fjoord
If you want the recipe to build all targets at once you'll need a recent enough GNU make version (4.3 or later) and its new rule with grouped targets (x y z&: w):
TXT := $(wildcard *.txt)
.PHONY: all
.DEFAULT_GOAL := all
define MY_MACRO
$(1)-targets := $$(shell cat $(1))
$$($(1)-targets)&: $(1)
touch $$($(1)-targets)
all: $$($(1)-targets)
endef
$(foreach t,$(TXT),$(eval $(call MY_MACRO,$(t))))
And then:
$ make
touch foo bar baz
touch naarf fjoord
Note that in this case we could also use a simpler and less GNU make-dependent solution. Just use empty dummy files as time stamps, for instance .a.txt.tag for a.txt, and a static pattern rule:
TXT := $(wildcard *.txt)
TAG := $(patsubst %,.%.tag,$(TXT))
.PHONY: all
all: $(TAG)
$(TAG): .%.tag: %
touch `cat $<` $#
I want to have a makefile that will take some strings as prefixes for some files (NAMES) and from there work on the target and prerequisite file names. In the example below, for example, the idea would be to convert 2 a csv files (foo.csv and bar.csv) to tabular (although I'm just echoing the target and prerequisite).
NAMES = foo bar
PR = $(patsubst %,%.csv,$(NAMES))
TB = $(patsubst %,%.tsv,$(NAMES))
all: $(TB)
%.tsv: $(PR)
#echo $< $#
This prints:
foo.csv foo.tsv
foo.csv bar.tsv
So, it looks like that makefile is not expanding correctly the prerequisites in PR as I would expect to see bar.csv bar.tsv on the second line.
However, if I print $PR and $TB, both seem to be set properly:
$(info $$PR is [${PR}])
$(info $$TB is [${TB}])
# prints
$PR is [foo.csv bar.csv]
$TB is [foo.tsv bar.tsv]
Any idea how to get this working properly?
Note that I have both foo.csv and bar.csv files in the working directory.
The problem lies in the way you're using the built in variable $<. If you expand the variables manually and rewrite the makefile it becomes...
NAMES = foo bar
PR = $(patsubst %,%.csv,$(NAMES))
TB = $(patsubst %,%.tsv,$(NAMES))
all: foo.tsv bar.tsv
%.tsv: foo.csv bar.csv
#echo $< $#
But $< refers to the first prerequisite which is always foo.csv regardless of the target.
One solution might be to use a scoped static pattern rule. So something like...
NAMES = foo bar
PR = $(patsubst %,%.csv,$(NAMES))
TB = $(patsubst %,%.tsv,$(NAMES))
all: $(TB)
# Tell make how to build a .tsv from a .csv but constrain the rule
# so that it only applies to .tsv files that are part of $(TB).
#
$(TB): %.tsv: %.csv
#echo 'building target [$#] with $$< = [$<]'
The above results in...
building target [foo.tsv] with $< = [foo.csv]
building target [bar.tsv] with $< = [bar.csv]
This is my contrived Makefile to illustrate my question:
allinfiles = file1.in file2.in file3.in
alloutfiles = file1.out file2.out file3.out
all: $(alloutfiles)
clean:
-rm *.pyc *.in *.out > /dev/null 2>&1
# my.py generates file1.in file2.in file3.in
$(allinfiles): my.py; python my.py
%.out: %.in
cp $< $#
My contrived Python script:
#!/usr/bin/env python
for i in 1,2,3:
filename = 'file{}.in'.format( i )
f = file( filename, 'wt' )
f.write( filename )
f.close()
Result:
$ make clean; make -j 8
python my.py
python my.py
python my.py
Question 1: how do I write my rules so that my.py is executed only once since it generates all the .in files?
NB: Unless I misunderstood, I'm trying to use this answer and not getting the results I expected.
Question 2: is there a way to log all the build outputs by their target? That is, if instead of $(shell cp $< $#), the recipe generated much output, how do I separate the output by target or input file?
I'm writing a GNU makefile to create a license file like so: if the customer's file exists then copy it to the package directory, else use the generic license. I have about a dozen files that have the same copy pattern. The goal is to copy one of the two mutually exclusive source files onto the target filename.
Is there a better way to express this with GNU make syntax? Here's what I currently have. I considered copying all GENERIC files to the directory, then overwriting with the existing CUST files.
$(PDIR)/license.txt: | $(PDIR)
if [ -f Package/license.txt.$(CUST) ] ; \
then \
cat Package/license.txt.$(CUST) >$(PDIR)/license.txt ; \
else \
cat Package/license.txt.GENERIC >$(PDIR)/license.txt ; \
fi
Edit: Thanks to MadScientist for the help. Here's my final working version:
TARGETS = license.txt ...
final: $(addprefix ${PDIR}/,${TARGETS})
#echo some output
$(foreach T,${TARGETS},$(eval ${PDIR}/$T: $(firstword $(wildcard Package/$T.${CUST} Package/$T.GENERIC Package/$T)) | ${PDIR}))
$(addprefix ${PDIR}/,${TARGETS}):
#echo Creating substituted version of $< as $#
#sed --expression="\
... \
< '$<' \
> '$#'
${PDIR}:
mkdir $#
You can use wildcard, then (as always) you should use automatic variables. Like this:
$(PDIR)/license.txt: $(firstword $(wildcard Package/license.txt.$(CUST) Package/license.txt)) | $(PDIR)
cat '$<' > '$#'
If you have a lot of these you can also use a loop to define the targets:
TARGETS = license.txt
$(foreach T,$(TARGETS),$(eval $(PDIR)/$T: $(firstword $(wildcard Package/$T.$(CUST) Package.$T) | $(PDIR))))
$(addprefix $(PDIR)/,$(TARGETS)):
cat '$<' > '$#'