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..1ff5bf3c5d6 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 +// there is no need to probe for them: they are declared unconditionally. A slice +// that failed to arrive then shows up as an "Invalid Resource ... File not found" +// warning when a consumer builds. That is a warning and the build still succeeds, +// so it makes the absence visible rather than fatal; the release job is the place +// to assert the files arrived. +// If a probe is ever added here, anchor it to this file's own directory the way +// the main-side manifest does, with URL(fileURLWithPath: #filePath). A bare +// relative path is resolved against the process working directory, which for a +// dependency is the consumer's root, so every slice would appear missing. +let mlxMetallibSlices = ["mlx-ios", "mlx-ios-simulator", "mlx-macos"] +if products.keys.contains("backend_mlx") { + packageTargets.append(.target( + name: "backend_mlx_resources", + path: ".Package.swift/backend_mlx_resources", + 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