Mobile Dev

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.

1 answer 1 view

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:

  1. Choose your project in the Project Navigator and select the target you want to enable logging for. Click Build Settings at the top.
  2. Search for “Other Metal Compiler Flags”.
  3. Add -fmetal-enable-logging as 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:

swift
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”

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:

  1. Add the plugin as a package dependency in your Package.swift (use the repo/registry entry).
  2. Add the plugin to the shader-only target with the plugins: entry and declare your Metal files as processed resources.
  3. Create a metal-compiler-plugin.json next to your shaders (example below) to enable logging/flags.
  4. Build — the plugin will produce a debug.metallib you must load explicitly from your bundle.

Example Package.swift excerpt (replace the package URL and version with the plugin’s current release):

swift
// 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:


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:

json
{
 "metal-enable-logging": true
}

Variant B — explicit flags:

json
{
 "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:

swift
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, or iphonesimulator):
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:).


Troubleshooting & gotchas


Sources


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.

Authors
Verified by moderation
Moderation
Set Metal Compiler Flags in Swift Package.swift