-
-
Notifications
You must be signed in to change notification settings - Fork 73
Expand file tree
/
Copy pathTS2Skeleton.swift
More file actions
122 lines (112 loc) · 4.29 KB
/
TS2Skeleton.swift
File metadata and controls
122 lines (112 loc) · 4.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
@preconcurrency import class Foundation.JSONDecoder
@preconcurrency import class Foundation.Process
@preconcurrency import class Foundation.Pipe
@preconcurrency import class Foundation.ProcessInfo
@preconcurrency import class Foundation.FileManager
@preconcurrency import struct Foundation.URL
@preconcurrency import struct Foundation.Data
@preconcurrency import struct Foundation.ObjCBool
@preconcurrency import func Foundation.kill
@preconcurrency import var Foundation.SIGINT
@preconcurrency import var Foundation.SIGTERM
import protocol Dispatch.DispatchSourceSignal
import class Dispatch.DispatchSource
#if canImport(BridgeJSCore)
import BridgeJSCore
#endif
#if canImport(BridgeJSSkeleton)
import BridgeJSSkeleton
#endif
internal func which(
_ executable: String,
environment: [String: String] = ProcessInfo.processInfo.environment
) throws -> URL {
func checkCandidate(_ candidate: URL) -> Bool {
var isDirectory: ObjCBool = false
let fileExists = FileManager.default.fileExists(atPath: candidate.path, isDirectory: &isDirectory)
return fileExists && !isDirectory.boolValue && FileManager.default.isExecutableFile(atPath: candidate.path)
}
do {
// Check overriding environment variable
let envVariable = "JAVASCRIPTKIT_" + executable.uppercased().replacingOccurrences(of: "-", with: "_") + "_EXEC"
if let executablePath = environment[envVariable] {
let url = URL(fileURLWithPath: executablePath)
if checkCandidate(url) {
return url
}
}
}
let pathSeparator: Character
#if os(Windows)
pathSeparator = ";"
#else
pathSeparator = ":"
#endif
let paths = environment["PATH"]?.split(separator: pathSeparator) ?? []
for path in paths {
let url = URL(fileURLWithPath: String(path)).appendingPathComponent(executable)
if checkCandidate(url) {
return url
}
}
throw BridgeJSCoreError("Executable \(executable) not found in PATH")
}
extension ImportTS {
/// Processes a TypeScript definition file and extracts its API information
public mutating func addSourceFile(_ sourceFile: String, tsconfigPath: String) throws {
let nodePath = try which("node")
let ts2skeletonPath = URL(fileURLWithPath: #filePath)
.deletingLastPathComponent()
.appendingPathComponent("JavaScript")
.appendingPathComponent("bin")
.appendingPathComponent("ts2skeleton.js")
let arguments = [ts2skeletonPath.path, sourceFile, "--project", tsconfigPath]
progress.print("Running ts2skeleton...")
progress.print(" \(([nodePath.path] + arguments).joined(separator: " "))")
let process = Process()
let stdoutPipe = Pipe()
nonisolated(unsafe) var stdoutData = Data()
process.executableURL = nodePath
process.arguments = arguments
process.standardOutput = stdoutPipe
stdoutPipe.fileHandleForReading.readabilityHandler = { handle in
let data = handle.availableData
if data.count > 0 {
stdoutData.append(data)
}
}
try process.forwardTerminationSignals {
try process.run()
process.waitUntilExit()
}
if process.terminationStatus != 0 {
throw BridgeJSCoreError("ts2skeleton returned \(process.terminationStatus)")
}
let skeleton = try JSONDecoder().decode(ImportedFileSkeleton.self, from: stdoutData)
self.addSkeleton(skeleton)
}
}
extension Foundation.Process {
// Monitor termination/interrruption signals to forward them to child process
func setSignalForwarding(_ signalNo: Int32) -> DispatchSourceSignal {
let signalSource = DispatchSource.makeSignalSource(signal: signalNo)
signalSource.setEventHandler { [self] in
signalSource.cancel()
kill(processIdentifier, signalNo)
}
signalSource.resume()
return signalSource
}
func forwardTerminationSignals(_ body: () throws -> Void) rethrows {
let sources = [
setSignalForwarding(SIGINT),
setSignalForwarding(SIGTERM),
]
defer {
for source in sources {
source.cancel()
}
}
try body()
}
}