Set Metal Compiler Flags in Swift Package.swift
Learn why Swift Package Manager can't set Other Metal Compiler Flags directly in Package.swift and how to enable -fmetal-enable-logging using MetalCompilerPlugin, debug.metallib, and alternatives for Metal shaders.
How can I set “Other Metal Compiler Flags” (for example, -fmetal-enable-logging) for a Swift Package target in Package.swift?
I want to enable Metal shader logging for a Swift Package. Xcode instructions are:
- Choose your project in the Project Navigator and select the target you want to enable logging for. Click Build Settings at the top.
- Search for “Other Metal Compiler Flags”.
- Add
-fmetal-enable-loggingas a compile flag.
This guide is for an Xcode project, but I don’t know how to find those settings in a Swift package. My Package.swift targets snippet:
targets: [
// A separate target for shaders.
.target(
name: "MetalShaders",
resources: [
.process("Metal")
],
cSettings: [
// This doesn't work
//.unsafeFlags(["-fmetal-enable-logging"], .when(configuration: .debug))
]
),
.target(
name: "MegApp",
dependencies: ["MetalShaders"]
)
]
I tried adding cSettings: [.unsafeFlags(["-fmetal-enable-logging"], .when(configuration: .debug))] to the target but it doesn’t work. How can I enable Metal shader logging or set the equivalent of “Other Metal Compiler Flags” for a Swift Package target? Is there a supported way in Swift Package Manager to pass Metal compiler flags to a target, or do I need to use an Xcode target, a plugin, or a custom build step?
swift package: You can’t set Xcode’s “Other Metal Compiler Flags” directly from Package.swift — Swift Package Manager doesn’t expose Metal-specific compiler-flag hooks. To enable Metal shader logging for a Swift package, use a build plugin (for example, schwa’s MetalCompilerPlugin configured with a metal-compiler-plugin.json), implement a BuildToolPlugin or manual compile step that runs metal/metallib, or move the shaders into an Xcode target where build settings are editable. The MetalCompilerPlugin can add -fmetal-enable-logging and produce a debuggable debug.metallib that you load with MTLDevice.makeLibrary(url:).
Contents
- Why Swift Package Manager / Package.swift can’t set “Other Metal Compiler Flags”
- Use MetalCompilerPlugin (recommended) — setup and example
- Example metal-compiler-plugin.json to enable -fmetal-enable-logging
- How to load debug.metallib in Swift (MTLDevice.makeLibrary(url:))
- Alternatives: BuildToolPlugin, manual compile, or move shaders to an Xcode target
- Troubleshooting & gotchas
- Sources
- Conclusion
Why Swift Package Manager / Package.swift can’t set “Other Metal Compiler Flags”
Swift Package Manager (SPM) doesn’t provide a first-class place in Package.swift for Metal-specific compiler flags. The manifest API exposes cSettings, cxxSettings, swiftSettings, linkerSettings and unsafeFlags scoped to those languages, but there’s no documented hook that maps to the Metal compiler invocation used for .metal files, so cSettings/.unsafeFlags won’t affect how SPM compiles Metal sources. See the PackageDescription reference for the available settings and unsafeFlags behavior: https://docs.swift.org/package-manager/PackageDescription/PackageDescription.html.
Community and project threads back this up: an open issue on the SwiftPM tracker documents the lack of a way to pass Metal compiler flags from Package.swift, and forum posts show developers hitting the same limitation and resorting to moving shaders out of the package for debugging: https://github.com/apple/swift-package-manager/issues/5823 and https://forums.swift.org/t/cant-profile-metal-shaders-within-a-package/49607. Apple Developer Forum replies also note that flags like -fcikernel (Core Image) require manual build steps or Xcode target build settings: https://developer.apple.com/forums/thread/649579.
So why didn’t cSettings(.unsafeFlags(...)) work for you? Because those unsafeFlags are passed to C/C++/Swift toolchains — not to the separate Metal tool that SPM invokes for .metal files — so the Metal tool never sees -fmetal-enable-logging.
Use MetalCompilerPlugin (recommended) — setup and example
The simplest supported route today is to use a SwiftPM plugin that runs the Metal tool with the flags you want. The community-maintained MetalCompilerPlugin compiles .metal files with debug-friendly flags (it adds -gline-tables-only, -frecord-sources and can add -fmetal-enable-logging) and emits a debug.metallib you can load at runtime. See the plugin README for details: https://raw.githubusercontent.com/schwa/MetalCompilerPlugin/main/README.md and the registry entry: https://swiftpackageregistry.com/schwa/MetalCompilerPlugin.
Step-by-step:
- Add the plugin as a package dependency in your Package.swift (use the repo/registry entry).
- Add the plugin to the shader-only target with the
plugins:entry and declare your Metal files as processed resources. - Create a
metal-compiler-plugin.jsonnext to your shaders (example below) to enable logging/flags. - Build — the plugin will produce a
debug.metallibyou must load explicitly from your bundle.
Example Package.swift excerpt (replace the package URL and version with the plugin’s current release):
// Package.swift (excerpt)
let package = Package(
name: "Meg",
platforms: [.iOS(.v15)],
dependencies: [
.package(url: "https://github.com/schwa/MetalCompilerPlugin.git", .upToNextMajor(from: "0.1.0")), // example
],
targets: [
.target(
name: "MetalShaders",
resources: [
.process("Metal") // your .metal files folder
],
plugins: [
.plugin(name: "MetalCompilerPlugin", package: "MetalCompilerPlugin")
]
),
.target(
name: "MegApp",
dependencies: ["MetalShaders"]
)
]
)
Notes:
- The plugin’s README shows this usage and the exact config keys the plugin accepts: https://raw.githubusercontent.com/schwa/MetalCompilerPlugin/main/README.md.
- Some plugin examples expect a “pure” Metal target (no Swift source files) for the shader-only target; see examples and tutorials: https://iosexample.com/swift-package-manager-plug-in-to-compile-metal-files-that-can-be-debugged-in-xcode-metal-debugger/.
Example metal-compiler-plugin.json to enable -fmetal-enable-logging
Create a metal-compiler-plugin.json in the target folder (next to your Metal files or in the target root). Minimal examples the plugin accepts:
Variant A — set the convenience boolean:
{
"metal-enable-logging": true
}
Variant B — explicit flags:
{
"flags": [
"-gline-tables-only",
"-frecord-sources",
"-fmetal-enable-logging"
]
}
Either approach tells the plugin to invoke the Metal compiler with -fmetal-enable-logging. The README documents these keys and the behavior: https://raw.githubusercontent.com/schwa/MetalCompilerPlugin/main/README.md.
If you only want these flags in debug builds you have two choices: (a) see whether the plugin supports per-configuration config (check the plugin docs/versions), or (b) implement a small BuildToolPlugin or script that inspects the build configuration and applies flags only for debug builds.
How to load debug.metallib in Swift (MTLDevice.makeLibrary(url:))
The plugin produces a debug.metallib (not the default default.metallib). That file must be loaded explicitly at runtime; MTLCreateSystemDefaultDevice()?.makeDefaultLibrary() will not pick it up automatically. Example:
import Metal
let device = MTLCreateSystemDefaultDevice()!
do {
// Bundle.module requires SPM resources; adjust if you're building an executable.
let url = Bundle.module.url(forResource: "debug", withExtension: "metallib")!
let library = try device.makeLibrary(URL: url)
// use `library` to create functions/pipelines
} catch {
print("Failed to load debug.metallib: (error)")
}
See Apple’s docs on shader logging and the Metal debugger for why you want the debug build of the metallib: https://developer.apple.com/documentation/metal/logging-shader-debug-messages and https://developer.apple.com/documentation/xcode/metal-debugger/.
A gotcha: SPM treats resources differently for libraries vs executables — resources are processed for library products but may not be packaged for executables the same way, so Bundle.module behavior can vary (see discussion and examples): https://mtldoc.com/metal/2022/06/18/build-swift-executable-with-metal-library.
Alternatives: BuildToolPlugin, manual compile, or move shaders to an Xcode target
If you don’t want to rely on a community plugin, here are other viable approaches.
- Manual pre-build step / script
Run the Metal tools yourself and check the files into your package (or copy the generated metallib into resources). Example commands (choose the SDK appropriately:macosx,iphoneos, oriphonesimulator):
xcrun -sdk iphoneos metal -c Shader.metal -o Shader.air -gline-tables-only -frecord-sources -fmetal-enable-logging
xcrun -sdk iphoneos metallib Shader.air -o debug.metallib
Then package debug.metallib as a resource and load it with MTLDevice.makeLibrary(URL:).
-
Implement a BuildToolPlugin
Use an SPMbuildToolplugin to runxcrun metalandxcrun metallibas part of the package build. Community discussion and examples for replacing default build rules with a BuildToolPlugin exist: https://forums.swift.org/t/let-buildtoolplugin-replace-default-build-rules/62968. This gives you fine-grained control and can apply flags conditionally. -
Move shaders into an Xcode target
If you need Xcode’s GUI to toggleOther Metal Compiler Flags, put the shaders in an Xcode target (or an embedded framework) and set the flags there; that’s the simplest approach if you only need the flags during development and can’t change the package layout. Several developers use this workaround for profiling/debugging: https://forums.swift.org/t/cant-profile-metal-shaders-within-a-package/49607 and https://developer.apple.com/forums/thread/649579.
Troubleshooting & gotchas
- cSettings/.unsafeFlags don’t affect Metal:
unsafeFlagsfor C/C++/Swift don’t get forwarded to the Metal compiler; you’ll still see the behavior you experienced. See the PackageDescription docs: https://docs.swift.org/package-manager/PackageDescription/PackageDescription.html and the SwiftPM issue: https://github.com/apple/swift-package-manager/issues/5823. - Plugin-only targets: some plugin examples expect the shader target to be “pure Metal” (no Swift) or to follow a particular layout — check the plugin README and example
Package.swift: https://raw.githubusercontent.com/schwa/MetalCompilerPlugin/main/README.md and https://iosexample.com/swift-package-manager-plug-in-to-compile-metal-files-that-can-be-debugged-in-xcode-metal-debugger/. - Resources vs executables: if your product is an executable, SPM might not package resources the same way as with a library;
Bundle.modulebehavior will differ. See the MTLDoc resource notes: https://mtldoc.com/metal/2022/06/18/build-swift-executable-with-metal-library. - Viewing logs and debugging: enable the logging flags and open the Metal debugger / GPU Frame Capture in Xcode to inspect shader logs and breakpoints: https://developer.apple.com/documentation/metal/logging-shader-debug-messages and https://developer.apple.com/documentation/xcode/metal-debugger/.
- Core Image kernels: if you’re using Core Image kernels you may need
-fcikernel(and corresponding linker flags); that’s another example where Xcode build settings or explicit tool invocations are required: https://stackoverflow.com/questions/79157802/core-image-metal-kernel-compile-flag-makes-swiftui-can-not-find-metal-shader.
Sources
- MetalCompilerPlugin README
- Provide flags to configure metal compilation · Issue #5823 · swift-package-manager · GitHub
- Can’t profile Metal shaders within a package - Swift Forums
- Swift Package with Metal | Apple Developer Forums
- Core Image Metal kernel compile flag makes SwiftUI can not find Metal shader - Stack Overflow
- How to combine .metal and .swift in the same package - Stack Overflow
- Package — Swift Package Manager (PackageDescription)
- SPM metal includes flag | Scientific Witchery (jackyoustra blog)
- Pitfalls and solutions when building Metal Shaders for Core Image Kernels (juniperphoton)
- MetalCompilerPlugin - Swift Package Registry
- Swift Package Manager plug-in to compile Metal files that can be debugged in Xcode Metal Debugger (iosexample)
- Build Swift Executable with Metal Library | MTLDoc
- Logging shader debug messages | Apple Developer Documentation
- Metal debugger | Apple Developer Documentation
- Let BuildToolPlugin replace default build rules - Swift Forums
Conclusion
Short answer: Package.swift can’t directly set Xcode’s “Other Metal Compiler Flags” for Metal sources. Use a SwiftPM plugin like MetalCompilerPlugin (configured with a metal-compiler-plugin.json) to inject -fmetal-enable-logging and produce a debug.metallib, write a BuildToolPlugin or manual pre-build script to run metal/metallib, or move the shaders into an Xcode target so you can edit build settings there. For most cases where you only want shader logging and debuggable metallibs inside a swift package, the plugin route is the cleanest and easiest to adopt.