Out-of-Tree Pigweed Kernel Build (from a Fork)

This guide explains how to set up an out-of-tree project that builds against a fork of Pigweed rather than upstream, using the Earl Grey integration in this repo as the reference pattern.

1. Fork Pigweed

# Fork https://pigweed.googlesource.com/pigweed/pigweed to your GitHub/GitLab
# Example: https://github.com/<your-org>/pigweed

2. Point MODULE.bazel at Your Fork

In the root MODULE.bazel, change the git_override for Pigweed:

# ── BEFORE (upstream) ──
git_override(
    module_name = "pigweed",
    commit = "f52059663fee1e2ab2f2ab752301b159da3ca806",
    remote = "https://pigweed.googlesource.com/pigweed/pigweed",
)

# ── AFTER (your fork) ──
git_override(
    module_name = "pigweed",
    commit = "<your_fork_commit_sha>",
    remote = "https://github.com/<your-org>/pigweed",
)

If you want to track a branch instead of a pinned commit, you can use local_path_override during development (faster iteration, no re-fetch):

# For local development against a co-located Pigweed checkout:
local_path_override(
    module_name = "pigweed",
    path = "../pigweed",   # Relative path to your local Pigweed clone
)

Warning: local_path_override is not reproducible across machines. Always switch back to git_override with a pinned commit for CI/production.

3. Full MODULE.bazel Template

module(
    name = "my_pigweed_project",
    version = "0.0.1",
)

# ── Core dependencies ──
bazel_dep(name = "bazel_skylib", version = "1.8.2")
bazel_dep(name = "pigweed")
bazel_dep(name = "platforms", version = "1.0.0")
bazel_dep(name = "rules_rust", version = "0.66.0")
bazel_dep(name = "rules_platform", version = "0.1.0")
bazel_dep(name = "rules_python", version = "1.6.3")

# ── Point to your fork ──
git_override(
    module_name = "pigweed",
    commit = "<your_fork_commit_sha>",
    remote = "https://github.com/<your-org>/pigweed",
)

# ── Register crate generator (if using ureg for peripheral registers) ──
bazel_dep(name = "ureg")
git_override(
    module_name = "ureg",
    commit = "412ca40146d5d2012417e493b4a01096b04edf4b",
    remote = "https://github.com/chipsalliance/caliptra-ureg",
)

# ── Rust toolchain (managed by Pigweed) ──
pw_rust = use_extension("@pigweed//pw_toolchain/rust:extensions.bzl", "pw_rust")
pw_rust.toolchain(cipd_tag = "<rust_toolchain_cipd_tag>")
use_repo(pw_rust, "pw_rust_toolchains")

# ── C/C++ and Rust toolchain registration ──
register_toolchains(
    # Host
    "@pigweed//pw_toolchain/host_clang:host_cc_toolchain_linux",
    "@pigweed//pw_toolchain/host_clang:host_cc_toolchain_macos",
    # Target (pick one or more):
    "@pigweed//pw_toolchain/riscv_clang:riscv_clang_cc_toolchain_rv32imc",
    # "@pigweed//pw_toolchain/arm_clang:arm_clang_cc_toolchain_cortex_m4",
    "@pw_rust_toolchains//:all",
)

# ── Rust crate dependencies ──
crate = use_extension("@rules_rust//crate_universe:extension.bzl", "crate")
crate.from_cargo(
    name = "rust_crates",
    cargo_lockfile = "//third_party/crates_io:Cargo.lock",
    manifests = ["//third_party/crates_io:Cargo.toml"],
    supported_platform_triples = [
        "aarch64-unknown-linux-gnu",
        "x86_64-unknown-linux-gnu",
        "aarch64-apple-darwin",
        "x86_64-apple-darwin",
        # Your SoC target triple:
        "riscv32imc-unknown-none-elf",
        # "thumbv7em-none-eabihf",
    ],
)
use_repo(crate, "rust_crates")

