Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,29 @@ jobs:
working-directory: ${{ matrix.directory }}
run: cargo build

bare-metal-tests:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup Bazel cache
uses: ./.github/workflows/setup-bazel-cache

- name: Install QEMU
uses: ./.github/workflows/apt-get-install
with:
packages: >
build-essential
gdb-multiarch
libudev-dev
picocom
pkg-config
qemu-system-arm

- name: Run QEMU tests
run: bazel test //:bare_metal_tests

find-languages:
runs-on: ubuntu-latest
outputs:
Expand Down
20 changes: 20 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Root BUILD file for Comprehensive Rust workspace.

This file defines a central test_suite for running manual QEMU tests.
"""

package(default_visibility = ["//visibility:public"])

test_suite(
name = "bare_metal_tests",
tags = ["manual"],
tests = [
"//src/bare-metal/aps/examples:improved_test",
"//src/bare-metal/aps/examples:logger_test",
"//src/bare-metal/aps/examples:minimal_test",
"//src/bare-metal/aps/examples:psci_test",
"//src/bare-metal/aps/examples:rt_test",
"//src/bare-metal/aps/examples:safemmio_test",
"//src/exercises/bare-metal/rtc:rtc_test",
],
)
112 changes: 112 additions & 0 deletions GEMINI.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,3 +282,115 @@ Use `--mochaOpts.grep` to run a single test within a file:
```bash
npm test -- --spec redbox --mochaOpts.grep "should be hidden by default"
```

## Bazel Bare-Metal and Microcontroller Integration

This project integrates Bazel rules for bare-metal AArch64 and microcontroller
target platforms. Key findings and conventions include:

### Checking Bazel Version

Always check the `.bazelversion` file at the workspace root (e.g., `9.1.1`)
before inspecting the host Bazel version.

### Target Platforms & Constraints

Bare-metal and microcontroller examples specify target CPU/OS constraint
configurations (e.g., `os:none`):

- `aarch64-unknown-none`: Mapped in `platforms/BUILD.bazel` to
`@platforms//os:none` and `@platforms//cpu:aarch64`.
- `thumbv7em-none-eabihf`: Mapped to `@platforms//os:none` and
`@platforms//cpu:armv7e-mf` (matching rules_rust's internal CPU naming
convention).

### Wildcard Host Builds & Compatibility

To prevent raw target compilation failures during wildcard host commands (like
`bazel build //...` or `bazel test //...`), all target-specific `rust_binary`
targets declare compatibility constraints:

```bazel
rust_binary(
name = "my_target",
...
target_compatible_with = ["@platforms//os:none"],
)
```

This causes Bazel to automatically skip building these targets on host
configurations (where OS is Linux/macOS) instead of failing.

### Cargo Universe Imports & Host Splicing

When importing external crates via `rules_rust`'s Cargo Universe
(`crate.from_cargo`), the host platform triple (e.g.,
`x86_64-unknown-linux-gnu`) must be included in `supported_platform_triples`:

```bazel
crate.from_cargo(
...
supported_platform_triples = [
"x86_64-unknown-linux-gnu",
"aarch64-unknown-none",
],
)
```

Even if the imported dependencies are purely for bare-metal targets,
`cargo-bazel` runs on the host during the splicing phase and invokes
`cargo tree` to perform feature and dependency resolution. Excluding the host
execution platform triple prevents Bazel from resolving a host cargo toolchain,
resulting in feature generation failures during the analysis phase.

### Hermetic Binary Extraction (Objcopy)

Since host systems may lack target-specific `rust-objcopy` executables, the
extraction of `.bin` flat images is managed hermetically inside the Bazel
sandbox using a `genrule` target pointing to the nightly compiler's bundled
`rust-objcopy` tool:

```bazel
genrule(
name = "my_target_bin",
srcs = [":my_target"],
outs = ["my_target.bin"],
cmd = "$(execpath @rust_host_tools_nightly//:rust-objcopy) -O binary $(location :my_target) $@",
tools = ["@rust_host_tools_nightly//:rust-objcopy"],
)
```

### Host-Executed QEMU Tests, Runners & Platform Transitions

By default, Bazel test and binary runner targets run on the host system. Since
the target binaries require cross-compiling, we use Starlark configuration
transitions in `platforms/transition.bzl` (via rules like `aarch64_binary` or
`thumbv7em_binary`) to transition the target binary's compilation mode to the
target platform while letting the wrapper `sh_test` or `sh_binary` execute on
the host:

```bazel
# In platforms/transition.bzl:
def _aarch64_transition_impl(settings, attr):
return {"//command_line_option:platforms": ["//platforms:aarch64-unknown-none"]}

# In target BUILD.bazel:
aarch64_binary(
name = "my_target_bin_aarch64",
dep = ":my_target_bin",
)

# Automated host test (non-interactive, pipes 'q'):
sh_test(
name = "my_target_test",
srcs = ["run_test.sh"],
data = [":my_target_bin_aarch64"],
)

# Interactive host runner (run via `bazel run`):
sh_binary(
name = "my_target_run",
srcs = ["run_test.sh"],
data = [":my_target_bin_aarch64"],
)
```
73 changes: 73 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,25 @@ module(
version = "0.1.0",
)

bazel_dep(name = "platforms", version = "1.1.0")
bazel_dep(name = "rules_rust", version = "0.70.0")
bazel_dep(name = "rules_shell", version = "0.8.0")

# Remove when https://github.com/bazelbuild/rules_rust/pull/4019 is
# released and we update past version 0.70.0.
git_override(
module_name = "rules_rust",
commit = "d2d856d13f89f234298db1606ece7afd1c3eee1f",
remote = "https://github.com/bazelbuild/rules_rust.git",
)

rust = use_extension("@rules_rust//rust:extensions.bzl", "rust")
rust.toolchain(
edition = "2024",
extra_target_triples = [
"aarch64-unknown-none",
"thumbv7em-none-eabihf",
],
)
use_repo(rust, "rust_toolchains")

Expand Down Expand Up @@ -77,3 +91,62 @@ crate.from_specs(
host_tools = "@rust_host_tools_nightly",
)
use_repo(crate, "svgbob_plugin")

# Bare-Metal and Microcontroller Examples
#
# Note: Host triple is required in supported_platform_triples below so
# cargo-bazel can invoke cargo tree on the host during splicing.
crate.from_cargo(
name = "bare_metal_alloc_example",
cargo_lockfile = "//src/bare-metal/alloc-example:Cargo.lock",
manifests = ["//src/bare-metal/alloc-example:Cargo.toml"],
supported_platform_triples = [
"x86_64-unknown-linux-gnu",
"aarch64-unknown-none",
],
)
use_repo(crate, "bare_metal_alloc_example")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why do we need both these and the build files in the various directories? How do they interact?


crate.from_cargo(
name = "bare_metal_aps",
cargo_lockfile = "//src/bare-metal/aps/examples:Cargo.lock",
manifests = ["//src/bare-metal/aps/examples:Cargo.toml"],
supported_platform_triples = [
"x86_64-unknown-linux-gnu",
"aarch64-unknown-none",
],
)
use_repo(crate, "bare_metal_aps")

crate.from_cargo(
name = "bare_metal_microcontrollers",
cargo_lockfile = "//src/bare-metal/microcontrollers/examples:Cargo.lock",
manifests = ["//src/bare-metal/microcontrollers/examples:Cargo.toml"],
supported_platform_triples = [
"x86_64-unknown-linux-gnu",
"thumbv7em-none-eabihf",
],
)
use_repo(crate, "bare_metal_microcontrollers")

crate.from_cargo(
name = "bare_metal_compass",
cargo_lockfile = "//src/exercises/bare-metal/compass:Cargo.lock",
manifests = ["//src/exercises/bare-metal/compass:Cargo.toml"],
supported_platform_triples = [
"x86_64-unknown-linux-gnu",
"thumbv7em-none-eabihf",
],
)
use_repo(crate, "bare_metal_compass")

crate.from_cargo(
name = "bare_metal_rtc",
cargo_lockfile = "//src/exercises/bare-metal/rtc:Cargo.lock",
manifests = ["//src/exercises/bare-metal/rtc:Cargo.toml"],
supported_platform_triples = [
"x86_64-unknown-linux-gnu",
"aarch64-unknown-none",
],
)
use_repo(crate, "bare_metal_rtc")
3,279 changes: 2,477 additions & 802 deletions MODULE.bazel.lock

Large diffs are not rendered by default.

33 changes: 33 additions & 0 deletions platforms/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# platforms/BUILD.bazel
"""Target Platform Definitions for Embedded Cross-Compilation.

