Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions Fixtures/Metal/SimpleLibrary/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// swift-tools-version: 6.2
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription

let package = Package(
name: "MyRenderer",
products: [
.library(
name: "MyRenderer",
targets: ["MyRenderer"]),
],
targets: [
.target(
name: "MyRenderer",
dependencies: ["MySharedTypes"]),

.target(name: "MySharedTypes")
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import MySharedTypes


let vertex = AAPLVertex(position: .init(250, -250), color: .init(1, 0, 0, 1))
12 changes: 12 additions & 0 deletions Fixtures/Metal/SimpleLibrary/Sources/MyRenderer/Shaders.metal
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// A relative path to SharedTypes.h.
#import "../MySharedTypes/include/SharedTypes.h"

#include <metal_stdlib>
using namespace metal;

vertex float4 simpleVertexShader(const device AAPLVertex *vertices [[buffer(0)]],
uint vertexID [[vertex_id]]) {
AAPLVertex in = vertices[vertexID];
return float4(in.position.x, in.position.y, 0.0, 1.0);
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#ifndef SharedTypes_h
#define SharedTypes_h


#import <simd/simd.h>


typedef struct {
vector_float2 position;
vector_float4 color;
} AAPLVertex;


#endif /* SharedTypes_h */
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Testing
@testable import MyRenderer

@Test func example() async throws {
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
}
7 changes: 7 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -991,6 +991,13 @@ let package = Package(
name: "SwiftBuildSupportTests",
dependencies: ["SwiftBuildSupport", "_InternalTestSupport", "_InternalBuildTestSupport"]
),
.testTarget(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: Nice to see a new test target dedicated to validating Metal. 🥳

name: "BuildMetalTests",
dependencies: [
"_InternalTestSupport",
"Basics"
]
),
// Examples (These are built to ensure they stay up to date with the API.)
.executableTarget(
name: "package-info",
Expand Down
37 changes: 37 additions & 0 deletions Sources/SwiftBuildSupport/PackagePIFBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import struct Basics.SourceControlURL
import struct Basics.Diagnostic
import struct Basics.ObservabilityMetadata
import class Basics.ObservabilityScope
import class Basics.AsyncProcess
import struct Basics.AsyncProcessResult

import class PackageModel.Manifest
import class PackageModel.Package
Expand Down Expand Up @@ -179,6 +181,9 @@ public final class PackagePIFBuilder {
/// The file system to read from.
let fileSystem: FileSystem

/// Path to the Metal compiler, resolved via `xcrun --find metal` or 'metal' if not found.
let metalCompilerPath: String

/// Whether to suppress warnings from compilers, linkers, and other build tools for package dependencies.
private var suppressWarningsForPackageDependencies: Bool {
UserDefaults.standard.bool(forKey: "SuppressWarningsForPackageDependencies", defaultValue: true)
Expand Down Expand Up @@ -215,6 +220,7 @@ public final class PackagePIFBuilder {
self.fileSystem = fileSystem
self.observabilityScope = observabilityScope
self.addLocalRpaths = addLocalRpaths
self.metalCompilerPath = Self.findMetalCompilerPath(observabilityScope: observabilityScope)
}

public init(
Expand All @@ -239,6 +245,7 @@ public final class PackagePIFBuilder {
self.packageDisplayVersion = packageDisplayVersion
self.fileSystem = fileSystem
self.observabilityScope = observabilityScope
self.metalCompilerPath = Self.findMetalCompilerPath(observabilityScope: observabilityScope)
}

/// Build an empty PIF project.
Expand Down Expand Up @@ -660,6 +667,36 @@ public final class PackagePIFBuilder {
self.rule = resource.rule
}
}

private static func findMetalCompilerPath(observabilityScope: ObservabilityScope) -> String {
do {
let result = try AsyncProcess.popen(
arguments: ["xcrun", "--find", "metal"]
)
guard result.exitStatus == .terminated(code: 0) else {
observabilityScope.emit(
debug: "Failed to find Metal compiler using xcrun, exited with \(result.exitStatus). Defaulting to 'metal'"
)
return "metal"
}

let output = try result.utf8Output()

let path = output.trimmingCharacters(in: .whitespacesAndNewlines)

guard !path.isEmpty else {
observabilityScope.emit(debug: "Metal compiler path is empty, defaulting to 'metal'")
return "metal"
}

return path
} catch {
observabilityScope.emit(
debug: "Error \(error) finding Metal compiler, defaulting to 'metal'"
)
return "metal"
}
}
}

// MARK: - Helpers
Expand Down
2 changes: 2 additions & 0 deletions Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,8 @@ struct PackagePIFProjectBuilder {
settings[.COREML_COMPILER_CONTAINER] = "swift-package"
settings[.COREML_CODEGEN_LANGUAGE] = "None"

settings[.MTL_COMPILER_PATH] = self.pifBuilder.metalCompilerPath

self.project[keyPath: resourcesTargetKeyPath].common.addBuildConfig { id in
BuildConfig(id: id, name: "Debug", settings: settings)
}
Expand Down
1 change: 1 addition & 0 deletions Sources/XCBuildSupport/PIF.swift
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,7 @@ public enum PIF {
case CLANG_COVERAGE_MAPPING_LINKER_ARGS
case MACH_O_TYPE
case MACOSX_DEPLOYMENT_TARGET
case MTL_COMPILER_PATH
case MODULEMAP_FILE
case MODULEMAP_FILE_CONTENTS
case MODULEMAP_PATH
Expand Down
58 changes: 58 additions & 0 deletions Tests/BuildMetalTests/BuildMetalTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import _InternalTestSupport
import Testing
import Basics

@Suite
struct BuildMetalTests {

@Test(
.requireHostOS(.macOS),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: can we add the .tags(.TestSize.large) trait to this test?

arguments: getBuildData(for: [.swiftbuild]),
)
func simpleLibrary(data: BuildData) async throws {
let buildSystem = data.buildSystem
let configuration = data.config

try await fixture(name: "Metal/SimpleLibrary") { fixturePath in

// Build the package
let (_, _) = try await executeSwiftBuild(
fixturePath,
configuration: configuration,
buildSystem: buildSystem,
throwIfCommandFails: true
)

// Get the bin path
let (binPathOutput, _) = try await executeSwiftBuild(
fixturePath,
configuration: configuration,
extraArgs: ["--show-bin-path"],
buildSystem: buildSystem,
throwIfCommandFails: true
)

let binPath = try AbsolutePath(validating: binPathOutput.trimmingCharacters(in: .whitespacesAndNewlines))

// Check that default.metallib exists
let metallibPath = binPath.appending(components:["MyRenderer_MyRenderer.bundle", "Contents", "Resources", "default.metallib"])
#expect(
localFileSystem.exists(metallibPath),
"Expected default.metallib to exist at \(metallibPath)"
)
}
}

}
Loading