Initial Bazel setup and gcc arm_none_eabi toolchain#447
Conversation
- 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
b7ee703 to
8a1d4fb
Compare
|
Implemented the following changes:
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
|
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 |
/home/build/.cache was created as root during image build, making it unwritable by the runtime UID injected via DOCKER_UID Change-Id: I34081b4e016a90baa46627774e04b47f17b01637
christophruethingbmw
left a comment
There was a problem hiding this comment.
Looks good to me, just some minor comments also raising further follow-up discussions.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
| target: /home/build/.bash_history | ||
| bind: | ||
| create_host_path: false | ||
| - type: bind |
There was a problem hiding this comment.
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
.cachefolder 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/buildin 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/
There was a problem hiding this comment.
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: trueand 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?
There was a problem hiding this comment.
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.
| "src/util/command/HelpCommand.cpp", | ||
| "src/util/command/ParentCommand.cpp", | ||
| ], | ||
| hdrs = glob(["include/**/*.h"]), |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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?
| 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", |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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/bazeliskis only bind-mounted from the host at container startup
| load("@bazel_skylib//rules:common_settings.bzl", "string_flag") | ||
|
|
||
| string_flag( | ||
| name = "executable_config", |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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 :)
Bazel gcc arm_none_eabi toolchain POC
The intent of this PR is to get a discussion rolling an steer the Bazel migration in the right direction.