Makefile recognizes only changed prerequisites, not new ones - makefile

Trying to write a simple makefile to compile Markdown files to HTML with Pandoc. I don't want to have to add all the prerequisite .md files explicitly to the makefile, so I've tried to use a pattern rule along with a wildcard prerequisite:
all: www/*.html
www/%.html: src/%.md
pandoc -f markdown -t html $< > $#
This is close, but only processes prerequisite .md files for which an .html target file already exists and is out of date. New .md files which don't already have an .html file get ignored (so if all the non-new files are built, I get make: Nothing to be done for 'all'.)
What's the concept I'm missing? I'm not sure how to tell make to run on both the changed AND new .md files in src/ and apply the pattern rule to each.

You could obtain a list of the .html files to be generated from the already existing .md files you have in the src/ directory.
First, the list of the existing .md files can be obtained with the wildcard built-in function:
md-files := $(wildcard src/*.md)
Then, apply a substitution reference to the md-files variable, so that the src/ prefix is removed and the suffix .md is replaced by .html for each element of the list of the .md files:
$(md-files:src/%.md=%.html)
Finally, by applying the addprefix built-in function to the resulting list, the prefix www/ can be added to each element of that list:
html-files := $(addprefix www/,$(md-files:src/%.md=%.html))
Putting everything together, the resulting makefile would be:
md-files := $(wildcard src/*.md)
html-files := $(addprefix www/,$(md-files:src/%.md=%.html))
.PHONY: all
all: $(html-files)
www/%.html: src/%.md
pandoc -f markdown -t html $< > $#

Try this:
$ cat Makefile
input := foo.md bar.md baz.md
output := $(input:%.md=www/%.html)
.PHONY: all
all: ${output}
www/%.html: src/%.md
pandoc -f markdown -t html $< > $#
$ make -n
pandoc -f markdown -t html src/foo.md > www/foo.html
pandoc -f markdown -t html src/bar.md > www/bar.html
pandoc -f markdown -t html src/baz.md > www/baz.html

Related

How to create a makefile to sort all the .txt files in the directory and save it as .sorted using makefile?

How to create a makefile to sort all the .txt files in the directory and save it as .sorted using makefile?
%.sorted: %.txt
sort $< -o $#
all: (*.sorted)
default:all
(*.sorted) is just looking for a file with that literal name. You probably want
all: $(patsubst %.txt,%.sorted,$(wildcard *.txt))
%.sorted: %.txt
sort $< -o $#
The $(wildcard *.txt) generates a list of all your text files, and the $(patsubst ...) generates a parallel list with .sorted instead of .txt.
(Notice that Stack Overflow renders tabs as spaces, so you will not be able to simply copy/paste this code from the rendered page.)
For just make to do what you want, you need the target to be the first one in the Makefile. Perhaps see also Makefile: all vs default targets

Why does make recompile all files?

My goal is the following: I have a directory src which contains markdown files (.md). I want to run a command on each of these files so that the comments are removed and the edited files are stored in a separate directory. For this I want to use make.
This is the Makefile I have:
.PHONY: clean all
BUILD_DIR := build
SRC_DIRS := src
SRCS := $(shell find $(SRC_DIRS) -name *.md)
DSTS := $(patsubst $(SRC_DIRS)/%.md,$(BUILD_DIR)/%.md,$(SRCS))
all: $(DSTS)
# The aim of this is to remove all my comments from the final documents
$(DSTS): $(SRCS)
pandoc --strip-comments -f markdown -i $< -t markdown -o $#
clean:
rm $(BUILD_DIR)/*.md
While this works in general, I noticed that the command is executed on all files, even though I changed only one single file.
Example: I have 3 Files src/a.md, src/b.md and src/c.md. Now I run make and all files are correctly generated in the build folder. Now I only edit c.md and run make again. I would expect that make only "compiles" src/c.md anew but instead all three files are compiled again. What am I doing wrong?
Your line
$(DSTS): $(SRCS)
is saying ‘All of the DSTS depend on all of the SRCS’, so whenever any one of the $(SRCS) is newer than any of the $(DSTS), this pandoc action will be run.
That's not what you want to express. What you want is something more like
$(BUILD_DIR)/%.md: $(SRC_DIRS)/%.md
pandoc --strip-comments -f markdown -i $< -t markdown -o $#
all: $(DSTS)
That says that all of the $(DSTS) should be up to date, and the pattern rule teaches Make what each one depends on, and how to build it, if it is out of date.
(As a general point, looking your original rule, it's rarely the right thing to do to have multiple targets in a rule, as you have with $(DSTS); also note that in your original, $< always refers only to the first of the dependencies in $(SRCS))

Makefile recipe that works when target file base name is different from prerequisite file?

I have a Makefile in which a couple of targets need to be made the same way, but one of the targets is a file whose basename is different from its prerequisite. Here is a minimal example:
ABOUT.html: README.md
help.html: help.md
%.html: %.md
pandoc --standalone --quiet -f gfm -H $(github-css) -o tmp.html $<
inliner -n < tmp.html > $#
rm -f tmp.html
With this Makefile, help.html gets made but ABOUT.html is never made. The reason, I presume, is because the base file name for %.html and %.md don't match up in the case of ABOUT.html because that target depends on README.md.
Is there a way to make this work without having to make a separate recipe for ABOUT.html?
One option is to create ABOUT.md symlink, so that your pattern rule works, by adding the following rule:
ABOUT.md : README.md
ln -s ${<F} ${#F}
You may like to avoid using the same temporary file in the recipe because that breaks in parallel builds. A better way is to use a unique temporary file for the target based on the target name:
%.html: %.md
pandoc --standalone --quiet -f gfm -H $(github-css) -o $#~ $<
inliner -n < $#~ > $#
rm -f $#~

How to target multiple directories with a single Makefile?

I'm using GNU Make to build three different editions of a static html document.
I use Less as a CSS preprocessor.
My directory structure looks like this:
Makefile
160x600/style.less
300x250/style.less
728x90/style.less
This is my Makefile:
LESSC=lessc -x # use -x for debugging
.PHONY: all clean
all: 160x600 300x250 728x90
%.css: %.less
$(LESSC) $< > $#
160x600: 160x600/style.css
300x250: 300x250/style.css
728x90: 728x90/style.css
clean:
rm -f 160x600/*.css
rm -f 300x250/*.css
rm -f 728x90/*.css
This way, I can use make 160x600 to build style.css from style.less.
But I don't want to explicitly list a target rule for each directory. Instead, I tried adding this rule instead of the three directory specific ones:
%: %/style.css
But that does not work. I assume it's clear from that example what my goal is. Is there a way to accept any directory as a target, so that I just have to list the directory names in the all: rule?
use static pattern rule:
res_dirs = 160x600 300x250 728x90
$(res_dirs): %: %/style.css

Unix find with GNU Make to auto-update files

I have .haml files and want to convert them automatically into .html files and update the latter when .haml is changed.
The generic makefile rule is no problem:
%.html: %.haml
hamlpy $< $#
But now I need a rule or a command to do the following:
find all X.haml files in templates/
execute make X.html command, where X is the same filename (haml is replaced with html).
I can't find how to do this with GNU Make or Unix find.
If all of your *.haml files are well name (i.e. no spaces or other funny characters), you can do it with a call to find(1):
HAML_FILES = $(shell find templates/ -type f -name '*.haml')
HTML_FILES = $(HAML_FILES:.haml=.html)
all: $(HTML_FILES)
%.html : %.haml
hamlpy $< $#
You can use GNU make wildcard function to find files in a certain directory:
INDIR := templates
OUTDIR := ${CURDIR}
haml_files := $(wildcard ${INDIR}/*.haml)
html_files := $(subst ${INDIR}/,${OUTDIR}/,${haml_files:.haml=.html})
all : ${html_files}
clean :
rm -f ${html_files}
${OUTDIR}/%.html : ${INDIR}/%.haml
hamlpy $< $#
.PHONY : all clean
INDIR and OUTDIR can be customized on the command line, for example, to use the current directory for inputs and iutputs:
$ make INDIR=. OUTDIR=.

Resources