aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorEuAndreh <eu@euandre.org>2024-01-02 17:28:59 -0300
committerEuAndreh <eu@euandre.org>2024-01-05 05:43:22 -0300
commit2e96fc06f325110af8c7177c0c60694eed5fd245 (patch)
treeba4e2ae8c50136cbf2f00a0edc8f31c90901bb64 /tools
parentsrc/internal/version.h: Check-in file with fixed value for compilation (diff)
downloadgrovel-2e96fc06f325110af8c7177c0c60694eed5fd245.tar.gz
grovel-2e96fc06f325110af8c7177c0c60694eed5fd245.tar.xz
Fix the build system.
The improvements are: - use most of the default "Makefile" for standard packaging; - also use the default ".gitignore" with for the derived assets; - don't impose so many $CFLAGS on the user. GCC still needs to be given the `-ffreestanding` flag explicitly for us to get a good binary; - stop using ad-hoc tools/* scripts, and avoid the code-generation anti-pattern overall on the build. Some of the generated files were checked-in, and some were removed; - remove empty files; - use POSIX make(1) over gmake; - add fuzz targets; - partial "install" and "uninstall" targets; - complete "clean" target. The shortcomings are: - only working on x86_64. More platforms coming soon; - code is still messy: way too many warnings, GNU/BSD specific extensions, inline assembly, and all kinds of unportable code; - still only works with GCC and GCC-like compilers, and completly fails with tcc(1) and cproc(1); - the `deps.mk` file is being maintained manually. As I work on the source files I'll finish automating its generation with `mkdeps.sh`; - still seems to be coupled with Linux; - still is missing tests setup; - still uses `#include <$NAME.h>` instead of the correct `#include "$NAME.h"` form. The generated libgrovel.a did match the previous lib/libc.a 100%.
Diffstat (limited to 'tools')
-rw-r--r--tools/add-cfi.common.awk26
-rw-r--r--tools/add-cfi.i386.awk209
-rw-r--r--tools/add-cfi.x86_64.awk196
-rw-r--r--tools/lib.sh49
-rwxr-xr-xtools/makehelp.sh149
-rwxr-xr-xtools/manpages.sh126
-rw-r--r--tools/mkalltypes.sed15
7 files changed, 324 insertions, 446 deletions
diff --git a/tools/add-cfi.common.awk b/tools/add-cfi.common.awk
deleted file mode 100644
index 04482d43..00000000
--- a/tools/add-cfi.common.awk
+++ /dev/null
@@ -1,26 +0,0 @@
-function hex2int(str, i) {
- str = tolower(str)
-
- for (i = 1; i <= 16; i++) {
- char = substr("0123456789abcdef", i, 1)
- lookup[char] = i-1
- }
-
- result = 0
- for (i = 1; i <= length(str); i++) {
- result = result * 16
- char = substr(str, i, 1)
- result = result + lookup[char]
- }
- return result
-}
-
-function parse_const(str) {
- sign = sub(/^-/, "", str)
- hex = sub(/^0x/, "", str)
- if (hex)
- n = hex2int(str)
- else
- n = str+0
- return sign ? -n : n
-}
diff --git a/tools/add-cfi.i386.awk b/tools/add-cfi.i386.awk
deleted file mode 100644
index d05037de..00000000
--- a/tools/add-cfi.i386.awk
+++ /dev/null
@@ -1,209 +0,0 @@
-# Insert GAS CFI directives ("control frame information") into x86-32 asm input
-#
-# CFI directives tell the assembler how to generate "stack frame" debug info
-# This information can tell a debugger (like gdb) how to find the current stack
-# frame at any point in the program code, and how to find the values which
-# various registers had at higher points in the call stack
-# With this information, the debugger can show a backtrace, and you can move up
-# and down the call stack and examine the values of local variables
-
-BEGIN {
- # don't put CFI data in the .eh_frame ELF section (which we don't keep)
- print ".cfi_sections .debug_frame"
-
- # only emit CFI directives inside a function
- in_function = 0
-
- # emit .loc directives with line numbers from original source
- printf ".file 1 \"%s\"\n", ARGV[1]
- line_number = 0
-
- # used to detect "call label; label:" trick
- called = ""
-}
-
-function get_const1() {
- # for instructions with 2 operands, get 1st operand (assuming it is constant)
- match($0, /-?(0x[0-9a-fA-F]+|[0-9]+),/)
- return parse_const(substr($0, RSTART, RLENGTH-1))
-}
-
-function canonicalize_reg(register) {
- if (match(register, /^e/))
- return register
- else if (match(register, /[hl]$/)) # AH, AL, BH, BL, etc
- return "e" substr(register, 1, 1) "x"
- else # AX, BX, CX, etc
- return "e" register
-}
-function get_reg() {
- # only use if you already know there is 1 and only 1 register
- match($0, /%e?([abcd][hlx]|si|di|bp)/)
- return canonicalize_reg(substr($0, RSTART+1, RLENGTH-1))
-}
-function get_reg1() {
- # for instructions with 2 operands, get 1st operand (assuming it is register)
- match($0, /%e?([abcd][hlx]|si|di|bp),/)
- return canonicalize_reg(substr($0, RSTART+1, RLENGTH-2))
-}
-function get_reg2() {
- # for instructions with 2 operands, get 2nd operand (assuming it is register)
- match($0, /,%e?([abcd][hlx]|si|di|bp)/)
- return canonicalize_reg(substr($0, RSTART+2, RLENGTH-2))
-}
-
-function adjust_sp_offset(delta) {
- if (in_function)
- printf ".cfi_adjust_cfa_offset %d\n", delta
-}
-
-{
- line_number = line_number + 1
-
- # clean the input up before doing anything else
- # delete comments
- gsub(/(#|\/\/).*/, "")
-
- # canonicalize whitespace
- gsub(/[ \t]+/, " ") # mawk doesn't understand \s
- gsub(/ *, */, ",")
- gsub(/ *: */, ": ")
- gsub(/ $/, "")
- gsub(/^ /, "")
-}
-
-# check for assembler directives which we care about
-/^\.(section|data|text)/ {
- # a .cfi_startproc/.cfi_endproc pair should be within the same section
- # otherwise, clang will choke when generating ELF output
- if (in_function) {
- print ".cfi_endproc"
- in_function = 0
- }
-}
-/^\.type [a-zA-Z0-9_]+,@function/ {
- functions[substr($2, 1, length($2)-10)] = 1
-}
-# not interested in assembler directives beyond this, just pass them through
-/^\./ {
- print
- next
-}
-
-/^[a-zA-Z0-9_]+:/ {
- label = substr($1, 1, length($1)-1) # drop trailing :
-
- if (called == label) {
- # note adjustment of stack pointer from "call label; label:"
- adjust_sp_offset(4)
- }
-
- if (functions[label]) {
- if (in_function)
- print ".cfi_endproc"
-
- in_function = 1
- print ".cfi_startproc"
-
- for (register in saved)
- delete saved[register]
- for (register in dirty)
- delete dirty[register]
- }
-
- # an instruction may follow on the same line, so continue processing
-}
-
-/^$/ { next }
-
-{
- called = ""
- printf ".loc 1 %d\n", line_number
- print
-}
-
-# KEEPING UP WITH THE STACK POINTER
-# We do NOT attempt to understand foolish and ridiculous tricks like stashing
-# the stack pointer and then using %esp as a scratch register, or bitshifting
-# it or taking its square root or anything stupid like that.
-# %esp should only be adjusted by pushing/popping or adding/subtracting constants
-#
-/pushl?/ {
- if (match($0, / %(ax|bx|cx|dx|di|si|bp|sp)/))
- adjust_sp_offset(2)
- else
- adjust_sp_offset(4)
-}
-/popl?/ {
- if (match($0, / %(ax|bx|cx|dx|di|si|bp|sp)/))
- adjust_sp_offset(-2)
- else
- adjust_sp_offset(-4)
-}
-/addl? \$-?(0x[0-9a-fA-F]+|[0-9]+),%esp/ { adjust_sp_offset(-get_const1()) }
-/subl? \$-?(0x[0-9a-fA-F]+|[0-9]+),%esp/ { adjust_sp_offset(get_const1()) }
-
-/call/ {
- if (match($0, /call [0-9]+f/)) # "forward" label
- called = substr($0, RSTART+5, RLENGTH-6)
- else if (match($0, /call [0-9a-zA-Z_]+/))
- called = substr($0, RSTART+5, RLENGTH-5)
-}
-
-# TRACKING REGISTER VALUES FROM THE PREVIOUS STACK FRAME
-#
-/pushl? %e(ax|bx|cx|dx|si|di|bp)/ { # don't match "push (%reg)"
- # if a register is being pushed, and its value has not changed since the
- # beginning of this function, the pushed value can be used when printing
- # local variables at the next level up the stack
- # emit '.cfi_rel_offset' for that
-
- if (in_function) {
- register = get_reg()
- if (!saved[register] && !dirty[register]) {
- printf ".cfi_rel_offset %s,0\n", register
- saved[register] = 1
- }
- }
-}
-
-/movl? %e(ax|bx|cx|dx|si|di|bp),-?(0x[0-9a-fA-F]+|[0-9]+)?\(%esp\)/ {
- if (in_function) {
- register = get_reg()
- if (match($0, /-?(0x[0-9a-fA-F]+|[0-9]+)\(%esp\)/)) {
- offset = parse_const(substr($0, RSTART, RLENGTH-6))
- } else {
- offset = 0
- }
- if (!saved[register] && !dirty[register]) {
- printf ".cfi_rel_offset %s,%d\n", register, offset
- saved[register] = 1
- }
- }
-}
-
-# IF REGISTER VALUES ARE UNCEREMONIOUSLY TRASHED
-# ...then we want to know about it.
-#
-function trashed(register) {
- if (in_function && !saved[register] && !dirty[register]) {
- printf ".cfi_undefined %s\n", register
- }
- dirty[register] = 1
-}
-# this does NOT exhaustively check for all possible instructions which could
-# overwrite a register value inherited from the caller (just the common ones)
-/mov.*,%e?([abcd][hlx]|si|di|bp)$/ { trashed(get_reg2()) }
-/(add|addl|sub|subl|and|or|xor|lea|sal|sar|shl|shr).*,%e?([abcd][hlx]|si|di|bp)$/ {
- trashed(get_reg2())
-}
-/^i?mul [^,]*$/ { trashed("eax"); trashed("edx") }
-/^i?mul.*,%e?([abcd][hlx]|si|di|bp)$/ { trashed(get_reg2()) }
-/^i?div/ { trashed("eax"); trashed("edx") }
-/(dec|inc|not|neg|pop) %e?([abcd][hlx]|si|di|bp)/ { trashed(get_reg()) }
-/cpuid/ { trashed("eax"); trashed("ebx"); trashed("ecx"); trashed("edx") }
-
-END {
- if (in_function)
- print ".cfi_endproc"
-}
diff --git a/tools/add-cfi.x86_64.awk b/tools/add-cfi.x86_64.awk
deleted file mode 100644
index 7e1513d6..00000000
--- a/tools/add-cfi.x86_64.awk
+++ /dev/null
@@ -1,196 +0,0 @@
-# Insert GAS CFI directives ("control frame information") into x86-64 asm input
-
-BEGIN {
- # don't put CFI data in the .eh_frame ELF section (which we don't keep)
- print ".cfi_sections .debug_frame"
-
- # only emit CFI directives inside a function
- in_function = 0
-
- # emit .loc directives with line numbers from original source
- printf ".file 1 \"%s\"\n", ARGV[1]
- line_number = 0
-
- # used to detect "call label; label:" trick
- called = ""
-}
-
-function get_const1() {
- # for instructions with 2 operands, get 1st operand (assuming it is constant)
- match($0, /-?(0x[0-9a-fA-F]+|[0-9]+),/)
- return parse_const(substr($0, RSTART, RLENGTH-1))
-}
-
-function canonicalize_reg(register) {
- if (match(register, /^r/))
- return register
- else if (match(register, /^e/))
- return "r" substr(register, 2, length(register)-1)
- else if (match(register, /[hl]$/)) # AH, AL, BH, BL, etc
- return "r" substr(register, 1, 1) "x"
- else # AX, BX, CX, etc
- return "r" register
-}
-function get_reg() {
- # only use if you already know there is 1 and only 1 register
- match($0, /%[er]?([abcd][xlh]|si|di|bp|8|9|10|11|12|13|14|15)/)
- return canonicalize_reg(substr($0, RSTART+1, RLENGTH-1))
-}
-function get_reg1() {
- # for instructions with 2 operands, get 1st operand (assuming it is register)
- match($0, /%[er]?([abcd][xlh]|si|di|bp|8|9|10|11|12|13|14|15),/)
- return canonicalize_reg(substr($0, RSTART+1, RLENGTH-2))
-}
-function get_reg2() {
- # for instructions with 2 operands, get 2nd operand (assuming it is register)
- match($0, /,%[er]?([abcd][xlh]|si|di|bp|8|9|10|11|12|13|14|15)/)
- return canonicalize_reg(substr($0, RSTART+2, RLENGTH-2))
-}
-
-function adjust_sp_offset(delta) {
- if (in_function)
- printf ".cfi_adjust_cfa_offset %d\n", delta
-}
-
-{
- line_number = line_number + 1
-
- # clean the input up before doing anything else
- # delete comments
- gsub(/(#|\/\/).*/, "")
-
- # canonicalize whitespace
- gsub(/[ \t]+/, " ") # mawk doesn't understand \s
- gsub(/ *, */, ",")
- gsub(/ *: */, ": ")
- gsub(/ $/, "")
- gsub(/^ /, "")
-}
-
-# check for assembler directives which we care about
-/^\.(section|data|text)/ {
- # a .cfi_startproc/.cfi_endproc pair should be within the same section
- # otherwise, clang will choke when generating ELF output
- if (in_function) {
- print ".cfi_endproc"
- in_function = 0
- }
-}
-/^\.type [a-zA-Z0-9_]+,@function/ {
- functions[substr($2, 1, length($2)-10)] = 1
-}
-# not interested in assembler directives beyond this, just pass them through
-/^\./ {
- print
- next
-}
-
-/^[a-zA-Z0-9_]+:/ {
- label = substr($1, 1, length($1)-1) # drop trailing :
-
- if (called == label) {
- # note adjustment of stack pointer from "call label; label:"
- adjust_sp_offset(8)
- }
-
- if (functions[label]) {
- if (in_function)
- print ".cfi_endproc"
-
- in_function = 1
- print ".cfi_startproc"
-
- for (register in saved)
- delete saved[register]
- for (register in dirty)
- delete dirty[register]
- }
-
- # an instruction may follow on the same line, so continue processing
-}
-
-/^$/ { next }
-
-{
- called = ""
- printf ".loc 1 %d\n", line_number
- print
-}
-
-# KEEPING UP WITH THE STACK POINTER
-# %rsp should only be adjusted by pushing/popping or adding/subtracting constants
-#
-/pushl?/ {
- adjust_sp_offset(8)
-}
-/popl?/ {
- adjust_sp_offset(-8)
-}
-/addl? \$-?(0x[0-9a-fA-F]+|[0-9]+),%rsp/ { adjust_sp_offset(-get_const1()) }
-/subl? \$-?(0x[0-9a-fA-F]+|[0-9]+),%rsp/ { adjust_sp_offset(get_const1()) }
-
-/call/ {
- if (match($0, /call [0-9]+f/)) # "forward" label
- called = substr($0, RSTART+5, RLENGTH-6)
- else if (match($0, /call [0-9a-zA-Z_]+/))
- called = substr($0, RSTART+5, RLENGTH-5)
-}
-
-# TRACKING REGISTER VALUES FROM THE PREVIOUS STACK FRAME
-#
-/pushl? %r(ax|bx|cx|dx|si|di|bp|8|9|10|11|12|13|14|15)/ { # don't match "push (%reg)"
- # if a register is being pushed, and its value has not changed since the
- # beginning of this function, the pushed value can be used when printing
- # local variables at the next level up the stack
- # emit '.cfi_rel_offset' for that
-
- if (in_function) {
- register = get_reg()
- if (!saved[register] && !dirty[register]) {
- printf ".cfi_rel_offset %s,0\n", register
- saved[register] = 1
- }
- }
-}
-
-/movl? %r(ax|bx|cx|dx|si|di|bp|8|9|10|11|12|13|14|15),-?(0x[0-9a-fA-F]+|[0-9]+)?\(%rsp\)/ {
- if (in_function) {
- register = get_reg()
- if (match($0, /-?(0x[0-9a-fA-F]+|[0-9]+)\(%rsp\)/)) {
- offset = parse_const(substr($0, RSTART, RLENGTH-6))
- } else {
- offset = 0
- }
- if (!saved[register] && !dirty[register]) {
- printf ".cfi_rel_offset %s,%d\n", register, offset
- saved[register] = 1
- }
- }
-}
-
-# IF REGISTER VALUES ARE UNCEREMONIOUSLY TRASHED
-# ...then we want to know about it.
-#
-function trashed(register) {
- if (in_function && !saved[register] && !dirty[register]) {
- printf ".cfi_undefined %s\n", register
- }
- dirty[register] = 1
-}
-# this does NOT exhaustively check for all possible instructions which could
-# overwrite a register value inherited from the caller (just the common ones)
-/mov.*,%[er]?([abcd][xlh]|si|di|bp|8|9|10|11|12|13|14|15)$/ { trashed(get_reg2()) }
-/(add|addl|sub|subl|and|or|xor|lea|sal|sar|shl|shr).*,%[er]?([abcd][xlh]|si|di|bp|8|9|10|11|12|13|14|15)$/ {
- trashed(get_reg2())
-}
-/^i?mul [^,]*$/ { trashed("rax"); trashed("rdx") }
-/^i?mul.*,%[er]?([abcd][xlh]|si|di|bp|8|9|10|11|12|13|14|15)$/ { trashed(get_reg2()) }
-/^i?div/ { trashed("rax"); trashed("rdx") }
-
-/(dec|inc|not|neg|pop) %[er]?([abcd][xlh]|si|di|bp|8|9|10|11|12|13|14|15)/ { trashed(get_reg()) }
-/cpuid/ { trashed("rax"); trashed("rbx"); trashed("rcx"); trashed("rdx") }
-
-END {
- if (in_function)
- print ".cfi_endproc"
-}
diff --git a/tools/lib.sh b/tools/lib.sh
new file mode 100644
index 00000000..0412d7c6
--- /dev/null
+++ b/tools/lib.sh
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+assert_arg() {
+ if [ -z "$1" ]; then
+ printf 'Missing %s.\n\n' "$2" >&2
+ cat <<-'EOF'
+ usage >&2
+ exit 2
+ EOF
+ fi
+}
+
+uuid() {
+ od -xN20 /dev/urandom |
+ head -n1 |
+ awk '{OFS="-"; print $2$3,$4,$5,$6,$7$8$9}'
+}
+
+tmpname() {
+ echo "${TMPDIR:-/tmp}/uuid-tmpname with spaces.$(uuid)"
+}
+
+mkstemp() {
+ name="$(tmpname)"
+ touch "$name"
+ echo "$name"
+}
+
+mkdtemp() {
+ name="$(tmpname)"
+ mkdir "$name"
+ echo "$name"
+}
+
+END="\033[0m"
+yellow() {
+ YELLOW="\033[0;33m"
+ printf "${YELLOW}${1}${END}"
+}
+
+green() {
+ GREEN="\033[0;32m"
+ printf "${GREEN}${1}${END}"
+}
+
+red() {
+ RED="\033[0;31m"
+ printf "${RED}${1}${END}"
+}
diff --git a/tools/makehelp.sh b/tools/makehelp.sh
new file mode 100755
index 00000000..e6118de7
--- /dev/null
+++ b/tools/makehelp.sh
@@ -0,0 +1,149 @@
+#!/bin/sh
+set -eu
+
+. tools/lib.sh
+
+
+usage() {
+ cat <<-'EOF'
+ Usage:
+ makehelp.sh < MAKEFILE
+ makehelp.sh -h
+ EOF
+}
+
+help() {
+ cat <<-'EOF'
+
+
+ Options:
+ -h, --help show this message
+
+
+ Generate a help message from the given Makefile.
+
+ Any target or variable commented with two "#" characters gets
+ picked up. Multi-line comments are supported:
+
+ VAR1 = 1
+ # a comment
+ VAR2 = 2
+ ## another comment -> this one is included in the docs
+ VAR3 = 3
+
+ ## with a big
+ ## comment, which is also included
+ a-target:
+
+
+ Examples:
+
+ Generate help messages from "Makefile":
+
+ $ aux/makehelp.sh < Makefile
+
+
+ Generate help messages for all targets:
+
+ $ cat Makefile dev.mk | aux/makehelp.sh
+ EOF
+}
+
+
+for flag in "$@"; do
+ case "$flag" in
+ (--)
+ break
+ ;;
+ (--help)
+ usage
+ help
+ exit
+ ;;
+ (*)
+ ;;
+ esac
+done
+
+while getopts 'h' flag; do
+ case "$flag" in
+ (h)
+ usage
+ help
+ exit
+ ;;
+ (*)
+ usage >&2
+ exit 2
+ ;;
+ esac
+done
+shift $((OPTIND - 1))
+
+
+TARGETS="$(mkstemp)"
+VARIABLES="$(mkstemp)"
+trap 'rm -f "$TARGETS" "$VARIABLES"' EXIT
+
+awk -vCOLUMN=15 -vTARGETS="$TARGETS" -vVARIABLES="$VARIABLES" '
+function indent(n, where) {
+ for (INDENT = 0; INDENT < n; INDENT++) {
+ printf " " > where
+ }
+}
+
+/^## / { doc[len++] = substr($0, 4) }
+
+/^[-_a-zA-Z]+:/ && len {
+ printf "\033[36m%s\033[0m", substr($1, 1, length($1) - 1) > TARGETS
+ for (i = 0; i < len; i++) {
+ n = COLUMN - (i == 0 ? length($1) - 1 : 0)
+ indent(n, TARGETS)
+ printf "%s\n", doc[i] > TARGETS
+ }
+ len = 0
+}
+
+/^.++=/ && len {
+ printf "\033[36m%s\033[0m", $1 > VARIABLES
+ for (i = 0; i < len; i++) {
+ n = COLUMN - (i == 0 ? length($1) : 0)
+ indent(n, VARIABLES)
+ printf "%s\n", doc[i] > VARIABLES
+ }
+ len = 0
+}'
+
+
+
+indent() {
+ sed 's|^| |'
+}
+
+cat <<-EOF
+ Usage:
+
+ make [VARIABLE=value...] [target...]
+
+
+ Targets:
+
+ $(indent < "$TARGETS")
+
+
+ Variables:
+
+ $(indent < "$VARIABLES")
+
+
+ Examples:
+
+ Build "all", the default target:
+
+ $ make
+
+
+ Test and install, with \$(DESTDIR) set to "tmp/":
+
+ $ make DESTDIR=tmp check install
+EOF
diff --git a/tools/manpages.sh b/tools/manpages.sh
new file mode 100755
index 00000000..755ff777
--- /dev/null
+++ b/tools/manpages.sh
@@ -0,0 +1,126 @@
+#!/bin/sh
+set -eu
+
+. tools/lib.sh
+
+
+usage() {
+ cat <<-'EOF'
+ Usage:
+ sh doc/manpages.sh -i|-u -p MANDIR FILE...
+ sh doc/manpages.sh -h
+ EOF
+}
+
+help() {
+ cat <<-'EOF'
+
+
+ Options:
+ -i set ACTION=install
+ -u set ACTION=uninstall
+ -p MANDIR use $MANDIR as the prefix directory
+ -h, --help show this message
+
+
+ Installs/uninstalls manpage files into MANDIR using the original
+ filename as a hint towards which subdirectory to pick: the
+ `prog.ru.1` file is installed under the `man1/` directory
+ hierarchy under the `ru/` language folder. When then language
+ is `en`, a the toplevel `man1/` gets a symlink to the `en/`
+ language folder:
+
+ man/
+ en/
+ man1/
+ prog.1
+ ru/
+ man1/
+ prog.1
+ man1/
+ prog.1 -> ../en/man1/prog.1
+
+
+ Examples:
+
+ Install prog.en.1 under `/usr/man`:
+
+ $ sh doc/manpages.sh -ip/usr/man prog.en.1
+
+
+ Uninstall all files listed under doc/ from `$PREFIX`:
+
+ sh doc/manpages.sh -up "$PREFIX" doc/*.[0-9]
+ EOF
+}
+
+
+for flag in "$@"; do
+ case "$flag" in
+ (--)
+ break
+ ;;
+ (--help)
+ usage
+ help
+ exit
+ ;;
+ (*)
+ ;;
+ esac
+done
+
+while getopts 'iup:h' flag; do
+ case "$flag" in
+ (i)
+ ACTION=install
+ ;;
+ (u)
+ ACTION=uninstall
+ ;;
+ (p)
+ MANDIR="$OPTARG"
+ ;;
+ (h)
+ usage
+ help
+ exit
+ ;;
+ (*)
+ usage >&2
+ exit 2
+ ;;
+ esac
+done
+shift $((OPTIND - 1))
+
+eval "$(assert_arg "${ACTION:-}" '-[iu] for choosing action')"
+eval "$(assert_arg "${MANDIR:-}" '-p MANDIR')"
+eval "$(assert_arg "${1:-}" 'FILE...')"
+
+
+for f in "$@"; do
+ l="$(echo "$f" | awk -F. '{print $(NF-1)}')"
+ n="$(echo "$f" | awk -F. '{print $NF}')"
+ case "$ACTION" in
+ (install)
+ to_name="$(basename "${f%."$l"."$n"}.$n")"
+ mkdir -p "$MANDIR/$l/man$n" "$MANDIR/man$n"
+ cp "$f" "$MANDIR/$l/man$n/$to_name"
+ if [ "$l" = 'en' ]; then
+ ln -fs "../en/man$n/$to_name" \
+ "$MANDIR/man$n/$to_name"
+ fi
+ ;;
+ (uninstall)
+ to_name="$(basename "${f%."$l"."$n"}.$n")"
+ rm -f \
+ "$MANDIR/$l/man$n/$to_name" \
+ "$MANDIR/man$n/$to_name"
+ ;;
+ (*)
+ echo "Bad ACTION: $ACTION"
+ exit 1
+ ;;
+ esac
+done
diff --git a/tools/mkalltypes.sed b/tools/mkalltypes.sed
deleted file mode 100644
index fa15efc3..00000000
--- a/tools/mkalltypes.sed
+++ /dev/null
@@ -1,15 +0,0 @@
-/^TYPEDEF/s/TYPEDEF \(.*\) \([^ ]*\);$/#if defined(__NEED_\2) \&\& !defined(__DEFINED_\2)\
-typedef \1 \2;\
-#define __DEFINED_\2\
-#endif\
-/
-/^STRUCT/s/STRUCT * \([^ ]*\) \(.*\);$/#if defined(__NEED_struct_\1) \&\& !defined(__DEFINED_struct_\1)\
-struct \1 \2;\
-#define __DEFINED_struct_\1\
-#endif\
-/
-/^UNION/s/UNION * \([^ ]*\) \(.*\);$/#if defined(__NEED_union_\1) \&\& !defined(__DEFINED_union_\1)\
-union \1 \2;\
-#define __DEFINED_union_\1\
-#endif\
-/