-
Notifications
You must be signed in to change notification settings - Fork 0
/
dtk-make-bossert.tex
301 lines (264 loc) · 12.5 KB
/
dtk-make-bossert.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
\documentclass[ngerman]{dtk}
\addbibresource{\jobname.bib}
\begin{document}
\title{Zur Nutzung von \texttt{makefile}-Dateien}
\Author{Lukas C.}{Bossert}%
{Cranachstr. 24\\
12157 Berlin\\
\Email{[email protected]}}
\maketitle
\begin{abstract}
Größere \LaTeX -Projekte mit vielen Dateien zu managen ist nicht immer einfach
und man muss nach dem Übersetzen verschiedene Schritte ggf. manuell ausführen und das PDF weiter verarbeiten.
Beispielsweise wenn man das PDF zusätzlich noch in einer komprimierten
Fassung haben möchte oder wissen muss,
auf welchen Seiten Farbinformationen im PDF
hinterlegt sind.
Die folgenden Ausführungen beziehen sich auf praxisnahe Funktionsweisen des Programms \texttt{GNUmake} (Unix/macOS).
Für andere Betriebssysteme gibt es entsprechende Varianten (bspw. für Windows \texttt{nmake}).
\end{abstract}
Mittels einer \texttt{makefile}-Datei und dem Programm \texttt{make} können mehrere Befehle gleichzeitig und/oder
hintereinander ausgeführt werden,
sodass verschiedene händische Arbeitsschritte abgenommen werden können.\footnote{Dieser Beitrag stellt eine Ergänzung zu
\citetitle{dtk01.1:niepraschk:make} \parencite{dtk01.1:niepraschk:make} dar,
da es hierbei um konkrete Beispiele (m)eines \LaTeX -Alltags geht.}
Zunächst erläutere ich die Eigenschaften und den Aufbau einer \texttt{makefile}-Datei.
Anschließend zeige ich an kleinen Beispielen,
worin das Potenzial dieser unscheinbaren Datei liegt.
Mir ist weniger daran gelegen,
die (durchaus komplexe) Logik bei den Abhängigkeiten von \enquote{Ziel}
und \enquote{Quelle} (s.\,u.) zu durchdringen \parencite{peschel-findelsen}
als vielmehr einen praktisch orientierten Einblick zu geben.
\section{Der Aufbau einer minimalen \texttt{makefile}-Datei}
Eine \texttt{makefile}-Datei ist eine schlichte Textdatei \emph{ohne} Endung.
Sie liegt idealerweise im gleichen Ordner wie die Hauptdatei des \LaTeX -Projekts.
Sie kann mit Variablen arbeiten und man kann alle Befehle ausführen lassen,
die man auch im Terminal eingeben kann.
Dies sind die zwei wichtigsten Merkmale,
die wir gleich nutzen werden.
Zunächst definieren wir die Variable \texttt{PROJECT},
die den Dateinamen der Hauptdatei unseres \LaTeX -Projekts beinhaltet.
\begin{lstlisting}[style=number,language=make]
PROJECT = dtk-make-bossert
\end{lstlisting}
Nun wollen wir den Arbeitsschritt zum Erstellen der PDF einbauen.
\begin{lstlisting}[style=number,language=make]
all:
lualatex $(PROJECT)
\end{lstlisting}
Mit \texttt{all} wird ein \enquote{Ziel} angegeben.
In diesem Fall ist es die Standardausführung,
wenn keine weiteren Angaben beim Ausführen der
\texttt{makefile}-Datei gemacht werden.
Alle folgenden Zeilen, die zu diesem \enquote{Ziel} gehören,
werden mit einem Tab eingerückt.
Mit \texttt{lualatex \$(PROJECT)} wird die oben definierte
Variable aufgerufen, sodass \texttt{lualatex dtk-make-bossert}
ausgeführt wird.
In der Reinform sieht ein Befehl also in etwa so aus.
\begin{lstlisting}[style=noNumber]
Ziel: Quelle(, ..., Quelle)
Befehl1
Befehl2
.
\end{lstlisting}
Um die \texttt{makefile}-Datei auszuführen,
navigiert man im Terminal zum Hauptordner des \LaTeX -Projects
und führt lediglich den Befehl \texttt{make} aus.
\section{Weitere Variablen und Arbeitsanweisen in der \texttt{makefile}-Datei}
Nach dieser kurzen Einführung können wir verschiedene \enquote{Ziele}
basteln, um sie bei Bedarf oder immer ausführen zu lassen.
Es empfielt sich anzugeben,
wo \texttt{make} die Shell findet.
Dies erfolgt mit einer Variable.
\begin{lstlisting}[style=number,language=make]
SHELL = bash
\end{lstlisting}
Anschließend führen wir noch ein paar Farben ein,
um die Lesbarkeit der Informationsdarstellung zu erhöhen.
\begin{lstlisting}[style=number,language=make]
# Colors
RED = \033[0;31m
CYAN = \033[0;36m
NC = \033[0m # No color
echoPROJECT = @echo -e "$(CYAN) <$(PROJECT)>"
\end{lstlisting}
Die letzte Variable gibt im Terminal den Projektnamen farblich aus.
Jetzt kommt noch die Definition von \texttt{PHONY}-Zielen \parencite[13--15]{gnu-make}.
Anhand dieser Wortliste weiß \texttt{make},
dass es sich hierbei nicht um Dateinamen handelt,
sondern um auszuführende \enquote{Ziele}.
\begin{lstlisting}[style=number,language=make]
.PHONY: all article zip
\end{lstlisting}
Als erstes \enquote{Ziel} definieren wir die Erstellung des Artikels,
wofür wir eine weitere Variable nutzen, die das aktuelle Datum abruft.
\begin{lstlisting}[style=number,language=make]
DATE = $(shell /bin/date "+%Y-%m-%d")
\end{lstlisting}
Jetzt das \enquote{Ziel} selbst.
\begin{lstlisting}[style=number,language=make]
article:
$(echoPROJECT) "* compiling article * $(NC)"
latexmk -lualatex -quiet -f -cd -view=pdf -output-directory=tmp $(PROJECT).tex
@cp tmp/$(DATE)/$(PROJECT).pdf .
$(echoPROJECT) "* article compiled * $(NC)"
\end{lstlisting}
Als erstes soll im Terminal angezeigt werden, welches \enquote{Ziel}
von \texttt{make} gerade ausgeführt wird (Z.2) bzw. abgeschlossen wurde (Z.6).
Anschließend wird das PDF mittels \texttt{latexmk} erstellt, wozu weitere Optionen angegeben sind:
Um den Hauptordner von allen temporären Dateien frei zu halten,
werden diese in ein separates Verzeichnis erstellt.
Das PDF wird schließlich in den Hauptordner kopiert (Z. 5).
Mit dem Präfix \texttt{@} wird die auszuführende Befehlszeile nicht
im Terminal angezeigt, lediglich deren Result.
Mit \texttt{make article} lässt sich diese Passage direkt ansteuern und ausführen.
Besonders bei bildlastigen PDFs ist deren Dateigröße manchmal auch zu groß,
um sie für Korrekturen etc. zu verschicken.
Das PDF muss dann in einem weiteren Schritt komprimiert werden.
Dieser Vorgang lässt sich ebenfalls von \texttt{make} mittels Ghostscript ausführen.\footnote{Zu den einzelnen Optionen des Ghostscriptbefehls s. XXX}
Das \enquote{Ziel} ist \texttt{minimize} und als \enquote{Quelle}
geben wir das oben formulierte \enquote{Ziel} \texttt{article} an.
Das heißt, dass beim Aufruf von \texttt{minimize} zuerst das
\enquote{Ziel} \texttt{article} ausgeführt wird -- Dank \texttt{latexmk} wird nur
bei veränderter \texttt{tex}-Datei neu übersetzt.
Somit wird gewährleistet, dass immer die neuste PDF-Version
minimiert wird.
\begin{lstlisting}[style=number,language=make]
minimize: article
$(echoPROJECT) "* minimizing article * $(NC)"
@-mkdir archive
@rm -f archive/$(PROJECT)-$(DATE)*.pdf
gs \
-sDEVICE=pdfwrite \
-dCompatibilityLevel=1.4 \
-dPDFSETTINGS=/printer \
-dNOPAUSE \
-dQUIET \
-dBATCH \
-sOutputFile=archive/$(PROJECT)-$(VERS).pdf \
$(PROJECT).pdf
$(echoPROJECT) "* article minimized * $(NC)"
\end{lstlisting}
Zunächst wird ein Ordner \texttt{archive} erstellt (Z. 3).
Sollte dieser Ordner bereits existieren,
wirft \texttt{make} zwar einen Fehler,
dieser wird jedoch dank des vorangestellten \texttt{-} bei \texttt{mkdir} nicht zum Abbruch des Skripts führen.
In Zeile 4 wird ggf. eine ältere PDF gelöscht.
Das PDF wird nun mit Ghostscript komprimiert (Z. 5\,ff.)
und mit Datumsangabe im Dateinamen im Ordner \texttt{archive} abgelegt.
Um auch zugleich den Status quo des \LaTeX -Projects festzuhalten,
kann man alle notwendige Dateien tagesaktuell zippen.
Somit hat man immer den letzten Tagesstand im Ordner \texttt{archive} gesichert.
Dafür bedarf es noch ein paar Variablen,
die wir vorweg definieren.
\begin{lstlisting}[style=number,language=make]
# zip
PWD = $(shell pwd)
TEMP := $(shell mktemp -d -t tmp.XXXXXXXXXX)
TDIR = $(TEMP)/$(PROJECT)
VERS = $(shell /bin/date "+%Y-%m-%d---%H-%M-%S")
DATE = $(shell /bin/date "+%Y-%m-%d")
\end{lstlisting}
Das \enquote{Ziel} heißt \texttt{zip} und es wird wiederum
zuerst \texttt{article} ausgeführt,
um die aktuelle Projektversion zu zippen.
\begin{lstlisting}[style=number,language=make]
zip: article
$(echoPROJECT) "* zipping files * $(NC)"
@-mkdir archive
@rm -f archive/$(PROJECT)-$(DATE)*.zip
@mkdir $(TDIR)
@cp $(PROJECT).{bib,tex,pdf,csv} README.md makefile $(TDIR)
@cd $(TEMP); \
zip -Drq $(PWD)/archive/$(PROJECT)-$(VERS).zip $(PROJECT)
$(echoPROJECT) "* files zipped * $(NC)"
\end{lstlisting}
In Zeile 3 wird wiederum zuerst ein Ordner \texttt{archive} erstellt,
in den später die gezippte Datei abgelegt wird.
Mit Zeile 4 wird die tagesaktuelle Datei gelöscht,
sodass für jeden Tag immer nur eine und die letzte Version in \texttt{archive} abgelegt wird.
In den folgenden Zeilen wird der Packvorgang ausgeführt,
zunächst erfolgt die Erstellung eines temporären Ordners,
anschließend werden die zu zippenden Dateien ausgewählt
und schließlich die \texttt{zip}-Datei im Ordner \texttt{archive} abgelegt.
In Zeile 6 ist eine sehr effiziente Syntax von \texttt{make} eingebaut:
\begin{lstlisting}[style=noNumber,language=make]
$(PROJECT).{bib,tex,pdf,csv}
\end{lstlisting}
Dies ist gleichbedeutend mit
\begin{lstlisting}[style=noNumber,language=make]
$(PROJECT).bib $(PROJECT).tex $(PROJECT).pdf $(PROJECT).csv
\end{lstlisting}
Die kommaseparierten Werte in den geschweiften Klammern
werden expandiert und mit (in diesem Fall) dem Präfix gekoppelt.
Damit erspart man sich manche Tipparbeit.\footnote{In diesem konkreten Fall wäre \$\texttt{(PROJECT).*} einfacher gewesen, würde aber die spezielle Syntax nicht zeigen.}
Möchte man sein PDF an eine Druckerei geben,
braucht man die genaue Anzahl der Farbseiten plus der Auflistung der Farben.
Es wäre fatal (und unnötig), dies bei größeren PDFs von Hand zu tun.
Folgende Code gibt eine Tabulator getrennte \texttt{csv}-Datei
mit der prozentualen Farbabdeckung von jeder Seite.\footnote{\url{https://stackoverflow.com/a/28369599}}
Damit kann man sehr leicht erkennen,
ob das CMYK-Farbmodell korrekt ist und auf welchen Seiten
\textcolor{cyan}{Cyan},
\textcolor{magenta}{Magenta} oder
\textcolor{yellow}{Gelb} (CMY) verwendet wird.
\begin{lstlisting}[style=number,language=make]
count.colorpages:
$(echoPROJECT) "* listing and counting colored pages * $(NC)"
@echo "Meta information about colors in $(PROJECT)"
@gs -o - -sDEVICE=inkcov $(PROJECT).pdf \
| tail -n +5 \
| sed '/^Page*/N;s/\n//' \
| tee $(PROJECT).csv
@echo -n "List of pages with colors: "
@cat $(PROJECT).csv \
| awk '$$3!="0.00000" || $$4!="0.00000" || $$5!="0.00000"{if(length(colored))colored=colored","$$2;else colored=$$2} END{print colored}' \
| tee -a $(PROJECT).csv
@echo -n "Total amount of pages with color: "
@gs -o - -sDEVICE=inkcov $(PROJECT).pdf \
| grep -v "^ 0.00000 0.00000 0.00000" \
| grep "^ " \
| wc -l \
| sed 's/[[:space:]]//g' \
| tee -a $(PROJECT).csv
$(echoPROJECT) "* colored pages listed and counted * $(NC)"
\end{lstlisting}
In Zeile 3 rufen wir Ghostscript auf lassen die Farbabdeckung jeder Seite ausgeben.
Anschließend (Z. 4) werden die ersten fünf Zeilen dieser Liste gelöscht
(es sind für unser Vorhaben nicht notwendige Metadaten),
und schließlich (Z. 5) ein unschöner Absatz entfernt,
sodass in Zeile 6 das Speichern einer \texttt{csv}-Datei ausgeführt wird.
Schließlich werden alle Farbseiten
kommaseparierte aufgelistet und ebenfalls in der \texttt{csv}-Datei ergänzt (Z.8--11).
Mit dem nochmaligen Aufruf von Ghostscript in Zeile 12 und
der direkten Weiterverarbeitung im Suchen/Ersetzen-Prinzip
(\texttt{grep}),
wird die Gesamtzahl der Farbseiten ermittelt.
Diese Zahl wird in die letzte Zeile der \texttt{csv}-Datei geschrieben.
Die \texttt{csv}-Datei mit der Liste der Farbseiten für diesen Artikel sieht dann so aus.
\lstinputlisting[%
style = number
]{{dtk-make-bossert.csv}}
\section{Der Einsatz von \texttt{make}}
Damit haben wir nun ein paar hilfreiche \enquote{Ziele} und
Vorgehensweisen kennengelernt,
die wir nun in eine \texttt{makefile}-Datei schreiben.\footnote{Diese \texttt{makefile}-Datei ist auch online verfügbar: \url{https://github.com/LukasCBossert/dtk-make/blob/master/makefile}.}
\lstinputlisting[%
language = make,
style = number
]{{makefile}}
Wie bereits erwähnt, wird das \enquote{Ziel} \texttt{all} ausgeführt (weil es an erste Stelle steht),
wenn man im Terminal lediglich \texttt{make} eingibt.
In unserer Datei werden alle \enquote{Ziele} nun standardmäßig ausgeführt.
% Um Rechenzeit zu sparen, laufen die \enquote{Ziele} \texttt{minimize},
% \texttt{zip} und \texttt{count.colorpages} als Sub-Prozesse ab,
% sobald die fertige PDF des \LaTeX -Projekts in den
% Hauptordner kopiert wurde.
Möchte man hingegen nur ein bestimmtes \enquote{Ziel} ausführen,
kann man dieses mit \texttt{make <ZIEL>} direkt ansteuern,
beispielsweise \texttt{make zip}.
Diese \texttt{makefile}-Datei lässt sich nun nach Belieben ergänzen und verändern,
um auch Projekt spezifische Anforderungen in der Nachbearbeitung effizient zu bearbeiten.
\printbibliography
\end{document}