In Bazel, platforms represent execution and target build environments.
We define target platforms by declaring their constraint values (CPU
architecture and Operating System). The registered Rust toolchain
matches these constraint combinations to determine which
compiler/linker options to use:

- `aarch64-unknown-none` represents AArch64 bare-metal (no OS)
environments.

- `thumbv7em-none-eabihf` represents ARM Cortex-M4F microcontroller
environments.
"""

package(default_visibility = ["//visibility:public"])

platform(
name = "aarch64-unknown-none",
constraint_values = [
"@platforms//os:none",
"@platforms//cpu:aarch64",
],
)

platform(
name = "thumbv7em-none-eabihf",
constraint_values = [
"@platforms//os:none",
"@platforms//cpu:armv7e-mf",
],
)
62 changes: 62 additions & 0 deletions platforms/transition.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""Starlark Configuration Transitions for Cross-Compilation.

In Bazel, test targets (like sh_test) are host-executed. If the global
invocation sets --platforms=//platforms:aarch64-unknown-none, Bazel
attempts to run the test runners themselves on the target bare-metal
platform. Because there are no test execution toolchains for
bare-metal targets, this results in analysis failures.

To resolve this, we keep the global invocation targeting the host
platform, and use these custom rules (aarch64_binary,
thumbv7em_binary) to apply a "transition". A transition modifies the
configuration (the --platforms flag) for the dependency edge to the
target binary. This ensures:

1. The binaries are always compiled for their specific bare-metal
target platform.

2. The parent test targets (sh_test) are built and run on the host
platform.
"""

