Skip to content

Commit f705ed2

Browse files
committed
SwiftBuild support for invoking metal compiler using xcrun
1 parent 2096cff commit f705ed2

File tree

15 files changed

+297
-1
lines changed

15 files changed

+297
-1
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// swift-tools-version: 6.2
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "MyRenderer",
8+
products: [
9+
.library(
10+
name: "MyRenderer",
11+
targets: ["MyRenderer"]),
12+
.plugin(
13+
name: "MetalCompilerPlugin",
14+
targets: ["MetalCompilerPlugin"]),
15+
],
16+
targets: [
17+
.target(
18+
name: "MyRenderer",
19+
dependencies: ["MySharedTypes"],
20+
plugins: [
21+
.plugin(name: "MetalCompilerPlugin")
22+
]),
23+
24+
.target(name: "MySharedTypes"),
25+
26+
.plugin(
27+
name: "MetalCompilerPlugin",
28+
capability: .buildTool()
29+
),
30+
],
31+
swiftLanguageModes: [.v6]
32+
)
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import PackagePlugin
2+
import struct Foundation.URL
3+
4+
@main
5+
struct MetalCompilerPlugin: BuildToolPlugin {
6+
7+
func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
8+
Diagnostics.remark("MetalCompilerPlugin: Starting plugin execution")
9+
10+
guard let sourceFiles = target.sourceModule?.sourceFiles else {
11+
Diagnostics.remark("MetalCompilerPlugin: No source files found")
12+
return []
13+
}
14+
15+
Diagnostics.remark("MetalCompilerPlugin: Found \(sourceFiles.count) source files")
16+
for file in sourceFiles {
17+
Diagnostics.remark(" - \(file.path.lastComponent)")
18+
}
19+
20+
let metalFiles = sourceFiles.filter { $0.path.extension == "metal" }
21+
22+
Diagnostics.remark("MetalCompilerPlugin: Found \(metalFiles.count) .metal files")
23+
24+
guard !metalFiles.isEmpty else {
25+
Diagnostics.remark("MetalCompilerPlugin: No .metal files to compile")
26+
return []
27+
}
28+
29+
var commands: [Command] = []
30+
var airFiles: [URL] = []
31+
32+
// Compile each .metal file to .air
33+
for metalFile in metalFiles {
34+
let airFile = context.pluginWorkDirectoryURL.appendingPathComponent(
35+
metalFile.path.stem + ".air"
36+
)
37+
airFiles.append(airFile)
38+
39+
commands.append(.buildCommand(
40+
displayName: "Compiling Metal shader \(metalFile.path.lastComponent)",
41+
executable: URL(fileURLWithPath: "/usr/bin/xcrun"),
42+
arguments: [
43+
"metal",
44+
"-c",
45+
"-g", // Include debug symbols
46+
metalFile.url.path,
47+
"-o",
48+
airFile.path
49+
],
50+
inputFiles: [metalFile.url],
51+
outputFiles: [airFile]
52+
))
53+
}
54+
55+
// Link all .air files into default.metallib
56+
let metallibPath = context.pluginWorkDirectoryURL.appendingPathComponent("default.metallib")
57+
58+
var metallibArgs = ["metallib", "-o", metallibPath.path]
59+
metallibArgs.append(contentsOf: airFiles.map { $0.path })
60+
61+
commands.append(.buildCommand(
62+
displayName: "Linking Metal library",
63+
executable: URL(fileURLWithPath: "/usr/bin/xcrun"),
64+
arguments: metallibArgs,
65+
inputFiles: airFiles,
66+
outputFiles: [metallibPath]
67+
))
68+
69+
Diagnostics.remark("MetalCompilerPlugin: Will generate metallib at \(metallibPath.path)")
70+
71+
return commands
72+
}
73+
}
74+
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import MySharedTypes
2+
3+
4+
let vertex = AAPLVertex(position: .init(250, -250), color: .init(1, 0, 0, 1))
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// A relative path to SharedTypes.h.
2+
#import "../MySharedTypes/include/SharedTypes.h"
3+
4+
#include <metal_stdlib>
5+
using namespace metal;
6+
7+
vertex float4 simpleVertexShader(const device AAPLVertex *vertices [[buffer(0)]],
8+
uint vertexID [[vertex_id]]) {
9+
AAPLVertex in = vertices[vertexID];
10+
return float4(in.position.x, in.position.y, 0.0, 1.0);
11+
}
12+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#ifndef SharedTypes_h
2+
#define SharedTypes_h
3+
4+
5+
#import <simd/simd.h>
6+
7+
8+
typedef struct {
9+
vector_float2 position;
10+
vector_float4 color;
11+
} AAPLVertex;
12+
13+
14+
#endif /* SharedTypes_h */
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// swift-tools-version: 6.2
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "MyRenderer",
7+
products: [
8+
.library(
9+
name: "MyRenderer",
10+
targets: ["MyRenderer"]),
11+
],
12+
targets: [
13+
.target(
14+
name: "MyRenderer",
15+
dependencies: ["MySharedTypes"]),
16+
17+
.target(name: "MySharedTypes")
18+
]
19+
)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import MySharedTypes
2+
3+
4+
let vertex = AAPLVertex(position: .init(250, -250), color: .init(1, 0, 0, 1))
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// A relative path to SharedTypes.h.
2+
#import "../MySharedTypes/include/SharedTypes.h"
3+
4+
#include <metal_stdlib>
5+
using namespace metal;
6+
7+
vertex float4 simpleVertexShader(const device AAPLVertex *vertices [[buffer(0)]],
8+
uint vertexID [[vertex_id]]) {
9+
AAPLVertex in = vertices[vertexID];
10+
return float4(in.position.x, in.position.y, 0.0, 1.0);
11+
}
12+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#ifndef SharedTypes_h
2+
#define SharedTypes_h
3+
4+
5+
#import <simd/simd.h>
6+
7+
8+
typedef struct {
9+
vector_float2 position;
10+
vector_float4 color;
11+
} AAPLVertex;
12+
13+
14+
#endif /* SharedTypes_h */
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import Testing
2+
@testable import MyRenderer
3+
4+
@Test func example() async throws {
5+
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
6+
}

0 commit comments

Comments
 (0)