-
Notifications
You must be signed in to change notification settings - Fork 135
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
### Summary Another GH Actions article, this time for STM32CubeIDE 🧊. Grammar pass and formatting. ### Test Plan Visual check
- Loading branch information
Showing
1 changed file
with
360 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,360 @@ | ||
--- | ||
title: GitHub Actions for STM32CubeIDE | ||
description: Using GitHub Actions to automate building STM32CubeIDE projects. | ||
author: noah | ||
--- | ||
|
||
<!-- excerpt start --> | ||
|
||
In this article, we will explore how to use GitHub Actions to automate building | ||
STM32CubeIDE projects. Eclipse-based IDEs like STM32CubeIDE are often used for | ||
developing embedded systems but can be a little tricky to build in a headless | ||
environment. | ||
|
||
<!-- excerpt end --> | ||
|
||
{% include newsletter.html %} | ||
|
||
{% include toc.html %} | ||
|
||
## STM32CubeIDE, Docker, and GitHub Actions | ||
|
||
![Picture of GitHub Actions calling STM32CubeIDE in a Docker container](/img/stm32cubeide/arch.excalidraw.svg) | ||
|
||
The STM32 series of microcontrollers from STMicroelectronics are very popular in | ||
the embedded industry. ST provides an in-house Eclipse-based IDE, [STM32CubeIDE](https://www.st.com/en/development-tools/stm32cubeide.html), | ||
for developing projects using these chips. | ||
|
||
The STM32CubeIDE provides an all-in-one starting point for STM32-based projects, | ||
with example code, drivers, and middleware, and tools for configuring | ||
peripherals and generating code. | ||
|
||
This IDE comes with both the graphical frontend (using Eclipse) and the | ||
necessary tools for building, flashing, and debugging STM32 projects (it bundles | ||
a pretty standard GCC ARM toolchain and the ST-Link tools for flashing and | ||
debugging). | ||
|
||
Typically, developers install the STM32CubeIDE on their local machines and build | ||
projects using the GUI, but that manual and UI-based workflow doesn't fit well | ||
with modern CI systems- we want to build our projects automatically, without | ||
clicking through a UI, and in a reproducible way. | ||
|
||
To accomplish this, we'll use Docker to create a containerized environment with | ||
STM32CubeIDE and its dependencies, and GitHub Actions to automate the build | ||
process! | ||
|
||
## Step 1: Setting Up the Docker Image | ||
|
||
> Note: STM32CubeIDE generates `Makefile`'s when building a project- these | ||
> _could_ be checked into source control, and used directly when building a | ||
> project, similar to this: | ||
> | ||
> ```bash | ||
> $ PATH=~/STMicroelectronics/stm32cubeide_1.15.0/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.12.3.rel1.linux64_1.0.100.202403111256/tools/bin:$PATH make -r -j8 -C Debug all | ||
> arm-none-eabi-gcc | ||
> ``` | ||
> | ||
> The downside is this diverges from the normal UI-based workflow- and if the | ||
> project configuration changes, requiring a Makefile update, that might not be | ||
> automatically reflected in the CI build. | ||
> | ||
> We're going to stick with hard mode and build directly from the Eclipse | ||
> command line to better replicate the UI workflow. | ||
Since Docker containers are generally Linux-based and work best on the cheap | ||
Linux VMs provided by GitHub Actions (or other CI systems), we'll use a | ||
Linux-based Docker image as our base. | ||
Our Docker image only needs a few dependencies to run STM32CubeIDE: | ||
1. `git`: we need this if we want to be able to clone our project from within | ||
the container | ||
2. Linux version of `STM32CubeIDE`: the main event! We'll download the installer | ||
and run it when generating the image, which will "freeze" the IDE in the | ||
Docker image | ||
With tools of this nature, I like to follow this process to create the image: | ||
1. start with a base Linux image | ||
2. interactively run through the installation steps until I get to a functional | ||
state | ||
3. copy the commands into a Dockerfile and generate the image | ||
4. test the image | ||
```bash | ||
# run a container from the base image, interactively | ||
$ docker run --tty --interactive ubuntu:22.04 | ||
``` | ||
I first tried following the | ||
[installation instructions provided by ST (PDF warning)](https://www.st.com/resource/en/user_manual/um2563-stm32cubeide-installation-guide-stmicroelectronics.pdf), | ||
which are pretty straightforward; in my case it was running this command after | ||
downloading the installer: | ||
```bash | ||
sudo sh ./st-stm32cubeide_1.15.0_20695_20240315_1429_amd64.deb_bundle.sh | ||
``` | ||
Unfortunately, the embedded script in that self-extracting installer doesn't | ||
fully support an unattended mode (at least in the versions I tested, 1.15 + | ||
1.16). | ||
After some experimentation, I found a way to work around this; instead of | ||
running the installer as-is, I extracted the installation files from the | ||
package, and run them independently with a minor tweak. | ||
Below is the final Dockerfile I used to create the image, with some comments | ||
explaining the more exotic parts. | ||
```dockerfile | ||
# This is the newest Ubuntu version with simple support for STM32CubeIDE- it | ||
# the Cube .deb has a dependency on the python2.7 package, which is no longer | ||
# available in the latest Ubuntu LTS 24.04 | ||
FROM ubuntu:22.04 | ||
# Typical dockerfile stuff, try to suppress interactive prompts when installing | ||
# packages | ||
ENV DEBIAN_FRONTEND=noninteractive | ||
# Install dependencies for STM32CubeIDE | ||
RUN apt-get update && apt-get install --no-install-recommends -y \ | ||
build-essential \ | ||
ca-certificates \ | ||
# we use git to clone our project when we build in GitHub actions | ||
git \ | ||
# part of the Cube install script uses "killall", which is in psmisc | ||
psmisc \ | ||
# we use this to extract the Cube installer | ||
unzip | ||
# don't clear apt cache, the stm32cubeide installer needs it | ||
# && rm -rf /var/lib/apt/lists/* | ||
ARG STM32CUBE_VERSION=1.16.0_21983_20240628_1741 | ||
# Copy the installer file into the image. It needs to be downloaded into the | ||
# directory where the Dockerfile is. | ||
COPY en.st-stm32cubeide_${STM32CUBE_VERSION}_amd64.deb_bundle.sh.zip /tmp/stm32cubeide.sh.zip | ||
RUN mkdir -p /tmp/stm32cubeide && \ | ||
mv /tmp/stm32cubeide.sh.zip /tmp/stm32cubeide/stm32cubeide.sh.zip && \ | ||
cd /tmp/stm32cubeide && \ | ||
unzip stm32cubeide.sh.zip && \ | ||
chmod +x st-stm32cubeide_${STM32CUBE_VERSION}_amd64.deb_bundle.sh && \ | ||
# run the self-unpacker script, but don't actually install anything | ||
./st-stm32cubeide_${STM32CUBE_VERSION}_amd64.deb_bundle.sh --target ./ --noexec && \ | ||
# this is required to avoid an error during apt-get install | ||
chmod a+r /tmp/stm32cubeide/*.deb && \ | ||
chmod 777 /tmp/stm32cubeide/*.deb && \ | ||
# need to set this env var for unattended install. install everything | ||
# manually, to avoid issues with the installer script, which does not have | ||
# an unattended install mode. | ||
LICENSE_ALREADY_ACCEPTED=1 apt-get install -y \ | ||
/tmp/stm32cubeide/st-st*.deb && \ | ||
rm -rf /tmp/stm32cubeide | ||
``` | ||
### Building the Docker Image | ||
Once we've created the Dockerfile, we'll build the image as usual: | ||
```bash | ||
# note that I'm tagging the image with the build date and putting it into a | ||
# GitHub container registry namespace for later uploading to the GitHub | ||
# container registry | ||
$ DOCKER_BUILDKIT=1 docker build -t ghcr.io/noahp/stm32wba55-example:2024-08-01 -f Dockerfile . | ||
``` | ||
### Testing the Docker Image | ||
To test it, we first need to figure out how to actually build an STM32CubeIDE | ||
project from the command line. | ||
After a bit of digging, I found that the Eclipse IDE has a command-line build | ||
option, aptly named `-build`. When running from a container, we need to first | ||
import the project from the file system into the Eclipse workspace (in this | ||
case, from our cloned Git repo), using the `-import` command, then we can build | ||
it. | ||
> Note: I'm using a test project from | ||
> [here](https://github.com/memfault/stm32wba55-example), because it's one I'm | ||
> familiar with, but the same process should work with any STM32CubeIDE project. | ||
From the root of our test process, start the Docker container with the current | ||
directory mounted inside it, so we can build it! | ||
```bash | ||
$ docker run --rm --tty --interactive --volume=${PWD}:/workdir ghcr.io/noahp/stm32wba55-example:2024-08-01 | ||
``` | ||
Inside the Docker container, the STM32CubeIDE executable is located at | ||
`/opt/st/stm32cubeide_1.16.0/stm32cubeide`. We will run this with the `-import` | ||
command to import the project into the workspace, and the `-data` flag to | ||
specify the workspace directory: | ||
```bash | ||
$ /opt/st/stm32cubeide_1.16.0/stm32cubeide --launcher.suppressErrors -nosplash \ | ||
-application org.eclipse.cdt.managedbuilder.core.headlessbuild \ | ||
-data /tmp/stm-workspace \ | ||
-import "STM32CubeIDE" | ||
``` | ||
Then, we run the `-build` command to build the project: | ||
```bash | ||
$ /opt/st/stm32cubeide_1.16.0/stm32cubeide --launcher.suppressErrors -nosplash \ | ||
-application org.eclipse.cdt.managedbuilder.core.headlessbuild \ | ||
-data /tmp/stm-workspace \ | ||
-build BLE_p2pServer/Debug | ||
``` | ||
That command will emit lots of output, then if it succeeds: | ||
```plaintext | ||
Finished building target: BLE_p2pServer.elf | ||
arm-none-eabi-size BLE_p2pServer.elf | ||
arm-none-eabi-objdump -h -S BLE_p2pServer.elf > "BLE_p2pServer.list" | ||
arm-none-eabi-objcopy -O binary BLE_p2pServer.elf "BLE_p2pServer.bin" | ||
text data bss dec hex filename | ||
205944 1480 59560 266984 412e8 BLE_p2pServer.elf | ||
Finished building: default.size.stdout | ||
Finished building: BLE_p2pServer.bin | ||
Finished building: BLE_p2pServer.list | ||
13:25:31 Build Finished. 0 errors, 1 warnings. (took 2s.487ms) | ||
``` | ||
🎉 Phew, we made it! That's the most exciting part of this article. Now on to | ||
running the same build commands in a GitHub action. | ||
## Creating a GitHub Actions Workflow | ||
For those unfamiliar with GitHub Actions, a quick overview can be found at the | ||
[GitHub Actions documentation](https://docs.github.com/en/actions). | ||
We're going to create a workflow that does the following: | ||
1. clone the project code | ||
2. run the STM32CubeIDE build commands | ||
3. upload the build artifacts | ||
Before we can start, let's first upload our Docker image to the GitHub Container | ||
Registry. Uploading to the registry will allow us to use the image in our GitHub | ||
Actions workflow. | ||
Pushing the image to the GitHub container registry requires logging in | ||
(instructions | ||
[here](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-to-the-container-registry)), | ||
then doing the normal docker push command: | ||
```bash | ||
$ docker push ghcr.io/noahp/stm32wba55-example:2024-08-01 | ||
``` | ||
Next, we'll open the container URL in our browser | ||
(<https://ghcr.io/noahp/stm32wba55-example>) and set the image to public so our | ||
GitHub Actions workflow can use it without logging in. | ||
Now we're ready to write our GitHub Actions workflow. | ||
### Implementing the Workflow | ||
Our workflow is pretty simple- using the Docker image we just pushed, we'll go | ||
through the steps outlined above. | ||
Here's the workflow file: | ||
```yaml | ||
name: Build STM32 Project | ||
on: | ||
pull_request: | ||
branches: | ||
- main | ||
push: | ||
branches: | ||
- main | ||
# enable manual triggering | ||
workflow_dispatch: | ||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
container: | ||
image: ghcr.io/noahp/stm32wba55-example:2024-08-01 | ||
steps: | ||
- name: Checkout code | ||
uses: actions/checkout@v4 | ||
with: | ||
# submodules too | ||
submodules: recursive | ||
- name: Build project | ||
run: | | ||
/opt/st/stm32cubeide_1.16.0/stm32cubeide --launcher.suppressErrors -nosplash \ | ||
-application org.eclipse.cdt.managedbuilder.core.headlessbuild \ | ||
-data /tmp/stm-workspace \ | ||
-import "STM32CubeIDE" | ||
/opt/st/stm32cubeide_1.16.0/stm32cubeide --launcher.suppressErrors -nosplash \ | ||
-application org.eclipse.cdt.managedbuilder.core.headlessbuild \ | ||
-data /tmp/stm-workspace \ | ||
-build BLE_p2pServer/Debug | ||
- name: Upload artifacts | ||
uses: actions/upload-artifact@v4 | ||
with: | ||
name: BLE_p2pServer.artifacts | ||
# The output artifacts are BLE_p2pServer.elf, BLE_p2pServer.map, etc. | ||
# Use a wildcard pattern to get them all | ||
path: STM32CubeIDE/Debug/BLE_p2pServer.* | ||
``` | ||
And that's it! We'll check that into our test project, push it to a branch, and | ||
open a test pull request to see it in action. | ||
![Screenshot of the GitHub Actions workflow running](/img/stm32cubeide/github-actions-success.png) | ||
We can also download the artifacts from the GitHub Actions UI and confirm the | ||
uploaded files are correct ✅. | ||
## In Conclusion | ||
I hope you enjoyed this article! It's always a fun challenge to coax some of the | ||
less cooperative tools into working in a CI environment, and STM32CubeIDE was no | ||
exception. | ||
You could use a similar process for other Eclipse-based IDEs since most support | ||
the `-build` command line argument. | ||
One last note- this example is saving the build artifacts (`.elf` and `.bin` | ||
files, etc.) as GitHub Actions artifacts, but you could also upload them to a | ||
cloud storage service, or even use the GitHub API to create a release with the | ||
artifacts attached. One simple approach is to push the artifacts into AWS S3 | ||
storage under a folder named after the commit hash so you can easily find the | ||
artifacts for a given commit. | ||
Thanks for reading! | ||
<!-- Interrupt Keep START --> | ||
{% include newsletter.html %} | ||
{% include submit-pr.html %} | ||
<!-- Interrupt Keep END --> | ||
{:.no_toc} | ||
## References | ||
<!-- prettier-ignore-start --> | ||
- [The example project in this article](https://github.com/noahp/stm32wba55-example) | ||
- [STM32CubeIDE website](https://www.st.com/en/development-tools/stm32cubeide.html) | ||
- [MCU on Eclipse article about command-line building Eclipse](https://mcuoneclipse.com/2014/09/12/building-projects-with-eclipse-from-the-command-line/) | ||
- [A pre-built GitHub action for STM32CubeIDE](https://github.com/xanderhendriks/action-build-stm32cubeide) | ||
- [The Dockerfile used in the above action](https://github.com/xanderhendriks/docker-stm32cubeide) | ||
<!-- prettier-ignore-end --> |