Docker and GitHub Actions for Embedded Compilation
Using Docker and GitHub actions to build an embedded project.
These scripts have been added to to the modern C++ RISC-V blinky project to implement build on push.
The general flow is:
- The
github/workflows/main.yml
has a trigger on push, this invokes an action inaction.yml
. - The
action.yml
action will create the Docker image and execute - The
Dockerfile
builds an image that includes to tool-chain and adocker_entrypoint.sh
script to call CMake. - The docker image is loaded and passed the path to the
GITHUB_WORKSPACE
that has a clone of the git repo. - The arguments passed to docker and forwarded to the
docker_entrypoint.sh
script and it changes to that folder and builds using CMake. - On exit
github/workflows/main.yml
packages up the build artifacts.
Refer to the github documentation:
- (https://docs.github.com/en/actions/creating-actions/creating-a-docker-container-action)
- (https://docs.github.com/en/actions/advanced-guides/storing-workflow-data-as-artifacts)
The Dockerfile
This simply takes the instructions from the
tool-chain and
scriptifies them. (although updating the PATH
like this is not
recommended by the xpack tools maintainer..)
RUN npm install --global xpm@latest
RUN xpm install --global --verbose @xpack-dev-tools/riscv-none-embed-gcc@10.1.0-1.1.1
ENV PATH=/root/.xpack/repos/@xpack-dev-tools/riscv-none-embed-gcc/10.1.0-1.1.1/.content/bin:/bin/:/usr/bin
It then configures the ENTRYPOINT
script that is run when the docker image is started.
# Copies your code file from your action repository to the filesystem path `/` of the container
COPY docker_entrypoint.sh /docker_entrypoint.sh
# Code file to execute when the docker container starts up (`entrypoint.sh`)
ENTRYPOINT ["/docker_entrypoint.sh"]
The ENTRYPOINT script.
The first argument is the path to the cloned git repo.
if [ -d $1 ] ; then
echo "Changing to working directory: $1"
cd $1
else
echo "Not changing directory: $1"
fi
# Remove $1 from args.
shift
This iterates over each argument, using it to determine the path to the project.
while (( "$#" )) ; do
PROJECT_SRC=$1
BUILD_DIR=${BUILD_BASE}/$(basename ${PROJECT_SRC/\/src})
#... CMake work ...
shift
done
The work is done by CMake.
Create the build scripts: (-B
is the output directory, -S
is the source).
The RISC-V toolchain file is from five-embeddev.com.
rm -rf ${BUILD_DIR}
mkdir -p ${BUILD_DIR}
cmake \
-DCMAKE_MAKE_PROGRAM=make \
-DCMAKE_TOOLCHAIN_FILE=../../${CMAKE_DIR}/riscv.cmake \
-G "Unix Makefiles" \
-B ${BUILD_DIR} \
-S ${PROJECT_SRC} \
Run then and check the output:
cmake --build ${BUILD_DIR}
if [ "$?" != "0" ] ; then
echo "CMAKE: ${PROJECT_SRC}; Build failed: $?"
rc = $[$rc + 1]
else
echo "CMAKE: ${PROJECT_SRC}; Build success"
fi
Github Actions
Modified from here.
The syntax is here
The inputs.workspace
is defined as the docker file can also be tested locally by changing this argument.
The start_time
and end_time
are not really needed, they are inherited from the github example.
# action.yml
name: 'Build'
description: 'Build and record the time'
inputs:
workspace:
description: Path to repo
outputs:
start_time: # id of output
description: 'before build'
end_time: # id of output
description: 'after build'
runs:
using: 'docker'
image: 'Dockerfile'
args:
- $\{\{inputs.workspace\}\}
- blinky/src
Looking at the github log it is translated into this (when run on a repo called: github-workflows-test
).
/usr/bin/docker run \
--name fa4e14a6f1948d972d5ce0546cd13fe8f27891_23dc6d \
--label fa4e14 \
--workdir /github/workspace \
--rm \
-e INPUT_WORKSPACE \
-e HOME \
-e GITHUB_JOB \
-e GITHUB_REF \
-e GITHUB_SHA \
-e GITHUB_REPOSITORY \
-e GITHUB_REPOSITORY_OWNER \
-e GITHUB_RUN_ID \
-e GITHUB_RUN_NUMBER \
-e GITHUB_RETENTION_DAYS \
-e GITHUB_RUN_ATTEMPT \
-e GITHUB_ACTOR \
-e GITHUB_WORKFLOW \
-e GITHUB_HEAD_REF \
-e GITHUB_BASE_REF \
-e GITHUB_EVENT_NAME \
-e GITHUB_SERVER_URL \
-e GITHUB_API_URL \
-e GITHUB_GRAPHQL_URL \
-e GITHUB_WORKSPACE \
-e GITHUB_ACTION \
-e GITHUB_EVENT_PATH \
-e GITHUB_ACTION_REPOSITORY
-e GITHUB_ACTION_REF \
-e GITHUB_PATH \
-e GITHUB_ENV \
-e RUNNER_OS \
-e RUNNER_NAME \
-e RUNNER_TOOL_CACHE \
-e RUNNER_TEMP \
-e RUNNER_WORKSPACE \
-e ACTIONS_RUNTIME_URL \
-e ACTIONS_RUNTIME_TOKEN \
-e ACTIONS_CACHE_URL \
-e GITHUB_ACTIONS=true \
-e CI=true \
-v "/var/run/docker.sock":"/var/run/docker.sock" \
-v "/home/runner/work/_temp/_github_home":"/github/home" \
-v "/home/runner/work/_temp/_github_workflow":"/github/workflow" \
-v "/home/runner/work/_temp/_runner_file_commands":"/github/file_commands" \
-v "/home/runner/work/github-workflows-test/github-workflows-test":"/github/workspace" fa4e14:a6f1948d972d5ce0546cd13fe8f27891 \
"/home/runner/work/github-workflows-test/github-workflows-test" \
"blinky/src"
Github Workflow
This file was created by the github template. The additions are below.
The interface to actions are.
- Path to
actions.yml
:uses: ./
- Arguments to the action:
with: workspace: $\{\{ github.workspace \}\}
- Results from the actions:
$\{\{ steps.build.outputs.start_time \}\}
# Runs a single command using the runners shell
- name: Docker Build
uses: ./
id: Build
with:
workspace: $\{\{ github.workspace \}\}
- name: Get the output time
run: echo "The time was $\{\{ steps.build.outputs.start_time \}\} -> $\{\{ steps.build.outputs.end_time \}\}"
Another action saves the build output:
- name: Archive production artifacts
uses: actions/upload-artifact@v2
with:
name: build-files
path: |
build/**/*.elf
build/**/*.hex
build/**/*.disasm
build/**/*.map
Subscribe via RSS