#!/bin/sh # # fat cosmopolitan c/c++ compiler # # - this command is a drop-in replacement for the cc or gcc command. # the difference is that (1) your binaries will be linked with the # cosmopolitan c library, rather than your system specific tooling # and (2) they'll be fat ape executables that run on the platforms # # * amd64 # + linux # + macos # + windows # + freebsd # + openbsd # + netbsd # * arm64 # + linux # + macos # + windows (non-native) # # - you need to use linux to build your binaries currently, but you # can scp and distribute the output files to the above platforms! # # installation # # sudo chmod 1777 /opt # sticky bit isn't required # git clone https://github.com/jart/cosmopolitan /opt/cosmo # export PATH="/opt/cosmo/bin:/opt/cosmos/bin:$PATH" # echo 'export PATH="/opt/cosmo/bin:/opt/cosmos/bin:$PATH"' >>~/.profile # ape-install # optionally install a faster systemwide ape loader # fatcosmocc --update # pull and rebuild toolchain artifacts # # getting started # # fatcosmocc -o hello.com hello.c # ./foo.com # unzip -vl ./foo.com # ./foo.com --strace # ./foo.com --ftrace # # building in tiny mode # # export MODE=tiny # fatcosmocc --update # fatcosmocc -Os -o foo.com foo.c # # building in debug mode # # export MODE=dbg # fatcosmocc --update # fatcosmocc -g -o foo.com foo.c # # how to build a project like lua 5.4.6 # # make all test CC=fatcosmocc AR='fatcosmoar rcu' # make install INSTALL_TOP=/opt/cosmos INSTALL=fatcosmoinstall # # how to build a project like ncurses 6.4 # # ./configure CC=fatcosmocc \ # CXX=fatcosmoc++ \ # AR=fatcosmoar \ # INSTALL="$(command -v fatcosmoinstall)" \ # --prefix=/opt/cosmos \ # --disable-shared # make -j8 # make install # # detecting this environment # # - `__FATCOSMOCC__` is defined by fatcosmocc # - `__COSMOCC__` is defined by cosmocc and fatcosmocc # - `__COSMOPOLITAN__` is always defined by cosmopolitan # # some notes on this compiler # # - the underlying compiler itself is gcc # - we use cosmopolitan libc rather than glibc # - we use llvm's compiler-rt and libcxx runtimes # - we patched gcc so switch case can have symbols # - our scanf() implementation is somewhat troubled # - you may need to recalibrate `make -jN` as `N/2` # # compiler flags that work differently # # - `-v` will log fatcosmocc subcommands to stderr # you can also use `export BUILDLOG=/tmp/build.log` # - `-s` will ask apelink to not embed symbol tables in zip # - `-E` can't be fat and runs once with x86_64 macros undefined # - `-save-temps` will prevent deleting your arch-specific executables # # compiler flags that aren't supported # # - `-fexceptions` cosmopolitan doesn't support c++ exceptions yet # - `-frtti` cosmopolitan doesn't support c++ runtime reflection yet # - `-mred-zone` the system v red zone doesn't exist on windows and metal # - `-fpic`, '-fPIC', `-shared`, `-pie`, etc. no shared object support yet # - `-fsanitize=thread` cosmopolitan doesn't have thread sanitizer runtime yet # - `-fomit-frame-pointer` is partially supported (apple forbids full removal) # # for further details, run `man gcc` export PROG=${0##*/} export COSMO=${COSMO:-/opt/cosmo} export COSMOS=${COSMOS:-/opt/cosmos} export ORIGINAL="$0 $*" export TMPDIR=${TMPDIR:-/tmp} GCC_VERSION=11.2.0 if [ "$1" = "--version" ]; then cat <&2 exit 1 fi if [ "$1" = "--update" ]; then cd /opt/cosmo || exit if GIT=$(command -v git); then echo "running git pull on cosmo..." >&2 "$GIT" pull --quiet || exit fi echo "building cosmo host toolchain..." >&2 make --silent -j toolchain MODE= || exit echo "building cosmo x86_64 target (MODE=$MODE) toolchain..." >&2 make --silent -j toolchain MODE="$MODE" || exit echo "building cosmo aarch64 target (MODE=$MODE_AARCH64) toolchain..." >&2 make --silent -j toolchain MODE="$MODE_AARCH64" || exit echo "setting up your cosmos..." >&2 for arch in "" .aarch64/; do mkdir -p "$COSMOS/lib/$arch" || exit for lib in c dl gcc_s m pthread resolv rt dl z stdc++; do if [ ! -f "$COSMOS/lib/${arch}lib${lib}.a" ]; then printf '\041\074\141\162\143\150\076\012' >"$COSMOS/lib/${arch}lib${lib}.a" || exit fi done done echo "successfully updated your cosmo toolchain" >&2 exit fi if [ ! -d "$COSMO" ]; then echo "$PROG: you need to clone cosmopolitan to your $COSMO directory" >&2 exit 1 fi if [ ! -f "$COSMOS/lib/libc.a" ] || [ ! -f "$COSMOS/lib/.aarch64/libc.a" ] || [ ! -f "$COSMO/o/$MODE/cosmopolitan.a" ] || [ ! -f "$COSMO/o/$MODE_AARCH64/cosmopolitan.a" ]; then echo "$PROG: you need to run: $PROG --update" >&2 exit 1 fi export FIXUPOBJ="$COSMO/o//tool/build/fixupobj.com" TEMP_FILES= SAVE_TEMPS=0 Exit() { rc=${1:-$?} if [ $SAVE_TEMPS -eq 0 ]; then rm -f $TEMP_FILES fi exit $rc } show_warning() { echo "$PROG: warning: $1" >&2 } fatal_error() { echo "$PROG: fatal error: $1" >&2 echo "compilation terminated." >&2 Exit 1 } log_command() { if [ -n "$BUILDLOG" ]; then printf '# %s\n(cd %s; %s)\n' "$ORIGINAL" "$PWD" "$*" >>"$BUILDLOG" fi } if [ x"$TMPDIR" != x"${TMPDIR#* }" ]; then fatal_error '$TMPDIR containing spaces not supported' elif [ ! -d "$TMPDIR" ]; then if ! mkdir -p "$TMPDIR" 2>/dev/null; then fatal_error "$TMPDIR: not a directory" fi fi OPT= ARGS= FLAGS= OUTPUT= INTENT=ld NEED_JOIN= NEED_EQUAL= NEED_OUTPUT= APELINKFLAGS= INPUT_FILE_COUNT=0 for x; do if [ x"$x" != x"${x#* }" ]; then fatal_error "arguments containing spaces unsupported: $x" fi if [ -n "$NEED_OUTPUT" ]; then NEED_OUTPUT= OUTPUT=$x continue elif [ -n "$NEED_JOIN" ]; then x="${NEED_JOIN}${x}" NEED_JOIN= elif [ -n "$NEED_EQUAL" ]; then x="${NEED_EQUAL}=${x}" NEED_EQUAL= elif [ x"$x" = x"-" ] || # is alias for stdin [ x"$x" = x"${x#-*}" ]; then # !startswith(x, "-") if [ x"$x" != x"${x%.s}" ] || [ x"$x" != x"${x%.S}" ]; then fatal_error "$x: assembler input files not supported" elif [ x"$x" != x"${x%.so}" ] || [ x"$x" != x"${x%.dll}" ] || [ x"$x" != x"${x%.dylib}" ]; then fatal_error "$x: dynamic shared object input files not supported" elif [ x"$x" != x"-" ] && [ ! -f "$x" ]; then fatal_error "$x: no such file" fi INPUT_FILE_COUNT=$((INPUT_FILE_COUNT + 1)) ARGS="$ARGS $x" # don't add to $FLAGS array continue elif [ x"$x" = x"-o" ]; then NEED_OUTPUT=1 continue elif [ x"$x" != x"${x#-o}" ]; then # startswith(x, "-o") OUTPUT=${x#-o} continue elif [ x"$x" != x"${x#-O}" ]; then # startswith(x, "-O") OPT=$x elif [ x"$x" = x"-c" ]; then INTENT=cc elif [ x"$x" = x"-E" ]; then INTENT=cpp elif [ x"$x" = x"-s" ]; then APELINKFLAGS="$APELINKFLAGS -s" continue elif [ x"$x" = x"-v" ]; then exec 3<&2 # dup2(2, 3) b/c stderr will be redirected later export BUILDLOG=/dev/fd/3 continue elif [ x"$x" = x"-save-temps" ]; then SAVE_TEMPS=1 elif [ x"$x" = x"-fomit-frame-pointer" ]; then # Quoth Apple: "The frame pointer register must always address a # valid frame record. Some functions — such as leaf functions or # tail calls — may opt not to create an entry in this list. As a # result, stack traces are always meaningful, even without debug # information." x="-momit-leaf-frame-pointer -foptimize-sibling-calls" elif [ x"$x" = x"-r" ] || [ x"$x" = x"-S" ] || [ x"$x" = x"-pie" ] || [ x"$x" = x"-frtti" ] || [ x"$x" = x"-shared" ] || [ x"$x" = x"-nostdlib" ] || [ x"$x" = x"-mred-zone" ] || [ x"$x" = x"-fexceptions" ] || [ x"$x" = x"-fsanitize=thread" ]; then fatal_error "$x flag not supported" elif [ x"$x" = x"-fsanitize=all" ] || [ x"$x" = x"-fsanitize=address" ] || [ x"$x" = x"-fsanitize=undefined" ]; then fatal_error "$x use cosmo MODE=dbg rather than passing $x" elif [ x"$x" = x"-mno-red-zone" ]; then # "Any memory below the stack beyond the red zone is considered # volatile and may be modified by the operating system at any time." # https://devblogs.microsoft.com/oldnewthing/20190111-00/?p=100685 continue elif [ x"$x" = x"-fpic" ] || [ x"$x" = x"-fPIC" ]; then # no support for building dynamic shared objects yet. reports # indicate that ignoring these flags, helps let autoconf know continue elif [ x"$x" = x"-Werror" ] || \ [ x"$x" = x"-pedantic-errors" ]; then # this toolchain is intended for building other people's code # elevating warnings into errors, should only be done by devs continue elif [ x"$x" = x"-static-libgcc" ] || \ [ x"$x" = x"-shared-libgcc" ]; then # cosmopolitan.a always has llvm compiler runtime static code continue elif [ x"$x" = x"-march=native" ]; then fatal_error "-march=native can't be used when building fat binaries" elif [ x"$x" = x"-dumpversion" ]; then echo $GCC_VERSION Exit 0 elif [ x"$x" = x"-e" ] || [ x"$x" = x"-z" ] || [ x"$x" = x"-T" ] || [ x"$x" = x"-L" ] || [ x"$x" = x"-I" ] || [ x"$x" = x"-D" ] || [ x"$x" = x"-U" ] || [ x"$x" = x"-iquote" ] || [ x"$x" = x"-isystem" ] || [ x"$x" = x"-include" ]; then NEED_JOIN=$x continue elif [ x"$x" = x"--param" ]; then NEED_EQUAL=$x continue fi FLAGS="$FLAGS $x" ARGS="$ARGS $x" done if [ $INPUT_FILE_COUNT -eq 0 ]; then fatal_error "no input files" elif [ -z "$INPUT" ] && [ $INTENT != ld ] && [ $INPUT_FILE_COUNT -gt 1 ]; then fatal_error "cannot specify '-o' with '-c', or '-E' with multiple files" fi PLATFORM="-D__COSMOPOLITAN__ -D__COSMOCC__ -D__FATCOSMOCC__" PREDEF="-include libc/integral/normalize.inc" CPPFLAGS="-nostdinc -iquote $COSMO -isystem $COSMOS/include -isystem $COSMO/libc/isystem -fno-pie -fno-math-errno" CCFLAGS_START="-fportcosmo -fno-dwarf2-cfi-asm -fno-unwind-tables -fno-asynchronous-unwind-tables -fno-semantic-interposition" CCFLAGS_END="-fno-omit-frame-pointer" if [ x"$MODE" = x"dbg" ]; then CPPFLAGS_START="-fsanitize=address -fsanitize=undefined" fi if [ x"$OPT" != x"-Os" ] && [ x"${MODE#tiny}" != x"${MODE}" ]; then CCFLAGS_START="${CCFLAGS_START} -fno-optimize-sibling-calls -mno-omit-leaf-frame-pointer" fi if [ $INTENT = cpp ]; then if [ -n "$OUTPUT" ]; then ARGS="$ARGS -o$OUTPUT" fi CC="$COSMO/o/third_party/gcc/bin/x86_64-linux-musl-gcc" if [ x"$PROG" != x"${PROG%++}" ]; then CC="$COSMO/o/third_party/gcc/bin/x86_64-linux-musl-g++" fi set -- \ "$CC" \ -U__k8 \ -U__k8__ \ -U__amd64 \ -U__amd64__ \ -U__x86_64 \ -U__x86_64__ \ -U__SSE__ \ -U__SSE2__ \ -U__SSE2_MATH__ \ -mno-red-zone \ $PLATFORM \ $CPPFLAGS \ $ARGS log_command "$@" MODE="$MODE" exec "$@" fi mangle_object_path() { path=$1 arch=$2 outdir=${path%/*} outbas=${path##*/} if [ x"$outdir" = x"$path" ]; then outdir= elif [ -n "$outdir" ]; then outdir="$outdir/" fi if [ ! -d "$outdir.$arch" ]; then mkdir -p "$outdir.$arch" || Exit fi mangled_path="${outdir}.$arch/$outbas" } mktemper() { "$COSMO/o//tool/build/mktemper.com" \ "$TMPDIR/fatcosmocc.XXXXXXXXXXXXX$1" } build_object() { out2=$(mktemper .txt) || Exit TEMP_FILES="${TEMP_FILES} $out2" MODE="$MODE" \ "$COSMO/tool/scripts/fat-x86_64" \ -o"$OUTPUT_X86_64" \ $PLATFORM \ $PREDEF \ $CPPFLAGS \ $CCFLAGS_START \ "$@" \ $CCFLAGS_END & pid1=$! MODE="$MODE_AARCH64" \ "$COSMO/tool/scripts/fat-aarch64" \ -o"$OUTPUT_AARCH64" \ $PLATFORM \ $PREDEF \ $CPPFLAGS \ $CCFLAGS_START \ "$@" \ $CCFLAGS_END \ 2>"$out2" & pid2=$! if ! wait $pid1; then kill $pid2 2>/dev/null wait Exit 1 fi if ! wait $pid2; then echo "$PROG: x86_64 succeeded but failed to build object for aarch64:" >&2 cat "$out2" >&2 Exit 1 fi } # turn source files into objects LDARGS_X86_64= LDARGS_AARCH64= for x in $ARGS; do if [ x"$x" != x"-" ] && # is alias for stdin [ x"$x" != x"${x#-*}" ]; then # startswith(x, "-") # this argument is a flag LDARGS_X86_64="${LDARGS_X86_64} $x" LDARGS_AARCH64="${LDARGS_AARCH64} $x" else # this argument is an input file if [ x"$x" != x"${x%.o}" ] || [ x"$x" != x"${x%.a}" ]; then if [ $INTENT = cc ]; then show_warning "$x: linker input file unused because linking not done" else mangle_object_path "$x" aarch64 if [ ! -f "$mangled_path" ]; then fatal_error "$x: linker input missing concomitant $mangled_path file" fi LDARGS_X86_64="${LDARGS_X86_64} $x" LDARGS_AARCH64="${LDARGS_AARCH64} $mangled_path" fi elif [ $INTENT = cc ]; then if [ -n "$OUTPUT" ]; then # e.g. `cc -c -o bar.o foo.c` is specified by user OUTPUT_X86_64=$OUTPUT mangle_object_path "$OUTPUT" aarch64 OUTPUT_AARCH64="$mangled_path" build_object $FLAGS -c "$x" else # e.g. `cc -c dir/foo.c` builds foo.o o=${x##*/} OUTPUT_X86_64="${o%.*}.o" mangle_object_path "${o%.*}.o" aarch64 OUTPUT_AARCH64="$mangled_path" build_object $FLAGS -c "$x" fi else # e.g. `cc foo.c` should build a.out if [ -z "$OUTPUT" ]; then OUTPUT=a.out fi # e.g. `cc -o foo foo.c` should *not* build foo.o OUTPUT_X86_64=$(mktemper .o) || Exit OUTPUT_AARCH64=$(mktemper .o) || Exit TEMP_FILES="${TEMP_FILES} ${OUTPUT_X86_64} ${OUTPUT_AARCH64}" build_object $FLAGS -c "$x" LDARGS_X86_64="${LDARGS_X86_64} ${OUTPUT_X86_64}" LDARGS_AARCH64="${LDARGS_AARCH64} ${OUTPUT_AARCH64}" fi fi done if [ $INTENT != ld ]; then Exit fi OUTPUT_X86_64=$(mktemper ".com.dbg") || Exit OUTPUT_AARCH64=$(mktemper ".aarch64.elf") || Exit out2=$(mktemper .txt) || Exit TEMP_FILES="${TEMP_FILES} $out2" MODE="$MODE" \ "$COSMO/tool/scripts/fat-x86_64" -o"$OUTPUT_X86_64" $LDARGS_X86_64 & pid1=$! MODE="$MODE_AARCH64" \ "$COSMO/tool/scripts/fat-aarch64" -o"$OUTPUT_AARCH64" $LDARGS_AARCH64 2>"$out2" & pid2=$! if ! wait $pid1; then kill $pid2 2>/dev/null wait Exit 1 fi if ! wait $pid2; then echo "$PROG: x86_64 succeeded but failed to link executable for aarch64:" >&2 cat "$out2" >&2 Exit 1 fi set -- \ "$COSMO/o//tool/build/apelink.com" \ -l "$COSMO/o/$MODE/ape/ape.elf" \ -l "$COSMO/o/$MODE_AARCH64/ape/ape.elf" \ -M "$COSMO/ape/ape-m1.c" \ -o "$OUTPUT" \ $APELINKFLAGS \ "$OUTPUT_X86_64" \ "$OUTPUT_AARCH64" log_command "$@" "$@" || Exit set -- \ "$COSMO/o//tool/build/pecheck.com" "$OUTPUT" log_command "$@" "$@" || Exit if [ $INTENT = ld ] && [ $SAVE_TEMPS -eq 0 ]; then mv -f "$OUTPUT_X86_64" "${OUTPUT%.com}.com.dbg" || Exit mv -f "$OUTPUT_AARCH64" "${OUTPUT%.com}.aarch64.elf" || Exit fi Exit