cancel
Showing results for 
Search instead for 
Did you mean: 

How to use headless build for continuous integration?

Miles1
Associate III

I'd like to check if any pull requests break the build by automatically running headlessbuild as part of CI.

Unfortunately, there's a critical flaw with how STM32CubeIDE projects are setup, which makes this impossible.

The headless build depends on content in .metadata. This folder is automatically created when importing a project into STM32CubeIDE. But .metadata is also automatically added to .gitignore. It's definitely correct to ignore .metadata, since that contains a lot of user-specific content that you don't want to share among developers. But this now means that headless builds from a fresh clone don't work either.

You can try this out yourself by cloning this repo and branch. Note that this project is setup for Linux, but I'm sure you can reproduce the same issue on Windows too from a new project. You'll just need to call the headless build command directly, instead of using the ci.sh script.

git clone --branch issue-report https://github.com/milesfrain/stm32demo.git

And running:

./ci.sh
+ /opt/st/stm32cubeide_1.4.0/stm32cubeide --launcher.suppressErrors -nosplash -application org.eclipse.cdt.managedbuilder.core.headlessbuild -build all -data .
Building All Projects...
Building workspace
Saving workspace.
+ rtval=0
+ echo exit status: 0
exit status: 0
+ exit 0

Note that the build appears to work (exit code of zero indicates success), but it simply failed to find any projects to build.

In order to actually get headless build to work, you must open it once in the IDE first:

Open STM32CubeIDE
Select the cloned repo your workspact and import the ci_test project with:
File > Open Projects From File System

If you run `git status` you'll see that a .gitignore file was created to ignore .metadata.

Now the headless build will actually execute the build (with an intentional compilation error for testing purposes). Note that you need to close the IDE before building via command line.

./ci.sh
+ /opt/st/stm32cubeide_1.4.0/stm32cubeide --launcher.suppressErrors -nosplash -application org.eclipse.cdt.managedbuilder.core.headlessbuild -build all -data .
Building All Projects...
Building workspace
Building '/RemoteSystemsTempFiles'
Building '/ci_test'
 
...
 
../custom/src/mainCpp.cpp: In function 'int main()':
../custom/src/mainCpp.cpp:3:3: error: 'broken' was not declared in this scope
   broken();
   ^~~~~~
make: *** [custom/src/subdir.mk:18: custom/src/mainCpp.o] Error 1
make: *** Waiting for unfinished jobs....
"make -j4 all" terminated with exit code 2. Build might be incomplete.
 
17:42:58 Build Failed. 3 errors, 0 warnings. (took 643ms)

But if you simulate sharing this code with another developer or pushing to CI, for example with a commit and a clean, then the headless build will will fail to actually build the project again and report a false success (exit 0). Note that we'd generally want to ignore the build outputs, but I'm committing those in this case for simplicity.

git add .
git commit -am "some commit"
git clean -xdf
 
./ci.sh
 
+ /opt/st/stm32cubeide_1.4.0/stm32cubeide --launcher.suppressErrors -nosplash -application org.eclipse.cdt.managedbuilder.core.headlessbuild -build all -data .
Building All Projects...
Building workspace
Saving workspace.
+ rtval=0
+ echo exit status: 0
exit status: 0
+ exit 0

So now I'm not sure how to more forward. I'd really like to take advantage of the built-in MxCube tooling, but it seems like the only way to have a shareable project among developers that's also testable via CI is to ditch STM32CubeIDE and just use the classic Makefile approach. I could try to muck around in .metadata to figure out what to explicitly NOT ignore for headless builds to work, but who knows if that will turn into another huge waste of time.

