tamasgal
tamasgal

Reputation: 26329

Dynamic targets based on the dependency in Makefile

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

Upvotes: 1

Views: 790

Answers (1)

Renaud Pacalet
Renaud Pacalet

Reputation: 29345

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 $<` $@

Upvotes: 3

Related Questions