Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
24 changes: 20 additions & 4 deletions Plugins/MetalCompilerPlugin/MetalPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ struct MetalCompiler: Decodable {
case metalPath = "metal"
case scanInputsInDirectory = "find-inputs"
case includeDependencies = "include-dependencies"
case dependencyPathSuffix = "dependency-path-suffix"
case inputs = "inputs"
case output = "output"
case cache = "cache"
Expand All @@ -42,12 +43,14 @@ struct MetalCompiler: Decodable {
case verboseLogging = "verbose-logging"
case metalEnableLogging = "metal-enable-logging"
case extraEnvironment = "env"
case loggingPrefix = "logging-prefix"
}

var useXcrun: Bool = true
var metalPath: String?
var scanInputsInDirectory: Bool = true
var includeDependencies: Bool = false
var dependencyPathSuffix: String?
var inputs: [String]
var output: String = "debug.metallib"
var cache: String?
Expand All @@ -56,6 +59,7 @@ struct MetalCompiler: Decodable {
var verboseLogging: Bool = false
var metalEnableLogging: Bool = false
var extraEnvironment: [String: String]?
var loggingPrefix: String?

init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
Expand All @@ -64,6 +68,7 @@ struct MetalCompiler: Decodable {
metalPath = try container.decodeIfPresent(String.self, forKey: .metalPath)
scanInputsInDirectory = try container.decodeIfPresent(Bool.self, forKey: .scanInputsInDirectory) ?? true
includeDependencies = try container.decodeIfPresent(Bool.self, forKey: .includeDependencies) ?? false
dependencyPathSuffix = try container.decodeIfPresent(String.self, forKey: .dependencyPathSuffix)
inputs = try container.decodeIfPresent([String].self, forKey: .inputs) ?? []
output = try container.decodeIfPresent(String.self, forKey: .output) ?? "debug.metallib"
cache = try container.decodeIfPresent(String.self, forKey: .cache)
Expand All @@ -72,6 +77,7 @@ struct MetalCompiler: Decodable {
verboseLogging = try container.decodeIfPresent(Bool.self, forKey: .verboseLogging) ?? false
metalEnableLogging = try container.decodeIfPresent(Bool.self, forKey: .metalEnableLogging) ?? false
extraEnvironment = try container.decodeIfPresent([String: String].self, forKey: .extraEnvironment)
loggingPrefix = try container.decodeIfPresent(String.self, forKey: .loggingPrefix)
}
}

Expand All @@ -82,14 +88,18 @@ struct MetalCompiler: Decodable {
}

func buildCommand(context: PackagePlugin.PluginContext, target: PackagePlugin.Target) -> PackagePlugin.Command {
let prefix = config.loggingPrefix.map { $0 + " " } ?? ""

let logger: ((String) -> Void)? = config.pluginLogging ? { (string: String) in
Diagnostics.remark(string)
Diagnostics.remark(prefix + string)
} : nil

let verbose: ((String) -> Void)? = config.pluginLogging && config.verboseLogging ? { (string: String) in
Diagnostics.remark(string)
Diagnostics.remark(prefix + string)
} : nil

logger?("Current working directory: \(FileManager.default.currentDirectoryPath)")

verbose?("Input environment:")
if config.pluginLogging && config.verboseLogging {
for (key, value) in ProcessInfo.processInfo.environment.sorted(by: { $0.key < $1.key }) {
Expand Down Expand Up @@ -141,8 +151,14 @@ struct MetalCompiler: Decodable {
case .product:
Diagnostics.error("Product dependencies are not supported for include paths in MetalCompilerPlugin.")
case .target(let target):
verbose?(" -I \(target.directory.string)")
arguments += ["-I", target.directory.string]
let includePath: String
if let suffix = config.dependencyPathSuffix {
includePath = target.directory.appending([suffix]).string
} else {
includePath = target.directory.string
}
verbose?(" -I \(includePath)")
arguments += ["-I", includePath]
@unknown default:
Diagnostics.error("Unknown dependency type in MetalCompilerPlugin.")
}
Expand Down
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,16 @@ All configuration options are optional. Without any configuration file, the plug
"xcrun": true,
"metal": "/path/to/metal",
"find-inputs": true,
"include-dependencies": false,
"dependency-path-suffix": "include",
"inputs": ["additional/file.metal"],
"output": "debug.metallib",
"cache": "/path/to/cache",
"flags": ["-gline-tables-only", "-frecord-sources"],
"plugin-logging": false,
"verbose-logging": false,
"metal-enable-logging": false,
"logging-prefix": "[Metal]",
"env": {
"TMPDIR": "/private/tmp"
}
Expand All @@ -90,6 +94,10 @@ All configuration options are optional. Without any configuration file, the plug

- **`find-inputs`** (boolean, default: `true`): Whether to automatically scan the target directory for `.metal` files. When `true`, all `.metal` files in the target are included.

- **`include-dependencies`** (boolean, default: `false`): Whether to include target dependencies as include paths (`-I`) when compiling. This allows Metal files to import headers from dependency targets.

- **`dependency-path-suffix`** (string, optional): A path suffix to append to each dependency directory when generating include paths. Useful when headers are in a subdirectory like `include/`. Only applies when `include-dependencies` is `true`.

- **`inputs`** (array of strings, default: `[]`): Additional input files to compile, in addition to those found by scanning (if enabled).

- **`output`** (string, default: `"debug.metallib"`): Name of the output metallib file.
Expand All @@ -98,7 +106,11 @@ All configuration options are optional. Without any configuration file, the plug

- **`flags`** (array of strings, default: `["-gline-tables-only", "-frecord-sources"]`): Compiler flags to pass to the metal compiler. The default flags enable debugging in Xcode Metal Debugger.

- **`plugin-logging`** (boolean, default: `false`): Enable verbose logging from the plugin itself for debugging purposes.
- **`plugin-logging`** (boolean, default: `false`): Enable logging from the plugin itself for debugging purposes.

- **`verbose-logging`** (boolean, default: `false`): Enable more detailed verbose logging. Only takes effect when `plugin-logging` is also `true`. Shows additional details like all environment variables, full command arguments, and input/output file lists.

- **`logging-prefix`** (string, optional): Custom prefix to prepend to all log messages from the plugin. Useful for distinguishing plugin output in complex build logs.

- **`metal-enable-logging`** (boolean, default: `false`): Enable metal compiler logging by adding the `-fmetal-enable-logging` flag.

Expand All @@ -114,6 +126,16 @@ For basic usage with debugging enabled:
}
```

For verbose debugging with a custom prefix:

```json
{
"plugin-logging": true,
"verbose-logging": true,
"logging-prefix": "[MyShaders]"
}
```

For custom compiler flags:

```json
Expand All @@ -122,6 +144,15 @@ For custom compiler flags:
}
```

For including headers from dependency targets:

```json
{
"include-dependencies": true,
"dependency-path-suffix": "include"
}
```

## License

BSD 3-clause. See [LICENSE.md](LICENSE.md).
Expand Down