0

I want

I am trying to compile some latex that has snippets of python code and the output of those snippets. I need the document to be always updated with the last changes made in the snippets and in their outputs, so the idea is maintain a makefile that could monitor this changes and generate the updated outputs.

So if I modify the file a/11.py, I want make to execute it to generate a new output a/11.out.

I have

This is my makefile

DOC=myPdf
STY=st
PY_DIR=a/
TEX=pdflatex -shell-escape -interaction=batchmode -file-line-error

$(DOC).pdf: $(PY_DIR)11.out $(PY_DIR)12.out $(DOC).tex $(STY).sty
    $(TEX) $(DOC).tex

$(PY_DIR)11.out:
    $(cd PY_DIR && python3 11.py > 11.out)

$(PY_DIR)12.out:
    $(cd PY_DIR && python3 12.py > 12.out)

.PHONY: clean
clean:
    rm *.aux *.log > /dev/null 2>&1

I wonder

Even when the file a/11.out doesn't exist, and I instruct make a/11.out make says: make: 'a/11.out' is up to date. (I am still learning make, so I probably have more mistakes).

I saw

Thank you for your time :)


Update

This is my new version, based in the answer of Renaud (thanks for your help), some python scripts are intended to output text (xxxt.py), and others to plot images (xxxi.py), so there is no redirection for them:

DOC    :=myPdf
STY    :=st
PY_DIR :=a/
TEX    :=pdflatex -shell-escape -interaction=batchmode -file-line-error
PYS    := $(wildcard $(PY_DIR)*.py)
OUTS   := $(patsubst %.py,%.out,$(PYS))
.PHONY: all clean

all: $(DOC).pdf

%.pdf: %.tex $(STY).sty $(OUTS)
    $(TEX) $<

$(PY_DIR)%.out: $(PY_DIR)%t.py
    cd $(PY_DIR) && python3 $*t.py > $*.out

$(PY_DIR)%.png: $(PY_DIR)%i.py
    cd $(PY_DIR) && python3 $*i.py

clean:
    rm *.aux *.log > /dev/null 2>&1

The directory looks like this:

./st.sty
./myPdf.tex
./myPdf.pdf
./a/11t.py
./a/11.out
./a/12i.py
./a/12.png
./a/21t.py
./a/...

However, now right after modifying myPdf.tex, make says make: Nothing to be done for 'all'.

What am I doing wrong?

onlycparra
  • 607
  • 4
  • 22

1 Answers1

1

Your recipes are wrong. Make expands the recipes before passing them to the shell. As there is no make variable named cd PY_DIR && python3 11.py > 11.out, $(cd PY_DIR && python3 11.py > 11.out) expands as the empty string and make considers that there is nothing to do for $(PY_DIR)11.out. Just write your recipes as plain shell (and fix the other bug with the unexpanded PY_DIR):

$(PY_DIR)11.out:
    cd $(PY_DIR) && python3 11.py > 11.out

$(PY_DIR)12.out:
    cd $(PY_DIR) && python3 12.py > 12.out

Note: if you want make to re-run the recipes when your python scripts change you should let him know that the output files depend on the python scripts. The best is probably to use a pattern rule instead of one specific rule per file:

$(PY_DIR)%.out: $(PY_DIR)%.py
    cd $(PY_DIR) && python3 $*.py > $*.out

($* is a make automatic variable, it expands as the stem of the pattern).

A few more improvements:

  • You could ask make to find alone the python scripts, compute the names of the output files and store all this in make variables that you can used in your other rules.
  • You can use a pattern rule for the xx.tex -> xx.pdf process. And use another make automatic variable for it: $< that expands as the first prerequisite.
DOC    := myPdf
STY    := st
PY_DIR := a/
TEX    := pdflatex -shell-escape -interaction=batchmode -file-line-error

PYS    := $(wildcard $(PY_DIR)*.py)
OUTS   := $(patsubst %.py,%.out,$(PYS))

.PRECIOUS: $(OUTS)
.PHONY: all clean

all: $(DOC).pdf

%.pdf: %.tex $(OUTS) $(STY).sty
    $(TEX) $<

$(PY_DIR)%.out: $(PY_DIR)%.py
    cd $(PY_DIR) && python3 $*.py > $*.out

.PHONY: clean
clean:
    rm *.aux *.log > /dev/null 2>&1

Note: I declared $(OUTS) as precious such that make does not delete them when it is done with the building of $(DOC).pdf.

