KiCad compilation through Docker

While trying to compile KiCad from the latest sources, I had problems installing the large number of required dependencies with the correct versions on my two computers.

I already know of a tool that allows building a system image that is in a known state and can be executed independently of the host system : Docker.

In this article, I will summarize the different steps I took to compile KiCad from the sources into an AppImage that can then be copied onto the different systems that I use for my keyboard project.

Packaging the dependencies

The first step is to package the dependencies needed for the compilation into a Docker image that can then be used. The KiCad website lists the required packages for Debian, we will thus start from this list to build our image.

Some of the dependencies required have been updated. The final list I obtained through trial and error was the following.

FROM debian:10

# Install kicad deps
RUN apt-get update -q && \
    apt-get install --no-upgrade -qqy \
        git cmake build-essential curl ccache \
        libcurl4 libcurl4-gnutls-dev \
        libboost-dev libboost-test-dev libboost-filesystem-dev libboost-regex-dev \
        liboce-foundation-dev liboce-ocaf-dev \
        ca-certificates libssl-dev \
        libngspice0-dev \
        libglew-dev libglm-dev swig \
        libcairo2-dev doxygen graphviz \
        python3-wxgtk4.0 \
        libwxgtk3.0-dev libwxgtk3.0-gtk3-dev python3 python3-dev \
        && \
    apt-get clean && \
    apt-get purge && \
    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

This Dockerfile will allow us to build a base system that allows us to then manually compile the sources through Docker. The following command can be executed from a clone KiCad repository on your computed containing the sources. We will create a build directory from inside the container, configure the build and compile the sources to check that everything needed has been packaged.

host $ docker run --rm -ti -v $(pwd):/kicad 196c68cf5e05 /bin/bash
ctnr $ cd kicad/
ctnr $ mkdir build
ctnr $ cmake \
         -DCMAKE_C_COMPILER_LAUNCHER=ccache \
         -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
         -DCMAKE_BUILD_TYPE=Release \
         -DCMAKE_INSTALL_PREFIX=/usr \
         -DKICAD_SCRIPTING_PYTHON3=on \
         -DKICAD_SCRIPTING_WXPYTHON_PHOENIX=on \
         ../
ctnr $ make -j16

You should get no error during the compilation, otherwise it means that some dependency must be updated or installed. I followed the different errors to build the list of dependencies given above.

Generating an AppImage

AppImage builds everything needed for the execution into one executable, allowing me to copy a single file to a new computer and not having to worry about installing any dependency and thus testing the generated executable quickly.

I extended the Docker image to also include the necessary tools for building a basic AppImage :

FROM debian:10

# Install kicad deps
RUN apt-get update -q && \
    apt-get install --no-upgrade -qqy \
        git cmake build-essential curl ccache \
        libcurl4 libcurl4-gnutls-dev \
        libboost-dev libboost-test-dev libboost-filesystem-dev libboost-regex-dev \
        liboce-foundation-dev liboce-ocaf-dev \
        ca-certificates libssl-dev \
        libngspice0-dev \
        libglew-dev libglm-dev swig \
        libcairo2-dev doxygen graphviz \
        python3-wxgtk4.0 \
        libwxgtk3.0-dev libwxgtk3.0-gtk3-dev python3 python3-dev \
        && \
    apt-get clean && \
    apt-get purge && \
    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Download and install linuxdeploy tool
RUN curl -O -J -L https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage && \
    chmod +x linuxdeploy-x86_64.AppImage && \
    mv linuxdeploy-x86_64.AppImage /usr/bin/linuxdeploy && \
    curl -O -J -L https://raw.githubusercontent.com/TheAssassin/linuxdeploy-plugin-conda/master/linuxdeploy-plugin-conda.sh && \
    mv linuxdeploy-plugin-conda.sh /usr/bin/linuxdeploy-plugin-conda && \
    chmod +x /usr/bin/linuxdeploy-plugin-conda

The first linuxdeploy tools allows us to easily build an AppImage. The second linuxdeploy-plugin-conda eases working with packaging applications that needs Python.

We can build the new Docker image and execute it again on the folder used previously. As we used a volume, our previous build will still be available allowing us to continue directly with the packaging.

host $ docker run --rm -ti -v $(pwd):/kicad cae4d304d730 /bin/bash
ctnr $ cd /kicad/build
ctrn $ make install DESTDIR=AppDir

This will install our compiled sources into an AppDir directory. We will now need to also copy some Python dependencies that will be required and not automatically handled :

ctnr $ mkdir -p AppDir/usr/lib/python3/dist-packages/
ctnr $ cp -r /usr/lib/python3/dist-packages/wx \
      /usr/lib/python3/dist-packages/wxPython-4.0.4.egg-info \
      AppDir/usr/lib/python3/dist-packages/

Then, we also need to update our KiCad binary so that the packaged Python will be used and not the system one. For that, we will replace the KiCad binary with a script that will simply extract the path at which the AppImage will be mounted and export the PYTHON_PATH variable accordingly.

