Convert pre-built arm64 iOS binaries (static libraries .a, static/dynamic .framework, IPA) to arm64 iOS Simulator format.
Useful when third-party SDKs ship only device slices and cannot be compiled or linked for the simulator.
Circe rewrites the LC_BUILD_VERSION load command in Mach-O files, changing the platform from iOS (2) to iOS Simulator (7) so the linker accepts the binary for simulator builds. For static archives (.a), it unpacks all .o members, converts each one, and repackages them.
# Convert a static archive
Circe input.a output.a
# Convert an IPA directory (in-place)
Circe path/to/ipa_directory
Add via bzlmod:
# MODULE.bazel
bazel_dep(name = "circe", version = "1.0.0")
Drop-in replacement for cc_import, converts a single .a file:
load("@circe//:arm2sim.bzl", "arm2sim_archive")
arm2sim_archive(
name = "some_lib",
archive = "libfoo.a",
)
Drop-in replacement for apple_static_framework_import:
load("@circe//:arm2sim.bzl", "arm2sim_static_framework_import")
arm2sim_static_framework_import(
name = "SomeSDK",
framework_imports = glob(["SomeSDK.framework/**"]),
sdk_frameworks = ["UIKit", "CoreMedia"],
)
Drop-in replacement for apple_dynamic_framework_import:
load("@circe//:arm2sim.bzl", "arm2sim_dynamic_framework_import")
arm2sim_dynamic_framework_import(
name = "SomeSDK",
framework_imports = glob(["SomeSDK.framework/**"]),
)
Drop-in replacement for objc_import:
load("@circe//:arm2sim.bzl", "arm2sim_objc_import")
arm2sim_objc_import(
name = "some_lib",
hdrs = glob(["include/**/*.h"]),
archives = glob(["lib/**/*.a"]),
includes = ["include"],
deps = ["@other_dep//:lib"],
)
All rules/macros automatically detect the target platform:
Downstream targets just use deps — no select() needed.
lipo -thin arm64.o members) are batched to avoid ARG_MAX overflow.o member names (e.g. template instantiations across bit depths) are preserved via custom ar parsing._*) are automatically filtered.o inside archives skip signing to avoid fork storms)In Greek mythology, Circe is the sorceress who transforms men into beasts. Rewriting Mach-O load commands to trick the linker into accepting device binaries on the simulator is the kind of dark magic that earns the name.
The core Mach-O patching technique is inspired by arm64-to-sim. Circe builds on that idea and integrates it into Bazel, with rules that convert prebuilt device binaries as part of the build graph.
bazel build //Circe:Circe