master xplshn/aruu / scripts / mkbox
  1#!/bin/sh
  2# mkbox - build aruu-box, the aruu multi-call binary
  3#
  4# expects CC CPPFLAGS CFLAGS LDFLAGS LDLIBS OBJCOPY YACC from the environment
  5# defaults match config.mk but can be overridden
  6#
  7# run from the source root (the Makefile does this automatically)
  8
  9set -e
 10
 11: "${CC:=cc}"
 12: "${CPPFLAGS:=-Ishared -D_DEFAULT_SOURCE -D_GNU_SOURCE -D_NETBSD_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_FILE_OFFSET_BITS=64}"
 13: "${CFLAGS:=-std=c99 -Wall -Wextra -pedantic}"
 14: "${LDFLAGS:=}"
 15: "${LDLIBS:=-lcrypt -lresolv}"
 16: "${OBJCOPY:=objcopy}"
 17: "${YACC:=yacc}"
 18
 19ROOT=$(CDPATH= cd "$(dirname "$0")/.." && pwd)
 20BDIR="${ROOT}/.box"
 21BOX="${ROOT}/aruu-box"
 22LIBREDLINE="${ROOT}/shared/libredline/libredline.a"
 23LIBUTIL="${ROOT}/shared/libutil/libutil.a"
 24LIBUTF="${ROOT}/shared/libutf/libutf.a"
 25
 26die() {
 27	printf 'mkbox: %s\n' "$*" >&2
 28	exit 1
 29}
 30
 31# Clean up build directory on exit or signal termination
 32trap 'rm -rf "${BDIR}"' EXIT INT QUIT TERM
 33
 34[ -f "${LIBREDLINE}" ] || die "libredline.a not found; run 'make' first"
 35[ -f "${LIBUTIL}" ] || die "libutil.a not found; run 'make' first"
 36[ -f "${LIBUTF}" ]  || die "libutf.a not found; run 'make' first"
 37
 38[ -f "${ROOT}/cmd/posix/getconf.h" ] || {
 39	printf 'mkbox: generating getconf.h\n'
 40	"${ROOT}/scripts/getconf.sh" > "${ROOT}/cmd/posix/getconf.h" ||
 41		die "scripts/getconf.sh failed"
 42}
 43
 44rm -rf "${BDIR}"
 45mkdir -p "${BDIR}"
 46
 47OBJS=""
 48DECL_F="${BDIR}/decls.inc"
 49: > "${DECL_F}"
 50for _c in posix linux net pseudo xsi; do
 51	: > "${BDIR}/entries_${_c}.inc"
 52done
 53unset _c
 54
 55alias_entry() {
 56	printf '\t{ "%s", %s_main, "%s" },\n' "$1" "$2" "$3" \
 57		>> "${BDIR}/entries_${3}.inc"
 58}
 59
 60compile_tool() {
 61	src="$1"
 62	name="$2"
 63	fn="$3"
 64	catg="$4"
 65	extra="${5:-}"
 66	out="${BDIR}/${fn}.o"
 67	symf="${BDIR}/${fn}.syms"
 68
 69	printf 'mkbox: cc %s\n' "${name}"
 70	eval "${CC} ${CPPFLAGS} ${extra} ${CFLAGS} -c \"\$src\" -o \"\$out\"" ||
 71		die "compile failed: ${src}"
 72	printf 'main %s_main\n' "${fn}" > "${symf}"
 73	${OBJCOPY} --redefine-syms="${symf}" "${out}" ||
 74		die "objcopy failed: ${name}"
 75	${OBJCOPY} --keep-global-symbol="${fn}_main" "${out}" ||
 76		die "objcopy localize failed: ${name}"
 77	rm -f "${symf}"
 78	OBJS="${OBJS} ${out}"
 79	printf 'int %s_main(int, char **);\n' "${fn}" >> "${DECL_F}"
 80	printf '\t{ "%s", %s_main, "%s" },\n' "${name}" "${fn}" "${catg}" \
 81		>> "${BDIR}/entries_${catg}.inc"
 82}
 83
 84compile_make() {
 85	mdir="${ROOT}/cmd/posix/make"
 86	symf="${BDIR}/make.syms"
 87	printf 'mkbox: cc make (multi)\n'
 88	make_objs=""
 89	for s in defaults parser posix rules; do
 90		out="${BDIR}/make_${s}.o"
 91		eval "${CC} ${CPPFLAGS} ${CFLAGS} -I\"\$mdir\" -c \"\$mdir/\$s.c\" -o \"\$out\"" ||
 92			die "compile failed: make/${s}.c"
 93		make_objs="${make_objs} ${out}"
 94	done
 95	out_main="${BDIR}/make_main.o"
 96	eval "${CC} ${CPPFLAGS} ${CFLAGS} -I\"\$mdir\" -c \"\$mdir/main.c\" -o \"\${out_main}\"" ||
 97		die "compile failed: make/main.c"
 98	make_objs="${make_objs} ${out_main}"
 99
100	# Combined incremental link to resolve internal symbols cleanly
101	combined="${BDIR}/make_combined.o"
102	${LD:-ld} -r -o "${combined}" ${make_objs} ||
103		die "ld -r failed: make"
104
105	printf 'main make_main\n' > "${symf}"
106	${OBJCOPY} --redefine-syms="${symf}" "${combined}" ||
107		die "objcopy failed: make"
108	${OBJCOPY} --keep-global-symbol=make_main "${combined}" ||
109		die "objcopy localize failed: make"
110	rm -f "${symf}" ${make_objs}
111
112	OBJS="${OBJS} ${combined}"
113	printf 'int make_main(int, char **);\n' >> "${DECL_F}"
114	printf '\t{ "make", make_main, "posix" },\n' >> "${BDIR}/entries_posix.inc"
115}
116
117compile_awk() {
118	adir="${ROOT}/cmd/posix/awk"
119	symf="${BDIR}/awk.syms"
120	awk_objs=""
121
122	printf 'mkbox: yacc awk/awkgram.y\n'
123	${YACC} -d -o "${adir}/awkgram.tab.c" "${adir}/awkgram.y" ||
124		die "yacc failed: awk/awkgram.y"
125
126	# maketab is a host code-generator, only needs CFLAGS, no CPPFLAGS
127	printf 'mkbox: cc awk/maketab\n'
128	${CC} ${CFLAGS} -o "${BDIR}/awk_maketab" "${adir}/maketab.c" ||
129		die "compile failed: awk/maketab.c"
130
131	printf 'mkbox: gen awk/proctab.c\n'
132	"${BDIR}/awk_maketab" "${adir}/awkgram.tab.h" > "${adir}/proctab.c" ||
133		die "maketab failed"
134
135	for s in b main parse proctab tran lib run lex math; do
136		out="${BDIR}/awk_${s}.o"
137		eval "${CC} ${CPPFLAGS} -I\"\$adir\" ${CFLAGS} -c \"\$adir/${s}.c\" -o \"\$out\"" ||
138			die "compile failed: awk/${s}.c"
139		awk_objs="${awk_objs} ${out}"
140	done
141	out="${BDIR}/awk_awkgram.tab.o"
142	eval "${CC} ${CPPFLAGS} -I\"\$adir\" ${CFLAGS} -c \"\$adir/awkgram.tab.c\" -o \"\$out\"" ||
143		die "compile failed: awk/awkgram.tab.c"
144	awk_objs="${awk_objs} ${out}"
145
146	combined="${BDIR}/awk_combined.o"
147	${LD:-ld} -r -o "${combined}" ${awk_objs} ||
148		die "ld -r failed: awk"
149
150	printf 'main awk_main\n' > "${symf}"
151	${OBJCOPY} --redefine-syms="${symf}" "${combined}" ||
152		die "objcopy failed: awk"
153	${OBJCOPY} --keep-global-symbol=awk_main "${combined}" ||
154		die "objcopy localize failed: awk"
155	rm -f "${symf}" ${awk_objs}
156
157	OBJS="${OBJS} ${combined}"
158	LDLIBS="${LDLIBS} -lm"
159	printf 'int awk_main(int, char **);\n' >> "${DECL_F}"
160	printf '\t{ "awk", awk_main, "posix" },\n' >> "${BDIR}/entries_posix.inc"
161}
162
163compile_sh() {
164	sdir="${ROOT}/cmd/posix/sh"
165	symf="${BDIR}/sh.syms"
166	sh_objs=""
167
168	# mknodes and mksyntax are host code-generators, not part of the shell
169	printf 'mkbox: cc sh/mknodes\n'
170	${CC} ${CFLAGS} -o "${BDIR}/sh_mknodes" "${sdir}/mknodes.c" ||
171		die "compile failed: sh/mknodes.c"
172
173	printf 'mkbox: cc sh/mksyntax\n'
174	${CC} ${CPPFLAGS} -I"${sdir}" ${CFLAGS} -o "${BDIR}/sh_mksyntax" "${sdir}/mksyntax.c" ||
175		die "compile failed: sh/mksyntax.c"
176
177	printf 'mkbox: gen sh/nodes.{c,h}\n'
178	(cd "${sdir}" && "${BDIR}/sh_mknodes" nodetypes nodes.c.pat) ||
179		die "mknodes failed"
180
181	printf 'mkbox: gen sh/syntax.{c,h}\n'
182	(cd "${sdir}" && "${BDIR}/sh_mksyntax") ||
183		die "mksyntax failed"
184
185	printf 'mkbox: gen sh/builtins.{c,h}\n'
186	(cd "${sdir}" && sh mkbuiltins .) ||
187		die "mkbuiltins failed"
188
189	printf 'mkbox: gen sh/token.h\n'
190	(cd "${sdir}" && sh mktokens) ||
191		die "mktokens failed"
192
193	for s in alias arith_yacc arith_yylex cd echo error eval exec \
194	          expand lineedit input jobs kill mail main memalloc \
195	          miscbltin mystring options output parser printf redir \
196	          show test trap var builtins nodes syntax; do
197		out="${BDIR}/sh_${s}.o"
198		eval "${CC} ${CPPFLAGS} -DSHELL -I\"\$sdir\" ${CFLAGS} -c \"\$sdir/${s}.c\" -o \"\$out\"" ||
199			die "compile failed: sh/${s}.c"
200		sh_objs="${sh_objs} ${out}"
201	done
202
203	combined="${BDIR}/sh_combined.o"
204	${LD:-ld} -r -o "${combined}" ${sh_objs} ||
205		die "ld -r failed: sh"
206
207	printf 'main sh_main\n' > "${symf}"
208	${OBJCOPY} --redefine-syms="${symf}" "${combined}" ||
209		die "objcopy failed: sh"
210	${OBJCOPY} --keep-global-symbol=sh_main "${combined}" ||
211		die "objcopy localize failed: sh"
212	rm -f "${symf}" ${sh_objs}
213
214	OBJS="${OBJS} ${combined}"
215	printf 'int sh_main(int, char **);\n' >> "${DECL_F}"
216	printf '\t{ "sh", sh_main, "posix" },\n' >> "${BDIR}/entries_posix.inc"
217}
218
219process_dir() {
220	dir="$1"
221	catg="$2"
222	extra="${3:-}"
223	for src in "${dir}"/*.c; do
224		[ -f "${src}" ] || continue
225		name=${src##*/}
226		name=${name%.c}
227		fn=$(printf '%s' "${name}" | tr '.-' '__')
228		compile_tool "${src}" "${name}" "${fn}" "${catg}" "${extra}"
229		if [ "${name}" = "test" ]; then
230			alias_entry "[" "test" "${catg}"
231		fi
232		if [ "${name}" = "xinstall" ]; then
233			alias_entry "install" "xinstall" "${catg}"
234		fi
235	done
236}
237
238process_dir "${ROOT}/cmd/posix" "posix"
239compile_awk
240compile_sh
241compile_make
242process_dir "${ROOT}/cmd/linux" "linux"
243process_dir "${ROOT}/cmd/net" "net"
244process_dir "${ROOT}/cmd/pseudo" "pseudo"
245process_dir "${ROOT}/cmd/xsi" "xsi"
246
247DISP="${BDIR}/aruu-box.c"
248printf 'mkbox: generating dispatch\n'
249
250cat > "${DISP}" << 'DISPATCH_HEAD'
251/* This software is released into the public domain, or
252   registered under the ISC License, see ./LICENSE for details
253   2026 xplshn [anto@xplshn.com.ar](mailto:anto@xplshn.com.ar) */
254
255/* routes argv[0] to the appropriate tool main */
256#include "util.h"
257
258#include <stdio.h>
259#include <stdlib.h>
260#include <string.h>
261#include <unistd.h>
262
263extern char *argv0;
264
265DISPATCH_HEAD
266
267cat "${DECL_F}" >> "${DISP}"
268
269cat >> "${DISP}" << 'DISPATCH_STRUCT'
270
271struct Cmd {
272	const char *name;
273	int (*fn)(int, char **);
274	const char *cat;
275};
276
277static struct Cmd cmds[] = {
278DISPATCH_STRUCT
279
280# sort each category alphabetically by tool name at build time;
281# entries have the form: \t{ "name", fn_main, "cat" },
282# splitting on " makes field 2 the tool name
283for _c in posix linux net pseudo xsi; do
284	sort -t'"' -k2,2 "${BDIR}/entries_${_c}.inc"
285done >> "${DISP}"
286unset _c
287
288cat >> "${DISP}" << 'DISPATCH_TAIL'
289	{ NULL, NULL, NULL }
290};
291
292static void
293usage(void)
294{
295	struct Cmd *c;
296	const char *last;
297	int col;
298
299	last = NULL;
300	col = 0;
301	fprintf(stderr, "usage: %s [-i path] [command [args...]]\n", argv0);
302	for (c = cmds; c->name; c++) {
303		if (!last || strcmp(c->cat, last) != 0) {
304			if (last)
305				fprintf(stderr, "\n\n");
306			last = c->cat;
307			fprintf(stderr, "%s:\n", last);
308			col = 0;
309		}
310		if (col == 0) {
311			fprintf(stderr, "\t%s", c->name);
312			col = 8 + strlen(c->name);
313		} else if (col + 2 + strlen(c->name) >= 78) {
314			fprintf(stderr, ",\n\t%s", c->name);
315			col = 8 + strlen(c->name);
316		} else {
317			fprintf(stderr, ", %s", c->name);
318			col += 2 + strlen(c->name);
319		}
320	}
321	if (last)
322		fputc('\n', stderr);
323	exit(1);
324}
325
326static void
327x_install(const char *path)
328{
329	struct Cmd *c;
330	char buf[4096];
331	int r;
332
333	for (c = cmds; c->name; c++) {
334		r = snprintf(buf, sizeof buf, "%s/%s", path, c->name);
335		if (r < 0 || r >= (int)sizeof buf)
336			eprintf("install: '%s/%s': path too long\n", path, c->name);
337		remove(buf);
338		if (symlink("aruu-box", buf) < 0)
339			eprintf("install: symlink '%s':", buf);
340	}
341}
342
343int
344main(int argc, char *argv[])
345{
346	struct Cmd *c;
347	char *s;
348
349	argv0 = argv[0];
350
351	s = strrchr(argv[0], '/');
352	if (s)
353		s++;
354	else
355		s = argv[0];
356
357	if (strcmp(s, "aruu-box") == 0) {
358		if (argc < 2)
359			usage();
360		argc--;
361		argv++;
362		if (strcmp(argv[0], "-i") == 0) {
363			if (argc < 2)
364				eprintf("usage: aruu-box -i path\n");
365			x_install(argv[1]);
366			return 0;
367		}
368		s = strrchr(argv[0], '/');
369		if (s)
370			s++;
371		else
372			s = argv[0];
373	}
374	for (c = cmds; c->name; c++) {
375		if (strcmp(c->name, s) == 0) {
376			argv0 = (char *)c->name;
377			argv[0] = (char *)c->name;
378			return c->fn(argc, argv);
379		}
380	}
381	enprintf(127, "%s: not found\n", s);
382	return 1;
383}
384DISPATCH_TAIL
385
386printf 'mkbox: cc dispatch\n'
387eval "${CC} ${CPPFLAGS} ${CFLAGS} -c \"\$DISP\" -o \"\${BDIR}/aruu-box.o\"" ||
388	die "compile failed: dispatch"
389OBJS="${OBJS} ${BDIR}/aruu-box.o"
390
391printf 'mkbox: ld aruu-box\n'
392eval "${CC} ${LDFLAGS} -o \"\$BOX\" \$OBJS \"\$LIBREDLINE\" \"\$LIBUTIL\" \"\$LIBUTF\" \${LDLIBS}" ||
393	die "link failed"
394
395printf 'mkbox: done => %s\n' "${BOX}"