Skip to content

Initial Bazel setup and gcc arm_none_eabi toolchain#447

Merged
christophruethingbmw merged 6 commits into
eclipse-openbsw:mainfrom
esrlabs:cr-324872
May 19, 2026
Merged

Initial Bazel setup and gcc arm_none_eabi toolchain#447
christophruethingbmw merged 6 commits into
eclipse-openbsw:mainfrom
esrlabs:cr-324872

Conversation

@DominikAFischer
Copy link
Copy Markdown
Contributor

@DominikAFischer DominikAFischer commented Apr 24, 2026

Bazel gcc arm_none_eabi toolchain POC

  • Add Bazel workspace setup (.bazelrc, .bazelversion, MODULE.bazel)
  • Add nonhermetic arm-none-eabi-gcc toolchain for s32k148 platform
  • Migrate libs/bsw/util, libs/bsw/platform, libs/3rdparty/etl to Bazel

The intent of this PR is to get a discussion rolling an steer the Bazel migration in the right direction.

@DominikAFischer DominikAFischer marked this pull request as draft April 24, 2026 12:06
Copy link
Copy Markdown
Contributor

@christophruethingbmw christophruethingbmw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall, thank you very much for progressing on the Bazel integration. I left some comments and questions for discussion.

Comment thread bazel/toolchain/arm_none_eabi/gcc/arm_none_eabi_gcc_cc_toolchain_config.bzl Outdated
Comment thread libs/3rdparty/etl/BUILD
Comment thread libs/3rdparty/etl/BUILD
- Add Bazel workspace setup (.bazelrc, .bazelversion, MODULE.bazel)
- Add nonhermetic arm-none-eabi-gcc toolchain for s32k148 platform
- Migrate libs/bsw/util, libs/bsw/platform, libs/3rdparty/etl to Bazel
- Build artifact analysis for libs/bsw/util Cmake vs. Bazel
- Add bazel migration and artifact analysis. Details in bazel_migration/README.md

Change-Id: I20082f9a2ba9b451c2a61d2e76d88786782d6235
@DominikAFischer DominikAFischer force-pushed the cr-324872 branch 6 times, most recently from b7ee703 to 8a1d4fb Compare May 8, 2026 07:47
@DominikAFischer
Copy link
Copy Markdown
Contributor Author

DominikAFischer commented May 8, 2026

Implemented the following changes:

  • Use Docker-pinned arm-none-eabi toolchain for reproducible local Bazel builds
  • Add Bazel and Bazelisk cache directory mounts to development containers
  • Generate Bazel shell completion lazily on first shell start, persisted via cache mount
  • Use label_flag for etl_config
  • Rename build_config to executable_config to match naming in CMake
  • Document build configuration points and usage
  • Adjust util Bazel target sources to replicate recent changes on the Cmake side
  • Added BUILD file to etl's ignored files for rim

I propose to implement CI checks and linting for Bazel in a separate PR in order to keep the size manageable.

- Use Docker-pinned arm-none-eabi toolchain for reproducible local Bazel builds
- Add Bazel and Bazelisk cache directory mounts to development containers
- Generate Bazel shell completion lazily on first shell start, persisted via cache mount
- Use label_flag for etl_config
- Rename build_config to executable_config to match naming in CMake
- Document build configuration points and usage
- Adjust util Bazel target sources to replicate recent changes on the Cmake side

Change-Id: I48609afd53e88c6e24fcc6cbf4a5ecbbde3ca029
Change-Id: I4d67064e8878b9b531e2cee883e9042f7bb8232c
Change-Id: I5ee35aa720feeee2796ae41b213177d1e4f194f9
- Rename BUILD files to BUILD.bazel
- Remove redundant .gitignore
- Add end of file newline

Change-Id: Ib9b93b1affbb13fe1024a7a6045f2476091ba700
@DominikAFischer
Copy link
Copy Markdown
Contributor Author

DominikAFischer commented May 8, 2026

Added final minor changes and converting draft to a full-fledged PR. Also removed artifact analysis - this should be done in a separate step.

In order to keep the PR size manageable I propose to add Bazel related CI tests and bazel formatting checks via buildifier in a separate PR. If desired, the other PR can precede this PR.