def _aarch64_transition_impl(settings, attr):
return {"//command_line_option:platforms": ["//platforms:aarch64-unknown-none"]}

_aarch64_transition = transition(
implementation = _aarch64_transition_impl,
inputs = [],
outputs = ["//command_line_option:platforms"],
)

def _transition_rule_impl(ctx):
# Retrieve the files from the transitioned target
return [DefaultInfo(files = ctx.attr.dep[0][DefaultInfo].files)]

aarch64_binary = rule(
implementation = _transition_rule_impl,
attrs = {
"dep": attr.label(cfg = _aarch64_transition),
"_allowlist_function_transition": attr.label(
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
),
},
)

def _thumbv7em_transition_impl(settings, attr):
return {"//command_line_option:platforms": ["//platforms:thumbv7em-none-eabihf"]}

_thumbv7em_transition = transition(
implementation = _thumbv7em_transition_impl,
inputs = [],
outputs = ["//command_line_option:platforms"],
)

thumbv7em_binary = rule(
implementation = _transition_rule_impl,
attrs = {
"dep": attr.label(cfg = _thumbv7em_transition),
"_allowlist_function_transition": attr.label(
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
),
},
)
28 changes: 28 additions & 0 deletions src/bare-metal/alloc-example/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""Bazel BUILD file for AArch64 Bare-Metal Allocator Example.

This package defines the allocator example target. Because this code
targets AArch64 bare-metal platforms and not the host OS:

1. The raw `rust_binary` is constrained using `target_compatible_with`
to run only on `os:none` to prevent compilation errors during
wildcard host builds.
"""

load("@rules_rust//rust:defs.bzl", "rust_binary")

package(default_visibility = ["//visibility:public"])

rust_binary(
name = "alloc-example",
srcs = ["src/main.rs"],
edition = "2024",
rustc_flags = [
"-C",
"linker=rust-lld",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We don't have this flag in the Cargo.toml, why is it needed here?

],
target_compatible_with = ["@platforms//os:none"],
deps = [
"@bare_metal_alloc_example//:buddy_system_allocator",
"@bare_metal_alloc_example//:panic-halt",
],
)
Loading
Loading