forked from swiftlang/swift-syntax
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCompilerPlugin.swift
117 lines (107 loc) · 4.3 KB
/
CompilerPlugin.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2023 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
//
//===----------------------------------------------------------------------===//
#if swift(>=6.0)
public import SwiftSyntaxMacros
@_spi(PluginMessage) private import SwiftCompilerPluginMessageHandling
#else
import SwiftSyntaxMacros
@_spi(PluginMessage) import SwiftCompilerPluginMessageHandling
#endif
//
// This source file contains the main entry point for compiler plugins.
// A plugin receives messages from the "plugin host" (typically
// 'swift-frontend'), and sends back messages in return based on its actions.
//
// Depending on the platform, plugins are invoked in a sandbox that blocks
// network access and prevents any file system changes.
//
// The host process and the plugin communicate using messages in the form of
// length-prefixed JSON-encoded Swift enums. The host sends messages to the
// plugin through its standard-input pipe, and receives messages through the
// plugin's standard-output pipe. The plugin's standard-error is considered
// to be free-form textual console output.
//
// Within the plugin process, `stdout` is redirected to `stderr` so that print
// statements from the plugin are treated as plain-text output, and `stdin` is
// closed so that any attempts by the plugin logic to read from console result
// in errors instead of blocking the process. The original `stdin` and `stdout`
// are duplicated for use as messaging pipes, and are not directly used by the
// plugin logic.
//
// The exit code of the plugin process indicates whether the plugin invocation
// is considered successful. A failure result should also be accompanied by an
// emitted error diagnostic, so that errors are understandable by the user.
//
// Using standard input and output streams for messaging avoids having to make
// allowances in the sandbox for other channels of communication, and seems a
// more portable approach than many of the alternatives. This is all somewhat
// temporary in any case — in the long term, something like distributed actors
// or something similar can hopefully replace the custom messaging.
//
// Usage:
// struct MyPlugin: CompilerPlugin {
// var providingMacros: [Macros.Type] = [
// StringifyMacro.self
// ]
public protocol CompilerPlugin {
init()
var providingMacros: [Macro.Type] { get }
}
extension CompilerPlugin {
@_spi(Testing)
public func resolveMacro(moduleName: String, typeName: String) throws -> Macro.Type {
let qualifedName = "\(moduleName).\(typeName)"
for type in providingMacros {
// FIXME: Is `String(reflecting:)` stable?
// Getting the module name and type name should be more robust.
let name = String(reflecting: type)
if name == qualifedName {
return type
}
}
let pluginPath: String
if CommandLine.argc > 0, let cPluginPath = CommandLine.unsafeArgv[0] {
pluginPath = String(cString: cPluginPath)
} else {
pluginPath = "<unknown>"
}
throw CompilerPluginError(
message:
"macro implementation type '\(moduleName).\(typeName)' could not be found in executable plugin '\(pluginPath)'"
)
}
}
struct CompilerPluginError: Error, CustomStringConvertible {
var description: String
init(message: String) {
self.description = message
}
}
struct MacroProviderAdapter<Plugin: CompilerPlugin>: PluginProvider {
let plugin: Plugin
init(plugin: Plugin) {
self.plugin = plugin
}
func resolveMacro(moduleName: String, typeName: String) throws -> Macro.Type {
try plugin.resolveMacro(moduleName: moduleName, typeName: typeName)
}
}
extension CompilerPlugin {
/// Main entry point of the plugin — sets up a standard I/O communication
/// channel with the plugin host and runs the main message loop.
public static func main() throws {
let connection = try StandardIOMessageConnection()
let provider = MacroProviderAdapter(plugin: Self())
let impl = CompilerPluginMessageListener(connection: connection, provider: provider)
impl.main()
}
}