Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version: 5.10
// swift-tools-version: 6.1
import PackageDescription

let package = Package(
Expand Down
14 changes: 7 additions & 7 deletions Sources/SkipDrive/SourceMap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import Foundation

/// A line and column in a particular source location.
public struct SourceLocation : Equatable {
public struct SourceLocation : Equatable, Sendable {
public var path: String
public var position: SourceLocation.Position

Expand All @@ -14,7 +14,7 @@ public struct SourceLocation : Equatable {

/// A line and column-based position in the source, appropriate for Xcode reporting.
/// Line and column numbers start with 1 rather than 0.
public struct Position: Equatable, Comparable, Decodable {
public struct Position: Equatable, Comparable, Decodable, Sendable {
public let line: Int
public let column: Int

Expand Down Expand Up @@ -64,28 +64,28 @@ public extension SourceLocation {
}

/// A decoded source map. This is the decodable counterpart to `SkipSyntax.OutputMap`
public struct SourceMap : Decodable {
public struct SourceMap : Decodable, Sendable {
public let entries: [Entry]

public struct Entry : Decodable {
public struct Entry : Decodable, Sendable {
public let sourceFile: Source.FilePath
public let sourceRange: Source.Range?
public let range: Source.Range
}

public struct Source {
public struct SourceLine : Decodable {
public struct SourceLine : Decodable, Sendable {
public let offset: Int
public let line: String
}

/// A Swift source file.
public struct FilePath: Hashable, Decodable {
public struct FilePath: Hashable, Decodable, Sendable {
public let path: String
}

/// A line and column-based range in the source, appropriate for Xcode reporting.
public struct Range: Equatable, Decodable {
public struct Range: Equatable, Decodable, Sendable {
public let start: SourceLocation.Position
public let end: SourceLocation.Position
}
Expand Down
34 changes: 21 additions & 13 deletions Sources/SkipDrive/ToolSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,12 @@ extension Range where Bound == Version {
}
}

/// Mutable reference cell for sharing optional read results between reader threads,
/// guarded externally by an NSLock so it is safe to cross the Sendable boundary.
fileprivate final class PendingResultBox: @unchecked Sendable {
var value: Result<[UInt8], Swift.Error>?
}

/// Process allows spawning new subprocesses and working with them.
///
/// Note: This class is thread safe.
Expand All @@ -532,7 +538,7 @@ extension Range where Bound == Version {
case stream(stdout: OutputClosure, stderr: OutputClosure, redirectStderr: Bool)

/// Default collect OutputRedirection that defaults to not redirect stderr. Provided for API compatibility.
public static let collect: OutputRedirection = .collect(redirectStderr: false)
nonisolated(unsafe) public static let collect: OutputRedirection = .collect(redirectStderr: false)

/// Default stream OutputRedirection that defaults to not redirect stderr. Provided for API compatibility.
public static func stream(stdout: @escaping OutputClosure, stderr: @escaping OutputClosure) -> Self {
Expand Down Expand Up @@ -591,7 +597,7 @@ extension Range where Bound == Version {
/// Typealias for logging handling closure
public typealias LoggingHandler = (String) -> Void

private static var _loggingHandler: LoggingHandler?
nonisolated(unsafe) private static var _loggingHandler: LoggingHandler?
private static let loggingHandlerLock = NSLock()

/// Global logging handler. Use with care! preferably use instance level instead of setting one globally.
Expand Down Expand Up @@ -707,7 +713,7 @@ extension Range where Bound == Version {
///
/// Key: Executable name or path.
/// Value: Path to the executable, if found.
private static var validatedExecutablesMap = [String: URL?]()
nonisolated(unsafe) private static var validatedExecutablesMap = [String: URL?]()
private static let validatedExecutablesMapLock = NSLock()

/// Create a new process instance.
Expand Down Expand Up @@ -1050,10 +1056,12 @@ extension Range where Bound == Version {
self.state = .outputReady(stdout: .success([]), stderr: .success([]))
}
} else {
var pending: Result<[UInt8], Swift.Error>?
let pending = PendingResultBox()
let pendingLock = NSLock()

let outputClosures = outputRedirection.outputClosures
let outputPipe = outputPipe
let stderrPipe = stderrPipe

// Close the local write end of the output pipe.
try close(fd: outputPipe[1])
Expand All @@ -1063,16 +1071,16 @@ extension Range where Bound == Version {
let stdoutThread = Thread { [weak self] in
if let readResult = self?.readOutput(onFD: outputPipe[0], outputClosure: outputClosures?.stdoutClosure) {
pendingLock.withLock {
if let stderrResult = pending {
if let stderrResult = pending.value {
self?.stateLock.withLock {
self?.state = .outputReady(stdout: readResult, stderr: stderrResult)
}
} else {
pending = readResult
pending.value = readResult
}
}
group.leave()
} else if let stderrResult = (pendingLock.withLock { pending }) {
} else if let stderrResult = (pendingLock.withLock { pending.value }) {
// TODO: this is more of an error
self?.stateLock.withLock {
self?.state = .outputReady(stdout: .success([]), stderr: stderrResult)
Expand All @@ -1092,16 +1100,16 @@ extension Range where Bound == Version {
stderrThread = Thread { [weak self] in
if let readResult = self?.readOutput(onFD: stderrPipe[0], outputClosure: outputClosures?.stderrClosure) {
pendingLock.withLock {
if let stdoutResult = pending {
if let stdoutResult = pending.value {
self?.stateLock.withLock {
self?.state = .outputReady(stdout: stdoutResult, stderr: readResult)
}
} else {
pending = readResult
pending.value = readResult
}
}
group.leave()
} else if let stdoutResult = (pendingLock.withLock { pending }) {
} else if let stdoutResult = (pendingLock.withLock { pending.value }) {
// TODO: this is more of an error
self?.stateLock.withLock {
self?.state = .outputReady(stdout: stdoutResult, stderr: .success([]))
Expand All @@ -1111,7 +1119,7 @@ extension Range where Bound == Version {
}
} else {
pendingLock.withLock {
pending = .success([]) // no stderr in this case
pending.value = .success([]) // no stderr in this case
}
}

Expand Down Expand Up @@ -2760,12 +2768,12 @@ public extension FileSystemError {


/// Public stdout stream instance.
public var stdoutStream: ThreadSafeOutputByteStream = try! ThreadSafeOutputByteStream(LocalFileOutputByteStream(
nonisolated(unsafe) public var stdoutStream: ThreadSafeOutputByteStream = try! ThreadSafeOutputByteStream(LocalFileOutputByteStream(
filePointer: stdout,
closeOnDeinit: false))

/// Public stderr stream instance.
public var stderrStream: ThreadSafeOutputByteStream = try! ThreadSafeOutputByteStream(LocalFileOutputByteStream(
nonisolated(unsafe) public var stderrStream: ThreadSafeOutputByteStream = try! ThreadSafeOutputByteStream(LocalFileOutputByteStream(
filePointer: stderr,
closeOnDeinit: false))

Expand Down
Loading