In this blog post, we will present the usage of docker containers as a development environment for an Embedded Linux BSP development project based on yocto and open embedded.
Nowadays Docker and containers, in general, are virtually everywhere, due to their versatility when it comes to solving application dependencies and packaging problems. At DigitalGate we are using Docker for many tasks from CI pipelines up to scalable IoT devices that need to provide a generic interface for integration of third-party applications. As an IDE we will be using Visual Studio Code, due to its excellent remote development capabilities. Visual Studio Code is an excellent IDE when it comes to development inside Docker Containers.
One advantage of doing Embedded Linux development inside Docker containers is the platform independence of the development environment. All dependencies of the build system are packaged inside the docker containers. Developers only need to have docker installed, such setups are ideal for corporate environments where for e.g. Windows is the de-facto platform for development machines.
Host Environment Setup
As a first step in setting up our development environment, we will need to install Docker on our machine. In this article, we will be using a Windows 10 machine with Docker version 19.03.13 installed. You can find the steps to install docker at the following link: https://docs.docker.com/desktop/windows/install/. Once Docker is installed, we will proceed to implement our docker-compose files. The files will describe our development environment and the tools that we need to install on it. The packages that we need to install are specified in the Yocto user manual https://www.yoctoproject.org/docs/1.8/yocto-project-qs/yocto-project-qs.html
The steps for setting up the environment and installing the required dependencies for Yocto will be done in our Dockerfile that defines the base image that we will be using.
The following docker-compose file implements our development platform, it describes the following:
- OS that we will be emulating inside the docker container, in this case, Ubuntu 18.04;
- The various volumes that we will be using to map the project files from the host PC into the docker container;
- Network interfaces that we will share with the host. In this particular case, we will run in bridged mode so the container has access directly to the network interfaces of the host machine.
version: "2" services: yocoto-dev-enviroment: build: . networ_mode: bridge volumes: - ../:/yocoto-dev-enviroment/ working_dir: /yocoto-dev-enviroment command: sleep infinity environment: - DEBIAN_FRONTEND=noninteractive
Development Environment Image
The docker image that we will be using for the development docker container is based on Ubuntu 18.04. Inside the docker file, we can specify all the packages that we need to install. That way, all packages required by Yocto are installed while the container is built.
RUN apt-get install -y \ bc \ build-essential \ chrpath \ cpio \ debianutils \ diffstat \ dos2unix \ fop \ g++-multilib \ gawk \ gcc-multilib \ git-core \ git-lfs \ iputils-ping \ libegl1-mesa \ libncurses5-dev \ libsdl1.2-dev \ pylint3 \ python3 \ python3-dev \ python3-git \ python3-jinja2 \ python3-pexpect \ python3-pip \ socat \ texinfo \ tmux \ unzip \ vim \ wget \ xsltproc \ xterm \ openssl \ tree \ xz-utils
Once all required packages are installed we can proceed to configure the locales and permissions for the various directories on the container filesys such that the build directories and artifacts can be shared with the host pc and with the board bootloader which boots the Linux image via Ethernet.
RUN apt-get update && apt-get install -y \ apt-utils locales sudo && \ dpkg-reconfigure locales && \ locale-gen en_US.UTF-8 && \ update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* ENV LANG en_US.utf8
An additional step is the configuration of a non-root user, by default the container has only the root user but bitbake cannot run under the root user. We need to create a user and add it to the sudoers group such it can run commands as root.
ARG host_uid=1001 ARG host_gid=1001 RUN groupadd -g $host_gid $USER_NAME && \ useradd -g $host_gid -m -s /bin/bash -p $(openssl passwd -crypt $PASS) -u $host_uid $USER_NAME && \ echo "build ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
One handy configuration is to switch to the non-root user at the end of the Dockerfile since anyways all commands that we will execute inside the docker container are required to run under a non-root user.
We can run the script when is ready using the Visual Studio remote containers extension. This will trigger the docker daemon to build our image and open the Yocto workspace inside the newly created container.
From now on we can proceed with setting up the bitbake environment for building our images. In this particular example, we will build a custom image for the sama5d2 xplained board.
In order to automate the initialization of the built environment for bitbake, we will use a small shell script. It performs the following actions.
Clones all required layers for the build:
Sources the bitbake init script which will set up our build environment. Inside it, we can run various bitbake commands for layer, recipe creation, machine configuration, etc.
Adds the required layers to the build configuration using the bitbake-layers add-layer command.
#!/bin/bash -xe #layers on which our image depends POKY_REPO=git://git.yoctoproject.org/poky.git OPENEMBEDDED_REPO=git://git.openembedded.org/meta-openembedded.git # Setup all source directories if [ ! -d /opt/build ] ; then git clone "$POKY_REPO" -b dunfell /opt/build/poky git clone "$OPENEMBEDDED_REPO" -b dunfell /opt/build/meta-openembedded fi # initialize build directory source /opt/build/poky/oe-init-build-env /opt/build # add all required layers to the build bitbake-layers add-layer meta-openembedded/meta*/ bitbake-layers add-layer /yocoto-dev-enviroment/layers/meta-atmel/ bitbake-layers add-layer /yocoto-dev-enviroment/layers/meta-aws/ bitbake-layers add-layer /yocoto-dev-enviroment/layers/meta-custom-app/
Once this is done the directory will be changed to the bitbake directory. Here we can start the build of an imag. For example, micro-chip-graphics-image, which includes support for the graphics interfaces on the board.
In conclusion, using a fully dockerized development environment for embedded Linux development is an efficient way to ensure reproducible builds and setups across an entire team. At the same time, it allows us to work in parallel on multiple projects with various setup requirements on the same machine. A dockerized development environment also enables us to keep a clean development machine that is not polluted with project-specific packages.