Are you tired of managing the installation of different cross-development toolchains on the same machine, fixing issues when your compiler does not work after a host OS upgrade or having to deal with the same toolchain being installed in heterogeneous environments?
Docker fixes some of these issues by providing a light-weight virtualization layer that isolates the cross-development toolchains from the host OS, allows the easier coexistence of different tools in the same machine, and facilitates their management and deployability.
We have been facing these problems while developing software for an Infineon XMC4800 microcontroller on a Linux host, and have improved our process by using a Docker cross-compilation container with the following features:
- Docker container based on Ubuntu 18.04 LTS
- GNU ARM toolchain
- Infineon XMC libraries for XMC4800
- Segger JLink tool for target flashing and debugging
- Container compiles code from the host invocation directory
- Use of ccache to speed up subsequent compilations
This is our resulting Dockerfile:
# Root image built from LTS ubuntu in Docker Hub. FROM ubuntu:18.04 MAINTAINER Juan Solano "firstname.lastname@example.org" # Update this variable to force a refresh of all base images and make # sure subsequent commands do not use old cache versions. ENV REFRESHED_AT 2018-11-26 ARG USERNAME="docker" ARG USERGROUP="dckrgroup" ARG DEBIAN_FRONTEND=noninteractive # These can be overriden with a command line option when the image is # built, e.g. --build-arg UID=$(id -u) --build-arg GID=$(id -g). ARG UID=1000 ARG GID=1000 ARG GCC_ARM_TOOLCHAIN_VER="gcc-arm-none-eabi-7-2018-q2-update" ARG GCC_ARM_TOOLCHAIN_URL="https://developer.arm.com/-/media/Files/downloads/gnu-rm/7-2018q2/"$GCC_ARM_TOOLCHAIN_VER-linux.tar.bz2 ARG XMC_LIB_VER="XMC_Peripheral_Library_v2.1.18" ARG XMC_LIB_URL="http://dave.infineon.com/Libraries/XMCLib/"$XMC_LIB_VER.zip ARG JLINK_VER="JLink_Linux_V634g_x86_64" # Set up the compiler path and other container environment variables. ENV PATH $PATH:/home/$USERNAME/opt/$GCC_ARM_TOOLCHAIN_VER/bin ENV GCC_ARM_TOOLCHAIN_VER $GCC_ARM_TOOLCHAIN_VER ENV GCC_COLORS="error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01" ENV USB_SCRIPT="usbdev_allow.sh" ENV TZ=Europe/Berlin RUN apt-get update -q \ && apt-get install --no-install-recommends -y apt-utils \ && apt-get install --no-install-recommends -y vim make sudo \ tzdata libncurses5 ca-certificates unzip bzip2 libtool ccache \ usbutils libusb-1.0-0-dev libusb-dev \ && rm -rf /var/lib/apt/lists/* # Set timezone and standard user. RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \ && echo $TZ > /etc/timezone \ && groupadd --gid $GID $USERGROUP \ && useradd -m -u $UID -g $GID -o -s /bin/bash $USERNAME \ && echo "root:root" | chpasswd \ && echo "$USERNAME:$USERNAME" | chpasswd \ && usermod -a -G 20 $USERNAME \ && adduser $USERNAME sudo \ && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers # Set up a build tools directory. RUN mkdir -p /home/$USERNAME/opt WORKDIR /home/$USERNAME/opt RUN chown $USERNAME /home/$USERNAME/opt \ && cd /home/$USERNAME/opt # Install JLink as root, before changing to standard user. COPY $JLINK_VER.deb /home/$USERNAME/opt RUN dpkg -i $JLINK_VER.deb \ && rm $JLINK_VER.deb COPY $USB_SCRIPT /home/$USERNAME/opt RUN chmod +x /home/$USERNAME/opt/$USER_SCRIPT # Further operations as standard user. USER $USERNAME # Install the XMC library. COPY $XMC_LIB_VER.zip /home/$USERNAME/opt RUN unzip $XMC_LIB_VER.zip \ && rm $XMC_LIB_VER.zip # Install the ARM cross-compilation toolchain. COPY $GCC_ARM_TOOLCHAIN_VER-linux.tar.bz2 /home/$USERNAME/opt RUN bunzip2 $GCC_ARM_TOOLCHAIN_VER-linux.tar.bz2 \ && tar xvf $GCC_ARM_TOOLCHAIN_VER-linux.tar \ && rm $GCC_ARM_TOOLCHAIN_VER-linux.tar # Required so that ccache files are kept in shared work directory. RUN cd /usr/lib/ccache \ && sudo ln -s ../../bin/ccache arm-none-eabi-gcc ENV PATH /usr/lib/ccache:$PATH # Create a directory for our project and setup a shared work directory. RUN mkdir -p /home/$USERNAME/project WORKDIR /home/$USERNAME/project VOLUME /home/$USERNAME/project RUN cd /home/$USERNAME/project \ && mkdir -p $HOME/.ccache \ && echo "cache_dir = $HOME/project/.ccache" >> \ $HOME/.ccache/ccache.conf
Initially we added wget commands to the Dockerfile, so that the tools were directly downloaded before usage, but we have later decided to keep a local copy of our tools to speed up the Docker image creation. After creating the Docker image, compiling is just a matter of going to the directory where our source code lives and executing our make alias, which can be defined like e.g.:
alias xmcmake='docker run --rm -it --device=/dev/bus/usb --volume=$(pwd):/home/docker/project docker-arm-xmc make'
This starts a container based on the previously created docker-arm-xmc image, allowing access to the JLink usb port from inside the container, and executes the make command. After the make command is executed, the container exits and we can see our compiled binaries as well as a directory with the .ccache artifacts which will be used the next time the make command is invoked.
In subsequent posts, I will delve into additional development steps that can be realized with the help of this container. I hope you find this useful.