ctnr $ mv AppDir/usr/bin/kicad AppDir/usr/bin/kicad_bin
ctnr $ cat << "EOF" > AppDir/usr/bin/kicad
#!/bin/sh
HERE="$(dirname "$(readlink -f "${0}")")/../../"
export PYTHON_PATH="${HERE}"/usr/lib/python3:${PYTHON_PATH}
exec "${HERE}/usr/bin/kicad_bin" "$@"
EOF
ctnr $ chmod +x AppDir/usr/bin/kicad

Now that everything is ready, we can package the AppDir into an AppImage using linuxdeploy.

ctnr $ LD_LIBRARY_PATH=$(pwd)/AppDir/usr/lib/x86_64-linux-gnu/ linuxdeploy \
    --appimage-extract-and-run \
    --appdir AppDir \
    -d $(pwd)/AppDir/usr/share/applications/kicad.desktop \
    --output appimage

This will generate a KiCad-*-x86_64.AppImage file in your build folder that can be executed on your host OS without having to install any dependency. It will not be possible to execute the AppImage from inside the container for multiple reasons, first of which that FUSE is needed to mount the AppImage content and is not available in a container. Second, an X Server will be needed to start the application and is also not available in the container.

Build script

Building manually without having to worry about dependencies is great but we can do better by simply scripting the different steps so that everything happens in one command line.

Once the steps have been verified, we can put them in a build script build-docker.sh that we include in the Docker image that will be called when starting the container.

#!/bin/bash

cd "$1"

BUILD_TYPE="$2"
if [ -z "$BUILD_TYPE" ]; then
  BUILD_TYPE="Debug"
fi

mkdir build
cd build
rm -rf AppDir

cmake \
    -DCMAKE_C_COMPILER_LAUNCHER=ccache \
    -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
    -DCMAKE_BUILD_TYPE=${BUILD_TYPE} \
    -DCMAKE_INSTALL_PREFIX=/usr \
    -DKICAD_SCRIPTING_PYTHON3=on \
    -DKICAD_SCRIPTING_WXPYTHON_PHOENIX=on \
    ../

make -j$(nproc) --output-sync

if [ $? -ne 0 ]; then
  exit 10
fi

make install DESTDIR=AppDir

mkdir -p AppDir/usr/lib/python3/dist-packages/

cp -r /usr/lib/python3/dist-packages/wx \
      /usr/lib/python3/dist-packages/wxPython-4.0.4.egg-info \
      AppDir/usr/lib/python3/dist-packages/

mv AppDir/usr/bin/kicad AppDir/usr/bin/kicad_bin

cat << "EOF" > AppDir/usr/bin/kicad
#!/bin/sh
HERE="$(dirname "$(readlink -f "${0}")")/../../"
export PYTHON_PATH="${HERE}"/usr/lib/python3:${PYTHON_PATH}
exec "${HERE}/usr/bin/kicad_bin" "$@"
EOF

chmod +x AppDir/usr/bin/kicad

LD_LIBRARY_PATH=$(pwd)/AppDir/usr/lib/x86_64-linux-gnu/ linuxdeploy \
    --appimage-extract-and-run \
    --appdir AppDir \
    -d $(pwd)/AppDir/usr/share/applications/kicad.desktop \
    --output appimage

mv KiCad*.AppImage ../KiCad-$(git describe).AppImage
FROM debian:10

# Install kicad deps
RUN apt-get update -q && \
    apt-get install --no-upgrade -qqy \
        git cmake build-essential curl ccache \
        libcurl4 libcurl4-gnutls-dev \
        libboost-dev libboost-test-dev libboost-filesystem-dev libboost-regex-dev \
        liboce-foundation-dev liboce-ocaf-dev \
        ca-certificates libssl-dev \
        libngspice0-dev \
        libglew-dev libglm-dev swig \
        libcairo2-dev doxygen graphviz \
        python3-wxgtk4.0 \
        libwxgtk3.0-dev libwxgtk3.0-gtk3-dev python3 python3-dev \
        && \
    apt-get clean && \
    apt-get purge && \
    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Download and install linuxdeploy tool
RUN curl -O -J -L https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage && \
    chmod +x linuxdeploy-x86_64.AppImage && \
    mv linuxdeploy-x86_64.AppImage /usr/bin/linuxdeploy && \
    curl -O -J -L https://raw.githubusercontent.com/TheAssassin/linuxdeploy-plugin-conda/master/linuxdeploy-plugin-conda.sh && \
    mv linuxdeploy-plugin-conda.sh /usr/bin/linuxdeploy-plugin-conda && \
    chmod +x /usr/bin/linuxdeploy-plugin-conda

COPY build-docker.sh /build-docker.sh

ENTRYPOINT ["/bin/bash", "/build-docker.sh"]
CMD ["/kicad"]

We can then simply build an AppImage from the latest sources with just one command line, without having to worry about updating our host system with the latest dependencies : docker run --rm -it -v $(pwd):/kicad kicad-builder-docker kicad Release