Last active
August 15, 2024 14:48
-
-
Save q3cpma/a943649267a07d67fc60261ada2c2015 to your computer and use it in GitHub Desktop.
Generate a compilation database in pure sh
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
# Portability: Linux, *BSD, MacOS, Illumos (mktemp -d) | |
# Dependencies: realpath (POSIX 2024), jq (optional) | |
set -eu | |
echo() { printf '%s\n' "$*"; } | |
die() { echo "$@" >&2; exit 1; } | |
quote() { printf "'%s'\n" "$(printf '%s\n' "$1" | sed "s#'#'\\\\''#g")"; } | |
usage() | |
{ | |
_name=$(basename "$0") | |
cat <<EOF | { [ $1 -eq 0 ] && cat || cat >&2; } | |
NAME | |
$_name - Generate a JSON compilation database | |
SYNOPSIS | |
$_name [-h] [-o OUT_PATH] COMPILER -- BUILD_CMD... | |
DESCRIPTION | |
Similar to bear [1], intercepts compiler calls during a clean build to | |
assemble a compilation database for use with clang tools (cf [2]). | |
The two mechanism to do so are PATH overriding (chosen when COMPILER is | |
a command, e.g. "gcc") and CC/CXX environment variable overriding | |
(fallback for when COMPILER is a path). | |
If no OUT_PATH is given, the result is written to "compile_commands.json". | |
[1] https://github.com/rizsotto/Bear | |
[2] https://clang.llvm.org/docs/JSONCompilationDatabase.html | |
OPTIONS | |
-h | |
Print this notice to stdout and exit. | |
EOF | |
exit $1 | |
} | |
out=compile_commands.json | |
while getopts "ho:" OPT | |
do | |
case "$OPT" in | |
h) usage 0;; | |
o) out=$OPTARG;; | |
\?) usage 1;; | |
esac | |
done | |
shift $((OPTIND - 1)) | |
{ [ $# -lt 3 ] || [ "$2" != -- ]; } && usage 1 | |
compiler=$1 | |
shift 2 | |
compiler_base=${compiler##*/} | |
if [ "$compiler_base" != "$compiler" ] | |
then | |
compiler_iscmd=false | |
echo "WARNING: compiler was specified by a path instead of a command, interception will" \ | |
"only work through CC/CXX environment variables." >&2 | |
else | |
compiler_iscmd=true | |
fi | |
workdir=$(mktemp -d) | |
trap 'exit 1' HUP INT QUIT ${ZSH_VERSION-ABRT} TERM | |
trap 'rm -r -- "$workdir"' EXIT | |
wrapper=$workdir/$compiler | |
json_tmpdir=$workdir/json_tmp | |
mkdir "$json_tmpdir" | |
cat <<EOF >"$wrapper" | |
#!/bin/sh | |
set -eu | |
compiler=$(quote "$compiler") | |
json_tmpdir=$(quote "$json_tmpdir") | |
is_source() | |
{ | |
case "\$1" in | |
EOF | |
case "$compiler_base" in | |
cc|c99|gcc|clang|icc) | |
echo "*.c) return 0" >>"$wrapper" | |
! $compiler_iscmd && export CC="$wrapper" | |
;; | |
g++|clang++|icpc) | |
echo "*.cc|*.cpp|*.cxx) return 0'" >>"$wrapper" | |
! $compiler_iscmd && export CXX="$wrapper" | |
;; | |
*) die "$compiler: unknown compiler";; | |
esac | |
cat <<'EOF' >>"$wrapper" | |
esac | |
return 1 | |
} | |
js_str() { printf '%s\n' "$1" | sed 's/["\\]/\\&/g; s/^/"/; s/$/"/'; } | |
for arg | |
do | |
if is_source "$arg" | |
then | |
abs=$(realpath -- "$arg") || continue | |
file=${abs##*/} | |
dir=${abs%/*} | |
mkdir -p "$json_tmpdir$dir" | |
! [ "${arg_jsarr:-}" ] && | |
arg_jsarr=[$(for i in "$compiler" "$@"; do js_str "$i"; done | paste -sd, -)] | |
printf '{"directory": %s, "arguments": %s, "file": %s}\n' \ | |
"$(js_str "$dir")" \ | |
"$arg_jsarr" \ | |
"$(js_str "$file")" >"$json_tmpdir/$abs.json" | |
fi | |
done | |
PATH="${PATH#*:}" exec "$compiler" "$@" | |
EOF | |
chmod u+x "$wrapper" | |
PATH="$workdir:$PATH" "$@" | |
find "$json_tmpdir" -type f -exec cat {} + | | |
{ buf=$(cat); ! [ "$buf" ] && die "No compiler calls intercepted"; echo "$buf"; } | | |
sed '1s/^/[\n/; $!s/$/,/; $s/$/\n]/' | | |
{ command -v jq >/dev/null && jq || cat; } >"${out:-compile_commands.json}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment