diff --git a/.Package.swift/backend_mlx/dummy.swift b/.Package.swift/backend_mlx/dummy.swift new file mode 100644 index 00000000000..e69de29bb2d diff --git a/.Package.swift/backend_mlx_debug/dummy.swift b/.Package.swift/backend_mlx_debug/dummy.swift new file mode 100644 index 00000000000..e69de29bb2d diff --git a/.Package.swift/backend_mlx_resources/dummy.swift b/.Package.swift/backend_mlx_resources/dummy.swift new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Package.swift.template b/Package.swift.template index e83c9384651..fca503efd28 100644 --- a/Package.swift.template +++ b/Package.swift.template @@ -46,6 +46,15 @@ let products = deliverables([ "sqlite3", ], ], + "backend_mlx": [ + "sha256": "__SHA256_backend_mlx__", + "sha256" + debug_suffix: "__SHA256_backend_mlx_debug__", + "frameworks": [ + "Metal", + "Foundation", + "QuartzCore", + ], + ], "backend_xnnpack": [ "sha256": "__SHA256_backend_xnnpack__", "sha256" + debug_suffix: "__SHA256_backend_xnnpack_debug__", @@ -134,11 +143,43 @@ for (key, value) in products { packageTargets.append(target) } +// The MLX Metal kernel libraries, one per platform slice, shipped as a single +// resource bundle both MLX products share. Kept out of the generic loop above so +// there is one bundle (executorch_backend_mlx_resources.bundle) rather than a +// separate debug copy, and so the release and debug delegates resolve the same +// name. Each slice's MLX binary asks for its own mlx-.metallib. +// +// The release job commits all three files to this branch before publishing, so +// they are declared unconditionally: a slice that failed to arrive is then +// reported as "Invalid Resource ... File not found" at resolve time, rather than +// silently producing a bundle with no kernels that the delegate would only fault +// on at Metal device init inside a consumer's app. +// Do not gate these on FileManager.fileExists: a relative path is resolved +// against the process working directory, and when this package is a dependency +// that is the consumer's root, so every slice would appear missing and the +// bundle would ship empty. +let mlxMetallibSlices = ["mlx-ios", "mlx-ios-simulator", "mlx-macos"] +let mlxResourcesDir = ".Package.swift/backend_mlx_resources" +if products.keys.contains("backend_mlx") { + packageTargets.append(.target( + name: "backend_mlx_resources", + path: mlxResourcesDir, + resources: mlxMetallibSlices.map { .copy("\($0).metallib") } + )) + for suffix in ["", debug_suffix] { + if let index = packageTargets.firstIndex(where: { + $0.name == "backend_mlx\(suffix)\(dependencies_suffix)" + }) { + packageTargets[index].dependencies.append(.target(name: "backend_mlx_resources")) + } + } +} + let package = Package( name: "executorch", platforms: [ .iOS(.v17), - .macOS(.v12), + .macOS(.v14), ], products: packageProducts, targets: packageTargets