1
0
Fork 0
mirror of https://github.com/notwa/rc synced 2024-12-21 21:49:40 -08:00
rc/sh/compile

358 lines
14 KiB
Bash
Executable file

#!/usr/bin/env zsh
# compat: -ash -bash -dash +zsh
setup_clang_ubuntu() { ### @-
### print (but don't execute) the commands necessary to install
### a fairly recent version of clang on ubuntu-based distros.
###
### ```sh
### $ setup_clang_ubuntu noble
### wget -O- http://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -
### echo > "/etc/apt/sources.list.d/llvm-toolchain-noble.list" \
### "
### deb http://apt.llvm.org/noble/ llvm-toolchain-noble main
### # deb-src http://apt.llvm.org/noble/ llvm-toolchain-noble main
### # 18
### deb http://apt.llvm.org/noble/ llvm-toolchain-noble-18 main
### # deb-src http://apt.llvm.org/noble/ llvm-toolchain-noble-18 main"
### export DEBIAN_FRONTEND=noninteractive NEEDRESTART_SUSPEND=1
### apt-get update -y && apt-get install -y clang-18 lld-18
### update-alternatives --install /usr/bin/clang clang /usr/bin/clang-18 1800
### update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-18 1800
### update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-18 1800
### ```
local site="http://apt.llvm.org"
local name="$1"
local version=18 # NOTE: no longer decimal-based
local priority=$(( version * 100 ))
[ -n "$name" ] || name="$(lsb_release -c | cut -f2)"
# TODO: use https? this is sketchy
echo wget -O- "$site/llvm-snapshot.gpg.key" \| apt-key add -
printf %s\\n "echo > \"/etc/apt/sources.list.d/llvm-toolchain-$name.list\" \\" \
'"' \
"deb $site/$name/ llvm-toolchain-$name main" \
"# deb-src $site/$name/ llvm-toolchain-$name main" \
"# $version" \
"deb $site/$name/ llvm-toolchain-$name-$version main" \
"# deb-src $site/$name/ llvm-toolchain-$name-$version main\""
echo export DEBIAN_FRONTEND=noninteractive NEEDRESTART_SUSPEND=1
echo apt-get update -y \&\& apt-get install -y clang-$version lld-$version
#echo needrestart -ra
for p in clang clang++ llvm-symbolizer; do
echo update-alternatives --install /usr/bin/$p $p /usr/bin/$p-$version $priority
done
}
compile() { ### @-
### compile single-file C and C++ programs, messily.
###
### supports gcc and clang on \*nix, and mingw64 gcc, msvc clang,
### and regular msvc on Windows. tested on x86\_64 and on ARMv7 as well.
### does not support MacOS, maybe someday…
###
### defaults to gnu11 and gnu++1z as C and C++ standards respectively.
### defaults to clang, gcc, and msvc in that order.
###
### `compile` attempts to guess the most sane switches for any program, so that compilation may reduce to:
###
### ```sh
### # debug build
### compile rd.c
### compile debug rd.c
### # debug build with warning/error flags defined in ~/sh/arrays
### # (requires .zshrc for global alias expansion)
### compile WHOA rd.c
### # likewise for C++
### compile WHOA WELP rd.cc
### compile WHOA WELP rd.cpp
### # "derelease" build (release build with debug information)
### compile derelease WHOA rd.c
### # release build (with symbols stripped)
### compile release WHOA rd.c
### # hardened build (only useful on *nix)
### compile hardened WHOA rd.c
### # specifying compiler
### compile gcc WHOA rd.c
### compile msvc WHOA rd.c
### compile release clang WHOA rd.c
### # compile and execute (FIXME: writing to /tmp is a security concern)
### compile derelease rd.c && /tmp/rd
### ```
# FIXME: compile gcc portrend.c -lsdl
# this causes mayhem!
local gcc="$(whence -p gcc 2>/dev/null)"
local clang="$(whence -p clang 2>/dev/null)"
local clang_flags=() # currently just for clang-msvc
local flag= d= # iterator variables
local cl= vc=
if [ -n "$MSYSTEM" ]; then # using msys2?
if [ -z "$clang" ]; then # don't have native clang?
# then maybe we have clang-msvc installed.
local dir=
printf "%s\n" "/c/Program Files/LLVM"*(On/N[1]) | read -r dir
if [ -d "$dir" ] && [ -e "$dir/bin/clang" ]; then
clang="$dir/bin/clang"
# not sure if i'll need this:
#local clang_include=
#printf "%s\n" "$dir/lib/clang/"*(On/N[1]) | read -r clang_include
#[ -n "$clang_include" ] || { echo "failed glob; missing clang include" >&2; return 1 }
#clang_flags+=(-I"$clang_include/include")
export PATH="$PATH:$dir/bin/"
fi
fi
local winkit=
printf "%s\n" "/c/Program Files (x86)/Windows Kits/"*(on/N[1]) | read -r winkit
[ -z "$winkit" ] || printf "%s\n" "$winkit/Lib/"*(On/N[1]) | read -r winkit
if [ -z "$winkit" ]; then
#echo "failed glob; missing winkit" >&2
:
else
# detect MSVC.
local clarch= arch= msvc_dig_deep=
[ "$MSYSTEM" = MINGW64 ] && clarch="/amd64" || clarch=""
[ "$MSYSTEM" = MINGW64 ] && arch="x64" || arch="x86"
if [ -d "/c/Program Files (x86)/Microsoft Visual Studio" ]; then # 2017+
printf "%s\n" "/c/Program Files (x86)/Microsoft Visual Studio/20"*(On/N[1]) | read vc
printf "%s\n" "$vc"/*/VC | read vc
printf "%s\n" "$vc/Tools/MSVC/"*(On/N[1]) | read vc
msvc_dig_deep="yes"
else # older versions
printf "%s\n" "/c/Program Files (x86)/Microsoft Visual Studio "*(On/N[1]) | read vc
vc="$vc/VC"
fi
# setup MSVC.
if [ -n "$msvc_dig_deep" ] && [ -e "$vc/bin/Host$arch/$arch/cl" ]; then
cl="$vc/bin/Host$arch/$arch/cl"
export PATH="$PATH:$vc/bin/Host$arch/$arch"
export LIB="$(cygpath -w "$vc/lib/$arch")"
export LIBPATH="$(cygpath -w "$vc/lib/$arch")"
elif [ -d "$vc/bin$clarch" ] && [ -e "$vc/bin$clarch/$cl" ]; then
cl="$vc/bin$clarch/cl"
export PATH="$PATH:$vc/bin$clarch"
export LIB="$(cygpath -w "$vc/LIB$clarch")"
export LIBPATH="$(cygpath -w "$vc/LIB$clarch")"
fi
# finish up.
if [ -n "$cl" ]; then
export INCLUDE="$(cygpath -w "$vc/INCLUDE")"
export INCLUDE="$INCLUDE;$(cygpath -w "${winkit/Lib/Include}/ucrt")"
export LIB="$LIB;$(cygpath -w "$winkit/um/$arch")"
export LIB="$LIB;$(cygpath -w "$winkit/ucrt/$arch")"
for d in "${(@s/;/)INCLUDE}"; do
clang_flags+=(-I"$d")
done
# ignore MSVC's non-standard deprecation warnings.
clang_flags+=(-D_CRT_SECURE_NO_WARNINGS)
fi
fi
fi
local sep_once=
print_separated() {
echo -n "${sep_once:+|}$1" >&2
sep_once=1
}
if [ $# -eq 0 ]; then
echo -n "usage: compile [" >&2
[ -n "$clang" ] && print_separated "clang"
[ -n "$gcc" ] && print_separated "gcc"
[ -n "$cl" ] && print_separated "msvc"
echo "] [debug|derelease|release|hardened] [flags...] {source file}" >&2
return 1
fi
# set some defaults.
local sepples=0 CC= CXX=
[ -n "$clang" ] && CC=clang || CC=gcc
[ -n "$clang" ] && CXX=clang++ || CXX=g++
local our_flags=(-I.)
# guess if we're compiling C++ by the file extension.
local file=${@[-1]}
[ "${file##*.}" = "c" ] || [ "${file##*.}" = "h" ] || sepples=1
# select the appropriate executable if a compiler name is given.
{ [ "$1" = clang ] && CC="clang" && CXX="clang++" && shift } || \
{ [ "$1" = gcc ] && CC="gcc" && CXX="g++" && shift } || \
{ [ "$1" = msvc ] && CC="cl" && CXX="cl" && shift }
# always color outputs.
[ "$CC" = cl ] || our_flags+=-fdiagnostics-color=always
# add our clang-specific flags. (currently just for clang-msvc)
if [ $CC = clang ] && [ -n "$clang_flags" ]; then
for flag in $clang_flags; do
our_flags+=($flag)
done
fi
# if they exist, include some directories that contain useful headers.
maybe_include() {
[ -d "$1" ] && our_flags+=("-I$1")
}
#maybe_include "$HOME/opt/local/include"
#maybe_include "$HOME/src/ustl"
# set the build flags for each mode.
if [ $CC = cl ]; then
our_flags+=(-nologo -utf-8)
local debug_flags=(-Od -ZI -sdl);
local release_flags=(-Ox)
local dr_flags=(-Ox -Zi)
local hardened_flags=(-Ox -sdl)
else
if [ $CC = clang ]; then
# clang doesn't like -march=native on ARM for some reason.
our_flags+=(-mcpu=native)
else
our_flags+=(-march=native)
fi
local debug_flags=(-O1 -g -D_DEBUG);
local release_flags=(-Ofast -fwhole-program -fweb -mtune=native -g0 -fomit-frame-pointer -s -DNDEBUG)
local dr_flags=(-Ofast -g -fomit-frame-pointer -DNDEBUG)
local hardened_flags=(-O3 -g0 -s
-DNDEBUG -D_FORTIFY_SOURCE=2
-Wformat -Wformat-security -Werror=format-security)
if [ -z "$MSYSTEM" ]; then
hardened_flags+=(-fPIE -pie)
hardened_flags+=(-Wl,-O1,--sort-common,--as-needed,-z,relro,-z,now)
fi
local nomalloc=(-fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-free)
if [ -e /usr/bin/pprof ]; then
#debug_flags+=(-ltcmalloc $nomalloc)
dr_flags+=(-lprofiler $nomalloc)
elif [ -e /usr/bin/google-pprof ]; then
#debug_flags+=(-l:libtcmalloc.so.4 $nomalloc)
dr_flags+=(-l:libtcmalloc_and_profiler.so.4 $nomalloc)
fi
fi
# select appropriate version and executable for the language.
local compiler=
if [ $sepples -eq 1 ]; then
compiler=$CXX
[ $CC = cl ] && std="-TP" || std="-std=gnu++1z"
else
compiler=$CC
[ $CC = cl ] && std="-TC" || std="-std=gnu11"
fi
local clang_msvc=0
if [ $CC = clang ]; then
if $compiler --version | grep -q windows-msvc; then
clang_msvc=1
fi
fi
local gold=
# utilize clang's vast debugging and hardening features where available.
if [ $CC = clang ]; then
debug_flags+=(-ftrapv)
[ -z "$MSYSTEM" ] && gold=gold || gold=lld
[ -n "$MSYSTEM" ] && our_flags+=(-fansi-escape-codes) || true
if [ $clang_msvc -eq 1 ] || [ -z "$MSYSTEM" ]; then
debug_flags+=(-fsanitize=undefined) # this SHOULD work with mingw,
# but it fails to link.
debug_flags+=(-fsanitize=address)
debug_flags+=(-fvisibility=hidden -fuse-ld=$gold -flto -fsanitize=cfi)
hardened_flags+=(-fsanitize=safe-stack)
hardened_flags+=(-fstack-protector-strong)
hardened_flags+=(-fvisibility=hidden -fuse-ld=$gold -flto -fsanitize=cfi)
else
fi
fi
# select and merge the flags for our build mode.
# TODO: add static option.
{ [ "$1" = debug ] && our_flags+=($debug_flags) && shift } || \
{ [ "$1" = release ] && our_flags+=($release_flags) && shift } || \
{ [ "$1" = derelease ] && our_flags+=($dr_flags) && shift } || \
{ [ "$1" = hardened ] && our_flags+=($hardened_flags) && shift } || \
{ our_flags+=($debug_flags) } # our default
local flags=(${@[1,-2]})
# drop everything past the first dot and use that as our output filename.
local out=/tmp/${${file##*/}%%.*}
if [ -n "$MSYSTEM" ]; then
# explicitly output as .exe to avoid weirdness.
out="$out.exe"
fi
# TODO: naive idea:
# allow multiple source files (using the firstmost to determine the program name)
# by generating a file that #includes each given file.
local final_flags=() libraries=() warnings=()
for flag in $our_flags $flags; do
# move -l flags to the end because gcc won't respect them otherwise.
if [[ $flag == -l* ]]; then
libraries+=($flag)
# split warning flags so they don't spam the console.
elif [[ $flag == -W* ]] && [[ $flag != -Wl* ]] || [[ $flag == -Wlogical-op ]]; then
warnings+=($flag)
if [ $sepples -eq 0 ] && [[ $flag == -Wextra ]] then
# enable some warnings just for C. too annoying in C++.
warnings+=(-Wshadow -Winline)
# these ones only work with C.
warnings+=(-Wjump-misses-init)
fi
if [ $CC = cl ] && [ $flag = -Wall ]; then
# disable some obnoxious msvc warnings.
warnings+=(
-wd4505 # unreferenced local function has been removed
-wd4514 # unreferenced inline function has been removed
-wd4625 # copy constructor was implicitly defined as deleted because a base class copy constructor is inaccessible or deleted
-wd4626 # assignment operator was implicitly defined as deleted because a base class assignment operator is inaccessible or deleted
-wd4710 # function not inlined
-wd4711 # function selected for automatic inline expansion
)
fi
else
if [ $CC = clang ]; then
# these are specific to gcc. (for now)
if [ $flag = "-findirect-inlining" ] \
|| [ $flag = "-finline-small-functions" ]; then
continue
fi
fi
if [ $clang_msvc -eq 1 ]; then
# remove linker-related flags from our compiler-only clang.
if [ $flag = "-Wl,--gc-sections" ] \
|| [ $flag = "-s" ]; then
continue
fi
fi
final_flags+=($flag)
fi
done
# do the thing!
[ $CC = cl ] && local outflag=-Fe: || local outflag=-o
echo $compiler $std ${final_flags[@]} $file ${libraries[@]} $outflag $out >&2
$compiler $std ${final_flags[@]} $file ${libraries[@]} ${warnings[@]} $outflag $out
}
[ -n "${preload+-}" ] || compile "$@"