@DominikAFischer DominikAFischer marked this pull request as ready for review May 8, 2026 13:26
/home/build/.cache was created as root during image build, making it
unwritable by the runtime UID injected via DOCKER_UID

Change-Id: I34081b4e016a90baa46627774e04b47f17b01637
Copy link
Copy Markdown
Contributor

@christophruethingbmw christophruethingbmw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, just some minor comments also raising further follow-up discussions.

Comment thread MODULE.bazel.lock
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder whether we want to check-in this file or not. I think i improves reproducibility, but it is can be very unhandy in case it is not updated properly because it is always modified during a build. Should we set --lockfile_mode=error in our .bazelrc? This would also ensure that in case something is changed CI will fail and the developer has to check-in an updated version?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's definitely a concern worth discussing. Thank you for bringing it up.

  • Bazel's best practices mention:

Include the lockfile in version control to facilitate collaboration and ensure that all team members have access to the same lockfile, promoting consistent development environments across the project.

  • In prinicple the lockfile should not be regenerated during normal builds even with the default lockfile_mode=update
  • For Ci I would propose to include this as one of the first steps: bazel mod graph --lockfile_mode=error

I personnally have not have problems with bazel lockfiles / version control in newer Bazel version. If you have another perspective I'm also happy to go with your proposal

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just know that we recently checked-in the lockfile in a bigger project recently and I always have the issue that it is changed on every build and I always end-up with a modified file. That's exetremely unhandy.

But maybe that is just an issue in this project and we do not have a problem here. I think we can give it a try and experience how it behaves. And, as we said, in case we have a check in CI by bazel mod graph --lockfile_mode=error it would be ensured that nobody forgets to check-in the lockfile.

Comment thread docker-compose.yaml
target: /home/build/.bash_history
bind:
create_host_path: false
- type: bind
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This raises an interesting discussion for me, but I think we do not need to conclude on it in this PR, it's not blocking for now.

Let me describe my personal workflow in combination of bazel and docker:

  • I do not bind-mount the actual .cache folder into the docker container.
    • This leads to problems for me, in case I e.g. build different projects in two docker containers in parallel. Both might choose the same output directory as bazel takes a hash of the workspace path which could be the same in different docker containers.
    • The paths in docker and on the host are then different, because the output directory is located under /home/build in the container but under /home/<user> on the host.

Therefore, I just bind-mount the repository and disk cache folders in the container to allow re-use of caches. The output directory I redirect into the source tree, we do a similar thing in cmake where we build in <src>/build, so I also redirect the bazel output directory to <src>/.build. This is quite handy for me.

  • I can simply browse also the generated files when opening the source directory in my editor of choice.
  • I do not run into conflicts between different source directories / projects.
  • Since we bind-mount the source directory at the same path in the docker container, paths will always be the same.

I also redirect the convenience symlinks into this directory, to not further pollute the source directory.

startup --output_base=.build/base
build --symlink_prefix=.build/

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the detailed write-up, this is something I did not consider.
After some research I agree that bind-mounting the entire ~/.cache/bazel is problematic.

Your suggestion looks good but I believe using relative paths for --output_base in .bazelrc might be problematic: bazelbuild/bazel#15137

But for now we could just split up the bind mounts to repo-cache and disk-cache to avoid naming clashes as you suggested:

- type: bind
  source: ${BAZEL_CACHE_DIR:-${XDG_CACHE_HOME:-${HOME}/.cache}/bazel}/repo-cache
  target: /home/build/.cache/bazel-repo-cache
  bind:
    create_host_path: true

- type: bind
  source: ${BAZEL_CACHE_DIR:-${XDG_CACHE_HOME:-${HOME}/.cache}/bazel}/disk-cache
  target: /home/build/.cache/bazel-disk-cache
  bind:
    create_host_path: true

and setting them in the .bazelrc like this:

common --repository_cache=/home/build/.cache/bazel-repo-cache
common --disk_cache=/home/build/.cache/bazel-disk-cache

Does that work for you?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mhh, I actually never though about the exact semantic of a relative path in --output_base because I always call bazel from the root of my workspace, I never did this differently. But the issue you linked clearly shows that it is not working in general.

