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