From ed5b74f65517a4984c2c725698f2bbbba682ed46 Mon Sep 17 00:00:00 2001 From: stackotter Date: Sun, 1 May 2022 17:08:40 +1000 Subject: [PATCH] Build metal shaders for the minimum selected macOS version This fixes crashes on older OSes when building apps built with newer OSes --- Sources/swift-bundler/Bundler/MetalCompiler.swift | 14 +++++++++----- .../swift-bundler/Bundler/ResourceBundler.swift | 6 +++++- .../Bundler/ResourceBundlerError.swift | 3 +++ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Sources/swift-bundler/Bundler/MetalCompiler.swift b/Sources/swift-bundler/Bundler/MetalCompiler.swift index 7a9aac68..d44ca420 100644 --- a/Sources/swift-bundler/Bundler/MetalCompiler.swift +++ b/Sources/swift-bundler/Bundler/MetalCompiler.swift @@ -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 { + static func compileMetalShaders(in directory: URL, minimumMacOSVersion: String, keepSources: Bool) -> Result { guard let enumerator = FileManager.default.enumerator(at: directory, includingPropertiesForKeys: []) else { return .failure(.failedToEnumerateShaders(directory: directory)) } @@ -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() @@ -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 { + static func compileMetalShaders(_ sources: [URL], destination: URL, minimumMacOSVersion: String) -> Result { // Create a temporary directory for compilation let tempDirectory = FileManager.default.temporaryDirectory .appendingPathComponent("metal_compilation-\(UUID().uuidString)") @@ -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) @@ -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 { + static func compileShader(_ shader: URL, to outputFile: URL, minimumMacOSVersion: String) -> Result { let process = Process.create( "/usr/bin/xcrun", arguments: [ "-sdk", "macosx", "metal", + "-mmacosx-version-min=\(minimumMacOSVersion)", "-o", outputFile.path, "-c", shader.path ]) diff --git a/Sources/swift-bundler/Bundler/ResourceBundler.swift b/Sources/swift-bundler/Bundler/ResourceBundler.swift index 9ad93230..f51c0e25 100644 --- a/Sources/swift-bundler/Bundler/ResourceBundler.swift +++ b/Sources/swift-bundler/Bundler/ResourceBundler.swift @@ -83,13 +83,17 @@ enum ResourceBundler { ) -> Result { 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 = { - MetalCompiler.compileMetalShaders(in: destinationBundleResources, keepSources: false) + MetalCompiler.compileMetalShaders(in: destinationBundleResources, minimumMacOSVersion: minimumMacOSVersion, keepSources: false) .mapError { error in .failedToCompileMetalShaders(error) } diff --git a/Sources/swift-bundler/Bundler/ResourceBundlerError.swift b/Sources/swift-bundler/Bundler/ResourceBundlerError.swift index efe873e5..b9cca596 100644 --- a/Sources/swift-bundler/Bundler/ResourceBundlerError.swift +++ b/Sources/swift-bundler/Bundler/ResourceBundlerError.swift @@ -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 { @@ -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" } } }