onlycparra
onlycparra

Reputation: 775

Make says file is up to date, but it does not exist

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?

Upvotes: 0

Views: 783

Answers (1)

Renaud Pacalet
Renaud Pacalet

Reputation: 28910

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

Upvotes: 1

Related Questions