From 1f45d1dc09c65e25fe72ec8a0fb307d132ef20bc Mon Sep 17 00:00:00 2001 From: Midgard <2885-Midgard@users.noreply.framagit.org> Date: Thu, 11 Jun 2020 01:15:21 +0200 Subject: [PATCH] Improve make blogpost --- blog/2020-06-08-svg-makefile-slides.md | 94 ++++++++++++++++---------- 1 file changed, 59 insertions(+), 35 deletions(-) diff --git a/blog/2020-06-08-svg-makefile-slides.md b/blog/2020-06-08-svg-makefile-slides.md index 4c4e905..d4520d0 100644 --- a/blog/2020-06-08-svg-makefile-slides.md +++ b/blog/2020-06-08-svg-makefile-slides.md @@ -4,20 +4,19 @@ published: 2020-06-02 --- -Text-based slides are boring and not effective. -While preparing a presentation for my intermediary thesis' defence, I wanted to create something that would support my story better. -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 importing slide-sized graphics in a GUI, I thought a makefile would be a nice way to do the trick. +Text-based slides are boring and inefficient. +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. +Rather than fiddling with shapes and images in traditional office software, I knew I'd be less frustrated just using the SVG editor Inkscape. +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. -But they can do much more than that! +## File dependencies with makefile +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. -Traditionally makefiles are used for C programs, but there's no reason you can't use them for any build you want. -Even this website is built by a makefile! +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. Want to join me and write one together? -## Writing the makefile -Putting the name of our output file in variables will make it easier to change it later. +Putting the name of our output file in a variable will make it to change it later, if we'd ever want that. ```makefile OUTPUT = slides.pdf ``` @@ -32,46 +31,70 @@ SLIDES_PDF ::= $(SLIDES:%.svg=build/%.pdf) I love this next rule. 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. -**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 -build/%.pdf: %.svg build +build/%.pdf: %.svg 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. -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. -And if any slide was changed, the end result will be updated too. +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 PDF was updated, the end result will be, too. 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?** ```makefile $(OUTPUT): $(SLIDES_PDF) - pdfjoin $(SLIDES_PDF) -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 "$@" + pdfjoin $+ -o "$@" ``` 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 -.PHONY: clean - clean: 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]). 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: +**TODO verify up to date** +**TODO verify actually works** + ```makefile OUTPUT = slides.pdf @@ -86,20 +112,18 @@ SLIDES ::= $(sort $(wildcard *.svg)) SLIDES_PDF ::= $(SLIDES:%.svg=build/%.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 "$@" "$<" -build: - mkdir -p "$@" - .PHONY: clean clean: rm -rf "$(OUTPUT)" build/ ``` -**TODO: write about first rule being the default** **TODO: split into 2 posts?** ## Further help with *make*