# Override Pigweed's internal crates with our unified crate universe
pw_rust_crates_ext = use_extension(
    "@pigweed//pw_build:pw_rust_crates_extension.bzl",
    "pw_rust_crates_extension",
)
override_repo(pw_rust_crates_ext, rust_crates = "rust_crates")

# ── (Optional) SoC-specific devbundle for simulator/runner tools ──
# bazel_dep(name = "my_soc_devbundle")
# archive_override(
#     module_name = "my_soc_devbundle",
#     integrity = "sha256-...",
#     url = "https://...",
# )

4. Required Project Structure

my-project/
├── MODULE.bazel                    # See above
├── BUILD.bazel                     # Root BUILD (can be minimal)
├── third_party/
│   └── crates_io/
│       ├── BUILD.bazel             # Empty (just package())
│       ├── Cargo.toml              # Rust deps for Bazel crate universe
│       └── Cargo.lock              # Generated: cargo generate-lockfile
└── target/
    └── <soc>/                      # Your SoC integration
        ├── BUILD.bazel             # platform(), entry, console, config libs
        ├── defs.bzl                # TARGET_COMPATIBLE_WITH
        ├── config.rs               # KernelConfig implementation
        ├── entry.rs                # Boot entry point
        ├── <mpu>.rs                # Memory protection setup
        ├── console.rs              # UART console backend
        ├── target.ld.jinja         # Linker script template
        ├── registers/              # Peripheral register crates
        │   ├── BUILD.bazel
        │   ├── registers.rs
        │   └── uart.rs (etc.)
        └── threads/kernel/         # Minimal test image
            ├── BUILD.bazel
            ├── system.json5
            └── target.rs

5. Third-Party Rust Crates (third_party/crates_io/)

Cargo.toml

[package]
name = "rust_crates"
version = "0.1.0"
edition = "2021"

[lib]
path = "fake.rs"

[dependencies]
# Shared embedded deps
embedded-io = "0.6.1"
panic-halt = "1.0.0"
bitflags = "2.9.1"

# RISC-V target
riscv = "0.12.1"
riscv-rt = "0.12.2"

# ARM Cortex-M target (uncomment if needed)
# cortex-m = "0.7"
# cortex-m-rt = "0.7"

BUILD.bazel

# Empty — crate_universe handles everything

Generate the lockfile

cd third_party/crates_io
touch fake.rs
cargo generate-lockfile

6. Root BUILD.bazel

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

# Pigweed CLI launcher
native_binary(
    name = "pw",
    src = "@pigweed//pw_build/py:workflows_launcher",
)

# Code formatter
alias(
    name = "format",
    actual = "@pigweed//pw_presubmit/py:format",
)

7. SoC Target — Key Files

See .github/copilot-instructions.md “Integrating a New SoC with Pigweed” section for complete templates of every file below.

7a. target/<soc>/defs.bzl

TARGET_COMPATIBLE_WITH = select({
    "//target/<soc>:target_<soc>": [],
    "//conditions:default": ["@platforms//:incompatible"],
})

7b. target/<soc>/BUILD.bazel (abbreviated)

load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
load("@pigweed//pw_build:merge_flags.bzl", "flags_from_dict")
load("@pigweed//pw_kernel:flags.bzl", "KERNEL_DEVICE_COMMON_FLAGS")
load("@rules_rust//rust:defs.bzl", "rust_library")
load("//target/<soc>:defs.bzl", "TARGET_COMPATIBLE_WITH")

