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}"