Update with the new specifications and separated python scripts for xx.out and xx.png production:

DOC    := myPdf
STY    := st
PY_DIR := a
TEX    := pdflatex -shell-escape -interaction=batchmode -file-line-error
PYTS   := $(wildcard $(PY_DIR)/*t.py)
PYIS   := $(wildcard $(PY_DIR)/*i.py)
OUTS   := $(patsubst $(PY_DIR)/%t.py,$(PY_DIR)/%.out,$(PYTS))
PNGS   := $(patsubst $(PY_DIR)/%i.py,$(PY_DIR)/%.png,$(PYIS))

.PRECIOUS: $(OUTS) $(PNGS)
.PHONY: all clean

all: $(DOC).pdf

%.pdf: %.tex $(STY).sty $(OUTS) $(PNGS)
    $(TEX) $<

$(PY_DIR)/%.out: $(PY_DIR)/%t.py
    cd $(PY_DIR) && python3 $*t.py > $*.out

$(PY_DIR)/%.png: $(PY_DIR)/%i.py
    cd $(PY_DIR) && python3 $*i.py

clean:
    rm -f *.aux *.log > /dev/null 2>&1

Notes:

  • I slightly modified the definition of PY_DIR such that, when used in other parts of the Makefile, it is clear that it is a directory path. Just a matter of taste, I guess.
  • I added the -f option to your clean recipe such that it doesn't fail if the files to delete do not exist.

Update:

As noted by MadScientist in a comment, using $* is less generic than referring to the target ($@) and the prerequisite ($<). But as we are operating not directly on them but on their directory ($(PY_DIR)) and base file names (xx[it].py, xx.out, xx.png), switching from $* to other, more generic, automatic variables is not that simple.

But make has some more tricks that can help here: $@, $<... have variants ($(@F), $(@D)...) that expand to just the directory part or the file part. Note that, according the GNU make manual:

These variants are semi-obsolete in GNU make since the functions dir and notdir can be used to get a similar effect.

Anyway, if we wanted to avoid $* here is what we could use instead:

$(PY_DIR)/%.out: $(PY_DIR)/%t.py
    cd $(@D) && python3 $(<F) > $(@F)

$(PY_DIR)/%.png: $(PY_DIR)/%i.py
    cd $(@D) && python3 $(<F)

Or (modern version):

$(PY_DIR)/%.out: $(PY_DIR)/%t.py
    cd $(dir $@) && python3 $(notdir $<) > $(notdir $@)

$(PY_DIR)/%.png: $(PY_DIR)/%i.py
    cd $(dir $@) && python3 $(notdir $<)
Renaud Pacalet
  • 25,260
  • 3
  • 34
  • 51
  • You should not use `$*.out` as the file to be generated. You must always use exactly `$@` as the file to generate, otherwise your makefile will not work correctly (unless it so happens that `$*.out` and `$@` are the same thing, which in this case they are not). – MadScientist Nov 18 '18 at 14:21
  • Did you consider the `cd $(PY_DIR) && ...`? I don't think `$@` would work here. But I would be glad to learn something new about make, of course. And no, we cannot really invoke python from the top directory: python is picky about where it is invoked from (modules path and so on). We need this `cd $(PY_DIR) && ...` stuff. – Renaud Pacalet Nov 18 '18 at 14:37
  • wow, I need some time to digest this, I'll try it now and I will tell you how it goes, Thanks very much for your explanation. – onlycparra Nov 18 '18 at 18:56
  • I don't understand the first improvement with the $(OUTS). Now make says `nothing to be done for 'all'`. even when I modify the `.tex` file – onlycparra Nov 18 '18 at 19:17
  • Are you sure you removed all other pdf building rules and kept only the pattern rule? Didn't you forget to remove an old `$(DOC).pdf:` rule? Anyway, there are a few new problems with your new Makefile. Let me update ma answer. – Renaud Pacalet Nov 19 '18 at 08:03
  • Just note: to be a bit more generic etc. you could use `cd $(@D) && python3 $( $(@F)` – MadScientist Nov 19 '18 at 17:48
  • @MadScientist: thanks, I updated my answer to add this. – Renaud Pacalet Nov 20 '18 at 07:25
  • Yeah... I'm removing all that verbiage about "semi-obsolete" in the next release :). I see no problem with using the shorthand style and they're certainly not going to be removed anytime soon. – MadScientist Nov 20 '18 at 13:07
  • You're the boss ;-). – Renaud Pacalet Nov 20 '18 at 15:14