Skip to content

Commit 462e954

Browse files
authored
Merge pull request #2598 from rintaro/macros-jsoncoder
[CompilerPlugin] Remove Foundation dependency
2 parents cbff435 + 1e2c3bd commit 462e954

File tree

14 files changed

+2542
-84
lines changed

14 files changed

+2542
-84
lines changed

Package.swift

+4-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ let package = Package(
3030
],
3131
targets: [
3232
// MARK: - Internal helper targets
33+
.target(
34+
name: "_SwiftSyntaxCShims"
35+
),
3336

3437
.target(
3538
name: "_AtomicBool"
@@ -74,7 +77,7 @@ let package = Package(
7477

7578
.target(
7679
name: "SwiftCompilerPlugin",
77-
dependencies: ["SwiftCompilerPluginMessageHandling", "SwiftSyntaxMacros"],
80+
dependencies: ["SwiftCompilerPluginMessageHandling", "SwiftSyntaxMacros", "_SwiftSyntaxCShims"],
7881
exclude: ["CMakeLists.txt"]
7982
),
8083

Sources/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
# See http://swift.org/LICENSE.txt for license information
77
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors
88

9+
add_subdirectory(_SwiftSyntaxCShims)
910
add_subdirectory(SwiftBasicFormat)
1011
add_subdirectory(SwiftSyntax)
1112
add_subdirectory(SwiftDiagnostics)

Sources/SwiftCompilerPlugin/CMakeLists.txt

+3-1
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,6 @@ add_swift_syntax_library(SwiftCompilerPlugin
1313

1414
target_link_swift_syntax_libraries(SwiftCompilerPlugin PUBLIC
1515
SwiftSyntaxMacros
16-
SwiftCompilerPluginMessageHandling)
16+
SwiftCompilerPluginMessageHandling
17+
_SwiftSyntaxCShims
18+
)

Sources/SwiftCompilerPlugin/CompilerPlugin.swift

+103-82
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,25 @@
1313
// https://github.com/apple/swift-package-manager/blob/main/Sources/PackagePlugin/Plugin.swift
1414

1515
#if swift(>=6.0)
16+
private import _SwiftSyntaxCShims
1617
public import SwiftSyntaxMacros
17-
private import Foundation
1818
@_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
1926
#else
27+
import _SwiftSyntaxCShims
2028
import SwiftSyntaxMacros
21-
import Foundation
2229
@_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)
2935
import ucrt
3036
#endif
3137
#endif
@@ -86,14 +92,21 @@ extension CompilerPlugin {
8692
}
8793
}
8894

89-
let pluginPath = CommandLine.arguments.first ?? Bundle.main.executablePath ?? ProcessInfo.processInfo.processName
95+
let pluginPath = CommandLine.arguments.first ?? "<unknown>"
9096
throw CompilerPluginError(
9197
message:
9298
"macro implementation type '\(moduleName).\(typeName)' could not be found in executable plugin '\(pluginPath)'"
9399
)
94100
}
95101
}
96102

103+
struct CompilerPluginError: Error, CustomStringConvertible {
104+
var description: String
105+
init(message: String) {
106+
self.description = message
107+
}
108+
}
109+
97110
struct MacroProviderAdapter<Plugin: CompilerPlugin>: PluginProvider {
98111
let plugin: Plugin
99112
init(plugin: Plugin) {
@@ -104,53 +117,61 @@ struct MacroProviderAdapter<Plugin: CompilerPlugin>: PluginProvider {
104117
}
105118
}
106119

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+
107129
extension CompilerPlugin {
108130

109131
/// Main entry point of the plugin — sets up a communication channel with
110132
/// the plugin host and runs the main message loop.
111133
public static func main() throws {
134+
let stdin = _ss_stdin()
135+
let stdout = _ss_stdout()
136+
let stderr = _ss_stderr()
137+
112138
// Duplicate the `stdin` file descriptor, which we will then use for
113139
// receiving messages from the plugin host.
114140
let inputFD = dup(fileno(stdin))
115141
guard inputFD >= 0 else {
116-
internalError("Could not duplicate `stdin`: \(describe(errno: errno)).")
142+
internalError("Could not duplicate `stdin`: \(describe(errno: _ss_errno())).")
117143
}
118144

119145
// Having duplicated the original standard-input descriptor, we close
120146
// `stdin` so that attempts by the plugin to read console input (which
121147
// are usually a mistake) return errors instead of blocking.
122148
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())).")
124150
}
125151

126152
// Duplicate the `stdout` file descriptor, which we will then use for
127153
// sending messages to the plugin host.
128154
let outputFD = dup(fileno(stdout))
129155
guard outputFD >= 0 else {
130-
internalError("Could not dup `stdout`: \(describe(errno: errno)).")
156+
internalError("Could not dup `stdout`: \(describe(errno: _ss_errno())).")
131157
}
132158

133159
// Having duplicated the original standard-output descriptor, redirect
134160
// `stdout` to `stderr` so that all free-form text output goes there.
135161
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())).")
137163
}
138164

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)
148169
#endif
149170

150171
// Open a message channel for communicating with the plugin host.
151172
let connection = PluginHostConnection(
152-
inputStream: FileHandle(fileDescriptor: inputFD),
153-
outputStream: FileHandle(fileDescriptor: outputFD)
173+
inputStream: inputFD,
174+
outputStream: outputFD
154175
)
155176

156177
// Handle messages from the host until the input stream is closed,
@@ -168,95 +189,95 @@ extension CompilerPlugin {
168189

169190
// Private function to report internal errors and then exit.
170191
fileprivate static func internalError(_ message: String) -> Never {
171-
fputs("Internal Error: \(message)\n", stderr)
192+
fputs("Internal Error: \(message)\n", _ss_stderr())
172193
exit(1)
173194
}
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-
}
180195
}
181196

182197
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
185202

186203
func sendMessage<TX: Encodable>(_ message: TX) throws {
187204
// Encode the message as JSON.
188-
let payload = try JSONEncoder().encode(message)
205+
let payload = try JSON.encode(message)
189206

190207
// 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) }
194211

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) }
198214
}
199215

200216
func waitForNextMessage<RX: Decodable>(_ ty: RX.Type) throws -> RX? {
201217
// 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.
206223
return nil
207224
}
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-
}
219225

220226
// 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)
227231

228232
// 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)))
230234
}
235+
}
231236

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+
}
236247
}
237248
}
238249

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)
245261
}
246262
}
263+
}
247264

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))"
253275
}
254276
}
255277
}
256278

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)
262283
}

Sources/SwiftCompilerPluginMessageHandling/CMakeLists.txt

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ add_swift_syntax_library(SwiftCompilerPluginMessageHandling
1313
PluginMacroExpansionContext.swift
1414
PluginMessageCompatibility.swift
1515
PluginMessages.swift
16+
JSON/CodingUtilities.swift
17+
JSON/JSON.swift
18+
JSON/JSONDecoding.swift
19+
JSON/JSONEncoding.swift
1620
)
1721

1822
target_link_swift_syntax_libraries(SwiftCompilerPluginMessageHandling PUBLIC

0 commit comments

Comments
 (0)