master
Makefile.sh
1#!/bin/sh
2# drives the build without needing a working make to bootstrap
3#
4set -e
5
6# per-run work directory, cleaned up on exit
7#
8WORKDIR="${TMPDIR:-/tmp}/aruu.$$"
9mkdir -p "$WORKDIR"
10trap 'rm -rf "$WORKDIR"' EXIT INT TERM HUP
11
12JOBS=1
13LOAD=0
14TARGET=all
15
16while [ $# -gt 0 ]; do
17 case "$1" in
18 -j) shift; JOBS="${1:?-j requires a number}" ;;
19 -j*) JOBS="${1#-j}" ;;
20 -l) shift; LOAD="${1:?-l requires a number}" ;;
21 -l*) LOAD="${1#-l}" ;;
22 -*) printf 'unknown flag: %s\n' "$1" >&2; exit 1 ;;
23 *) TARGET="$1" ;;
24 esac
25 shift
26done
27
28[ -f build.cfg ] || { printf 'build.cfg not found; see the repository README\n' >&2; exit 1; }
29# shellcheck disable=SC1091
30. ./build.cfg
31
32# cppflags derived from build.cfg; any feature_* added there is picked up automatically
33_feature_flags=""
34while IFS= read -r _line; do
35 _trimmed=$(printf '%s' "$_line" | tr -d ' \t')
36 case "$_trimmed" in
37 FEATURE_*=*)
38 _key=${_trimmed%%=*}
39 eval "_val=\$$_key"
40 _feature_flags="$_feature_flags -D${_key}=${_val}"
41 ;;
42 esac
43done <<EOF
44$(tr ';' '\n' < build.cfg)
45EOF
46unset _line _key _val _trimmed
47CPPFLAGS="-Ishared -DPREFIX=\"$PREFIX\" -D_DEFAULT_SOURCE -D_GNU_SOURCE -D_NETBSD_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_FILE_OFFSET_BITS=64$_feature_flags"
48if [ "$FEATURE_USE_BEARSSL" = "1" ]; then
49 if pkg-config --exists libbearssl 2>/dev/null; then
50 CPPFLAGS="$CPPFLAGS $(pkg-config --cflags libbearssl)"
51 TLS_LDLIBS="$TLS_LDLIBS $(pkg-config --libs libbearssl)"
52 elif pkg-config --exists bearssl 2>/dev/null; then
53 CPPFLAGS="$CPPFLAGS $(pkg-config --cflags bearssl)"
54 TLS_LDLIBS="$TLS_LDLIBS $(pkg-config --libs bearssl)"
55 else
56 TLS_LDLIBS="$TLS_LDLIBS -LexternalRepos/BearSSL/build -lbearssl"
57 fi
58fi
59if [ "$FEATURE_USE_LIBRESSL" = "1" ]; then
60 if pkg-config --exists libtls 2>/dev/null; then
61 CPPFLAGS="$CPPFLAGS $(pkg-config --cflags libtls)"
62 TLS_LDLIBS="$TLS_LDLIBS $(pkg-config --libs libtls)"
63 else
64 TLS_LDLIBS="$TLS_LDLIBS -ltls"
65 fi
66fi
67if [ "$FEATURE_USE_OPENSSL" = "1" ]; then
68 if pkg-config --exists openssl 2>/dev/null; then
69 CPPFLAGS="$CPPFLAGS $(pkg-config --cflags openssl)"
70 TLS_LDLIBS="$TLS_LDLIBS $(pkg-config --libs openssl)"
71 else
72 TLS_LDLIBS="$TLS_LDLIBS -lssl -lcrypto"
73 fi
74fi
75unset _feature_flags
76
77CC=${CC:-cc}
78AR=${AR:-ar}
79ARFLAGS=${ARFLAGS:-rc}
80RANLIB=${RANLIB:-ranlib}
81PREFIX=${PREFIX:-/usr/local}
82MANPREFIX=${MANPREFIX:-$PREFIX/share/man}
83
84# shared headers; every object depends on these
85HDR=$(echo shared/*.h)
86LIB="shared/libredline/libredline.a shared/libutil/libutil.a shared/libutf/libutf.a"
87
88# set by multi-file builders for generated headers, cleared after use
89EXTRA_HDR=
90
91# timestamp comparison
92#
93mtime() {
94 stat -c '%Y' "$1" 2>/dev/null || stat -f '%m' "$1" 2>/dev/null || echo 0
95}
96
97newer_than() {
98 local s t
99 [ -e "$2" ] || return 0
100 s=$(mtime "$1"); t=$(mtime "$2")
101 [ "$s" -gt "$t" ]
102}
103
104any_newer_than() {
105 local target="$1"; shift
106 for src do
107 newer_than "$src" "$target" && return 0
108 done
109 return 1
110}
111
112# parallel job queue
113#
114# objs_for must not run in a subshell; enqueue writes _qn/_queue in the current shell
115
116_qn=0
117_queue=""
118_objs=""
119
120# wraps arg in single quotes, escaping embedded ones, for safe eval
121shquote() {
122 printf "'"
123 printf '%s' "$1" | sed "s/'/'\\\\''/g"
124 printf "'"
125}
126
127enqueue() {
128 local desc="$1" cmd="$2"
129 _qn=$((_qn + 1))
130 local s="$WORKDIR/j${_qn}.sh"
131 {
132 printf '#!/bin/sh\n'
133 printf 'printf "%%s\\n" %s\n' "$(shquote "$desc")"
134 printf 'eval %s\n' "$(shquote "$cmd")"
135 } > "$s"
136 _queue="$_queue $_qn"
137}
138
139# silently skipped on systems without /proc/loadavg
140_wait_for_load() {
141 [ "$LOAD" -eq 0 ] && return 0
142 local avg
143 while true; do
144 avg=$(awk '{printf "%d", int($1 + 0.5)}' /proc/loadavg 2>/dev/null) || return 0
145 [ "$avg" -le "$LOAD" ] && return 0
146 sleep 1
147 done
148}
149
150drain() {
151 [ -z "$_queue" ] && return 0
152 local n
153 _wait_for_load
154 if [ "$JOBS" -le 1 ]; then
155 for n in $_queue; do
156 sh "$WORKDIR/j${n}.sh" || { printf 'error: build step failed\n' >&2; exit 1; }
157 done
158 else
159 # xargs -P (POSIX 2024), -n 1 so each path becomes its own sh invocation
160 for n in $_queue; do
161 printf '%s\n' "$WORKDIR/j${n}.sh"
162 done | xargs -P "$JOBS" -n 1 sh || { printf 'error: build step failed\n' >&2; exit 1; }
163 fi
164 _queue=""
165}
166
167# compile and link primitives
168#
169compile_c() {
170 local src="$1" obj="$2"; shift 2
171 # shellcheck disable=SC2086
172 any_newer_than "$obj" "$src" $HDR $EXTRA_HDR || return 0
173 enqueue " CC $obj" "$CC $* $CPPFLAGS $CFLAGS -o $obj -c $src"
174}
175
176link_bin() {
177 local bin="$1" objs="" libs="" sep=0; shift
178 for a do
179 if [ "$a" = "--" ]; then sep=1
180 elif [ "$sep" = 0 ]; then objs="$objs $a"
181 else libs="$libs $a"
182 fi
183 done
184 drain
185 # shellcheck disable=SC2086
186 any_newer_than "$bin" $objs || return 0
187 printf ' LD %s\n' "$bin"
188 # shellcheck disable=SC2086
189 if [ "${bin##*/}" = "wget" ]; then
190 eval "$CC $LDFLAGS -o $bin $objs $libs $LDLIBS $TLS_LDLIBS"
191 else
192 eval "$CC $LDFLAGS -o $bin $objs $libs $LDLIBS"
193 fi
194}
195
196# writes to _objs rather than stdout so the caller avoids a subshell that
197# would discard enqueued jobs
198objs_for() {
199 local dir="$1" skip=" $2 " flags="$3" src base
200 _objs=""
201 for src in "$dir"/*.c; do
202 [ -f "$src" ] || continue
203 base=${src##*/}
204 case "$skip" in *" $base "*) continue ;; esac
205 compile_c "$src" "${src%.c}.o" $flags
206 _objs="$_objs ${src%.c}.o"
207 done
208}
209
210# static libraries
211#
212# both directories enqueued before the single drain so they compile in parallel
213build_lib() {
214 local utf_objs util_objs redline_objs
215 objs_for shared/libutf "" ""
216 utf_objs="$_objs"
217 objs_for shared/libutil "" ""
218 util_objs="$_objs"
219 objs_for shared/libredline "" ""
220 redline_objs="$_objs"
221 drain
222 # shellcheck disable=SC2086
223 any_newer_than shared/libutf/libutf.a $utf_objs && {
224 printf ' AR shared/libutf/libutf.a\n'
225 $AR $ARFLAGS shared/libutf/libutf.a $utf_objs
226 $RANLIB shared/libutf/libutf.a
227 }
228 # shellcheck disable=SC2086
229 any_newer_than shared/libutil/libutil.a $util_objs && {
230 printf ' AR shared/libutil/libutil.a\n'
231 $AR $ARFLAGS shared/libutil/libutil.a $util_objs
232 $RANLIB shared/libutil/libutil.a
233 }
234 # shellcheck disable=SC2086
235 any_newer_than shared/libredline/libredline.a $redline_objs && {
236 printf ' AR shared/libredline/libredline.a\n'
237 $AR $ARFLAGS shared/libredline/libredline.a $redline_objs
238 $RANLIB shared/libredline/libredline.a
239 }
240 return 0
241}
242
243# per-tool category builder
244#
245cfgvar() {
246 local cat="$1" base="$2"
247 [ "$cat" = extra ] && cat=pseudo
248 printf 'BUILD_%s_%s' "$cat" "$base" | tr 'a-z-' 'A-Z_'
249}
250
251# individual flag overrides group flag
252cfg_enabled() {
253 local _var="$1" _v
254 while [ -n "$_var" ]; do
255 eval "_v=\${${_var}-UNSET}"
256 if [ "$_v" = "1" ]; then
257 return 0
258 elif [ "$_v" = "0" ]; then
259 return 1
260 fi
261 case "$_var" in
262 *_[!_]*) _var=$(printf '%s' "$_var" | sed 's/_[^_]*$//') ;;
263 *) break ;;
264 esac
265 done
266 return 1
267}
268
269build_simple_tools() {
270 local cat="$1" src base var staged=""
271 for src in cmd/"$cat"/*.c; do
272 [ -f "$src" ] || continue
273 base=${src##*/}; base=${base%.c}
274 var=$(cfgvar "$cat" "$base")
275 cfg_enabled "$var" || continue
276 compile_c "$src" "${src%.c}.o"
277 staged="$staged ${src%.c}"
278 done
279 drain
280 local bin
281 for bin in $staged; do
282 link_bin "$bin" "${bin}.o" -- $LIB
283 done
284}
285
286# generated multi-file tools under cmd/posix
287#
288build_awk() {
289 cfg_enabled BUILD_POSIX_AWK || return 0
290 local dir=cmd/posix/awk
291
292 any_newer_than "$dir/awkgram.tab.c" "$dir/awkgram.y" && {
293 if command -v yacc >/dev/null 2>&1; then
294 printf ' YACC %s/awkgram.tab.c\n' "$dir"
295 yacc -d -o "$dir/awkgram.tab.c" "$dir/awkgram.y"
296 if [ ! -f "$dir/awkgram.tab.h" ]; then
297 if [ -f y.tab.h ]; then
298 mv y.tab.h "$dir/awkgram.tab.h"
299 elif [ -f "$dir/y.tab.h" ]; then
300 mv "$dir/y.tab.h" "$dir/awkgram.tab.h"
301 fi
302 fi
303 elif command -v bison >/dev/null 2>&1; then
304 printf ' BISON %s/awkgram.tab.c\n' "$dir"
305 bison -d -o "$dir/awkgram.tab.c" "$dir/awkgram.y"
306 else
307 printf 'awk: no yacc/bison; using pre-generated awkgram.tab.c\n' >&2
308 fi
309 }
310
311 any_newer_than "$dir/maketab" "$dir/maketab.c" "$dir/awkgram.tab.h" && {
312 printf ' CC %s/maketab\n' "$dir"
313 eval "$CC $CFLAGS -o $dir/maketab $dir/maketab.c"
314 }
315
316 any_newer_than "$dir/proctab.c" "$dir/maketab" && {
317 printf ' GEN %s/proctab.c\n' "$dir"
318 "$dir/maketab" "$dir/awkgram.tab.h" > "$dir/proctab.c"
319 }
320
321 EXTRA_HDR="$dir/awk.h $dir/awkgram.tab.h $dir/proto.h"
322 objs_for "$dir" maketab.c "-I$dir"
323 EXTRA_HDR=
324 link_bin "$dir/awk" $_objs -- $LIB -lm
325}
326
327build_sh() {
328 cfg_enabled BUILD_POSIX_SH || return 0
329 local dir=cmd/posix/sh
330
331 any_newer_than "$dir/mknodes" "$dir/mknodes.c" && {
332 printf ' CC %s/mknodes\n' "$dir"
333 eval "$CC $CFLAGS -o $dir/mknodes $dir/mknodes.c"
334 }
335
336 any_newer_than "$dir/mksyntax" "$dir/mksyntax.c" && {
337 printf ' CC %s/mksyntax\n' "$dir"
338 eval "$CC $CPPFLAGS -I$dir $CFLAGS -o $dir/mksyntax $dir/mksyntax.c"
339 }
340
341 any_newer_than "$dir/token.h" "$dir/mktokens" && {
342 printf ' GEN %s/token.h\n' "$dir"
343 (cd "$dir" && sh mktokens)
344 }
345
346 any_newer_than "$dir/syntax.c" "$dir/mksyntax" && {
347 printf ' GEN %s/syntax.c\n' "$dir"
348 (cd "$dir" && ./mksyntax)
349 }
350
351 any_newer_than "$dir/nodes.c" "$dir/mknodes" "$dir/nodetypes" "$dir/nodes.c.pat" && {
352 printf ' GEN %s/nodes.c\n' "$dir"
353 (cd "$dir" && ./mknodes nodetypes nodes.c.pat)
354 }
355
356 any_newer_than "$dir/builtins.c" "$dir/mkbuiltins" "$dir/builtins.def" "$dir/shell.h" && {
357 printf ' GEN %s/builtins.c\n' "$dir"
358 (cd "$dir" && sh mkbuiltins .)
359 }
360
361 EXTRA_HDR="$dir/syntax.h $dir/nodes.h $dir/builtins.h $dir/token.h"
362 objs_for "$dir" "mknodes.c mksyntax.c" "-DSHELL -I$dir"
363 EXTRA_HDR=
364 link_bin "$dir/sh" $_objs -- $LIB
365}
366
367build_make() {
368 cfg_enabled BUILD_POSIX_MAKE || return 0
369 local dir=cmd/posix/make
370 EXTRA_HDR="$dir/make.h"
371 objs_for "$dir" "" ""
372 EXTRA_HDR=
373 link_bin "$dir/make" $_objs -- $LIB
374}
375
376build_posix() {
377 cfg_enabled BUILD_POSIX_GETCONF && [ ! -f cmd/posix/getconf.h ] && {
378 printf ' GEN cmd/posix/getconf.h\n'
379 scripts/getconf.sh > cmd/posix/getconf.h || { rm -f cmd/posix/getconf.h; exit 1; }
380 }
381 build_simple_tools posix
382 build_awk
383 build_sh
384 build_make
385}
386
387# multi-file dev toolchain
388#
389build_ar() {
390 cfg_enabled BUILD_DEV_AR || return 0
391 local dir=cmd/dev/ar
392 objs_for "$dir" tinyar.c "-I$dir"
393 link_bin "$dir/ar" $_objs -- $LIB
394}
395
396build_as() {
397 cfg_enabled BUILD_DEV_AS || return 0
398 local dir="cmd/dev/as"
399 local flags="-Icmd/dev/xcutil -I$dir -Ishared"
400 local objs=""
401 objs_for "cmd/dev/xcutil" "" "$flags"
402 objs="$objs $_objs"
403 objs_for "$dir" "" "$flags"
404 objs="$objs $_objs"
405 objs_for "$dir/arch/x64" "" "-Icmd/dev/xcutil -I$dir -I$dir/arch/x64 -Ishared"
406 objs="$objs $_objs"
407 link_bin "$dir/as" $objs -- $LIB
408}
409
410build_ld() {
411 cfg_enabled BUILD_DEV_LD || return 0
412 local dir="cmd/dev/ld"
413 local flags="-Icmd/dev/xcutil -I$dir"
414 local objs=""
415 objs_for "cmd/dev/xcutil" "" "$flags"
416 objs="$objs $_objs"
417 objs_for "$dir" "" "$flags"
418 objs="$objs $_objs"
419 link_bin "$dir/ld" $objs -- $LIB
420}
421
422build_cc() {
423 cfg_enabled BUILD_DEV_CC || return 0
424 local dir="cmd/dev/cc"
425 local cflags="-I$dir"
426 local common
427
428 # compiled once, linked into both cc1 and cpp
429 objs_for "$dir" "driver.c cc1.c cpp.c" "$cflags"
430 common="$_objs"
431 drain
432
433 compile_c "$dir/cc1.c" "$dir/cc1.o" $cflags
434 compile_c "$dir/cpp.c" "$dir/cpp.o" $cflags
435 compile_c "$dir/driver.c" "$dir/driver.o" $cflags
436 drain
437
438 link_bin "$dir/cc1" "$dir/cc1.o" $common -- $LIB
439 link_bin "$dir/cpp" "$dir/cpp.o" $common -- $LIB
440 link_bin "$dir/cc" "$dir/driver.o" "$dir/util.o" -- $LIB
441}
442
443build_dev() {
444 [ -f cmd/dev/configure ] && sh cmd/dev/configure
445 build_ar
446 build_as
447 build_ld
448 build_cc
449}
450
451# manual page generation
452#
453man_section() {
454 case "$1" in
455 */linux/*|*/net/*|*/xsi/*) printf '8\n' ;;
456 *) printf '1\n' ;;
457 esac
458}
459
460build_man_for() {
461 local var="$1" src="$2" base sec out_mdoc out_txt
462 cfg_enabled "$var" || return 0
463 [ -x scripts/mkman/mkman ] || { printf 'error: mkman not built\n' >&2; exit 1; }
464
465 grep -qE '!man|\?man' "$src" || return 0
466
467 base=${src##*/}; base=${base%.c}
468 sec=$(man_section "$src")
469
470 mkdir -p "man/man${sec}"
471 out_mdoc="man/man${sec}/${base}.${sec}"
472 out_txt="man/man${sec}/${base}.${sec}.txt"
473 if any_newer_than "$out_mdoc" "$src" config.mk scripts/mkman/mkman; then
474 printf ' MAN %s\n' "$out_mdoc"
475 scripts/mkman/mkman -fmt mdoc -config config.mk -section "$sec" "$src" > "$out_mdoc"
476 fi
477 if any_newer_than "$out_txt" "$src" config.mk scripts/mkman/mkman; then
478 printf ' MAN %s\n' "$out_txt"
479 scripts/mkman/mkman -fmt txt -config config.mk -section "$sec" "$src" > "$out_txt"
480 fi
481}
482
483build_man() {
484 local dir cat src base
485 if [ ! -x scripts/mkman/mkman ] || any_newer_than scripts/mkman/mkman scripts/mkman/main.go scripts/mkman/page.go scripts/mkman/parse.go scripts/mkman/mdoc.go; then
486 printf ' GO scripts/mkman/mkman\n'
487 (cd scripts/mkman && go build -o mkman .)
488 fi
489 for dir in cmd/*; do
490 [ -d "$dir" ] || continue
491 cat=${dir##*/}
492 for src in "$dir"/*.c; do
493 [ -f "$src" ] || continue
494 base=${src##*/}; base=${base%.c}
495 build_man_for "$(cfgvar "$cat" "$base")" "$src"
496 done
497 done
498}
499
500# install and clean
501#
502do_install() {
503 local bin f sec
504 mkdir -p "$PREFIX/bin" "$MANPREFIX/man1" "$MANPREFIX/man8"
505 find cmd -type f ! -name '*.*' -perm -100 | while read -r bin; do
506 printf ' INSTALL %s/bin/%s\n' "$PREFIX" "${bin##*/}"
507 cp "$bin" "$PREFIX/bin/${bin##*/}"
508 chmod 755 "$PREFIX/bin/${bin##*/}"
509 done
510 find man/man1 man/man8 -type f -name '*.[18]' 2>/dev/null | while read -r f; do
511 sec=${f%/*}; sec=${sec##*/}
512 cp "$f" "$MANPREFIX/${sec}/${f##*/}"
513 done
514}
515
516do_clean() {
517 find shared cmd -name '*.o' -exec rm -f {} +
518 find shared -name '*.a' -exec rm -f {} +
519 find cmd -type f ! -name '*.*' -perm -100 -exec rm -f {} +
520 rm -f shared/libaruuelf.so
521 rm -f cmd/posix/bc.c cmd/posix/getconf.h
522 rm -f cmd/posix/awk/maketab cmd/posix/awk/proctab.c
523 rm -f cmd/posix/awk/awkgram.tab.c cmd/posix/awk/awkgram.tab.h
524 rm -f cmd/posix/sh/mknodes cmd/posix/sh/mksyntax
525 rm -f cmd/posix/sh/token.h cmd/posix/sh/syntax.c cmd/posix/sh/syntax.h
526 rm -f cmd/posix/sh/nodes.c cmd/posix/sh/nodes.h
527 rm -f cmd/posix/sh/builtins.c cmd/posix/sh/builtins.h
528 rm -rf man/man1 man/man8
529 rm -rf aruu-box .box
530 rm -f scripts/mkman/mkman
531}
532
533case "$TARGET" in
534 all)
535 build_lib
536 build_posix
537 build_dev
538 build_simple_tools linux
539 build_simple_tools net
540 build_simple_tools xsi
541 build_simple_tools pseudo
542 build_simple_tools extra
543 ;;
544 lib) build_lib ;;
545 posix) build_lib; build_posix ;;
546 dev) build_lib; build_dev ;;
547 make) build_lib; build_make ;;
548 linux|net|xsi|pseudo|extra)
549 build_lib; build_simple_tools "$TARGET" ;;
550 man) build_man ;;
551 install) do_install ;;
552 clean) do_clean ;;
553 *)
554 printf 'usage: sh Makefile.sh [-jN] [-lN] [all|clean|install|man|lib|posix|linux|net|xsi|pseudo|extra|dev|make]\n' >&2
555 exit 1
556 ;;
557esac