Static patterns and conditionals with make - makefile

I have a problem with GNU Make 4.2.1. It seems there is some interaction
between the syntax of static pattern rules and conditional
functions that I do not quite understand.
The context: I have a project documented by a set of Markdown files, and
I would like to render these files to HTML in order to check them
locally. The directory structure should end up looking like this:
some_project/
├── README.md # entry page of documentation
├── doc/ # extra docs
│ ├── foo.md
│ ├── bar.md
│ └── ... # and some more
└── doc_html/ # HTML rendering of the docs
├── Makefile # the Makefile I am trying to write
├── index.html # rendered from README.md
├── foo.html # ............. doc/foo.md
├── bar.html # ............. doc/bar.md
└── ... # etc.
Without the special case of index.html, I could write something like:
%.html: ../doc/%.md
some list of commands $< $#
The problem is that the prerequisite of index.html (namely ../README.md)
does not match the pattern. I would like to handle this special case
without having to repeat the whole list of commands. This is what I have
so far:
DOC_PAGES = $(wildcard ../doc/*.md)
TARGETS = index.html $(patsubst %.md,%.html,$(notdir $(DOC_PAGES)))
# Function to find the source for page $(1)
source = $(if $(findstring index.html,$(1)), \
../README.md, \
$(patsubst %.html,../doc/%.md,$(1)) \
)
all: $(TARGETS)
$(TARGETS): %.html: $(call source,%.html)
#echo some list of commands $< $#
# Check the TARGETS variable and the `source' function
test:
#echo TARGETS = $(TARGETS)
#echo "source(index.html)" = $(call source,index.html)
#echo "source(foo.html)" = $(call source,foo.html)
My source function seems to work:
$ make test
TARGETS = index.html bar.html foo.html
source(index.html) = ../README.md
source(foo.html) = ../doc/foo.md
However, it doesn't behave properly in the static rule
$ make
make: *** No rule to make target '../doc/index.md', needed by 'index.html'. Stop.
Note that the rule does work if I remove index.html from $(TARGETS).
An idea of what I am doing wrong?

This line:
$(TARGETS): %.html: $(call source,%.html)
cannot work because you're expanding the source macro with an argument of the literal string %.html. You can't use patterns or automatic variables in macros in prerequisite lists: macros are expanded first, before parsing or expanding patterns.
However, it seems to me that you're making this way more complicated than it needs to be. If most of your targets are built one way but a few are built a different way, then just create a pattern rule for the "most" and write explicit rules for the "some":
%.html: ../doc/%.md
some list of commands $< $#
index.html : ../README.md
commands to build index.html
If the set of commands is identical and you don't want to repeat them, put them in a variable:
create_html = some list of commands $< $#
%.html: ../doc/%.md
$(create_html)
index.html : ../README.md
$(create_html)
(be sure to create the variable with = not := if you want to include $< and $# in the script).
ETA You asked about why things seemed to work: when make expands this it will substitute the literal string %.html. You can prove this to yourself by adding an $(info...) call like this:
source = $(info 1=$(1)) $(if $(findstring index.html,$(1)), \
../README.md, \
$(patsubst %.html,../doc/%.md,$(1)) \
)
and you'll see that it will print (one time only because the rule is only expanded once) 1=%.html.
What happens next? Well this means that your macro expands to this:
$(if $(findstring index.html,%.html), ../README.md, $(patsubst %.html,../doc/%.md,%.html))
(again, using the literal string %.html). The findstring always returns empty because index.html can't be found in the string %.html, so you expand the else-clause:
$(patsubst %.html,../doc/%.md,%.html)
Clearly %.html matches %.html with a stem of %, so the substitution is made and returns ../doc/%.md. So after all that your rule looks like this:
$(TARGETS): %.html: ../doc/%.md
#echo some list of commands $< $#
This exactly the same thing you had before with your simple pattern rule.

This would seem to be a solution:
DOC_PAGES := $(wildcard ../doc/*.md)
TARGETS := index.html $(patsubst %.md,%.html,$(notdir $(DOC_PAGES)))
all: $(TARGETS)
.INTERMEDIATE: ../doc/index.md
../doc/index.md: ../README.md
cp $< $#
%.html: ../doc/%.md
#echo some list of commands $< $#
test:
#echo $(TARGETS)
With output:
$ make
cp ../README.md ../doc/index.md
some list of commands ../doc/index.md index.html
some list of commands ../doc/bar.md bar.html
some list of commands ../doc/foo.md foo.html
rm ../doc/index.md
index.html looks for ../doc/index.md through the pattern rule, leading to the recipe copying ../README.md.
Prerequisites to the .INTERMEDIATE special target are removed when make completes. Optional.

Related

GNU Make how to make a static pattern rule for files that are not in the same directory?

I want to use make and create a static pattern rule that has the target in a output directory, and the prerequisite files are in the preceeding directory, and it has to work recursively.
I have a minimal example here:
.
├── anotherdir
│   ├── output
│   │   ├── source3.md
│   │   └── source4.md
│   ├── source3.json
│   └── source4.json
├── output
│   ├── source1.md
│   └── source2.md
├── source1.json
└── source2.json
I want to generate the output directories if they do not exist, and I want to generate *.md files from the *.json using make if they do not exist, or *.json is updated.
So far, I have the following Makefile:
SOURCE_FILES := $(shell find ./ -name "*.json")
OUTPUT_FILES := $(join $(addsuffix output/,$(dir $(SOURCE_FILES))), $(addsuffix .md,$(basename $(notdir $(SOURCE_FILES)))))
.PHONY: all
all: $(OUTPUT_FILES)
$(OUTPUT_FILES): %.md: %.json
mkdir -p $(dir $#)
# Command to create MD file from json file into the output directory here
The actual command to create the MD file from the json file doesn't matter here, because I have a script that I will call that will do this for me. The problem here, is that when I try to even run this at all, I get the following output:
> make all
make: *** No rule to make target 'anotherdir/output/source4.json', needed by 'anotherdir/output/source4.md'. Stop.
Obviously, source4.json is not in anotherdir/output, but rather, it's in the preceeding directory, which is just anotherdir. I don't know how to make it so that the pattern $(OUTPUT_FILES): %.md: %.json will match it properly.
Or is a static pattern rule not good here? I'm not sure what to do to fit my scenario.
EDIT: I tried to do something like this:
$(OUTPUT_FILES): %.md: $(join $(subst output,,$(dir %)), $(addsuffix .json,$(basename $(notdir %))))
and this doesn't work, I still get:
> make all
make: *** No rule to make target 'anotherdir/output/source4.json', needed by 'anotherdir/output/source4.md'. Stop.
Edit 2: to clarify, i start with the following files
.
├── anotherdir
│ ├── source3.json
│ └── source4.json
├── source1.json
└── source2.json
And then when i run make, i want it to generate the output folders like this
.
├── anotherdir
│ ├── output
│ │ ├── source3.md
│ │ └── source4.md
│ ├── source3.json
│ └── source4.json
├── output
│ ├── source1.md
│ └── source2.md
├── source1.json
└── source2.json
I want to use some kind of smart makefile syntax to pick up these files names without me hard coding it in myself. Hence, i looked at the documentation and saw that static pattern rules might be the solution that i want, except that i can't get the right prerequisite pattern down.
I would do it this way:
First, find the source files just as you did (with a small change to prevent the unsightly double-slash):
SOURCE_FILES := $(shell find . -name "*.json")
A pattern file would be nice, if we could use two wildcards at once, but Make can't quite do that. So I recommend using a template:
define template
TDIR := $(dir $(1))output
TARG := $$(TDIR)/$(notdir $(basename $(1))).md
$$(TARG): $(1)
mkdir -p $$#
#echo building $$# from $$<
# Command to create MD file from json file into the output directory here
endef
$(foreach SOURCE,$(SOURCE_FILES),$(eval $(call template,$(SOURCE))))
If this works, all that's left is to construct a list of output files, and a default rule that has all of them as prerequisites:
define template
TDIR := $(dir $(1))output
TARG := $$(TDIR)/$(notdir $(basename $(1))).md
OUTPUT_FILES += $$(TARG)
$$(TARG): $(1)
mkdir -p $$#
#echo building $$# from $$<
# Command to create MD file from json file into the output directory here
endef
all:
$(foreach SOURCE,$(SOURCE_FILES),$(eval $(call template,$(SOURCE))))
all: $(OUTPUT_FILES)
It isn't pretty, but it seems to work.
If it had not been proposed already in another answer I would have suggested foreach-eval-call. For completeness here are different solutions for GNU make (they may work also with other versions of make but I did not check):
Creating the output directories beforehand
If the output directories exist already you can refer to ../%.json in your pattern rule:
SOURCE_FILES := $(shell find . -name "*.json")
OUTPUT_FILES := $(join $(dir $(SOURCE_FILES)),\
$(patsubst %.json,output/%.md,$(notdir $(SOURCE_FILES))))
$(shell mkdir -p $(dir $(OUTPUT_FILES)))
.PHONY: all
all: $(OUTPUT_FILES)
%.md: ../%.json
: json2md $< -o $#
This may look strange but if you read carefully the Pattern match section of the GNU make manual you should quickly understand. The only constraint for this to work is that the output directories exist before make searches pattern rules that match the targets. If one does not exist make will complain that there is no eligible rule to build the target. This is the reason for the:
$(shell mkdir -p $(dir $(OUTPUT_FILES)))
at the beginning of the Makefile. Demonstration:
$ make
: json2md output/../source2.json -o output/source2.md
: json2md output/../source1.json -o output/source1.md
: json2md anotherdir/output/../source4.json -o anotherdir/output/source4.md
: json2md anotherdir/output/../source3.json -o anotherdir/output/source3.md
Using the secondary expansion
Secondary expansion gives you the possibility to use automatic variables in the list of prerequisites. The $$ are needed to escape the first expansion by make.
SOURCE_FILES := $(shell find . -name "*.json")
OUTPUT_FILES := $(join $(dir $(SOURCE_FILES)),\
$(patsubst %.json,output/%.md,$(notdir $(SOURCE_FILES))))
.PHONY: all
all: $(OUTPUT_FILES)
$(sort $(dir $(OUTPUT_FILES))):
mkdir -p $#
.SECONDEXPANSION:
$(OUTPUT_FILES): $$(patsubst %output,%,$$(#D))$$(basename $$(#F)).json | $$(dir $$#)
: json2md $< -o $#
Demonstration:
$ make
mkdir -p output/
mkdir -p anotherdir/output/
: json2md source2.json -o output/source2.md
: json2md source1.json -o output/source1.md
: json2md anotherdir/source4.json -o anotherdir/output/source4.md
: json2md anotherdir/source3.json -o anotherdir/output/source3.md
Note: instead of creating the output directories in the json-to-md rule (which has the drawback of creating them several times), I added them as order-only prerequisites and added a specific rule to create them.
Note: the sort function also removes duplicates.
Using recursive make
Here we invoke make (with always the same Makefile) recursively in each sub-directory (except output, of course). Each invocation handles only the local json files, which makes the paths of prerequisites and targets much simpler.
MF := $(realpath $(lastword $(MAKEFILE_LIST)))
SUB_DIRS := $(filter-out . ./output,$(shell find . -maxdepth 1 -type d))
SOURCE_FILES := $(filter-out $(SUB_DIRS),$(wildcard *.json))
OUTPUT_FILES := $(patsubst %.json,output/%.md,$(SOURCE_FILES))
.PHONY: $(SUB_DIRS) all
all: $(SUB_DIRS) $(OUTPUT_FILES)
$(OUTPUT_FILES): output/%.md: %.json | output
: json2md $< -o $#
output:
mkdir -p $#
$(SUB_DIRS):
$(MAKE) -C $# -f $(MF)
Demonstration:
$ make
make -C anotherdir -f /home/doe/json2md/Makefile
make[1]: Entering directory '/home/doe/json2md/anotherdir'
mkdir -p output
: json2md source4.json -o output/source4.md
: json2md source3.json -o output/source3.md
make[1]: Leaving directory '/home/doe/json2md/anotherdir'
mkdir -p output
: json2md source2.json -o output/source2.md
: json2md source1.json -o output/source1.md

In a Makefile, use patsubst and wildcard with subfolders and subfolder substitutions

I'm trying to use a makefile to convert some markdown files to html files. I'm trying to accomplish in a few lines that which I previously had a long python script doing.
In the simple example below, I would like to see this code:
build: $(patsubst src/pages/%.md, output/%.html, $(wildcard src/pages/*.md))
%.html: %.md
#echo $< to $#
and this output
src/pages/index.md to output/index.html
src/pages/about.md to output/about.html
src/pages/contact.md to output/contact.html
src/page/foo/bar.md to output/foo/bar.html
Instead, it says:
$ make build
make: *** No rule to make target 'output/index.html', needed by 'build'. Stop.
I'm missing something very basic here.
Consider the target output/index.html. The dependency...
%.html: %.md
will effectively expand to...
output/index.html: output/index.md
with $* equal to output/index. So make looks for output/index.md but can't find it -- hence the error message.
To get the correct pattern stem ($* == index) you need to add the base directories to the pattern rule...
output/%.html: src/pages/%.md
#echo $< to $#
EDIT 1:
If you're concerned about repeated hard-coded strings such as output and src/pages then you can always assign them to parameters...
OUTPUT_DIR := output
SOURCE_DIR := src/pages
build: $(patsubst $(SOURCE_DIR)/%.md, $(OUTPUT_DIR)/%.html, $(wildcard $(SOURCE_DIR)/*.md))
$(OUTPUT_DIR)/%.html: $(SOURCE_DIR)/%.md
#echo $< to $#
(Assuming that's what you meant by `optimization' in your comment.)

How to use make to build a static website into a dist directory

I have a website in an src directory that is several levels deep, containing html, markdown, less, coffee, png, jpg and other assets. I'd like to build my website into a dist directory using make. With build, I mean
converting markdown files to html
compiling coffee to js
compiling less to css
minifying html files
minifying js files (that where not compiled from coffee)
minifying css files (that where not compiled from less or sass)
preparing images (logo.png becomes logo#1x.png logo#2x.png logo#3x.png)
I have the following file. The cp statements will be replaced with the respective tools to do the transformation.
sources = $(shell find src -type f)
t1 := $(sources:.md=.html)
t2 := $(t1:.less=.css)
targets := $(t2:.coffee=.js)
targetdirs = $(dir $(targets))
all: $(targets)
%.html: %.md
cp $< $#
%.css: %.less
cp $< $#
%.js: %.coffee
cp $< $#
This creates outputs side by side. So src/index.md becomes src/index.html, src/assets/stylesheets/app.less becomes src/assets/stylesheets/app.css and src/assets/scripts/app.coffee becomes src/assets/scripts/app.js. What I'd like to do is change the make file such that it stores the output in the dist directory, so src/index.md is converted to dist/index.html, src/assets/stylesheets/app.less compiled to dist/assets/stylesheets/app.css and and src/assets/scripts/app.coffee becomes dist/assets/scripts/app.js.
So I changed the makefile as follows:
sources = $(shell find src -type f)
t0 := $(subst src/,dist/,$(sources:.md=.html))
t1 := $(t0:.md=.html)
t2 := $(t1:.less=.css)
targets := $(t2:.coffee=.js)
targetdirs = $(dir $(targets))
all: $(targets)
%.html: %.md
mkdir -p $(targetdirs)
cp $< $#
%.css: %.less
mkdir -p $(targetdirs)
cp $< $#
%.js: %.coffee
mkdir -p $(targetdirs)
cp $< $#
Now make fails with the following:
make: *** No rule to make target `dist/assets/scripts/app.js', needed by `all'. Stop.
Most examples I can find, is limited to a single directory, or compiles multiple source files into a single target.
How would one achieve this without knowing the contents of the source directory?
Environment:
GNU Make 3.81
OS X 10.11.1
In make pattern rules, the stems represented by the patterns in the target and prerequisite must match exactly. Take this rule:
%.html: %.md
mkdir -p $(targetdirs)
cp $< $#
If the target make wants to build is dist/index.html, then the stem is dist/index. So, it will look for the prerequisite dist/index.md. Which doesn't exist. So make ignores that pattern rule as not matching, and continues to look for more implicit rules that might match... and doesn't find any so it fails.
You have to fix your rules so that the change in directory is reflected in the pattern:
dist/%.html: src/%.md
mkdir -p $(#D)
cp $< $#
(I'm not sure why you're creating all directories in every recipe instead of just the current one). Now the stem for dist/index.html is just index, and the prerequisite matches src/index.md and it will work.

Makefile automatic variable changed by prerequisite

first time here. I am relatively new to makefiles. Here is my current makefile:
# Closure compiler php script path
closure = ../../cli/scripts/Compilers/closure.php
# Destination directory
dest = ../../static/js/
# Build directory
build = build/
# Tell "make" to search build and destination dirs
vpath %.o $(build)
vpath %.js $(dest)
all: main.js
#echo "Done.";
main.js: \
date.o \
jquery.autocomplete.o \
jquery.bullseye.o \
jquery.clickopen.o \
jquery.fbmodal.o \
jquery.helpers.o \
jquery.pulljson.o \
jquery.thumbrotate.o \
jquery.timefmt.o \
jquery.tools.o \
layout.main.o
cat $^ > $(dest)$#
%.o: %.js
php $(closure) $*.js $(build)$#
clean:
rm -rf $(build)*.o
rm -rf $(dest)*.js
The problem is with the following line:
cat $^ > $(dest)$#.
It is supposed to cat all the prerequisite objects (minified javascript) into one final js library. According to makefile docs, $^ is an automatic variable which contains a list of prerequisites with directories they are in. From my experience, it behaves differently depending on wether prerequisite needs to be compiled or not.
If prerequisite is up-to-date, this code works perfectly and $^ contains a list like:
build/date.o build/jquery.autocomplete.o build/jquery.bullseye.o....
However if prerequisite needs a fresh compile, then $^ gets directory part stripped and looks like:
date.o jquery.autocomplete.o jquery.bullseye.o
Only the file which needs a fresh compile gets directory part stripped.
I have managed to work around this issue by replacing
cat $^ > $(dest)$#
with
cat $(addprefix $(build), $(^F) ) > $(dest)$#.
I don't like it because:
It's a hack
$(^F) is semi-deprecated
I want to understand why make behaves like this.
thanks
Look here:
# Tell "make" to search build and destination dirs
vpath %.o $(build)
If Make is looking for foo.o, it will look in the local directory first. If it finds no foo.o there, it will look in $(build) (i.e. build/, and you might reconsider your variable names).
And how would Make build foo.o, if it couldn't find it anywhere? With this rule:
%.o: %.js
php $(closure) $*.js $(build)$#
This rule violates an important guideline of makefiles, in that the target (foo.o) is not the name of the thing actually built (build/foo.o).
Now consider what happens when Make tries to execute this rule:
main.js: date.o ...
cat $^ > $(dest)$#
So if date.o is up to date, it's in build/. Make finds it there, and the automatic variable $^ expands to build/date.o ...
But if date.o must be rebuilt, then Make looks to the %.o rule, which promises to build date.o (not build/date.o), so Make takes that rule at its word and $^ expands to date.o ...
There are several ways to solve this problem. I'd do something like this:
OBJS := date.o jquery.autocomplete.o jquery.bullseye.o ...
OBJS := $(addprefix $(build),$(OBJS))
$(dest)main.js: $(OBJS)
cat $^ > $#
# you might have to tinker with this rule a little
$(build)%.o: %.js
php $(closure) $< $#

Makefile to generate JSON from Python scripts in separate directories

I have a Makefile which generates JSON from several different Python scripts (the scripts print to stdout) in a single directory, e.g.
/src
scriptOne.py
scriptTwo.py
scriptThree.py
Which outputs the JSON to a folder:
/templates
scriptOne.json
scriptTwo.json
scriptThree.json
I'm trying to restructure so that, for example, each script is in its own subdirectory and the Makefile creates the JSON templates in their consequent subdirectories as follows:
/src
/importantTemplates
scriptOne.py
/notSoImportantTemplates
scriptTwo.py
scriptThree.py
And the output:
/templates
/importantTemplates
scriptOne.json
/notSoImportantTemplates
scriptTwo.json
scriptThree.json
The current Makefile is as follows:
SOURCES := $(shell echo src/*.py)
TARGETS := $(patsubst src/%.py,templates/%.json,$(SOURCES))
all: $(TARGETS)
clean:
rm -f $(TARGETS)
templates/%.json: src/%.py
python2 $< > $#
I've tried changing the wildcards to include a subdirectory for each line e.g. /src/*/*.py, although I just end up with the following:
make: Nothing to be done for `all'.
You want a static pattern rule (4.12 Static Pattern Rules) for this.
SOURCES := $(wildcard src/*/*.py)
TARGETS := $(patsubst src/%.py,templates/%.json,$(SOURCES))
all: $(TARGETS)
clean:
rm -rf templates
$(TARGETS) : templates/%.json: src/%.py
mkdir -p $(#D)
python2 $< > $#
You could avoid needing mkdir -p in that rule body if you wanted to (and go with an order-only prerequisite on the directory instead) but I'm not sure the effort is worth the savings in execution cost. You could avoid the extra shell by combining the two lines mkdir -p $(#D) && python2 $< > $# if you wanted to though.
Please note that the second time you run the make, it will give you the message (if there are no new files):
make: Nothing to be done for `all'.
Try to run make clean and see if you get the same message.
Here is the Makefile which will do what you want:
SOURCES := $(wildcard src/*/*.py)
TARGETS := $(patsubst src/%.py,templates/%.json,$(SOURCES))
FOLDERS := $(sort $(dir $(TARGETS)))
all: $(TARGETS)
clean:
rm -rf $(TARGETS) $(FOLDERS)
$(FOLDERS):
mkdir -p $#
$(TARGETS): $(SOURCES) $(FOLDERS)
python2 $< > $#
The FOLDERS variable will contain the folders you need to create in the template directory. (sort will remove duplicates, so each folder will be there only once)
The $(FOLDERS) rule will create the folders.
The clean rule will remove the folders also.
If you need to add more sources, just do it like this:
SOURCES := $(wildcard src/*/*.py)
SOURCES += $(wildcard src/*.py)
...

Resources