The GPIO toolchain

The toolchain is now complete; including support for C, C++.
GDC is no longer supported in this toolchain as building GDC breaks too often; I can't keep up.
  1. Set up build configuration
    binutils=binutils-2.28 newlib=newlib-2.1.0 gcc=gcc-4.9.4 target="arm-none-eabi" workdir="$HOME/toolchain"; [ "$1" ] && workdir="$1" [ "$toolchain" ] || toolchain="$target"; [ "$3" ] && toolchain="$3" [ "$prefix" ] || prefix="/opt"; [ "$2" ] && prefix="$2"
  2. Set up gcc short-version
    gcc_vers=${gcc##*-} gcc_vmaj=${gcc_vers%%.*} gcc_vmin=${gcc_vers#$gcc_vmaj.} gcc_vmin=${gcc_vmin%%.*} gcc_shortvers=$gcc_vmaj.$gcc_vmin
  3. Set up binutils configuration
    binutils_cfg=(--disable-nls --disable-libssp --with-gcc --with-gnu-as) binutils_cfg=(${binutils_cfg[@]} --with-gnu-ld --enable-multilib --with-system-zlib) binutils_cfg=(${binutils_cfg[@]} --enable-interwork --enable-plugins)
  4. Set up gcc configuration
    gcc_optional=(--with-tune=cortex-m4 --with-mode=thumb) headers=--with-headers="$source/$newlib/newlib/libc/include/" gcc_cfg=(--disable-nls --disable-libssp --with-gnu-as --with-gnu-ld --with-system-zlib) gcc_cfg=(${gcc_cfg[@]} --program-prefix=$target- --program-suffix=-$gcc_shortvers) gcc_cfg=(${gcc_cfg[@]} --with-gcc --with-dwarf2 --enable-interwork --with-newlib) gcc_cfg=(${gcc_cfg[@]} --disable-libphobos --disable-decimal-float --disable-libffi) gcc_cfg=(${gcc_cfg[@]} --disable-libmudflap --disable-libquadmath --disable-shared) gcc_cfg=(${gcc_cfg[@]} --disable-libgomp --disable-threads --disable-tls) gcc_cfg=(${gcc_cfg[@]} --disable-libstdcxx-pch --disable-bootstrap ${gcc_optional[@]}) gcc1_cfg=(${gcc_cfg[@]} --enable-languages="c,c++,lto" --without-headers) gcc2_cfg=(${gcc_cfg[@]} --enable-languages="c,c++,lto" --with-headers)
  5. Set up newlib configuration
    newlib_cfg=(--enable-interwork --enable-multilib --disable-libssp --disable-nls) newlib_cfg=(${newlib_cfg[@]} --enable-newlib-io-long-long --enable-newlib-register-fini) newlib_cfg=(${newlib_cfg[@]} --disable-newlib-supplied-syscalls)
  6. Set up other variables and paths
    start=`date +%s`; let cpucount=2*`getconf _NPROCESSORS_ONLN`; pmake="make -j$cpucount" prefix="$prefix${toolchain:+/$toolchain}"; build="$workdir/Build" source="$workdir/Source"; archives="$workdir/Archives" bin="$workdir/bin"; export PATH="$bin:$PATH"; mkdir -p "$build" "$source" "$bin" [ -d /arc ] && [ ! -e "$archives" ] && ln -s /arc "$archives"; mkdir -p "$archives"
  7. Set up convenience functions
    cls(){ echo -en "\033c$@"; }; HR(){ eval printf '=%.0s' {1..$(tput cols)}; echo; } CMsg(){ echo -en "\033[1;$1m${*:2}\033[m${2:+\n}"; }; Msg(){ CMsg 34 "$*"; } MsgHR(){ Msg "$@\n`HR`"; }; Good(){ CMsg 32 "$*"; }; Bad(){ CMsg 31 "$*"; } success(){ Good "Success${pkg:+ ($pkg)}"; }; status="success || failure" failure(){ Bad "Failed${1:+ $*}${pkg:+ ($pkg)}"; [ "" == "$LINES" ] && exit 1; } ipkg(){ pkg="${1%%-*}"; MsgHR "install${x:+ $x} $pkg"; mkdir -p "$build/$pkg"; } cdpkg(){ ipkg "$1"; cd "$build/$pkg"; } sl(){ for f in ${@:2}; do ln -s "$source/$f" "$source/$1/${f%%-*}" 2>/dev/null; done; }
  8. Setup build functions
    get_prefix(){ local p; for p in $@; do p="${p#--prefix=*}" [ "$p" ] && prefixdir="$p"; done; } cfg(){ cdpkg "$1" && "$source/$1/configure" ${@:2} || failure configuring; prefixdir=; get_prefix $@; } destdir_writable(){ [ "$destdir" ] || [ -w "$destdir${prefixdir:-/usr}" ]; } setup_imake(){ imake="`destdir_writable || echo sudo` $pmake"; } imake(){ setup_imake; $imake ${destdir+DESTDIR=$destdir} ${1:-install} $@; } build(){ cfg $@ && $pmake && imake && success || failure; unset pkg; } targ(){ x=-xaf; case $1 in *.gz) x=-xzf ;; *.bz2) x=-xjf ;; *.xz) x=-xJf ;; esac; } utar(){ local x; targ "$1"; tar "$x" "$1" || failure extracting "${1##*/}"; } dpk(){ local f="${1##*/}"; [ -d "${f%.tar*}" ] || utar "$1"; } dload(){ curl -fLk --retry 3 "$1" -o "$2" || failure downloading ${1##*/}; } dl(){ local a="$archives/${1##*/}"; [ -f "$a" ] || dload "$1" "$a"; } dlc(){ unset pkg; local c="$DL_CACHE"; [ "$c" ] && f="$c/${1##*/}" || f="$1"; dl "$f"; } dlx(){ dlc $1; cd "$source" && dpk "$archives/${1##*/}"; } dlxb(){ dlx $1; local f=${1##*/}; f=${f%.*}; f="${f%.tar}"; build ${f%.src} ${@:2}; }
  9. Setup a temporary (local) bin directory for gcc-4.2
    type gcc-select &>/dev/null && { gcc-select 4.2; } || { which gcc-4.2 >/dev/null && gcc_symlink=1; } [ $gcc_symlink ] && ln -s "/usr/bin/gcc-4.2" "$bin/gcc" 2>/dev/null [ $gcc_symlink ] && ln -s "/usr/bin/gcc-4.2" "$bin/gcc-4.2" 2>/dev/null [ $gcc_symlink ] && ln -s "/usr/bin/gcov-4.2" "$bin/gcov" 2>/dev/null [ $gcc_symlink ] && ln -s "/usr/bin/g++-4.2" "$bin/g++" 2>/dev/null
  10. Mirror repositories
    cd "$archives" [ -d newlib-nano-2 ] || git clone --mirror git:// newlib-nano-2 [ -d hidapi ] || git clone --mirror git:// hidapi [ -d openocd ] || git clone --mirror git:// openocd
  11. Download archives and extract sources
    dlx "$binutils.tar.bz2" dl "$gcc/$gcc.tar.bz2" dlx "" dlx "" dlx "" dlx "" dlx "" dlx "" dl "$newlib.tar.gz" dlx "" dlx "" dlx "" dlx "" dlx "" dlc "$gcc_vers.patch" dlc ""
  12. (update mirrored repository 'caches')
    ( cd "$archives/newlib-nano-2" && git remote update ) ( cd "$archives/hidapi" && git remote update ) ( cd "$archives/openocd" && git remote update )
    Note: hidapi and newlib-nano-2 can normally be skipped. Most of the time I only update OpenOCD.

  13. Prepare GCC for multiple ARM libraries and archive the patched sources (in case we mess up):
    MsgHR "Preparing GCC..."; cd "$source" gccp="$gcc+patch"; p="$archives/t-arm-elf-$gcc_vers.patch" f="$archives/$gccp.tar.bz2"; [ -f "$f" ] && dpk "$f" [ -e "$gccp" ] || dpk "$archives/$gcc.tar.bz2" [ -e "$gccp" ] || { ( cd "$source/$gcc/" && patch -p1 < "$p" ) && mv "$gcc" "$gccp"; } [ -e "$f" ] || tar -cjf "$f" "$gccp"
  14. Fix Cortex-M0 exception bug in newlib and archive the patched sources (in case we mess up):
    MsgHR "Preparing newlib..."; cd "$source" nlp="$newlib+patch"; p="$archives/cortex-m0-trap.S.patch" f="$archives/$nlp.tar.bz2"; [ -f "$f" ] && dpk "$f" [ -e "$nlp" ] || dpk "$archives/$newlib.tar.gz" [ -e "$nlp" ] || { ( cd "$source/$newlib/" && patch -p1 < "$p" ) && mv "$newlib" "$nlp"; } [ -e "$f" ] || tar -cjf "$f" "$nlp"
  15. Prepare GCC prerequisites
    sl $gcc+patch gmp-5.1.3 mpfr-3.1.2 mpc-1.0.3 libelf-0.8.13 isl-0.12.2 cloog-0.18.1 $newlib+patch/newlib
  16. Clone sources
    cd "$source" ( rm -Rf "$source/newlib-nano-2"; git clone "$archives/newlib-nano-2" && cd newlib-nano-2 && git checkout newlib-nano-2.1 && chmod 755 configure ) rm -Rf "$source/hidapi"; git clone "$archives/hidapi" rm -Rf "$source/openocd"; git clone "$archives/openocd" openocd
  17. Remove old build and any previously installed toolchain of the same kind, to avoid conflict
    MsgHR "Removing old toolchain..."; cd "$workdir"; rm -Rf "$build" [ -d "$prefix" ] && [ "$toolchain" ] && { rm -Rf "$prefix" || sudo rm -Rf "$prefix"; }
  18. Build Binutils
    build "$binutils" --target="$target" --prefix="$prefix" ${binutils_cfg[@]}
  19. Remove old gcc build directory and build GCC without bootstrap
    rm -Rf "$build/gcc"; mkdir -p "$build/gcc"; cd "$build/gcc" cfg "$gcc+patch" --target="$target" --prefix="$prefix" ${gcc1_cfg[@]} $pmake all-gcc && $pmake all-target-libgcc && $pmake all-target-libstdc++-v3 || failure imake install-gcc && imake install-target-libgcc && imake install-target-libstdc++-v3 && $status
  20. Add new $prefix/bin to $PATH, so the new binutils will be found:
    export PATH="$prefix/bin:$PATH"
  21. Make symlinks to our new gcc, so that it will be used for building newlib:
    for f in c++ cc cpp g++ gcc gcov; do p=$prefix/bin/$target-$f-$gcc_shortvers; [ -f $p ] && ln -s $p $bin/$target-$f; done
  22. Show supported multilib
    MsgHR "Supported multilib:"; $target-gcc -print-multi-lib; MsgHR
  23. Build newlib
    rm -Rf "$build/newlib"; mkdir -p "$build/newlib"; cd "$build/newlib" cfg "$newlib+patch" --target="$target" --prefix="$prefix" ${newlib_cfg[@]} cflags="-D__IEEE_BIG_ENDIAN -D__IEEE_BYTES_LITTLE_ENDIAN -D__BUFSIZ__=64" $pmake CFLAGS_FOR_TARGET="$cflags" || failure ienv="`destdir_writable || echo sudo` env" $ienv "PATH=$PATH" ${destdir+DESTDIR=$destdir} $pmake install && $status
    Note: If install fails, try it again - it was necessary to double-install gcc-4.9.2 with newlib 2.1.0 on both Mac OS X and Linux.

  24. Build final GCC with newlib (we're not reconfiguring as that will prevent the build from completing)
    [ "$gcc2_cfg" ] && cd "$build/gcc" #[ "$gcc2_cfg" ] && cfg "gcc" --target="$target" --prefix="$prefix" ${gcc2_cfg[@]} [ "$gcc2_cfg" ] && $pmake all && imake && $status
  25. Build GDB with PSIM (takes a while - not as long as GCC though; this requires ncurses on Linux)
    mkdir -p "$build/gdb"; cd "$build/gdb" cfg "gdb-7.12.1" --target="$target" --prefix="$prefix" --disable-nls --enable-sim-arm --enable-sim-stdio $pmake INHIBIT_LIBC_CFLAGS="-DUSE_TM_CLONE_REGISTRY=0" all && imake && $status
  26. Build LibConfuse 2.7
    dlxb ""
  27. Build LibUSB 1.0
    dlxb ""
  28. Install cmake (required for libftdi1)
    cd "$source/cmake-2.8.12" ./bootstrap && $pmake && sudo $pmake install && $status ./bootstrap && build cmake-2.8.12
  29. Build LibFTDI 1.1
    mkdir -p "$build/libftdi1"; cd "$build/libftdi1" cmake -DLIBUSB_INCLUDE_DIR="/usr/local/include/libusb-1.0" -DCMAKE_INSTALL_PREFIX="/usr/local" "$source/libftdi1-1.1/" $pmake all && imake && $status
  30. Build HIDAPI (requires libudev on Linux; see
    cd "$source/hidapi" ./bootstrap && build hidapi
  31. Build OpenOCD
    cd "$source/openocd" ./bootstrap build openocd --enable-internal-jimtcl --disable-shared --enable-arm-jtag-ew --enable-aice --enable-armjtagew --enable-cmsis-dap --enable-dummy --enable-ftdi --enable-jlink --enable-jtag_vpi --enable-legacy-ft2232_libftdi --enable-opendous --enable-openjtag_ftdi --enable-osbdm --enable-presto_libftdi --enable-remote-bitbang --enable-rlink --enable-stlink --enable-ti-icdi --enable-ulink --enable-usb-blaster-2 --enable-usb_blaster_libftdi --enable-usbprog --enable-vsllink
  32. Make tools available in PATH
    [ -d "/etc/paths.d" ] && { echo "$prefix/bin" >"/tmp/arm-toolchain"; sudo mv "/tmp/arm-toolchain" "/etc/paths.d/arm-toolchain"; } [ -d "/etc/paths.d" ] || grep "PATH=\"\$PATH:$prefix/bin\"" "$HOME/.bashrc" >/dev/null || echo "export PATH=\"\$PATH:$prefix/bin\"" >>"$HOME/.bashrc"

  33. The last two lines may look a bit cryptic, but I'll explain them here.

    The first line starts by checking if you have a modern system with a "/etc/paths.d" directory.
    This is a feature, that allows easy maintaining of PATH elements.
    Only a single file containing the path needs to be placed inside this directory, and $PATH will be constructed from the file "/etc/paths" plus the contents of all the files in "/etc/paths.d/".
    If the "/etc/paths.d" directory is available to us, we'll just install our path there.
    We can not use 'sudo echo' > file, as the redirection will not be executed with the new priviledges.
    Instead, we construct a temporary file in "/tmp", then we move it to "/etc/paths.d".

    In the next line, we first check if the "/etc/paths.d" directory exists.
    If it does, we don't need to do anything, because then we already installed our path above.
    If it doesn't, we check if ~/.bashrc contains the path already. If it doesn't, we'll finally add the path to ~/.bashrc!

  34. Make shorthand symlinks...
    cd "$prefix/bin" sudo ln -s $target-gcc arm-gcc sudo ln -s $target-g++ arm-g++ sudo ln -s $target-as arm-as sudo ln -s $target-ld arm-ld sudo ln -s $target-gdb arm-gdb sudo ln -s $target-objcopy arm-objcopy sudo ln -s $target-objdump arm-objdump sudo ln -s $target-run arm-run
  35. Test installation...
    cd "$workdir" which arm-gcc arm-gcc --version
  36. Build
    echo '#include <stdio.h>' >test.c; echo 'void main(){ printf("Hello World!\n"); }' >>test.c arm-gcc test.c -o test
    Note: This worked with earlier versions of gcc, but now you will most likely get a link error with a lot of undefined symbols. If you do, don't worry, the toolchain works.

  37. Run
    arm-run test

I hope this didn't give you any headache and hopefully you got the expected result.
I wish you happy coding!