platform(
    name = "<soc>",
    constraint_values = [
        ":target_<soc>",
        "@pigweed//pw_kernel/arch/riscv:timer_mtime",
        "@pigweed//pw_build/constraints/riscv/extensions:I",
        "@pigweed//pw_build/constraints/riscv/extensions:M",
        "@pigweed//pw_build/constraints/riscv/extensions:C",
        "@pigweed//pw_build/constraints/rust:no_std",
        "@platforms//cpu:riscv32",
        "@platforms//os:none",
    ],
    flags = flags_from_dict(
        KERNEL_DEVICE_COMMON_FLAGS | {
            "@pigweed//pw_kernel/config:kernel_config": ":config",
            "@pigweed//pw_kernel/subsys/console:console_backend": ":console",
            "@pigweed//pw_log/rust:pw_log_backend":
                "@pigweed//pw_kernel:log_backend_basic",
        },
    ),
    visibility = [":__subpackages__"],
)

constraint_value(
    name = "target_<soc>",
    constraint_setting = "@pigweed//pw_kernel/target:target",
    visibility = [":__subpackages__"],
)

# string_flag, config_settings, rust_library for entry/console/config...
# (see copilot-instructions.md Steps 2a-2d for full templates)

7c. Test Image — target/<soc>/threads/kernel/BUILD.bazel

load("@pigweed//pw_kernel/tooling:system_image.bzl", "system_image")
load("@pigweed//pw_kernel/tooling:target_codegen.bzl", "target_codegen")
load("@pigweed//pw_kernel/tooling:target_linker_script.bzl", "target_linker_script")
load("@rules_rust//rust:defs.bzl", "rust_binary")
load("//target/<soc>:defs.bzl", "TARGET_COMPATIBLE_WITH")

system_image(name = "threads", kernel = ":target", platform = "//target/<soc>")

target_linker_script(
    name = "linker_script",
    system_config = ":system_config",
    tags = ["kernel"],
    template = "//target/<soc>:linker_script_template",
)

filegroup(name = "system_config", srcs = ["system.json5"])

target_codegen(
    name = "codegen",
    arch = "@pigweed//pw_kernel/arch/riscv:arch_riscv",
    system_config = ":system_config",
)

rust_binary(
    name = "target",
    srcs = ["target.rs"],
    edition = "2024",
    target_compatible_with = TARGET_COMPATIBLE_WITH,
    deps = [
        ":codegen",
        ":linker_script",
        "//target/<soc>:entry",
        "@pigweed//pw_kernel/arch/riscv:arch_riscv",
        "@pigweed//pw_kernel/kernel",
        "@pigweed//pw_kernel/subsys/console:console_backend",
        "@pigweed//pw_kernel/target:target_common",
        "@pigweed//pw_kernel/tests/threads/kernel:threads",
        "@pigweed//pw_log/rust:pw_log",
    ],
)

8. Building

# Install bazelisk (if not already)
# https://github.com/bazelbuild/bazelisk

# Build everything for your SoC
bazelisk build //target/<soc>/...

# Build just the kernel threads test image
bazelisk build //target/<soc>/threads/kernel:threads

# VS Code rust-analyzer setup
bazelisk run @rules_rust//tools/rust_analyzer:gen_rust_project -- //target/...

9. Syncing with Upstream Pigweed

When upstream Pigweed has changes you want:

cd /path/to/your/pigweed-fork

# Add upstream remote (one-time)
git remote add upstream https://pigweed.googlesource.com/pigweed/pigweed

# Fetch and merge
git fetch upstream
git merge upstream/main    # or rebase: git rebase upstream/main
git push origin main

# Then update your project's MODULE.bazel commit hash:
# commit = "<new_commit_sha_from_your_fork>"

For local_path_override workflows, just git pull in your local Pigweed checkout — Bazel picks up changes immediately (no commit hash to update).

10. Troubleshooting

ProblemFix
ERROR: no such package '@pigweed//pw_kernel/...'Your fork is missing pw_kernel; merge upstream
ERROR: invalid registered toolchainCheck register_toolchains() matches your SoC arch
crate_universe resolution failuresRun cargo generate-lockfile in third_party/crates_io/
incompatible target warningsVerify constraint_values in your platform()
Stale Pigweed commit after fork updateRun bazelisk clean --expunge then rebuild
local_path_override not picking up changesBazel caches aggressively; try bazelisk clean