Version info:

  • Ubuntu 20.04
  • STM32CubeIDE 1.40 reported, but I believe it's actually 1.42
  • FW_F4_V1.25.1 symlinked to /usr/share/stm_repo (but that shouldn't matter for reproducing this issue).
1 ACCEPTED SOLUTION

Accepted Solutions
Julien D
ST Employee

Hi Miles,

Indeed fine tuning the command line for CI can be a fight.

On my side I always execute the headless build application on a fresh new workspace to make sure that reproducibility is insured over all CI runs.

The workspace path is given by the option -data, the current directory in you example.

Then instead of importing project through the UI, which is most of the time not compliant with pure CI tests, you could simply use the -import / -importAll options.

In addition opening the UI first will add another project useless for CI: RemoteSystemsTempFiles. Project not created if you use -data as suggested above and -import option.

Note also that -build all will build all imported project with all their build configurations. Ie Debug and Release by default. And will not perform a full rebuild if project is already built. That's why I usually prefer -cleanBuild all or -cleanBuild .*/Release (only Release configuration for all projects) or -cleanBuild ci_test (all configurations for ci_test project)

Few more options may also help:

C:\>C:\ST\STM32CubeIDE_1.4.0\STM32CubeIDE\stm32cubeidec.exe -nosplash  --launcher.suppressErrors -application org.eclipse.cdt.managedbuilder.core.headlessbuild  -help
[-help]
Error: Help Requested
Usage:
   -import     {[uri:/]/path/to/project}
   -importAll  {[uri:/]/path/to/projectTreeURI} Import all projects under URI
   -build      {project_name_reg_ex{/config_reg_ex} | all}
   -cleanBuild {project_name_reg_ex{/config_reg_ex} | all}
   -no-indexer Disable indexer
   -markerType Marker types to fail build on {all | cdt | marker_id}
   -printErrorMarkers Print all error markers
   -I          {include_path} additional include_path to add to tools
   -include    {include_file} additional include_file to pass to tools
   -D          {prepoc_define} addition preprocessor defines to pass to the tools
   -E          {var=value} replace/add value to environment variable when running all tools
   -Ea         {var=value} append value to environment variable when running all tools
   -Ep         {var=value} prepend value to environment variable when running all tools
   -Er         {var} remove/unset the given environment variable
   -T          {toolid} {optionid=value} replace a tool option value in each configuration built
   -Ta         {toolid} {optionid=value} append to a tool option value in each configuration built
   -Tp         {toolid} {optionid=value} prepend to a tool option value in each configuration built
   -Tr         {toolid} {optionid=value} remove a tool option value in each configuration built
               Tool option values are parsed as a string, comma separated list of strings or a boolean based on the option's type
Saving workspace.

This way of working should allow you to have shareable project among developers with working headless build.

HTH

View solution in original post

7 REPLIES 7
Julien D
ST Employee

Hi Miles,

Indeed fine tuning the command line for CI can be a fight.

On my side I always execute the headless build application on a fresh new workspace to make sure that reproducibility is insured over all CI runs.

The workspace path is given by the option -data, the current directory in you example.

Then instead of importing project through the UI, which is most of the time not compliant with pure CI tests, you could simply use the -import / -importAll options.

In addition opening the UI first will add another project useless for CI: RemoteSystemsTempFiles. Project not created if you use -data as suggested above and -import option.

Note also that -build all will build all imported project with all their build configurations. Ie Debug and Release by default. And will not perform a full rebuild if project is already built. That's why I usually prefer -cleanBuild all or -cleanBuild .*/Release (only Release configuration for all projects) or -cleanBuild ci_test (all configurations for ci_test project)

Few more options may also help:

C:\>C:\ST\STM32CubeIDE_1.4.0\STM32CubeIDE\stm32cubeidec.exe -nosplash  --launcher.suppressErrors -application org.eclipse.cdt.managedbuilder.core.headlessbuild  -help
[-help]
Error: Help Requested
Usage:
   -import     {[uri:/]/path/to/project}
   -importAll  {[uri:/]/path/to/projectTreeURI} Import all projects under URI
   -build      {project_name_reg_ex{/config_reg_ex} | all}
   -cleanBuild {project_name_reg_ex{/config_reg_ex} | all}
   -no-indexer Disable indexer
   -markerType Marker types to fail build on {all | cdt | marker_id}
   -printErrorMarkers Print all error markers
   -I          {include_path} additional include_path to add to tools
   -include    {include_file} additional include_file to pass to tools
   -D          {prepoc_define} addition preprocessor defines to pass to the tools
   -E          {var=value} replace/add value to environment variable when running all tools
   -Ea         {var=value} append value to environment variable when running all tools
   -Ep         {var=value} prepend value to environment variable when running all tools
   -Er         {var} remove/unset the given environment variable
   -T          {toolid} {optionid=value} replace a tool option value in each configuration built
   -Ta         {toolid} {optionid=value} append to a tool option value in each configuration built
   -Tp         {toolid} {optionid=value} prepend to a tool option value in each configuration built
   -Tr         {toolid} {optionid=value} remove a tool option value in each configuration built
               Tool option values are parsed as a string, comma separated list of strings or a boolean based on the option's type
Saving workspace.

This way of working should allow you to have shareable project among developers with working headless build.

HTH

Thanks so much for sharing a solution!

I didn't realize that the -help flag needed to be after the -application. I initially tried passing it to the original command:

/opt/st/stm32cubeide_1.4.0/stm32cubeide -help

In case anyone else is trying trying to setup CI for GitHub Actions (or similar), here's a working example:

https://github.com/milesfrain/stm32demo

Miles1
Associate III

An aside that I meant to report earlier: The headless-build.sh script always exits with status zero (pass). This should probably be changed to return the actual status code from the stm32cubeide command, so it's actually usable for CI purposes where you want to catch failures with a non-zero exit code.

/opt/st/stm32cubeide_1.4.0/headless-build.sh

Although it's definitely better for my use case to ignore this script and call stm32cubeide directly in order to have more control over the flags.

True ! Thanks for the report.

Looks like this has been fixed in v1.50.

In the release notes:

"93098 Headless build script should return with the same exit code as sub-process"

I also see this entry in the release notes:

"Added FreeRTOS™ support"

Where can I read more about that new feature? It seems like there's always been some basic level of FreeRTOS support in earlier versions, such as v1.40.

toroid
Associate III

Thanks everyone for pointing me to the right direction with this. I also first tried to use headless_build.sh script, which works fine on the local computer but could not get it to work in the docker container that gitlab uses. This is because there is no workspace available here. However, you can see the working command below.

The project I am using is generated with STM32CubeMX (generate project for cubeide) and STM32CubeIDE is used for development.

This is a stripped version of my .gitlab-ci.yml script that works for me:

image: "wsbu/stm32cubeide:1.5.0"

variables:

 GIT_SUBMODULE_STRATEGY: "recursive"

stages:

 - build

target_build:

 stage: build

 script:

# Build the project

   - pwd

   - /opt/stm32cubeide/stm32cubeide --launcher.suppressErrors -nosplash -application org.eclipse.cdt.managedbuilder.core.headlessbuild -build cubeideprojectname/Debug -import ../gitlabprojectname/

Notice that cubeideprojectname might not be what you expect. Importing the git cloned project to a new workspace will reveal the project name that Docker will see.