Improve make blogpost
This commit is contained in:
parent
f10d9df62d
commit
1f45d1dc09
1 changed files with 59 additions and 35 deletions
|
@ -4,20 +4,19 @@ published: 2020-06-02
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Text-based slides are boring and not effective.
|
Text-based slides are boring and inefficient.
|
||||||
While preparing a presentation for my intermediary thesis' defence, I wanted to create something that would support my story better.
|
While preparing a presentation for my intermediary thesis' defence, I wanted to use graphics instead of words so my audience wouldn't have to read and listen at the same time.
|
||||||
I wanted to use graphics instead of words so the language centres of my dear audience's brains could focus on my speech rather than reading.
|
Rather than fiddling with shapes and images in traditional office software, I knew I'd be less frustrated just using the SVG editor Inkscape.
|
||||||
Rather than importing slide-sized graphics in a GUI, I thought a makefile would be a nice way to do the trick.
|
And a makefile would be nice to stitch the SVGs together to create a PDF file that I could use during my exposé.
|
||||||
|
|
||||||
Makefiles these days are commonly used as a shell script with multiple entry points.
|
## File dependencies with makefile
|
||||||
But they can do much more than that!
|
Makefiles are commonly used as a shell script with multiple entry points, but they can do much more than that!
|
||||||
You give it a filename, and it will build that file if its dependencies have been changed.
|
You give it a filename, and it will build that file if its dependencies have been changed.
|
||||||
Traditionally makefiles are used for C programs, but there's no reason you can't use them for any build you want.
|
Traditionally makefiles are used for C programs, but you can use them for absolutely any build where you have dependencies between files.
|
||||||
Even this website is built by a makefile!
|
Even this website is built by a makefile.
|
||||||
Want to join me and write one together?
|
Want to join me and write one together?
|
||||||
|
|
||||||
## Writing the makefile
|
Putting the name of our output file in a variable will make it to change it later, if we'd ever want that.
|
||||||
Putting the name of our output file in variables will make it easier to change it later.
|
|
||||||
```makefile
|
```makefile
|
||||||
OUTPUT = slides.pdf
|
OUTPUT = slides.pdf
|
||||||
```
|
```
|
||||||
|
@ -32,46 +31,70 @@ SLIDES_PDF ::= $(SLIDES:%.svg=build/%.pdf)
|
||||||
|
|
||||||
I love this next rule.
|
I love this next rule.
|
||||||
It teaches *make* that it should use Inkscape if it needs to create a PDF.
|
It teaches *make* that it should use Inkscape if it needs to create a PDF.
|
||||||
`$@` refers to the target (`build/….pdf`), and `$<` refers to the first dependency (`….svg`).
|
|
||||||
Inkscape's `--export-pdf filename` option exports to PDF without opening the GUI.
|
Inkscape's `--export-pdf filename` option exports to PDF without opening the GUI.
|
||||||
|
|
||||||
**TODO: isn't there a way to automatically depend on all parent directories?**
|
`$@` refers to the target (`build/….pdf`), and `$<` refers to the first dependency (`….svg`).
|
||||||
|
|
||||||
|
Do note, the indentation *must* be a tab; spaces are not allowed.
|
||||||
|
|
||||||
```makefile
|
```makefile
|
||||||
build/%.pdf: %.svg build
|
build/%.pdf: %.svg
|
||||||
inkscape --export-pdf "$@" "$<"
|
inkscape --export-pdf "$@" "$<"
|
||||||
```
|
```
|
||||||
|
|
||||||
Now we tell *make* that the final output can be created from the individual slides (`$(SLIDES_PDF)`) by calling pdfjoin.
|
Next we tell *make* that the final output can be created from the individual slides (`$(SLIDES_PDF)`) by calling pdfjoin.
|
||||||
|
|
||||||
*Make* will recursively make sure that all dependencies are up to date.
|
*Make* will recursively make sure that all dependencies are up to date.
|
||||||
Because we add the SVG source file as a dependency, it will know that the individual PDF files have to be rebuilt if their source was changed.
|
We already declared the corresponding SVG file as a dependency for each slide's PDF, so *make* will know that it should rebuild them if the SVG was changed.
|
||||||
And if any slide was changed, the end result will be updated too.
|
And if any PDF was updated, the end result will be, too.
|
||||||
But if there was no change, it won't; nothing is rebuilt unnecessarily!
|
But if there was no change, it won't; nothing is rebuilt unnecessarily!
|
||||||
|
|
||||||
**TODO: DRY**
|
`$+` means all prerequisites, retaining duplicate entries.
|
||||||
|
|
||||||
**TODO: Do we need to fix sh's space disease here?**
|
**TODO: Do we need to fix sh's space disease here?**
|
||||||
```makefile
|
```makefile
|
||||||
$(OUTPUT): $(SLIDES_PDF)
|
$(OUTPUT): $(SLIDES_PDF)
|
||||||
pdfjoin $(SLIDES_PDF) -o "$@"
|
pdfjoin $+ -o "$@"
|
||||||
```
|
|
||||||
|
|
||||||
If you look at the `build/%.pdf` rule, you see that it depends on `build`.
|
|
||||||
With this rule, *make* will know how to create this directory.
|
|
||||||
```makefile
|
|
||||||
build:
|
|
||||||
mkdir -p "$@"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
It is common to create a `clean` to clean up.
|
It is common to create a `clean` to clean up.
|
||||||
Since `clean` is not a file, we have to mark the target as "phony".
|
|
||||||
Failing to do so would result in **TODO how to finish this sentence?**
|
|
||||||
```makefile
|
```makefile
|
||||||
.PHONY: clean
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf "$(OUTPUT)" build/
|
rm -rf "$(OUTPUT)" build/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you now create a file `clean` and run `make clean`, *make* will quite contently say it doesn't need to do anything: the file `clean` exists and it depends on nothing else.
|
||||||
|
To avoid this, non-file targets must be marked as "phony".
|
||||||
|
```makefile
|
||||||
|
.PHONY: clean
|
||||||
|
```
|
||||||
|
|
||||||
|
# Creating directories
|
||||||
|
If you would use our makefile up to now, Inkscape would fail, complaining that `build/` doesn't exist.
|
||||||
|
This is to be expected, since directories have to be created before you can place files in them.
|
||||||
|
Much to my dismay, there seems to be no clean, standard way to create the necessary directories in *make*.
|
||||||
|
In my opinion, this is *make*'s single biggest shortcoming.
|
||||||
|
|
||||||
|
There [are a few ways](https://www.cmcrossroads.com/article/making-directories-gnu-make), none of them elegant.
|
||||||
|
For our simple makefile, we can just put `@mkdir -p $(@D)` in each rule.
|
||||||
|
It's not ideal for performance, **TODO and it might give race conditions if there are more directories and rules, should think how to word this and stuff when less tired**
|
||||||
|
You'll have noticed by default *make* prints the commands it executes; the `@` in front of this trivial command suppresses this.
|
||||||
|
`$(@D)` refers to the parent directory of the current target.
|
||||||
|
|
||||||
|
`$(OUTPUT)` is currently defined as `slides.pdf`, but we might change it to another directory later.
|
||||||
|
So just for good measure its rule too gets the special `mkdir` treatment.
|
||||||
|
|
||||||
|
```makefile
|
||||||
|
build/%.pdf: %.svg
|
||||||
|
@mkdir -p $(@D)
|
||||||
|
inkscape --export-pdf "$@" "$<"
|
||||||
|
|
||||||
|
$(OUTPUT): $(SLIDES_PDF)
|
||||||
|
@mkdir -p $(@D)
|
||||||
|
pdfjoin $+ -o "$@"
|
||||||
|
```
|
||||||
|
|
||||||
|
# The final makefile
|
||||||
One more thing: the default target, for when you call *make* without arguments, is the first one in the makefile (with [some caveats][gmake_docs_targets]).
|
One more thing: the default target, for when you call *make* without arguments, is the first one in the makefile (with [some caveats][gmake_docs_targets]).
|
||||||
You could override this with `.DEFAULT_GOAL := your_target_name_here`, but it's just as easy to reorder the rules, which is what I've done in the summary below.
|
You could override this with `.DEFAULT_GOAL := your_target_name_here`, but it's just as easy to reorder the rules, which is what I've done in the summary below.
|
||||||
|
|
||||||
|
@ -79,6 +102,9 @@ You could override this with `.DEFAULT_GOAL := your_target_name_here`, but it's
|
||||||
|
|
||||||
This is the finished makefile:
|
This is the finished makefile:
|
||||||
|
|
||||||
|
**TODO verify up to date**
|
||||||
|
**TODO verify actually works**
|
||||||
|
|
||||||
```makefile
|
```makefile
|
||||||
OUTPUT = slides.pdf
|
OUTPUT = slides.pdf
|
||||||
|
|
||||||
|
@ -86,20 +112,18 @@ SLIDES ::= $(sort $(wildcard *.svg))
|
||||||
SLIDES_PDF ::= $(SLIDES:%.svg=build/%.pdf)
|
SLIDES_PDF ::= $(SLIDES:%.svg=build/%.pdf)
|
||||||
|
|
||||||
$(OUTPUT): $(SLIDES_PDF)
|
$(OUTPUT): $(SLIDES_PDF)
|
||||||
pdfjoin $(SLIDES_PDF) -o "$@"
|
@mkdir -p $(@D)
|
||||||
|
pdfjoin $+ -o "$@"
|
||||||
|
|
||||||
build/%.pdf: %.svg build
|
build/%.pdf: %.svg
|
||||||
|
@mkdir -p $(@D)
|
||||||
inkscape --export-pdf "$@" "$<"
|
inkscape --export-pdf "$@" "$<"
|
||||||
|
|
||||||
build:
|
|
||||||
mkdir -p "$@"
|
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
rm -rf "$(OUTPUT)" build/
|
rm -rf "$(OUTPUT)" build/
|
||||||
```
|
```
|
||||||
|
|
||||||
**TODO: write about first rule being the default**
|
|
||||||
**TODO: split into 2 posts?**
|
**TODO: split into 2 posts?**
|
||||||
|
|
||||||
## Further help with *make*
|
## Further help with *make*
|
||||||
|
|
Loading…
Reference in a new issue