#!/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 "$@"