Match the reality
[llpp.git] / build.bash
blobe7517ee18661e4af0368d88784bb875e97b94a12
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 isfresh() { test "$(<$1.past)" = "$2"; } 2>/dev/null
44 mbt=${mbt:-release}
45 test -n "${gmk:-}" && gmk=false || gmk=true
47 mulibs="$mudir/build/$mbt/libmupdf.a $mudir/build/$mbt/libmupdf-third.a"
48 make="make -C "$mudir" build=$mbt -j $mjobs libs"
49 $make -q -s || $make
51 oincs() {
52 local b=$1 incs
53 case "${2#$outd/}" in
54 $wsid/wsi.cm[oi]|confstruct.cmo|help.cmo) incs="-I $b -I $b/$wsid";;
55 glutils.cmo) incs="-I $b -I $b/lablGL";;
56 uiutils.cmo|main.cmo) incs="-I $b -I $b/$wsid -I $b/lablGL";;
57 ffi.cmo|help.cmi|parser.cmo) incs="-I $b";;
58 config.cmo)
59 incs="-I $b -I $b/$wsid"
60 test "$b" = $outd || incs="$incs -I $outd"
62 lablGL/*) incs="-I $b/lablGL";;
63 main.cmo|keys.cmo|utils.cmo|utf8syms.cmo) incs="-I $b";;
64 config.cmi) incs="-I $outd -I $b -I $b/$wsid";;
65 uiutils.cmi|ffi.cmi) incs="-I $b";;
66 glutils.cmi) incs="-I $b/lablGL";;
67 main.cmi|keys.cmi|utils.cmi|utf8syms.cmi|parser.cmi) ;;
68 *) die "ocaml include paths for '$2' aren't set";;
69 esac
70 test -z "${incs-}" || echo $incs
73 oflags() {
74 case "${1#$outd/}" in
75 lablGL/*) f="-g";;
76 utf8syms.cmo|confstruct.cmo|config.cmo|ffi.cmo|wsi/cocoa/wsi.cmo)
77 f="-g -strict-sequence -strict-formats -alert @all-missing-mli";;
78 *) f="-g -strict-sequence -strict-formats -alert @all -warn-error @A";;
79 esac
80 echo $(oincs $outd $1) $f
83 cflags() {
84 case "${1#$outd/}" in
85 version.o) f=-DLLPP_VERSION=$ver;;
86 lablGL/*.o) f="-g -Wno-pointer-sign -Werror -O2";;
87 link.o)
88 f="-g -std=c11 $muinc -Wall -Werror -Wextra -pedantic "
89 test "${mbt-}" = "debug" || f+="-O2 "
90 $darwin && f+="-DMACOS -D_GNU_SOURCE -DGL_H='<OpenGL/gl.h>'" \
91 || f+="-D_POSIX_C_SOURCE -DGL_H='<GL/gl.h>'"
92 f+=" -include $srcd/diag.h -DFIXME=0"
93 f+=" -DTEXT_TYPE=GL_TEXTURE_RECTANGLE_ARB"
94 #f+=" -DTEXT_TYPE=GL_TEXTURE_2D"
96 *) f="-g -O2 -Wall -Werror";;
97 esac
98 ! $darwin || f+=" -DGL_SILENCE_DEPRECATION"
99 echo $f
102 mflags() {
103 echo "-I $(ocamlc -where) -g -Wall -Werror -O2 -DGL_SILENCE_DEPRECATION"
106 overs=$(ocamlc -vnum 2>/dev/null) || overs=""
107 if test "$overs" != "4.14.0~alpha1"; then
108 url=https://caml.inria.fr/pub/distrib/ocaml-4.14/ocaml-4.14.0~alpha1.tar.xz
109 txz=$outd/$(basename $url)
110 keycmd="printf $url; digest $txz;"
111 isfresh $txz "$(eval $keycmd)" || {
112 if executable_p wget; then dl() { wget "$1" -O "$2"; }
113 elif executable_p curl; then dl() { curl -L "$1" -o "$2"; }
114 else die "no program to fetch remote urls found"
116 dl $url $txz
117 eval $keycmd >$txz.past
118 } && vecho "fresh $txz"
119 absprefix=$(realpath $outd)
120 export PATH=$absprefix/bin:$PATH
121 ocamlc=$absprefix/bin/ocamlc
122 keycmd="printf $url; digest $ocamlc;"
123 isfresh $ocamlc "$(eval $keycmd)" || (
124 # This will needlessly re{configure,make} ocaml since "past"
125 # of configure/make is hard to ascertain. "Better safe than
126 # sorry" approach is taken here. The check will work for a
127 # single ocaml url/version, but _will_ redo _everything_
128 # otherwise (even if fully built artifacts are available)
129 tar xf $txz -C $outd
130 bn=$(basename $url)
131 cd $outd/${bn%.tar.xz}
132 ./configure --disable-ocamldoc --disable-ocamltest \
133 --enable-debugger=no --prefix=$absprefix
134 make -j $mjobs world
135 make install
136 eval $keycmd >$absprefix/bin/ocamlc.past
137 ) && vecho "fresh ocamlc"
138 overs=$(ocamlc -vnum 2>/dev/null)
141 while read k v; do
142 case "$k" in
143 "bytecomp_c_compiler:") ccomp=${CAML_CC-$v};;
144 "word_size:") ! test "$darwin$v" = "true32" || die "need 64bit ocaml";;
145 esac
146 done < <(ocamlc -config)
148 read cvers < <($ccomp --version)
150 seen=
151 ord=
152 $gmk || :>$outd/Makefile
153 bocaml1() {
154 local n=$1 s=$2 o=$3 deps= cmd d
155 local keycmd="digest $s $o.depl"
156 cmd="ocamlc -depend -bytecode -one-line $(oincs $srcd $o) $s"
158 isfresh "$o.depl" "$overs$cmd$(eval $keycmd)" || {
159 read _ _ depl < <(eval $cmd) || die "$cmd failed"
160 for d in $depl; do
161 if test "$d" = "$outd/confstruct.cmo";
162 then d=confstruct.cmo; else d=${d#$srcd/}; fi
163 deps+="$d\n"
164 done
165 printf "$deps" >$o.depl
166 deps=
167 echo "$overs$cmd$(eval $keycmd)" >"$o.depl.past"
168 } && vecho "fresh $o.depl"
170 # this saves time but is overly optimistic as interface (dis)
171 # appearance will result in an invalid (stale) .depl (cache). not
172 # using a cache is correct but slow(er (much)) way to handle this.
173 while read d; do
174 bocaml $d $((n+1))
175 deps+=" $outd/$d"
176 done <$o.depl
178 cmd="ocamlc $(oflags $o) -c -o $o $s"
179 keycmd="digest $o $s $deps"
180 isfresh "$o" "$overs$cmd$(eval $keycmd)" || {
181 printf "%*.s%s\n" $n '' "${o#$outd/}"
182 eval "$cmd" || die "$cmd failed"
183 echo "$overs$cmd$(eval $keycmd)" >"$o.past"
184 } && vecho "fresh $o"
185 $gmk || printf "$o: $deps\n\t%s\n" "$cmd" >>$outd/Makefile
186 seen+=$o
187 ord+=" $o"
190 cycle=
191 bocaml() {
192 [[ ! $seen =~ $1 ]] || return 0
193 local s o=$1 n=$2 cycle1=$cycle
194 case $o in
195 confstruct.cmo) s=$outd/confstruct.ml;;
196 *.cmo) s=$srcd/${o%.cmo}.ml;;
197 *.cmi) s=$srcd/${o%.cmi}.mli;;
198 esac
199 o=$outd/$o
200 [[ "$cycle" =~ "$o" ]] && die cycle $o || cycle=$cycle$o
201 bocaml1 $n $s $o
202 cycle=$cycle1
205 baux() {
206 local o=$1 cmd=$2
207 read 2>/dev/null _ d <$o.dep || d=
208 local keycmd='digest $o $d'
209 isfresh "$o" "$cvers$cmd$(eval $keycmd)" || {
210 echo "${o#$outd/}"
211 eval "$cmd" || die "$cmd failed"
212 read _ d <$o.dep
213 echo "$cvers$cmd$(eval $keycmd)" >"$o.past"
214 } && vecho "fresh $o"
215 $gmk || printf "$o: $d\n\t$cmd\n" >>$outd/Makefile
218 bocamlc() {
219 local o=$outd/$1 s=$srcd/${1%.o}.c cc=${CAML_CC:+-cc "'$CAML_CC'" }
220 baux $o "ocamlc $cc-ccopt \"$(cflags $o) -MMD -MF $o.dep -MT_\" -o $o -c $s"
223 bobjc() {
224 local o=$outd/$1
225 baux $o "$mcomp $(mflags $o) -MD -MF $o.dep -MT_ -c -o $o $srcd/${1%.o}.m"
228 ver=$(cd $srcd && git describe --tags --dirty) || ver="'built on $(date)'"
230 gen=$srcd/genconfstruct.sh
231 out=$outd/confstruct.ml
232 cmd="(export print paste clip uopen; . $gen >$out)"
233 keycmd="{ echo '$print $paste $clip $uopen'; digest $gen $out; }"
234 isfresh "$out" "$cmd$(eval $keycmd)" || {
235 echo "generating $out"
236 eval "$cmd" || die $gen failed
237 echo "$cmd$(eval $keycmd)" > "${out}.past"
238 } && vecho "fresh $out"
240 shift 1
241 for target; do
242 case "$target" in
243 doc)
244 md=$outd/doc
245 mkdir -p $md
246 for m in llpp llppac; do
247 src=$srcd/adoc/$m.adoc
248 o=$md/$m.1
249 conf=$srcd/man/asciidoc.conf
250 keycmd="digest $o $src $conf"
251 cmd="a2x -f manpage -D $md $src"
252 isfresh "$o" "$cmd$(eval $keycmd)" || {
253 echo "${o#$outd/}"
254 eval "$cmd" || die "$cmd failed"
255 echo "$cmd$(eval $keycmd)" >"$o.past"
256 } && vecho "fresh $o"
257 done;
258 exit;;
259 *) die "no such target - '$target'";;
260 esac
261 done
263 flatten() {
264 local o
265 [[ ! "$seen" =~ "$1" ]] || return 0
266 bocaml $1 0
267 for o in $ord; do
268 local wooutd=${o#$outd/}
269 case $o in
270 *.cmi) flatten ${wooutd%.cmi}.cmo;;
271 *.cmo) flatten $wooutd;;
272 esac
273 done
275 flatten main.cmo
277 modules=
278 collectmodules() {
279 # it might appear that following can be done inside bocaml* but
280 # alas due to the early cmi->cmo descent this ought to be done
281 # here (at least the solution inside bocaml* eludes me)
282 local dep cmo this=$1
283 while read dep; do
284 case $dep in
285 *.cmi)
286 cmo=${dep%.cmi}.cmo
287 test $cmo = $this || collectmodules $cmo
289 *.cmo)
290 collectmodules $dep
291 cmo=$dep
293 esac
294 [[ $modules =~ $cmo ]] || modules+=" $outd/$cmo"
295 done <$outd/$1.depl
297 collectmodules main.cmo
299 cobjs=
300 for m in link cutils version; do
301 bocamlc $m.o
302 cobjs+=" $outd/$m.o"
303 done
304 for m in ml_gl ml_glarray ml_raw; do
305 bocamlc lablGL/$m.o
306 cobjs+=" $outd/lablGL/$m.o"
307 done
309 libs="str.cma unix.cma"
310 clibs="-L$mudir/build/$mbt -lmupdf -lmupdf-third -lpthread"
311 if $darwin; then
312 mcomp=$ccomp
313 clibs+=" -framework Cocoa -framework OpenGL"
314 cobjs+=" $outd/wsi/cocoa/cocoa.o"
315 bobjc wsi/cocoa/cocoa.o
316 else
317 clibs+=" -lGL -lX11"
318 cobjs+=" $outd/wsi/x11/keysym2ucs.o $outd/wsi/x11/xlib.o"
319 bocamlc wsi/x11/keysym2ucs.o
320 bocamlc wsi/x11/xlib.o
323 cmd="ocamlc -custom $libs -o $outd/llpp $cobjs $modules -cclib \"$clibs\""
324 keycmd="digest $outd/llpp $cobjs $modules $mulibs"
325 isfresh "$outd/llpp" "$cmd$(eval $keycmd)" || {
326 echo linking $outd/llpp
327 eval "$cmd" || die "$cmd failed"
328 echo "$cmd$(eval $keycmd)" >"$outd/llpp.past"
329 } && vecho "fresh llpp"
330 $gmk || printf "$outd/llpp: $cobjs $modules $mulibs\n\t$cmd\n" >>$outd/Makefile
332 if $darwin; then
333 out="$outd/llpp.app/Contents/Info.plist"
334 keycmd="digest $out $srcd/wsi/cocoa/genplist.sh; echo $ver"
335 isfresh $out "$(eval $keycmd)" || {
336 d=$(dirname $out)
337 mkdir -p "$d"
338 echo "generating $out"
339 (. $srcd/wsi/cocoa/genplist.sh) >"$out"
340 eval $keycmd>"$out.past"
341 } && vecho "fresh plist"
343 out=$outd/llpp.app/Contents/MacOS/llpp
344 keycmd="digest $out $outd/llpp"
345 isfresh $out "$(eval $keycmd)" || {
346 echo "bundling $out"
347 mkdir -p "$(dirname $out)"
348 cp $outd/llpp $out
349 eval $keycmd>"$out.past"
350 } && vecho "fresh bundle"