Skip to content

Commit

Permalink
Build metal shaders for the minimum selected macOS version
Browse files Browse the repository at this point in the history
This fixes crashes on older OSes when building apps built with newer OSes
  • Loading branch information
stackotter committed May 1, 2022
1 parent a529811 commit ed5b74f
Show file tree
Hide file tree
Showing 3 changed files with 17 additions and 6 deletions.
14 changes: 9 additions & 5 deletions Sources/swift-bundler/Bundler/MetalCompiler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ enum MetalCompiler {
/// Compiles any metal shaders present in a directory into a `default.metallib` file (in the same directory).
/// - Parameters:
/// - directory: The directory to compile shaders from.
/// - minimumMacOSVersion: The macOS version that the built shaders should target.
/// - keepSources: If `false`, the sources will get deleted after compilation.
/// - Returns: If an error occurs, a failure is returned.
static func compileMetalShaders(in directory: URL, keepSources: Bool) -> Result<Void, MetalCompilerError> {
static func compileMetalShaders(in directory: URL, minimumMacOSVersion: String, keepSources: Bool) -> Result<Void, MetalCompilerError> {
guard let enumerator = FileManager.default.enumerator(at: directory, includingPropertiesForKeys: []) else {
return .failure(.failedToEnumerateShaders(directory: directory))
}
Expand All @@ -24,7 +25,7 @@ enum MetalCompiler {
log.info("Compiling metal shaders")

// Compile metal shaders, and if successful, delete all shader sources
return compileMetalShaders(shaderSources, destination: directory)
return compileMetalShaders(shaderSources, destination: directory, minimumMacOSVersion: minimumMacOSVersion)
.flatMap { _ in
if keepSources {
return .success()
Expand All @@ -46,8 +47,9 @@ enum MetalCompiler {
/// - Parameters:
/// - sources: The source files to comile.
/// - destination: The directory to output `default.metallib` to.
/// - minimumMacOSVersion: The macOS version that the built shaders should target.
/// - Returns: Returns the location of the resulting `metallib`. If an error occurs, a failure is returned.
static func compileMetalShaders(_ sources: [URL], destination: URL) -> Result<URL, MetalCompilerError> {
static func compileMetalShaders(_ sources: [URL], destination: URL, minimumMacOSVersion: String) -> Result<URL, MetalCompilerError> {
// Create a temporary directory for compilation
let tempDirectory = FileManager.default.temporaryDirectory
.appendingPathComponent("metal_compilation-\(UUID().uuidString)")
Expand All @@ -62,7 +64,7 @@ enum MetalCompiler {
for shaderSource in sources {
let outputFileName = shaderSource.deletingPathExtension().appendingPathExtension("air").lastPathComponent
let outputFile = tempDirectory.appendingPathComponent(outputFileName)
if case let .failure(error) = compileShader(shaderSource, to: outputFile) {
if case let .failure(error) = compileShader(shaderSource, to: outputFile, minimumMacOSVersion: minimumMacOSVersion) {
return .failure(error)
}
airFiles.append(outputFile)
Expand All @@ -89,12 +91,14 @@ enum MetalCompiler {
/// - Parameters:
/// - shader: The shader file to compile.
/// - outputFile: The resulting `air` file.
/// - minimumMacOSVersion: The macOS version that the built shader should target.
/// - Returns: If an error occurs, a failure is returned.
static func compileShader(_ shader: URL, to outputFile: URL) -> Result<Void, MetalCompilerError> {
static func compileShader(_ shader: URL, to outputFile: URL, minimumMacOSVersion: String) -> Result<Void, MetalCompilerError> {
let process = Process.create(
"/usr/bin/xcrun",
arguments: [
"-sdk", "macosx", "metal",
"-mmacosx-version-min=\(minimumMacOSVersion)",
"-o", outputFile.path,
"-c", shader.path
])
Expand Down
6 changes: 5 additions & 1 deletion Sources/swift-bundler/Bundler/ResourceBundler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,17 @@ enum ResourceBundler {
) -> Result<Void, ResourceBundlerError> {
log.info("Fixing and copying resource bundle '\(bundle.lastPathComponent)'")

guard let minimumMacOSVersion = minimumMacOSVersion else {
return .failure(.mustProvideMinimimMacOSVersion)
}

let destinationBundle = destination.appendingPathComponent(bundle.lastPathComponent)
let destinationBundleResources = destinationBundle
.appendingPathComponent("Contents")
.appendingPathComponent("Resources")

let compileMetalShaders: () -> Result<Void, ResourceBundlerError> = {
MetalCompiler.compileMetalShaders(in: destinationBundleResources, keepSources: false)
MetalCompiler.compileMetalShaders(in: destinationBundleResources, minimumMacOSVersion: minimumMacOSVersion, keepSources: false)
.mapError { error in
.failedToCompileMetalShaders(error)
}
Expand Down
3 changes: 3 additions & 0 deletions Sources/swift-bundler/Bundler/ResourceBundlerError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ enum ResourceBundlerError: LocalizedError {
case failedToCopyResource(source: URL, destination: URL)
case failedToEnumerateBundleContents(directory: URL, Error)
case failedToCompileMetalShaders(MetalCompilerError)
case mustProvideMinimimMacOSVersion

var errorDescription: String? {
switch self {
Expand All @@ -26,6 +27,8 @@ enum ResourceBundlerError: LocalizedError {
return "Failed to enumerate bundle contents at '\(directory.relativePath)'"
case .failedToCompileMetalShaders(let metalCompilerError):
return "Failed to compile Metal shaders: \(metalCompilerError.localizedDescription)"
case .mustProvideMinimimMacOSVersion:
return "Must provide 'minimum_macos_version' when app contains resources"
}
}
}

0 comments on commit ed5b74f

Please sign in to comment.