Skip to content

Instantly share code, notes, and snippets.

@harrisonturton
Last active January 13, 2026 23:51
Show Gist options
  • Select an option

  • Save harrisonturton/fecda22244585cb1ca8216d5a4975e1d to your computer and use it in GitHub Desktop.

Select an option

Save harrisonturton/fecda22244585cb1ca8216d5a4975e1d to your computer and use it in GitHub Desktop.
Bazel cc toolchain based on the embedded Arm GNU Toolchain
load("//toolchains/arm_none_eabi:cc_toolchain_config.bzl", "cc_toolchain_config")
filegroup(
name = "empty",
srcs = [],
)
filegroup(
name = "compiler_files",
srcs = [
"@darwin_arm64_gcc_arm_none_eabi//:arm_none_eabi",
"@darwin_arm64_gcc_arm_none_eabi//:bin/arm-none-eabi-gcc",
"@darwin_arm64_gcc_arm_none_eabi//:include",
"@darwin_arm64_gcc_arm_none_eabi//:lib",
"@darwin_arm64_gcc_arm_none_eabi//:libexec",
],
)
filegroup(
name = "linker_files",
srcs = [
"STM32F446RETX_FLASH.ld",
"@darwin_arm64_gcc_arm_none_eabi//:arm_none_eabi",
"@darwin_arm64_gcc_arm_none_eabi//:bin/arm-none-eabi-gcc",
"@darwin_arm64_gcc_arm_none_eabi//:lib",
],
)
cc_toolchain_config(
name = "cc_toolchain_config",
compiler = "@darwin_arm64_gcc_arm_none_eabi//:bin/arm-none-eabi-gcc",
linker_script = "STM32F446RETX_FLASH.ld",
strip = "@darwin_arm64_gcc_arm_none_eabi//:bin/arm-none-eabi-strip",
toolchain_identifier = "macos_arm64_toolchain",
)
cc_toolchain(
name = "cc_toolchain",
all_files = ":empty",
ar_files = ":empty",
as_files = ":compiler_files",
compiler_files = ":compiler_files",
dwp_files = ":empty",
linker_files = ":linker_files",
objcopy_files = ":empty",
strip_files = ":empty",
supports_param_files = False,
toolchain_config = ":cc_toolchain_config",
toolchain_identifier = "macos_arm64_toolchain",
)
toolchain(
name = "arm_none_eabi",
toolchain = ":cc_toolchain",
toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
)
"cc toolchain configuration"
load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES", "ACTION_NAME_GROUPS")
load(
"@rules_cc//cc:cc_toolchain_config_lib.bzl",
"action_config",
"feature",
"flag_group",
"flag_set",
"tool",
"variable_with_value",
)
load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
load("@rules_cc//cc/toolchains:cc_toolchain_config_info.bzl", "CcToolchainConfigInfo")
def _disabled_legacy_features():
"""Bazel implicitly patches a bunch of legacy features unless you override them yourself"""
legacy_features = [
"legacy_compile_flags",
"default_compile_flags",
"dependency_file",
"pic",
"per_object_debug_info",
"preprocessor_defines",
"includes",
"include_paths",
"fdo_instrument",
"fdo_optimize",
"cs_fdo_instrument",
"cs_fdo_optimize",
"fdo_prefetch_hints",
"autofdo",
"build_interface_libraries",
"dynamic_library_linker_tool",
"shared_flag",
"linkstamps",
"output_execpath_flags",
"runtime_library_search_directories",
"library_search_directories",
"archiver_flags",
"libraries_to_link",
"force_pic_flags",
"user_link_flags",
"legacy_link_flags",
"static_libgcc",
"fission_support",
"strip_debug_symbols",
"coverage",
"llvm_coverage_map_format",
"gcc_coverage_map_format",
"fully_static_link",
"user_compile_flags",
"sysroot",
"unfiltered_compile_flags",
"linker_param_file",
"compiler_input_flags",
"compiler_output_flags",
]
return [
feature(
name = "no_legacy_features",
enabled = True,
),
] + [
feature(
name = name,
enabled = False,
)
for name in legacy_features
]
def _action_configs(ctx):
"""Configuration for actions used by this toolchain."""
return [
action_config(
action_name = ACTION_NAMES.c_compile,
enabled = True,
tools = [tool(tool = ctx.executable.compiler)],
),
action_config(
action_name = ACTION_NAMES.assemble,
enabled = True,
tools = [tool(tool = ctx.executable.compiler)],
),
action_config(
action_name = ACTION_NAMES.cpp_link_executable,
enabled = True,
tools = [tool(tool = ctx.executable.compiler)],
),
# Required by Bazel legacy features
action_config(
action_name = ACTION_NAMES.strip,
enabled = True,
tools = [tool(tool = ctx.executable.strip)],
),
]
def _features(ctx):
"Commandline flags for each compilation stage, using tools from the action configs"
compiler_include_flags = feature(
name = "compiler_include_flags",
enabled = True,
flag_sets = [
flag_set(
actions = ACTION_NAME_GROUPS.all_cc_compile_actions,
flag_groups = [
flag_group(
flags = [
"-no-canonical-prefixes",
"-fno-canonical-system-headers",
],
),
flag_group(
flags = ["-iquote", "%{quote_include_paths}"],
iterate_over = "quote_include_paths",
),
flag_group(
flags = ["-I%{include_paths}"],
iterate_over = "include_paths",
),
flag_group(
flags = ["-isystem", "%{system_include_paths}"],
iterate_over = "system_include_paths",
),
],
),
],
)
compiler_input_flags = feature(
name = "compiler_inputs",
enabled = True,
flag_sets = [
flag_set(
actions = ACTION_NAME_GROUPS.all_cc_compile_actions,
flag_groups = [
flag_group(
flags = [
"-ffunction-sections",
"-fdata-sections",
"-fstack-usage",
"-std=gnu11",
"-mcpu=cortex-m4",
"-mthumb",
"--specs=nano.specs",
"-mfpu=fpv4-sp-d16",
"-mfloat-abi=hard",
"-Wa,-mimplicit-it=thumb",
],
),
flag_group(
flags = ["-c", "%{source_file}"],
expand_if_available = "source_file",
),
],
),
],
)
compiler_output_flags = feature(
name = "compiler_outputs",
enabled = True,
flag_sets = [
flag_set(
actions = ACTION_NAME_GROUPS.all_cc_compile_actions,
flag_groups = [
flag_group(
flags = ["-S"],
expand_if_available = "output_assembly_file",
),
flag_group(
flags = ["-E"],
expand_if_available = "output_preprocess_file",
),
flag_group(
flags = ["-o", "%{output_file}"],
expand_if_available = "output_file",
),
# Bazel requires dependency files to be outputted during
# compilation, these are implicitly read and used to declare
# per-file dependency edges. This is undocumented.
flag_group(
flags = ["-MD", "-MF", "%{dependency_file}"],
expand_if_available = "dependency_file",
),
],
),
],
)
linker_input_flags = feature(
name = "linker_inputs",
enabled = True,
flag_sets = [
flag_set(
actions = ACTION_NAME_GROUPS.all_cc_link_actions,
flag_groups = [
flag_group(
flags = [
"-mcpu=cortex-m4",
"-mthumb",
"--specs=nosys.specs",
"-mfpu=fpv4-sp-d16",
"-mfloat-abi=hard",
"-static",
"--specs=nano.specs",
"-Wl,--gc-sections",
],
),
flag_group(
iterate_over = "libraries_to_link",
flag_groups = [
# User inputs
flag_group(
flags = ["%{linker_input_files}"],
iterate_over = "linker_input_files",
expand_if_available = "linker_input_files",
),
# Object files
flag_group(
flags = ["%{libraries_to_link.name}"],
expand_if_equal = variable_with_value(
name = "libraries_to_link.type",
value = "object_file",
),
),
# Static libraries
flag_group(
flags = ["-no-whole-archive"],
expand_if_true = "libraries_to_link.is_whole_archive",
expand_if_equal = variable_with_value(
name = "libraries_to_link.type",
value = "static_library",
),
),
# Dynamic libraries
flag_group(
flags = ["-l%{libraries_to_link.name}"],
expand_if_equal = variable_with_value(
name = "libraries_to_link.type",
value = "dynamic_library",
),
),
],
expand_if_available = "libraries_to_link",
),
flag_group(
flags = [
"-Wl,--start-group",
"-lc",
"-lm",
"-lstdc++",
"-lsupc++",
"-Wl,--end-group",
],
),
],
),
],
)
linker_output_flags = feature(
name = "linker_output_flags",
enabled = True,
flag_sets = [
flag_set(
actions = ACTION_NAME_GROUPS.all_cc_link_actions,
flag_groups = [
flag_group(
flags = ["-o", "%{output_execpath}"],
expand_if_available = "output_execpath",
),
],
),
],
)
linker_script_flags = feature(
name = "linker_script_flags",
enabled = True,
flag_sets = [
flag_set(
actions = ACTION_NAME_GROUPS.all_cc_link_actions,
flag_groups = [
flag_group(
flags = ["-T", ctx.file.linker_script.path],
),
],
),
],
)
return [
compiler_include_flags,
compiler_input_flags,
compiler_output_flags,
linker_input_flags,
linker_output_flags,
linker_script_flags,
]
def _cc_toolchain_config_impl(ctx):
return cc_common.create_cc_toolchain_config_info(
ctx = ctx,
toolchain_identifier = ctx.attr.toolchain_identifier,
# These features use gcc flavoured flags, so it should be safe to
# hardcode gcc given that this identifier is exposed via
# `@bazel_tools//tools/cpp:compiler` and typically used to select
# compiler-specific flags
compiler = "gcc",
features = _disabled_legacy_features() + _features(ctx),
action_configs = _action_configs(ctx),
)
cc_toolchain_config = rule(
implementation = _cc_toolchain_config_impl,
attrs = {
"toolchain_identifier": attr.string(
doc = "Identifier for the toolchain",
mandatory = True,
),
"compiler": attr.label(
mandatory = True,
doc = "Compiler binary",
executable = True,
allow_single_file = True,
cfg = "exec",
),
"linker_script": attr.label(
mandatory = True,
doc = "Linker script",
allow_single_file = True,
),
"strip": attr.label(
mandatory = True,
doc = "Strip binary",
executable = True,
allow_single_file = True,
cfg = "exec",
),
},
provides = [CcToolchainConfigInfo],
fragments = ["cpp"],
)
"ARM-provided GCC and newlib-based toolchain"
http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
# The toolchain is provided from the downloads on this page:
#
# https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads
#
# Note that these download links will redirect to Azure Blob Storage links. In
# order to get a stable URL that Bazel is happy with, we must resolve the
# redirected link and use that here.
http_archive(
name = "darwin_arm64_gcc_arm_none_eabi",
build_file = "//:third_party/gcc_arm_none_eabi/gcc_arm_none_eabi.BUILD",
strip_prefix = "arm-gnu-toolchain-15.2.rel1-darwin-arm64-arm-none-eabi",
# No integrity because the SHA256sum seems to change on every download.
# Possibly an embedded timestamp somewhere?
url = "https://armkeil.blob.core.windows.net/developer/files/downloads/gnu/15.2.rel1/binrel/arm-gnu-toolchain-15.2.rel1-darwin-arm64-arm-none-eabi.tar.xz",
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment