Skip to content

[CompilerPlugin] Remove Foundation dependency #2598

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 31 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
61c7338
[Macros] Implement own JSON encoder/decoder
rintaro Apr 11, 2024
e20a65c
[CompilerPlugin] Remove dependency to Foundation.FileHandle
rintaro Apr 11, 2024
29032ed
[Macros] Cleanup JSON
rintaro Apr 11, 2024
7886469
[Macros] Update CMakeLists.txt for JSON
rintaro Apr 11, 2024
3d676d0
[JSON] Improve error throwing
rintaro Apr 12, 2024
39acd5a
[JSON] Formatting
rintaro Apr 12, 2024
c830a21
[JSON] Import for non-Darwin platforms
rintaro Apr 12, 2024
adb2fb0
[CompilerPlugin] Exit cleanly
rintaro Apr 12, 2024
4edb04a
[JSON] Comment and cosmetic change
rintaro Apr 12, 2024
b8ae7a6
[JSON] Use only inside of string quote
rintaro Apr 12, 2024
d6f5327
[JSON] Add basic test cases
rintaro Apr 12, 2024
9246250
[JSON] Encode/decode escape sequence correctly
rintaro Apr 12, 2024
c986761
[JSON] Misc improvements
rintaro Apr 13, 2024
6b594b8
[JSON] Optimization and tweaks
rintaro Apr 13, 2024
10cc964
[CompilerPlugin] Add C shim for getting stdio names and errno
rintaro Apr 13, 2024
3b36bde
[CompilerPlugin] Use fread(3)/fwrite(3) instead of read(2)/write(2)
rintaro Apr 15, 2024
b3494aa
[Windows] Speculative fix
rintaro Apr 15, 2024
585dae0
[JSON] Fix UnkeyedContaier.decodeNil()
rintaro Apr 16, 2024
5250447
[JSON] Emit DecodingError.valueNotFound error if the value is null
rintaro Apr 16, 2024
27fc293
[JSON] expect(_:) should throw 'unexpectedEndOfFile' at EOF
rintaro Apr 18, 2024
278aea7
[CompilerPlugin] Rewrite MessageConnection
rintaro Apr 18, 2024
488c434
[JSON] Added some parse failure test
rintaro Apr 18, 2024
401229c
[JSON] Fix JSONMapValue.equals(to:)
rintaro Apr 19, 2024
c0fc9b5
[JSON] Collection values in JSONMap to store the mapSize
rintaro Apr 19, 2024
4c84edb
[JSON] Small changes
rintaro Apr 19, 2024
5ab0c7e
[JSON] Improve string decoding
rintaro Apr 20, 2024
197a45c
[JSON] Style and documentation improvements, and cleanups
rintaro Apr 21, 2024
17a0c6b
[JSON] Style and documentation improvements, and cleanups
rintaro Apr 22, 2024
cf4f3e4
[JSON] JSONWriter.serialize(string:) improvement
rintaro Apr 21, 2024
679b929
[JSON] Fix a potential buffer over read in floating point parsing.
rintaro Apr 21, 2024
1e2c3bd
Rename _CShims -> _SwiftSyntaxCShims
rintaro Apr 22, 2024
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
5 changes: 4 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ let package = Package(
],
targets: [
// MARK: - Internal helper targets
.target(
name: "_SwiftSyntaxCShims"
),

.target(
name: "_AtomicBool"
Expand Down Expand Up @@ -74,7 +77,7 @@ let package = Package(

.target(
name: "SwiftCompilerPlugin",
dependencies: ["SwiftCompilerPluginMessageHandling", "SwiftSyntaxMacros"],
dependencies: ["SwiftCompilerPluginMessageHandling", "SwiftSyntaxMacros", "_SwiftSyntaxCShims"],
exclude: ["CMakeLists.txt"]
),

Expand Down
1 change: 1 addition & 0 deletions Sources/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# See http://swift.org/LICENSE.txt for license information
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors

add_subdirectory(_SwiftSyntaxCShims)
add_subdirectory(SwiftBasicFormat)
add_subdirectory(SwiftSyntax)
add_subdirectory(SwiftDiagnostics)
Expand Down
4 changes: 3 additions & 1 deletion Sources/SwiftCompilerPlugin/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ add_swift_syntax_library(SwiftCompilerPlugin

target_link_swift_syntax_libraries(SwiftCompilerPlugin PUBLIC
SwiftSyntaxMacros
SwiftCompilerPluginMessageHandling)
SwiftCompilerPluginMessageHandling
_SwiftSyntaxCShims
)
185 changes: 103 additions & 82 deletions Sources/SwiftCompilerPlugin/CompilerPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,25 @@
// https://github.com/apple/swift-package-manager/blob/main/Sources/PackagePlugin/Plugin.swift

#if swift(>=6.0)
private import _SwiftSyntaxCShims
public import SwiftSyntaxMacros
private import Foundation
@_spi(PluginMessage) private import SwiftCompilerPluginMessageHandling
#if canImport(Darwin)
private import Darwin
#elseif canImport(Glibc)
private import Glibc
#elseif canImport(ucrt)
private import ucrt
#endif
#else
import _SwiftSyntaxCShims
import SwiftSyntaxMacros
import Foundation
@_spi(PluginMessage) import SwiftCompilerPluginMessageHandling
#endif

#if os(Windows)
#if swift(>=6.0)
private import ucrt
#else
#if canImport(Darwin)
import Darwin
#elseif canImport(Glibc)
import Glibc
#elseif canImport(ucrt)
import ucrt
#endif
#endif
Expand Down Expand Up @@ -86,14 +92,21 @@ extension CompilerPlugin {
}
}

let pluginPath = CommandLine.arguments.first ?? Bundle.main.executablePath ?? ProcessInfo.processInfo.processName
let pluginPath = CommandLine.arguments.first ?? "<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) {
Expand All @@ -104,53 +117,61 @@ struct MacroProviderAdapter<Plugin: CompilerPlugin>: PluginProvider {
}
}

#if canImport(ucrt)
private let dup = _dup(_:)
private let fileno = _fileno(_:)
private let dup2 = _dup2(_:_:)
private let close = _close(_:)
private let read = _read(_:_:_:)
private let write = _write(_:_:_:)
#endif

extension CompilerPlugin {

/// Main entry point of the plugin — sets up a communication channel with
/// the plugin host and runs the main message loop.
public static func main() throws {
let stdin = _ss_stdin()
let stdout = _ss_stdout()
let stderr = _ss_stderr()

// Duplicate the `stdin` file descriptor, which we will then use for
// receiving messages from the plugin host.
let inputFD = dup(fileno(stdin))
guard inputFD >= 0 else {
internalError("Could not duplicate `stdin`: \(describe(errno: errno)).")
internalError("Could not duplicate `stdin`: \(describe(errno: _ss_errno())).")
}

// Having duplicated the original standard-input descriptor, we close
// `stdin` so that attempts by the plugin to read console input (which
// are usually a mistake) return errors instead of blocking.
guard close(fileno(stdin)) >= 0 else {
internalError("Could not close `stdin`: \(describe(errno: errno)).")
internalError("Could not close `stdin`: \(describe(errno: _ss_errno())).")
}

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

// Having duplicated the original standard-output descriptor, redirect
// `stdout` to `stderr` so that all free-form text output goes there.
guard dup2(fileno(stderr), fileno(stdout)) >= 0 else {
internalError("Could not dup2 `stdout` to `stderr`: \(describe(errno: errno)).")
internalError("Could not dup2 `stdout` to `stderr`: \(describe(errno: _ss_errno())).")
}

// Turn off full buffering so printed text appears as soon as possible.
// Windows is much less forgiving than other platforms. If line
// buffering is enabled, we must provide a buffer and the size of the
// buffer. As a result, on Windows, we completely disable all
// buffering, which means that partial writes are possible.
#if os(Windows)
setvbuf(stdout, nil, _IONBF, 0)
#else
setvbuf(stdout, nil, _IOLBF, 0)
#if canImport(ucrt)
// Set I/O to binary mode. Avoid CRLF translation, and Ctrl+Z (0x1A) as EOF.
_ = _setmode(inputFD, _O_BINARY)
_ = _setmode(outputFD, _O_BINARY)
#endif

// Open a message channel for communicating with the plugin host.
let connection = PluginHostConnection(
inputStream: FileHandle(fileDescriptor: inputFD),
outputStream: FileHandle(fileDescriptor: outputFD)
inputStream: inputFD,
outputStream: outputFD
)

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

// Private function to report internal errors and then exit.
fileprivate static func internalError(_ message: String) -> Never {
fputs("Internal Error: \(message)\n", stderr)
fputs("Internal Error: \(message)\n", _ss_stderr())
exit(1)
}

// Private function to construct an error message from an `errno` code.
fileprivate static func describe(errno: Int32) -> String {
if let cStr = strerror(errno) { return String(cString: cStr) }
return String(describing: errno)
}
}

internal struct PluginHostConnection: MessageConnection {
fileprivate let inputStream: FileHandle
fileprivate let outputStream: FileHandle
// File descriptor for input from the host.
fileprivate let inputStream: CInt
// File descriptor for output to the host.
fileprivate let outputStream: CInt

func sendMessage<TX: Encodable>(_ message: TX) throws {
// Encode the message as JSON.
let payload = try JSONEncoder().encode(message)
let payload = try JSON.encode(message)

// Write the header (a 64-bit length field in little endian byte order).
var count = UInt64(payload.count).littleEndian
let header = Swift.withUnsafeBytes(of: &count) { Data($0) }
precondition(header.count == 8)
let count = payload.count
var header = UInt64(count).littleEndian
try withUnsafeBytes(of: &header) { try _write(outputStream, contentsOf: $0) }

// Write the header and payload.
try outputStream._write(contentsOf: header)
try outputStream._write(contentsOf: payload)
// Write the JSON payload.
try payload.withUnsafeBytes { try _write(outputStream, contentsOf: $0) }
}

func waitForNextMessage<RX: Decodable>(_ ty: RX.Type) throws -> RX? {
// Read the header (a 64-bit length field in little endian byte order).
guard
let header = try inputStream._read(upToCount: 8),
header.count != 0
else {
var header: UInt64 = 0
do {
try withUnsafeMutableBytes(of: &header) { try _read(inputStream, into: $0) }
} catch IOError.readReachedEndOfInput {
// Connection closed.
return nil
}
guard header.count == 8 else {
throw PluginMessageError.truncatedHeader
}

// Decode the count.
let count = header.withUnsafeBytes {
UInt64(littleEndian: $0.loadUnaligned(as: UInt64.self))
}
guard count >= 2 else {
throw PluginMessageError.invalidPayloadSize
}

// Read the JSON payload.
guard
let payload = try inputStream._read(upToCount: Int(count)),
payload.count == count
else {
throw PluginMessageError.truncatedPayload
}
let count = Int(UInt64(littleEndian: header))
let data = UnsafeMutableRawBufferPointer.allocate(byteCount: count, alignment: 1)
defer { data.deallocate() }
try _read(inputStream, into: data)

// Decode and return the message.
return try JSONDecoder().decode(RX.self, from: payload)
return try JSON.decode(ty, from: UnsafeBufferPointer(data.bindMemory(to: UInt8.self)))
}
}

enum PluginMessageError: Swift.Error {
case truncatedHeader
case invalidPayloadSize
case truncatedPayload
/// Write the buffer to the file descriptor. Throws an error on failure.
private func _write(_ fd: CInt, contentsOf buffer: UnsafeRawBufferPointer) throws {
guard var ptr = buffer.baseAddress else { return }
let endPtr = ptr.advanced(by: buffer.count)
while ptr != endPtr {
switch write(fd, ptr, numericCast(endPtr - ptr)) {
case -1: throw IOError.writeFailed(errno: _ss_errno())
case 0: throw IOError.writeFailed(errno: 0) /* unreachable */
case let n: ptr += Int(n)
}
}
}

private extension FileHandle {
func _write(contentsOf data: Data) throws {
if #available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) {
return try self.write(contentsOf: data)
} else {
return self.write(data)
/// Fill the buffer from the file descriptor. Throws an error on failure.
/// If the file descriptor reached the end-of-file before filling up the entire
/// buffer, throws IOError.readReachedEndOfInput
private func _read(_ fd: CInt, into buffer: UnsafeMutableRawBufferPointer) throws {
guard var ptr = buffer.baseAddress else { return }
let endPtr = ptr.advanced(by: buffer.count)
while ptr != endPtr {
switch read(fd, ptr, numericCast(endPtr - ptr)) {
case -1: throw IOError.readFailed(errno: _ss_errno())
case 0: throw IOError.readReachedEndOfInput
case let n: ptr += Int(n)
}
}
}

func _read(upToCount count: Int) throws -> Data? {
if #available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) {
return try self.read(upToCount: count)
} else {
return self.readData(ofLength: 8)
private enum IOError: Error, CustomStringConvertible {
case readReachedEndOfInput
case readFailed(errno: CInt)
case writeFailed(errno: CInt)

var description: String {
switch self {
case .readReachedEndOfInput: "read(2) reached end-of-file"
case .readFailed(let errno): "read(2) failed: \(describe(errno: errno))"
case .writeFailed(let errno): "write(2) failed: \(describe(errno: errno))"
}
}
}

struct CompilerPluginError: Error, CustomStringConvertible {
var description: String
init(message: String) {
self.description = message
}
// Private function to construct an error message from an `errno` code.
private func describe(errno: CInt) -> String {
if let cStr = strerror(errno) { return String(cString: cStr) }
return String(describing: errno)
}
4 changes: 4 additions & 0 deletions Sources/SwiftCompilerPluginMessageHandling/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ add_swift_syntax_library(SwiftCompilerPluginMessageHandling
PluginMacroExpansionContext.swift
PluginMessageCompatibility.swift
PluginMessages.swift
JSON/CodingUtilities.swift
JSON/JSON.swift
JSON/JSONDecoding.swift
JSON/JSONEncoding.swift
)

target_link_swift_syntax_libraries(SwiftCompilerPluginMessageHandling PUBLIC
Expand Down
Loading