Skip to content

Commit a295a7c

Browse files
committed
Move PluginMessageConnection to PluginMessageHandling
So that other modules (e.g. swift-plugin-server can use it) Renamed to 'StandardIOMessageConnection'
1 parent 27a1c07 commit a295a7c

File tree

3 files changed

+181
-166
lines changed

3 files changed

+181
-166
lines changed

Package.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ let package = Package(
7373

7474
.target(
7575
name: "SwiftCompilerPlugin",
76-
dependencies: ["SwiftCompilerPluginMessageHandling", "SwiftSyntaxMacros", "_SwiftSyntaxCShims"],
76+
dependencies: ["SwiftCompilerPluginMessageHandling", "SwiftSyntaxMacros"],
7777
exclude: ["CMakeLists.txt"]
7878
),
7979

@@ -87,6 +87,7 @@ let package = Package(
8787
.target(
8888
name: "SwiftCompilerPluginMessageHandling",
8989
dependencies: [
90+
"_SwiftSyntaxCShims",
9091
"SwiftDiagnostics",
9192
"SwiftOperators",
9293
"SwiftParser",

Sources/SwiftCompilerPlugin/CompilerPlugin.swift

Lines changed: 4 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,13 @@
99
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
1010
//
1111
//===----------------------------------------------------------------------===//
12-
// NOTE: This basic plugin mechanism is mostly copied from
13-
// https://github.com/apple/swift-package-manager/blob/main/Sources/PackagePlugin/Plugin.swift
1412

1513
#if swift(>=6.0)
16-
private import _SwiftSyntaxCShims
1714
public import SwiftSyntaxMacros
1815
@_spi(PluginMessage) private import SwiftCompilerPluginMessageHandling
19-
#if canImport(Darwin)
20-
private import Darwin
21-
#elseif canImport(Glibc)
22-
private import Glibc
23-
#elseif canImport(ucrt)
24-
private import ucrt
25-
#endif
2616
#else
27-
import _SwiftSyntaxCShims
2817
import SwiftSyntaxMacros
2918
@_spi(PluginMessage) import SwiftCompilerPluginMessageHandling
30-
#if canImport(Darwin)
31-
import Darwin
32-
#elseif canImport(Glibc)
33-
import Glibc
34-
#elseif canImport(ucrt)
35-
import ucrt
36-
#endif
3719
#endif
3820

3921
//
@@ -117,163 +99,20 @@ struct MacroProviderAdapter<Plugin: CompilerPlugin>: PluginProvider {
11799
}
118100
}
119101

120-
#if canImport(ucrt)
121-
private let dup = _dup(_:)
122-
private let fileno = _fileno(_:)
123-
private let dup2 = _dup2(_:_:)
124-
private let close = _close(_:)
125-
private let read = _read(_:_:_:)
126-
private let write = _write(_:_:_:)
127-
#endif
128-
129102
extension CompilerPlugin {
130103

131-
/// Main entry point of the plugin — sets up a communication channel with
132-
/// the plugin host and runs the main message loop.
104+
/// Main entry point of the plugin — sets up a standard I/O communication
105+
/// channel with the plugin host and runs the main message loop.
133106
public static func main() throws {
134-
// Duplicate the `stdin` file descriptor, which we will then use for
135-
// receiving messages from the plugin host.
136-
let inputFD = dup(fileno(_stdin))
137-
guard inputFD >= 0 else {
138-
internalError("Could not duplicate `stdin`: \(describe(errno: _errno)).")
139-
}
140-
141-
// Having duplicated the original standard-input descriptor, we close
142-
// `stdin` so that attempts by the plugin to read console input (which
143-
// are usually a mistake) return errors instead of blocking.
144-
guard close(fileno(_stdin)) >= 0 else {
145-
internalError("Could not close `stdin`: \(describe(errno: _errno)).")
146-
}
147-
148-
// Duplicate the `stdout` file descriptor, which we will then use for
149-
// sending messages to the plugin host.
150-
let outputFD = dup(fileno(_stdout))
151-
guard outputFD >= 0 else {
152-
internalError("Could not dup `stdout`: \(describe(errno: _errno)).")
153-
}
154-
155-
// Having duplicated the original standard-output descriptor, redirect
156-
// `stdout` to `stderr` so that all free-form text output goes there.
157-
guard dup2(fileno(_stderr), fileno(_stdout)) >= 0 else {
158-
internalError("Could not dup2 `stdout` to `stderr`: \(describe(errno: _errno)).")
159-
}
160-
161-
#if canImport(ucrt)
162-
// Set I/O to binary mode. Avoid CRLF translation, and Ctrl+Z (0x1A) as EOF.
163-
_ = _setmode(inputFD, _O_BINARY)
164-
_ = _setmode(outputFD, _O_BINARY)
165-
#endif
166-
167-
// Open a message channel for communicating with the plugin host.
168-
let connection = PluginHostConnection(
169-
inputStream: inputFD,
170-
outputStream: outputFD
171-
)
172-
173-
// Handle messages from the host until the input stream is closed,
174-
// indicating that we're done.
107+
let connection = try StandardIOMessageConnection()
175108
let provider = MacroProviderAdapter(plugin: Self())
176109
let impl = CompilerPluginMessageHandler(connection: connection, provider: provider)
177110
do {
178111
try impl.main()
179112
} catch {
180113
// Emit a diagnostic and indicate failure to the plugin host,
181114
// and exit with an error code.
182-
internalError(String(describing: error))
183-
}
184-
}
185-
186-
// Private function to report internal errors and then exit.
187-
fileprivate static func internalError(_ message: String) -> Never {
188-
fputs("Internal Error: \(message)\n", _stderr)
189-
exit(1)
190-
}
191-
}
192-
193-
internal struct PluginHostConnection: MessageConnection {
194-
// File descriptor for input from the host.
195-
fileprivate let inputStream: CInt
196-
// File descriptor for output to the host.
197-
fileprivate let outputStream: CInt
198-
199-
func sendMessage<TX: Encodable>(_ message: TX) throws {
200-
// Encode the message as JSON.
201-
let payload = try JSON.encode(message)
202-
203-
// Write the header (a 64-bit length field in little endian byte order).
204-
let count = payload.count
205-
var header = UInt64(count).littleEndian
206-
try withUnsafeBytes(of: &header) { try _write(outputStream, contentsOf: $0) }
207-
208-
// Write the JSON payload.
209-
try payload.withUnsafeBytes { try _write(outputStream, contentsOf: $0) }
210-
}
211-
212-
func waitForNextMessage<RX: Decodable>(_ ty: RX.Type) throws -> RX? {
213-
// Read the header (a 64-bit length field in little endian byte order).
214-
var header: UInt64 = 0
215-
do {
216-
try withUnsafeMutableBytes(of: &header) { try _read(inputStream, into: $0) }
217-
} catch IOError.readReachedEndOfInput {
218-
// Connection closed.
219-
return nil
220-
}
221-
222-
// Read the JSON payload.
223-
let count = Int(UInt64(littleEndian: header))
224-
let data = UnsafeMutableRawBufferPointer.allocate(byteCount: count, alignment: 1)
225-
defer { data.deallocate() }
226-
try _read(inputStream, into: data)
227-
228-
// Decode and return the message.
229-
return try JSON.decode(ty, from: UnsafeBufferPointer(data.bindMemory(to: UInt8.self)))
230-
}
231-
}
232-
233-
/// Write the buffer to the file descriptor. Throws an error on failure.
234-
private func _write(_ fd: CInt, contentsOf buffer: UnsafeRawBufferPointer) throws {
235-
guard var ptr = buffer.baseAddress else { return }
236-
let endPtr = ptr.advanced(by: buffer.count)
237-
while ptr != endPtr {
238-
switch write(fd, ptr, numericCast(endPtr - ptr)) {
239-
case -1: throw IOError.writeFailed(errno: _errno)
240-
case 0: throw IOError.writeFailed(errno: 0) /* unreachable */
241-
case let n: ptr += Int(n)
115+
fatalError("Internal Error: \(error)")
242116
}
243117
}
244118
}
245-
246-
/// Fill the buffer from the file descriptor. Throws an error on failure.
247-
/// If the file descriptor reached the end-of-file before filling up the entire
248-
/// buffer, throws IOError.readReachedEndOfInput
249-
private func _read(_ fd: CInt, into buffer: UnsafeMutableRawBufferPointer) throws {
250-
guard var ptr = buffer.baseAddress else { return }
251-
let endPtr = ptr.advanced(by: buffer.count)
252-
while ptr != endPtr {
253-
switch read(fd, ptr, numericCast(endPtr - ptr)) {
254-
case -1: throw IOError.readFailed(errno: _errno)
255-
case 0: throw IOError.readReachedEndOfInput
256-
case let n: ptr += Int(n)
257-
}
258-
}
259-
}
260-
261-
private enum IOError: Error, CustomStringConvertible {
262-
case readReachedEndOfInput
263-
case readFailed(errno: CInt)
264-
case writeFailed(errno: CInt)
265-
266-
var description: String {
267-
switch self {
268-
case .readReachedEndOfInput: "read(2) reached end-of-file"
269-
case .readFailed(let errno): "read(2) failed: \(describe(errno: errno))"
270-
case .writeFailed(let errno): "write(2) failed: \(describe(errno: errno))"
271-
}
272-
}
273-
}
274-
275-
// Private function to construct an error message from an `errno` code.
276-
private func describe(errno: CInt) -> String {
277-
if let cStr = strerror(errno) { return String(cString: cStr) }
278-
return String(describing: errno)
279-
}
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#if swift(>=6.0)
14+
private import _SwiftSyntaxCShims
15+
#if canImport(Darwin)
16+
private import Darwin
17+
#elseif canImport(Glibc)
18+
private import Glibc
19+
#elseif canImport(ucrt)
20+
private import ucrt
21+
#endif
22+
#else
23+
import _SwiftSyntaxCShims
24+
#if canImport(Darwin)
25+
import Darwin
26+
#elseif canImport(Glibc)
27+
import Glibc
28+
#elseif canImport(ucrt)
29+
import ucrt
30+
#endif
31+
#endif
32+
33+
#if canImport(ucrt)
34+
private let dup = _dup(_:)
35+
private let fileno = _fileno(_:)
36+
private let dup2 = _dup2(_:_:)
37+
private let close = _close(_:)
38+
private let read = _read(_:_:_:)
39+
private let write = _write(_:_:_:)
40+
#endif
41+
42+
/// Concrete 'MessageConnection' type using Standard I/O.
43+
///
44+
/// When creating, `stdout` is redirected to `stderr` so that print statements
45+
/// from the plugin are treated as plain-text output, and `stdin` is closed so
46+
/// that any attempts by the plugin logic to read from console result in errors
47+
/// instead of blocking the process. The original `stdin` and `stdout` are
48+
/// duplicated for use as messaging pipes, and are not directly used by the
49+
/// plugin logic.
50+
///
51+
/// Each message is serialized to UTF-8 encoded JSON text, prefixed with a
52+
/// 8 byte header which is the byte size of the JSON payload serialized to a
53+
/// little-endian 64bit unsigned integer.
54+
@_spi(PluginMessage)
55+
public struct StandardIOMessageConnection: MessageConnection {
56+
private var inputFileDescriptor: CInt
57+
private var outputFileDescriptor: CInt
58+
59+
public init() throws {
60+
// Duplicate the `stdin` file descriptor, which we will then use for
61+
// receiving messages from the plugin host.
62+
let inputFD = dup(fileno(_stdin))
63+
guard inputFD >= 0 else {
64+
throw IOError.systemError(function: "dup(fileno(stdin))", errno: _errno)
65+
}
66+
67+
// Having duplicated the original standard-input descriptor, we close
68+
// `stdin` so that attempts by the plugin to read console input (which
69+
// are usually a mistake) return errors instead of blocking.
70+
guard close(fileno(_stdin)) >= 0 else {
71+
throw IOError.systemError(function: "close(fileno(stdin))", errno: _errno)
72+
}
73+
74+
// Duplicate the `stdout` file descriptor, which we will then use for
75+
// sending messages to the plugin host.
76+
let outputFD = dup(fileno(_stdout))
77+
guard outputFD >= 0 else {
78+
throw IOError.systemError(function: "dup(fileno(_stdout))", errno: _errno)
79+
}
80+
81+
// Having duplicated the original standard-output descriptor, redirect
82+
// `stdout` to `stderr` so that all free-form text output goes there.
83+
guard dup2(fileno(_stderr), fileno(_stdout)) >= 0 else {
84+
throw IOError.systemError(function: "dup2(fileno(_stderr), fileno(_stdout))", errno: _errno)
85+
}
86+
87+
#if canImport(ucrt)
88+
// Set I/O to binary mode. Avoid CRLF translation, and Ctrl+Z (0x1A) as EOF.
89+
_ = _setmode(inputFD, _O_BINARY)
90+
_ = _setmode(outputFD, _O_BINARY)
91+
#endif
92+
93+
self.inputFileDescriptor = inputFD
94+
self.outputFileDescriptor = outputFD
95+
}
96+
97+
/// Write the buffer to the file descriptor. Throws an error on failure.
98+
private func _write(contentsOf buffer: UnsafeRawBufferPointer) throws {
99+
guard var ptr = buffer.baseAddress else { return }
100+
let endPtr = ptr.advanced(by: buffer.count)
101+
while ptr != endPtr {
102+
switch write(outputFileDescriptor, ptr, numericCast(endPtr - ptr)) {
103+
case -1: throw IOError.systemError(function: "write(_:_:_:)", errno: _errno)
104+
case 0: throw IOError.systemError(function: "write", errno: 0) /* unreachable */
105+
case let n: ptr += Int(n)
106+
}
107+
}
108+
}
109+
110+
/// Fill the buffer from the file descriptor. Throws an error on failure.
111+
/// If the file descriptor reached the end-of-file before filling up the entire
112+
/// buffer, throws IOError.readReachedEndOfInput
113+
private func _read(into buffer: UnsafeMutableRawBufferPointer) throws {
114+
guard var ptr = buffer.baseAddress else { return }
115+
let endPtr = ptr.advanced(by: buffer.count)
116+
while ptr != endPtr {
117+
switch read(inputFileDescriptor, ptr, numericCast(endPtr - ptr)) {
118+
case -1: throw IOError.systemError(function: "read(_:_:_:)", errno: _errno)
119+
case 0: throw IOError.readReachedEndOfInput
120+
case let n: ptr += Int(n)
121+
}
122+
}
123+
}
124+
125+
public func sendMessage<TX>(_ message: TX) throws where TX : Encodable {
126+
// Encode the message as JSON.
127+
let payload = try JSON.encode(message)
128+
129+
// Write the header (a 64-bit length field in little endian byte order).
130+
let count = payload.count
131+
var header = UInt64(count).littleEndian
132+
try withUnsafeBytes(of: &header) { try _write(contentsOf: $0) }
133+
134+
// Write the JSON payload.
135+
try payload.withUnsafeBytes { try _write(contentsOf: $0) }
136+
}
137+
138+
public func waitForNextMessage<RX>(_ type: RX.Type) throws -> RX? where RX : Decodable {
139+
// Read the header (a 64-bit length field in little endian byte order).
140+
var header: UInt64 = 0
141+
do {
142+
try withUnsafeMutableBytes(of: &header) { try _read(into: $0) }
143+
} catch IOError.readReachedEndOfInput {
144+
// Connection closed.
145+
return nil
146+
}
147+
148+
// Read the JSON payload.
149+
let count = Int(UInt64(littleEndian: header))
150+
let data = UnsafeMutableRawBufferPointer.allocate(byteCount: count, alignment: 1)
151+
defer { data.deallocate() }
152+
try _read(into: data)
153+
154+
// Decode and return the message.
155+
return try JSON.decode(type, from: UnsafeBufferPointer(data.bindMemory(to: UInt8.self)))
156+
}
157+
}
158+
159+
private enum IOError: Error, CustomStringConvertible {
160+
case readReachedEndOfInput
161+
case systemError(function: String, errno: CInt)
162+
163+
var description: String {
164+
switch self {
165+
case .readReachedEndOfInput: "read(2) reached end-of-file"
166+
case .systemError(let function, let errno): "\(function) failed: \(describe(errno: errno))"
167+
}
168+
}
169+
}
170+
171+
// Private function to construct an error message from an `errno` code.
172+
private func describe(errno: CInt) -> String {
173+
if let cStr = strerror(errno) { return String(cString: cStr) }
174+
return String(describing: errno)
175+
}

0 commit comments

Comments
 (0)