Cower all bases
[llpp.git] / build.bash
blob41c2397a42a90ef49dd27df6359e00c15687cad1
1 #!/bin/bash
2 set -eu
4 vecho() { ${vecho-:} "$@"; }
5 executable_p() { command -v "$1" >/dev/null 2>&1; }
6 dgst='cksum "$@" | while read d _; do printf $d; done'
7 ! executable_p b3sum || dgst='b3sum --no-names "$@"'
8 executable_p realpath || realpath() (cd "$1" &>/dev/null; pwd -P)
9 eval "digest() { $dgst; } 2>/dev/null"
10 die() { echo "$@" >&2; exit 111; }
11 trap 'test $? -eq 0 || echo "build failed"' EXIT
13 darwin=false
14 wsid="wsi/x11"
15 clip="LC_CTYPE=UTF-8 xclip -i"
16 paste="LC_CTYPE=UTF-8 xclip -o"
17 uopen="echo 'Open "%s"' >&2"
18 print="echo 'Print "%s"' >&2"
19 mjobs=$(getconf _NPROCESSORS_ONLN || echo 1)
20 case "$(uname)" in
21 Darwin)
22 darwin=true
23 wsid="wsi/cocoa"
24 clip="LC_CTYPE=UTF-8 pbcopy"
25 paste="LC_CTYPE=UTF-8 pbaste"
26 uopen='open "%s"';;
27 Linux) ;;
28 *) die $(uname) is not supported;;
29 esac
31 test -n "${1-}" || die "usage: $0 build-directory"
33 outd=$1
34 srcd=$(dirname $0)
35 mudir=$outd/mupdf
36 muinc="-I $mudir/include -I $mudir/thirdparty/freetype/include"
38 test -d $mudir || die muPDF wasn\'t found in $outd/, consult $srcd/BUILDING
40 mkdir -p $outd/{$wsid,lablGL}
42 # thanks to Sebastian Rasmussen
43 isfresh() { test "$(cat $1.past)" = "$2"; } 2>/dev/null
45 mbt=${mbt:-release}
46 test -n "${gmk:-}" && gmk=false || gmk=true
48 mulibs="$mudir/build/$mbt/libmupdf.a $mudir/build/$mbt/libmupdf-third.a"
49 make="make -C "$mudir" build=$mbt -j $mjobs libs"
50 $make -q -s || $make
52 oincs() {
53 local b=$1 incs
54 case "${2#$outd/}" in
55 $wsid/wsi.cm[oi]|confstruct.cmo|help.cmo) incs="-I $b -I $b/$wsid";;
56 glutils.cmo) incs="-I $b -I $b/lablGL";;
57 uiutils.cmo|main.cmo) incs="-I $b -I $b/$wsid -I $b/lablGL";;
58 ffi.cmo|help.cmi|parser.cmo) incs="-I $b";;
59 config.cmo)
60 incs="-I $b -I $b/$wsid"
61 test "$b" = $outd || incs="$incs -I $outd"
63 lablGL/*) incs="-I $b/lablGL";;
64 main.cmo|keys.cmo|utils.cmo|utf8syms.cmo) incs="-I $b";;
65 config.cmi) incs="-I $outd -I $b -I $b/$wsid";;
66 uiutils.cmi|ffi.cmi) incs="-I $b";;
67 glutils.cmi) incs="-I $b/lablGL";;
68 main.cmi|keys.cmi|utils.cmi|utf8syms.cmi|parser.cmi) ;;
69 *) die "ocaml include paths for '$2' aren't set";;
70 esac
71 test -z "${incs-}" || echo $incs
74 oflags() {
75 local f=""
76 case "${1#$outd/}" in
77 lablGL/*) f="-g";;
78 utf8syms.cmo|confstruct.cmo)
79 f="-g -strict-sequence -strict-formats -alert @all-missing-mli";;
80 wsi/x11/wsi.cm[io]) f="-g -I +unix";;
81 utils.cmi) f="-g -I +unix -I +str";;
82 config.cmo|ffi.cmo|main.cmo|utils.cmo|parser.cmo|uiutils.cmo|help.cmo)
83 f="-g -I +unix -I +str -strict-sequence -strict-formats -alert @all-missing-mli";;
84 *) f="-g -strict-sequence -strict-formats -alert @all -warn-error @A";;
85 esac
86 echo $(oincs $outd $1) $f
89 cflags() {
90 case "${1#$outd/}" in
91 version.o) f=-DLLPP_VERSION=$ver;;
92 lablGL/*.o) f="-g -Wno-pointer-sign -Werror -O2";;
93 link.o)
94 f="-g -std=c11 $muinc -Wall -Werror -Wextra -pedantic "
95 test "${mbt-}" = "debug" || f+="-O2 "
96 $darwin && f+="-DMACOS -D_GNU_SOURCE -DGL_H='<OpenGL/gl.h>'" \
97 || f+="-D_POSIX_C_SOURCE -DGL_H='<GL/gl.h>'"
98 f+=" -DTEXT_TYPE=GL_TEXTURE_RECTANGLE_ARB"
99 #f+=" -DLLPARANOIDP"
100 #f+=" -DTEXT_TYPE=GL_TEXTURE_2D"
102 *) f="-g -O2 -Wall -Werror";;
103 esac
104 ! $darwin || f+=" -DGL_SILENCE_DEPRECATION"
105 echo $f
108 mflags() {
109 echo "-I $(ocamlc -where) -g -Wall -Werror -O2 -DGL_SILENCE_DEPRECATION"
112 overs=$(ocamlc -vnum 2>/dev/null) || overs=""
113 if test "$overs" != "5.2.0"; then
114 url="https://caml.inria.fr/pub/distrib/ocaml-5.2/ocaml-5.2.0.tar.xz"
115 txz=$outd/$(basename $url)
116 keycmd="printf $url; digest $txz;"
117 isfresh $txz "$(eval $keycmd)" || {
118 if executable_p wget; then dl() { wget "$1" -O "$2"; }
119 elif executable_p curl; then dl() { curl -L "$1" -o "$2"; }
120 else die "no program to fetch remote urls found"
122 dl $url $txz
123 eval $keycmd >$txz.past
124 } && vecho "fresh $txz"
125 absprefix=$(realpath $outd)
126 export PATH=$absprefix/bin:$PATH
127 ocamlc=$absprefix/bin/ocamlc
128 keycmd="printf $url; digest $ocamlc;"
129 isfresh $ocamlc "$(eval $keycmd)" || (
130 # This will needlessly re{configure,make} ocaml since "past"
131 # of configure/make is hard to ascertain. "Better safe than
132 # sorry" approach is taken here. The check will work for a
133 # single ocaml url/version, but _will_ redo _everything_
134 # otherwise (even if fully built artifacts are available)
135 tar xf $txz -C $outd
136 bn=$(basename $url)
137 cd $outd/${bn%.tar.xz}
138 ./configure --disable-ocamldoc --disable-ocamltest \
139 --enable-debugger=no --prefix=$absprefix
140 make -j $mjobs world
141 make install
142 eval $keycmd >$absprefix/bin/ocamlc.past
143 ) && vecho "fresh ocamlc"
144 overs=$(ocamlc -vnum 2>/dev/null)
147 while read k v; do
148 case "$k" in
149 "bytecomp_c_compiler:") ccomp=${CAML_CC-$v};;
150 "word_size:") ! test "$darwin$v" = "true32" || die "need 64bit ocaml";;
151 esac
152 done < <(ocamlc -config)
154 read cvers < <($ccomp --version)
156 seen=
157 ord=
158 $gmk || :>$outd/Makefile
159 bocaml1() {
160 local n=$1 s=$2 o=$3 deps= cmd d
161 local keycmd="digest $s $o.depl"
162 cmd="ocamlc -depend -bytecode -one-line $(oincs $srcd $o) $s"
164 isfresh "$o.depl" "$overs$cmd$(eval $keycmd)" || {
165 read _ _ depl < <(eval $cmd) || die "$cmd failed"
166 for d in $depl; do
167 if test "$d" = "$outd/confstruct.cmo";
168 then d=confstruct.cmo; else d=${d#$srcd/}; fi
169 deps+="$d\n"
170 done
171 printf "$deps" >$o.depl
172 deps=
173 echo "$overs$cmd$(eval $keycmd)" >"$o.depl.past"
174 } && vecho "fresh $o.depl"
176 # this saves time but is overly optimistic as interface (dis)
177 # appearance will result in an invalid (stale) .depl (cache). not
178 # using a cache is correct but slow(er (much)) way to handle this.
179 while read d; do
180 bocaml $d $((n+1))
181 deps+=" $outd/$d"
182 done <$o.depl
184 cmd="ocamlc $(oflags $o) -c -o $o $s"
185 keycmd="digest $o $s $deps"
186 isfresh "$o" "$overs$cmd$(eval $keycmd)" || {
187 printf "%*.s%s\n" $n '' "${o#$outd/}"
188 eval "$cmd" || die "$cmd failed"
189 echo "$overs$cmd$(eval $keycmd)" >"$o.past"
190 } && vecho "fresh $o"
191 $gmk || printf "$o: $deps\n\t%s\n" "$cmd" >>$outd/Makefile
192 seen+=$o
193 ord+=" $o"
196 cycle=
197 bocaml() {
198 [[ ! $seen =~ $1 ]] || return 0
199 local s o=$1 n=$2 cycle1=$cycle
200 case $o in
201 confstruct.cmo) s=$outd/confstruct.ml;;
202 *.cmo) s=$srcd/${o%.cmo}.ml;;
203 *.cmi) s=$srcd/${o%.cmi}.mli;;
204 esac
205 o=$outd/$o
206 [[ "$cycle" =~ "$o" ]] && die cycle $o || cycle=$cycle$o
207 bocaml1 $n $s $o
208 cycle=$cycle1
211 baux() {
212 local o=$1 cmd=$2
213 read 2>/dev/null _ d <$o.dep || d=
214 local keycmd='digest $o $d'
215 isfresh "$o" "$cvers$cmd$(eval $keycmd)" || {
216 echo "${o#$outd/}"
217 eval "$cmd" || die "$cmd failed"
218 read _ d <$o.dep
219 echo "$cvers$cmd$(eval $keycmd)" >"$o.past"
220 } && vecho "fresh $o"
221 $gmk || printf "$o: $d\n\t$cmd\n" >>$outd/Makefile
224 bocamlc() {
225 local o=$outd/$1 s=$srcd/${1%.o}.c cc=${CAML_CC:+-cc "'$CAML_CC'" }
226 baux $o "ocamlc $cc-ccopt \"$(cflags $o) -MMD -MF $o.dep -MT_\" -o $o -c $s"
229 bobjc() {
230 local o=$outd/$1
231 baux $o "$mcomp $(mflags $o) -MD -MF $o.dep -MT_ -c -o $o $srcd/${1%.o}.m"
234 ver=$(cd $srcd && git describe --tags --dirty) || ver="'built on $(date)'"
236 gen=$srcd/genconfstruct.sh
237 out=$outd/confstruct.ml
238 cmd="(export print paste clip uopen; . $gen >$out)"
239 keycmd="{ echo '$print $paste $clip $uopen'; digest $gen $out; }"
240 isfresh "$out" "$cmd$(eval $keycmd)" || {
241 echo "generating $out"
242 eval "$cmd" || die $gen failed
243 echo "$cmd$(eval $keycmd)" > "${out}.past"
244 } && vecho "fresh $out"
246 shift 1
247 for target; do
248 case "$target" in
249 doc)
250 md=$outd/doc
251 mkdir -p $md
252 for m in llpp llppac; do
253 src=$srcd/adoc/$m.adoc
254 o=$md/$m.1
255 conf=$srcd/man/asciidoc.conf
256 keycmd="digest $o $src $conf"
257 cmd="a2x -f manpage -D $md $src"
258 isfresh "$o" "$cmd$(eval $keycmd)" || {
259 echo "${o#$outd/}"
260 eval "$cmd" || die "$cmd failed"
261 echo "$cmd$(eval $keycmd)" >"$o.past"
262 } && vecho "fresh $o"
263 done;
264 exit;;
265 *) die "no such target - '$target'";;
266 esac
267 done
269 flatten() {
270 local o
271 [[ ! "$seen" =~ "$1" ]] || return 0
272 bocaml $1 0
273 for o in $ord; do
274 local wooutd=${o#$outd/}
275 case $o in
276 *.cmi) flatten ${wooutd%.cmi}.cmo;;
277 *.cmo) flatten $wooutd;;
278 esac
279 done
281 flatten main.cmo
283 modules=
284 collectmodules() {
285 # it might appear that following can be done inside bocaml* but
286 # alas due to the early cmi->cmo descent this ought to be done
287 # here (at least the solution inside bocaml* eludes me)
288 local dep cmo this=$1
289 while read dep; do
290 case $dep in
291 *.cmi)
292 cmo=${dep%.cmi}.cmo
293 test $cmo = $this || collectmodules $cmo
295 *.cmo)
296 collectmodules $dep
297 cmo=$dep
299 esac
300 [[ $modules =~ $cmo ]] || modules+=" $outd/$cmo"
301 done <$outd/$1.depl
303 collectmodules main.cmo
305 cobjs=
306 for m in link cutils version; do
307 bocamlc $m.o
308 cobjs+=" $outd/$m.o"
309 done
310 for m in ml_gl ml_glarray ml_raw; do
311 bocamlc lablGL/$m.o
312 cobjs+=" $outd/lablGL/$m.o"
313 done
315 libs="str.cma unix.cma"
316 clibs="-L$mudir/build/$mbt -lmupdf -lmupdf-third -lpthread"
317 if $darwin; then
318 mcomp=$ccomp
319 clibs+=" -framework Cocoa -framework OpenGL"
320 cobjs+=" $outd/wsi/cocoa/cocoa.o"
321 bobjc wsi/cocoa/cocoa.o
322 else
323 clibs+=" -lGL -lX11"
324 cobjs+=" $outd/wsi/x11/keysym2ucs.o $outd/wsi/x11/xlib.o"
325 bocamlc wsi/x11/keysym2ucs.o
326 bocamlc wsi/x11/xlib.o
329 cmd="ocamlc -custom $libs -o $outd/llpp $cobjs $modules -cclib \"$clibs\""
330 cmd="$cmd -I +unix -I +str"
331 keycmd="digest $outd/llpp $cobjs $modules $mulibs"
332 isfresh "$outd/llpp" "$cmd$(eval $keycmd)" || {
333 echo linking $outd/llpp
334 eval "$cmd" || die "$cmd failed"
335 echo "$cmd$(eval $keycmd)" >"$outd/llpp.past"
336 } && vecho "fresh llpp"
337 $gmk || printf "$outd/llpp: $cobjs $modules $mulibs\n\t$cmd\n" >>$outd/Makefile
339 if $darwin; then
340 out="$outd/llpp.app/Contents/Info.plist"
341 keycmd="digest $out $srcd/wsi/cocoa/genplist.sh; echo $ver"
342 isfresh $out "$(eval $keycmd)" || {
343 d=$(dirname $out)
344 mkdir -p "$d"
345 echo "generating $out"
346 (. $srcd/wsi/cocoa/genplist.sh) >"$out"
347 eval $keycmd>"$out.past"
348 } && vecho "fresh plist"
350 out=$outd/llpp.app/Contents/MacOS/llpp
351 keycmd="digest $out $outd/llpp"
352 isfresh $out "$(eval $keycmd)" || {
353 echo "bundling $out"
354 mkdir -p "$(dirname $out)"
355 cp $outd/llpp $out
356 eval $keycmd>"$out.past"
357 } && vecho "fresh bundle"