13
13
// https://github.com/apple/swift-package-manager/blob/main/Sources/PackagePlugin/Plugin.swift
14
14
15
15
#if swift(>=6.0)
16
+ private import _SwiftSyntaxCShims
16
17
public import SwiftSyntaxMacros
17
- private import Foundation
18
18
@_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
19
26
#else
27
+ import _SwiftSyntaxCShims
20
28
import SwiftSyntaxMacros
21
- import Foundation
22
29
@_spi ( PluginMessage) import SwiftCompilerPluginMessageHandling
23
- #endif
24
-
25
- #if os(Windows)
26
- #if swift(>=6.0)
27
- private import ucrt
28
- #else
30
+ #if canImport(Darwin)
31
+ import Darwin
32
+ #elseif canImport(Glibc)
33
+ import Glibc
34
+ #elseif canImport(ucrt)
29
35
import ucrt
30
36
#endif
31
37
#endif
@@ -86,14 +92,21 @@ extension CompilerPlugin {
86
92
}
87
93
}
88
94
89
- let pluginPath = CommandLine . arguments. first ?? Bundle . main . executablePath ?? ProcessInfo . processInfo . processName
95
+ let pluginPath = CommandLine . arguments. first ?? " <unknown> "
90
96
throw CompilerPluginError (
91
97
message:
92
98
" macro implementation type ' \( moduleName) . \( typeName) ' could not be found in executable plugin ' \( pluginPath) ' "
93
99
)
94
100
}
95
101
}
96
102
103
+ struct CompilerPluginError : Error , CustomStringConvertible {
104
+ var description : String
105
+ init ( message: String ) {
106
+ self . description = message
107
+ }
108
+ }
109
+
97
110
struct MacroProviderAdapter < Plugin: CompilerPlugin > : PluginProvider {
98
111
let plugin : Plugin
99
112
init ( plugin: Plugin ) {
@@ -104,53 +117,61 @@ struct MacroProviderAdapter<Plugin: CompilerPlugin>: PluginProvider {
104
117
}
105
118
}
106
119
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
+
107
129
extension CompilerPlugin {
108
130
109
131
/// Main entry point of the plugin — sets up a communication channel with
110
132
/// the plugin host and runs the main message loop.
111
133
public static func main( ) throws {
134
+ let stdin = _ss_stdin ( )
135
+ let stdout = _ss_stdout ( )
136
+ let stderr = _ss_stderr ( )
137
+
112
138
// Duplicate the `stdin` file descriptor, which we will then use for
113
139
// receiving messages from the plugin host.
114
140
let inputFD = dup ( fileno ( stdin) )
115
141
guard inputFD >= 0 else {
116
- internalError ( " Could not duplicate `stdin`: \( describe ( errno: errno ) ) . " )
142
+ internalError ( " Could not duplicate `stdin`: \( describe ( errno: _ss_errno ( ) ) ) . " )
117
143
}
118
144
119
145
// Having duplicated the original standard-input descriptor, we close
120
146
// `stdin` so that attempts by the plugin to read console input (which
121
147
// are usually a mistake) return errors instead of blocking.
122
148
guard close ( fileno ( stdin) ) >= 0 else {
123
- internalError ( " Could not close `stdin`: \( describe ( errno: errno ) ) . " )
149
+ internalError ( " Could not close `stdin`: \( describe ( errno: _ss_errno ( ) ) ) . " )
124
150
}
125
151
126
152
// Duplicate the `stdout` file descriptor, which we will then use for
127
153
// sending messages to the plugin host.
128
154
let outputFD = dup ( fileno ( stdout) )
129
155
guard outputFD >= 0 else {
130
- internalError ( " Could not dup `stdout`: \( describe ( errno: errno ) ) . " )
156
+ internalError ( " Could not dup `stdout`: \( describe ( errno: _ss_errno ( ) ) ) . " )
131
157
}
132
158
133
159
// Having duplicated the original standard-output descriptor, redirect
134
160
// `stdout` to `stderr` so that all free-form text output goes there.
135
161
guard dup2 ( fileno ( stderr) , fileno ( stdout) ) >= 0 else {
136
- internalError ( " Could not dup2 `stdout` to `stderr`: \( describe ( errno: errno ) ) . " )
162
+ internalError ( " Could not dup2 `stdout` to `stderr`: \( describe ( errno: _ss_errno ( ) ) ) . " )
137
163
}
138
164
139
- // Turn off full buffering so printed text appears as soon as possible.
140
- // Windows is much less forgiving than other platforms. If line
141
- // buffering is enabled, we must provide a buffer and the size of the
142
- // buffer. As a result, on Windows, we completely disable all
143
- // buffering, which means that partial writes are possible.
144
- #if os(Windows)
145
- setvbuf ( stdout, nil , _IONBF, 0 )
146
- #else
147
- setvbuf ( stdout, nil , _IOLBF, 0 )
165
+ #if canImport(ucrt)
166
+ // Set I/O to binary mode. Avoid CRLF translation, and Ctrl+Z (0x1A) as EOF.
167
+ _ = _setmode ( inputFD, _O_BINARY)
168
+ _ = _setmode ( outputFD, _O_BINARY)
148
169
#endif
149
170
150
171
// Open a message channel for communicating with the plugin host.
151
172
let connection = PluginHostConnection (
152
- inputStream: FileHandle ( fileDescriptor : inputFD) ,
153
- outputStream: FileHandle ( fileDescriptor : outputFD)
173
+ inputStream: inputFD,
174
+ outputStream: outputFD
154
175
)
155
176
156
177
// Handle messages from the host until the input stream is closed,
@@ -168,95 +189,95 @@ extension CompilerPlugin {
168
189
169
190
// Private function to report internal errors and then exit.
170
191
fileprivate static func internalError( _ message: String ) -> Never {
171
- fputs ( " Internal Error: \( message) \n " , stderr )
192
+ fputs ( " Internal Error: \( message) \n " , _ss_stderr ( ) )
172
193
exit ( 1 )
173
194
}
174
-
175
- // Private function to construct an error message from an `errno` code.
176
- fileprivate static func describe( errno: Int32 ) -> String {
177
- if let cStr = strerror ( errno) { return String ( cString: cStr) }
178
- return String ( describing: errno)
179
- }
180
195
}
181
196
182
197
internal struct PluginHostConnection : MessageConnection {
183
- fileprivate let inputStream : FileHandle
184
- fileprivate let outputStream : FileHandle
198
+ // File descriptor for input from the host.
199
+ fileprivate let inputStream : CInt
200
+ // File descriptor for output to the host.
201
+ fileprivate let outputStream : CInt
185
202
186
203
func sendMessage< TX: Encodable > ( _ message: TX ) throws {
187
204
// Encode the message as JSON.
188
- let payload = try JSONEncoder ( ) . encode ( message)
205
+ let payload = try JSON . encode ( message)
189
206
190
207
// Write the header (a 64-bit length field in little endian byte order).
191
- var count = UInt64 ( payload. count) . littleEndian
192
- let header = Swift . withUnsafeBytes ( of : & count) { Data ( $0 ) }
193
- precondition ( header. count == 8 )
208
+ let count = payload. count
209
+ var header = UInt64 ( count) . littleEndian
210
+ try withUnsafeBytes ( of : & header) { try _write ( outputStream , contentsOf : $0 ) }
194
211
195
- // Write the header and payload.
196
- try outputStream. _write ( contentsOf: header)
197
- try outputStream. _write ( contentsOf: payload)
212
+ // Write the JSON payload.
213
+ try payload. withUnsafeBytes { try _write ( outputStream, contentsOf: $0) }
198
214
}
199
215
200
216
func waitForNextMessage< RX: Decodable > ( _ ty: RX . Type ) throws -> RX ? {
201
217
// Read the header (a 64-bit length field in little endian byte order).
202
- guard
203
- let header = try inputStream. _read ( upToCount: 8 ) ,
204
- header. count != 0
205
- else {
218
+ var header : UInt64 = 0
219
+ do {
220
+ try withUnsafeMutableBytes ( of: & header) { try _read ( inputStream, into: $0) }
221
+ } catch IOError . readReachedEndOfInput {
222
+ // Connection closed.
206
223
return nil
207
224
}
208
- guard header. count == 8 else {
209
- throw PluginMessageError . truncatedHeader
210
- }
211
-
212
- // Decode the count.
213
- let count = header. withUnsafeBytes {
214
- UInt64 ( littleEndian: $0. loadUnaligned ( as: UInt64 . self) )
215
- }
216
- guard count >= 2 else {
217
- throw PluginMessageError . invalidPayloadSize
218
- }
219
225
220
226
// Read the JSON payload.
221
- guard
222
- let payload = try inputStream. _read ( upToCount: Int ( count) ) ,
223
- payload. count == count
224
- else {
225
- throw PluginMessageError . truncatedPayload
226
- }
227
+ let count = Int ( UInt64 ( littleEndian: header) )
228
+ let data = UnsafeMutableRawBufferPointer . allocate ( byteCount: count, alignment: 1 )
229
+ defer { data. deallocate ( ) }
230
+ try _read ( inputStream, into: data)
227
231
228
232
// Decode and return the message.
229
- return try JSONDecoder ( ) . decode ( RX . self , from: payload )
233
+ return try JSON . decode ( ty , from: UnsafeBufferPointer ( data . bindMemory ( to : UInt8 . self ) ) )
230
234
}
235
+ }
231
236
232
- enum PluginMessageError : Swift . Error {
233
- case truncatedHeader
234
- case invalidPayloadSize
235
- case truncatedPayload
237
+ /// Write the buffer to the file descriptor. Throws an error on failure.
238
+ private func _write( _ fd: CInt , contentsOf buffer: UnsafeRawBufferPointer ) throws {
239
+ guard var ptr = buffer. baseAddress else { return }
240
+ let endPtr = ptr. advanced ( by: buffer. count)
241
+ while ptr != endPtr {
242
+ switch write ( fd, ptr, numericCast ( endPtr - ptr) ) {
243
+ case - 1 : throw IOError . writeFailed ( errno: _ss_errno ( ) )
244
+ case 0 : throw IOError . writeFailed ( errno: 0 ) /* unreachable */
245
+ case let n: ptr += Int ( n)
246
+ }
236
247
}
237
248
}
238
249
239
- private extension FileHandle {
240
- func _write( contentsOf data: Data ) throws {
241
- if #available( macOS 10 . 15 . 4 , iOS 13 . 4 , watchOS 6 . 2 , tvOS 13 . 4 , * ) {
242
- return try self . write ( contentsOf: data)
243
- } else {
244
- return self . write ( data)
250
+ /// Fill the buffer from the file descriptor. Throws an error on failure.
251
+ /// If the file descriptor reached the end-of-file before filling up the entire
252
+ /// buffer, throws IOError.readReachedEndOfInput
253
+ private func _read( _ fd: CInt , into buffer: UnsafeMutableRawBufferPointer ) throws {
254
+ guard var ptr = buffer. baseAddress else { return }
255
+ let endPtr = ptr. advanced ( by: buffer. count)
256
+ while ptr != endPtr {
257
+ switch read ( fd, ptr, numericCast ( endPtr - ptr) ) {
258
+ case - 1 : throw IOError . readFailed ( errno: _ss_errno ( ) )
259
+ case 0 : throw IOError . readReachedEndOfInput
260
+ case let n: ptr += Int ( n)
245
261
}
246
262
}
263
+ }
247
264
248
- func _read( upToCount count: Int ) throws -> Data ? {
249
- if #available( macOS 10 . 15 . 4 , iOS 13 . 4 , watchOS 6 . 2 , tvOS 13 . 4 , * ) {
250
- return try self . read ( upToCount: count)
251
- } else {
252
- return self . readData ( ofLength: 8 )
265
+ private enum IOError : Error , CustomStringConvertible {
266
+ case readReachedEndOfInput
267
+ case readFailed( errno: CInt )
268
+ case writeFailed( errno: CInt )
269
+
270
+ var description : String {
271
+ switch self {
272
+ case . readReachedEndOfInput: " read(2) reached end-of-file "
273
+ case . readFailed( let errno) : " read(2) failed: \( describe ( errno: errno) ) "
274
+ case . writeFailed( let errno) : " write(2) failed: \( describe ( errno: errno) ) "
253
275
}
254
276
}
255
277
}
256
278
257
- struct CompilerPluginError : Error , CustomStringConvertible {
258
- var description : String
259
- init ( message: String ) {
260
- self . description = message
261
- }
279
+ // Private function to construct an error message from an `errno` code.
280
+ private func describe( errno: CInt ) -> String {
281
+ if let cStr = strerror ( errno) { return String ( cString: cStr) }
282
+ return String ( describing: errno)
262
283
}
0 commit comments