Search code examples
c++gitdockercmakegithub-actions

GitHub Continuous Integration CMake/C++


I am trying to set up a CI for my cmake/c++ project hosted on a private repository on GitHub.

The project depends on lots of third party libraries that should be git-cloned and built. The latter takes a while, hence, I created a docker image with all dependencies installed and hosted it on the docker hub. (Ideally, I would like the docker image to be private also, but if it is not possible, I can make it public.)

I want to achieve the following:

On pull requests to the master branch, the application is automatically built on the docker container(because all dependencies are there), all unit tests (gtest) are run and, if everything is alright, the branch is merged to master.

Ideally, I would like to see the logs and statistics generated by gcovr/lcov.

OS: Ubuntu 18.04

I wonder if this is even achievable as I have been searching for 2 days with no luck and a billion of possible readings.


Solution

  • My 2 cents (more a comment) on controlled build using docker.
    For automatic merge, I don't know since I would be against it since code review can't be replaced by CI only IMHO...

    Take a look at https://github.com/Mizux/cmake-cpp

    Introduction

    I use a Makefile for orchestration (docker command can be way too long ;)) and docker for isolated build on various distro.

    pro:

    • Be able to test locally (Just need a GNU/Linux distro with Docker & Make)
    • Can migrate easily to various CI runners provider (Travis-CI, GitHub Workflow, gitlab-runner, bitbucket?)
    • Contributors can test locally before sending a PR

    cons:

    • Less coupled to github -> more complex to maintain.
    • more difficult to have a cache between workflow

    note: Dockerfile are stored in the repository in ci/docker i.e. I rebuild the images in the first steps but you should be able to replace this step by a simple docker load if your image is located on docker hub (not tested)

    Setup

    Dockerfile

    I split my Dockerfile in several stages (mostly for debug).

    note: you can replace ubuntu:rolling with your own image...

    ci/docker/ubuntu/Dockerfile:

    # Create a virtual environment with all tools installed
    # ref: https://hub.docker.com/_/ubuntu
    FROM ubuntu:rolling AS env
    # Install system build dependencies
    # note: here we use the CMake package provided by Ubuntu
    # see: https://repology.org/project/cmake/versions
    ENV PATH=/usr/local/bin:$PATH
    RUN apt-get update -q && \
    apt-get install -yq git build-essential cmake && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
    
    CMD [ "/bin/sh" ]
    
    # Add the library src to our build env
    FROM env AS devel
    # Create lib directory
    WORKDIR /home/lib
    # Bundle lib source
    COPY . .
    
    # Build in an other stage
    FROM devel AS build
    # CMake configure
    RUN cmake -H. -Bbuild
    # CMake build
    RUN cmake --build build --target all
    # CMake install
    RUN cmake --build build --target install
    
    # Create an install image to check cmake install config
    FROM env AS install
    # Copy lib from build to install
    COPY --from=build /usr/local /usr/local/
    # Copy  sample
    WORKDIR /home/sample
    COPY ci/sample .
    

    Runner jobs

    Github action runners have docker installed.

    note: you can have one badge per yml file. e.g. You could should use one job per distro for example to have one jobs per distro or one file for Release and one file for Debug...

    .github/workflows/docker.yml:

    name: C++ CI
    
    on: [push, pull_request]
    
    jobs:
      build-docker:
        runs-on: ubuntu-latest
        steps:
        - uses: actions/checkout@v2
        - name: Build env image
          run: docker build --target=env --tag project:env -f ci/docker/ubuntu/Dockerfile . 
        - name: Build devel image
          run: docker build --target=devel --tag project:devel -f ci/docker/ubuntu/Dockerfile .
        - name: Build build image
          run: docker build --target=build --tag project:build -f ci/docker/ubuntu/Dockerfile .
    

    For testing you can add an other stage or run it using the project:build image:

    docker run --rm --init -t --name test project:build cmake --build build --target test
    

    Annexes

    Faster build

    You can add a .dockerignore file to remove unneeded files (e.g. LICENCES, doc, local build dir if testing locally...) to reduce docker context and the COPY . .

    .dockerignore:

    # Project Files unneeded by docker
    ci/cache
    ci/docker
    ci/Makefile
    .git
    .gitignore
    .github
    .dockerignore
    .travis.yml
    .appveyor.yml
    .clang-format
    AUTHORS
    CONTRIBUTING.md
    CONTRIBUTHORS
    INSTALL
    LICENSE
    README.md
    doc
    
    # Native CMake build
    build/
    
    # Editor directories and files
    *.user
    *.swp
    

    Custom CMake version install

    You can use the following instead of apt install -y cmake

    Can take time since you rebuild CMake...

    # Install CMake 3.16.4
    RUN wget "https://cmake.org/files/v3.16/cmake-3.16.4.tar.gz" \
    && tar xzf cmake-3.16.4.tar.gz \
    && rm cmake-3.16.4.tar.gz \
    && cd cmake-3.16.4 \
    && ./bootstrap --prefix=/usr/local/ \
    && make \
    && make install \
    && cd .. \
    && rm -rf cmake-3.16.4
    

    so you can use the prebuild version instead using:

    # Install CMake 3.16.4
    RUN wget "https://cmake.org/files/v3.16/cmake-3.16.4-Linux-x86_64.sh" \
    && chmod a+x cmake-3.16.4-Linux-x86_64.sh \
    && ./cmake-3.16.4-Linux-x86_64.sh --prefix=/usr/local/ --skip-license \
    && rm cmake-3.16.4-Linux-x86_64.sh