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