Regarding the caches I think the approach you proposed should work, then the output_base is by default inside the container and if wanted, I can still set my --output_base to a relative path with the known caveats. Then we have persistent repository_cache and disk_cache.

Comment thread libs/bsw/util/BUILD.bazel
"src/util/command/HelpCommand.cpp",
"src/util/command/ParentCommand.cpp",
],
hdrs = glob(["include/**/*.h"]),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should decide whether we want to use globs or not, I know people considering this as a performance issue. I personally do not see it too problematic (in contrast to cmake), but maybe since we start now, we should think of whether we already define some practices we want to apply.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, recursive globs are generally discouraged: https://bazel.build/build/style-guide#recursive

That said, globs should be fine for our use case in my opinion:

  • Performance concerns should not apply given our codebase size
  • With our module structure one BUILD file per module is sufficient since we should never need to depend on anything more granular than the whole module. If this is not the case, we would need to split up the module into smaller packages using BUILD files.

Proposed practice:

  • prefer explicit file lists when lists are short (up to ~10 files)
  • prefer shallow globs over recursive globs
  • recursive globs are allowed to reference files inside subfolders of a module (with extension, as explicit as possible)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me, I also think globs can make the BUILD files more readable to avoid a very long listing of files. Maybe we can put a page in our documentation folder to write down some practices we apply for bazel in OpenBSW?

Comment thread libs/3rdparty/etl/BUILD
actual = select({
"//bazel/config/executable_config:reference_app": "//executables/referenceApp/etl_profile",
"//bazel/config/executable_config:unit_test": "//executables/unitTest/etl_profile",
"//conditions:default": "//executables/referenceApp/etl_profile",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we raise an error in this case? I think in case somebody does not explicitly specify this, it's better to throw an error instead of taking a "random" profile from the reference app?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Causing an error here could make sense. Just mind we're currently setting defaults in multiple other places. E.g.: in .bazelrc, bazel/config/executable_config/BUILD.bazel
So it all depends on what the expected behavior should be


# Bazel shell completion, generated once, persisted via the bazelisk cache mount
_bazelisk_cache_dir="$HOME/.cache/bazelisk"
_bazel_completions="$_bazelisk_cache_dir/bazel-complete.bash"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The completion script is so generic that it only depends on the bazelisk version? Couldn't we then also generate it within the docker image build instead of doing it every time when starting?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the completion script is also tied to an active Bazel version.

Two reasons this can't be moved to image build time:

  • bazelisk completion bash requires the Bazel binary to be downloaded first, which doesn't happen during docker build
  • Also ~/.cache/bazelisk is only bind-mounted from the host at container startup

load("@bazel_skylib//rules:common_settings.bzl", "string_flag")

string_flag(
name = "executable_config",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder whether we need this? I think we use it for the select in the label_flag right? I wonder whether we could also handle this through a transition such that we can simply build the reference app or the unit tests?

Copy link
Copy Markdown

@the-nick-fischer the-nick-fischer May 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that it's a bit awkward to model the OpenBSW structures in Bazel out of the box.

Personally, I've not worked with Bazel transitions yet, but I've heard that they can get quite complex and lead to bad performance if not used correctly.
We're currently setting some defaults for executable_config so reference_app will be set in general,but gets overwritten by unit_tets for bazel test.

What would be the advantages of using transitions? Is the "payoff" big enough to warrant the increased complexity (if there is any)?

NOTE: executable_config will also be used in selects in other lib targets (expected: app_configuration, common_impl, uds_configuration, lwip_configuration, bsp_configuration)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So transitions can be really hard to understand, I agree, and they can also (in case used wildly) introduce performance issues (especially in terms of caching and building the same files multiple times). Let's have a separate discussion on transition, since I think it is not a trivial topic :)

@DominikAFischer DominikAFischer changed the title Bazel gcc arm_none_eabi toolchain POC Initial Bazel setup and gcc arm_none_eabi toolchain May 13, 2026
@christophruethingbmw christophruethingbmw merged commit c73614f into eclipse-openbsw:main May 19, 2026
127 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants