Skip to content

Commit 7de6666

Browse files
committed
Add a C (@_cdecl) API for the driver's getSingleFrontendInvocationFromDriverArguments
This will enable non-Swift clients to query this utility as an alternative to the legacy C++ driver's equivalent.
1 parent c647e91 commit 7de6666

File tree

8 files changed

+253
-48
lines changed

8 files changed

+253
-48
lines changed

Package.swift

+7-2
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ let package = Package(
6969
/// Driver tests.
7070
.testTarget(
7171
name: "SwiftDriverTests",
72-
dependencies: ["SwiftDriver", "SwiftDriverExecution", "TestUtilities"]),
72+
dependencies: ["SwiftDriver", "SwiftDriverExecution", "TestUtilities", "ToolingTestShim"]),
7373

7474
/// IncrementalImport tests
7575
.testTarget(
@@ -93,6 +93,11 @@ let package = Package(
9393
dependencies: ["SwiftDriver", "SwiftDriverExecution"],
9494
path: "Tests/TestUtilities"),
9595

96+
.target(
97+
name: "ToolingTestShim",
98+
dependencies: ["SwiftDriver"],
99+
path: "Tests/ToolingTestShim"),
100+
96101
/// The options library.
97102
.target(
98103
name: "SwiftOptions",
@@ -120,7 +125,7 @@ let package = Package(
120125
],
121126
exclude: ["CMakeLists.txt"]),
122127

123-
/// The help executable.
128+
/// Build SDK Interfaces tool executable.
124129
.executableTarget(
125130
name: "swift-build-sdk-interfaces",
126131
dependencies: ["SwiftDriver", "SwiftDriverExecution"],

Sources/SwiftDriver/SwiftScan/SwiftScan.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -787,8 +787,8 @@ private extension swiftscan_functions_t {
787787

788788
// TODO: Move to TSC?
789789
/// Perform an `action` passing it a `const char **` constructed out of `[String]`
790-
func withArrayOfCStrings<T>(_ strings: [String],
791-
_ action: (UnsafeMutablePointer<UnsafePointer<Int8>?>?) -> T) -> T
790+
@_spi(Testing) public func withArrayOfCStrings<T>(_ strings: [String],
791+
_ action: (UnsafeMutablePointer<UnsafePointer<Int8>?>?) -> T) -> T
792792
{
793793
let cstrings = strings.map { strdup($0) } + [nil]
794794
let unsafeCStrings = cstrings.map { UnsafePointer($0) }

Sources/SwiftDriver/ToolingInterface/ToolingUtil.swift

+87-19
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,96 @@ import enum TSCBasic.ProcessEnv
1717
import var TSCBasic.localFileSystem
1818
import SwiftOptions
1919

20+
//typedef enum {
21+
// SWIFTDRIVER_TOOLING_DIAGNOSTIC_ERROR = 0,
22+
// SWIFTDRIVER_TOOLING_DIAGNOSTIC_WARNING = 1,
23+
// SWIFTDRIVER_TOOLING_DIAGNOSTIC_REMARK = 2,
24+
// SWIFTDRIVER_TOOLING_DIAGNOSTIC_NOTE = 3
25+
//} swiftdriver_tooling_diagnostic_kind;
26+
public let SWIFTDRIVER_TOOLING_DIAGNOSTIC_ERROR: CInt = 0;
27+
public let SWIFTDRIVER_TOOLING_DIAGNOSTIC_WARNING: CInt = 1;
28+
public let SWIFTDRIVER_TOOLING_DIAGNOSTIC_REMARK: CInt = 2;
29+
public let SWIFTDRIVER_TOOLING_DIAGNOSTIC_NOTE: CInt = 3;
30+
31+
@_cdecl("getSingleFrontendInvocationFromDriverArgumentsV2")
32+
public func getSingleFrontendInvocationFromDriverArgumentsV2(driverPath: UnsafePointer<CChar>,
33+
argListCount: CInt,
34+
argList: UnsafePointer<UnsafePointer<CChar>?>,
35+
action: @convention(c) (CInt, UnsafePointer<UnsafePointer<CChar>?>) -> Bool,
36+
diagnosticCallback: @convention(c) (CInt, UnsafePointer<CChar>) -> Void,
37+
forceNoOutputs: Bool = false) -> Bool {
38+
// Bridge the driver path argument
39+
let bridgedDriverPath = String(cString: driverPath)
40+
41+
// Bridge the argv equivalent
42+
let argListBufferPtr = UnsafeBufferPointer<UnsafePointer<CChar>?>(start: argList, count: Int(argListCount))
43+
let bridgedArgList = [bridgedDriverPath] + argListBufferPtr.map { String(cString: $0!) }
44+
45+
// Bridge the action callback
46+
let bridgedAction: ([String]) -> Bool = { args in
47+
return withArrayOfCStrings(args) {
48+
return action(CInt(args.count), $0!)
49+
}
50+
}
51+
52+
// Bridge the diagnostic callback
53+
let bridgedDiagnosticCallback: (CInt, String) -> Void = { diagKind, message in
54+
diagnosticCallback(diagKind, message)
55+
}
56+
57+
var diagnostics: [Diagnostic] = []
58+
let result = getSingleFrontendInvocationFromDriverArgumentsV2(driverPath: bridgedDriverPath,
59+
argList: bridgedArgList,
60+
action: bridgedAction,
61+
diagnostics: &diagnostics,
62+
diagnosticCallback: bridgedDiagnosticCallback,
63+
forceNoOutputs: forceNoOutputs)
64+
return result
65+
}
66+
2067
/// Generates the list of arguments that would be passed to the compiler
21-
/// frontend from the given driver arguments.
68+
/// frontend from the given driver arguments, for a single-compiler-invocation
69+
/// context.
2270
///
23-
/// \param ArgList The driver arguments (i.e. normal arguments for \c swiftc).
24-
/// \param ForceNoOutputs If true, override the output mode to "-typecheck" and
71+
/// \param driverPath the driver executable path
72+
/// \param argList The driver arguments (i.e. normal arguments for \c swiftc).
73+
/// \param diagnostics Contains the diagnostics emitted by the driver
74+
/// \param action invokes a user-provided action on the resulting frontend invocation command
75+
/// \param forceNoOutputs If true, override the output mode to "-typecheck" and
2576
/// produce no outputs. For example, this disables "-emit-module" and "-c" and
2677
/// prevents the creation of temporary files.
27-
/// \param outputFrontendArgs Contains the resulting frontend invocation command
28-
/// \param emittedDiagnostics Contains the diagnostics emitted by the driver
2978
///
3079
/// \returns true on error
3180
///
3281
/// \note This function is not intended to create invocations which are
3382
/// suitable for use in REPL or immediate modes.
34-
public func getSingleFrontendInvocationFromDriverArguments(argList: [String],
35-
outputFrontendArgs: inout [String],
36-
emittedDiagnostics: inout [Diagnostic],
37-
forceNoOutputs: Bool = false) -> Bool {
83+
public func getSingleFrontendInvocationFromDriverArgumentsV2(driverPath: String,
84+
argList: [String],
85+
action: ([String]) -> Bool,
86+
diagnostics: inout [Diagnostic],
87+
diagnosticCallback: @escaping (CInt, String) -> Void,
88+
forceNoOutputs: Bool = false) -> Bool {
89+
/// Handler for emitting diagnostics to tooling clients.
90+
let toolingDiagnosticsHandler: DiagnosticsEngine.DiagnosticsHandler = { diagnostic in
91+
let diagnosticKind: CInt
92+
switch diagnostic.message.behavior {
93+
case .error:
94+
diagnosticKind = SWIFTDRIVER_TOOLING_DIAGNOSTIC_ERROR
95+
case .warning:
96+
diagnosticKind = SWIFTDRIVER_TOOLING_DIAGNOSTIC_WARNING
97+
case .note:
98+
diagnosticKind = SWIFTDRIVER_TOOLING_DIAGNOSTIC_NOTE
99+
case .remark:
100+
diagnosticKind = SWIFTDRIVER_TOOLING_DIAGNOSTIC_REMARK
101+
default:
102+
diagnosticKind = SWIFTDRIVER_TOOLING_DIAGNOSTIC_ERROR
103+
}
104+
diagnosticCallback(diagnosticKind, diagnostic.message.text)
105+
}
106+
let diagnosticsEngine = DiagnosticsEngine(handlers: [toolingDiagnosticsHandler])
107+
defer { diagnostics = diagnosticsEngine.diagnostics }
108+
109+
var singleFrontendTaskCommand: [String] = []
38110
var args: [String] = []
39111
args.append(contentsOf: argList)
40112

@@ -55,13 +127,10 @@ public func getSingleFrontendInvocationFromDriverArguments(argList: [String],
55127
args.append("-driver-filelist-threshold");
56128
args.append(String(Int.max));
57129

58-
let diagnosticsEngine = DiagnosticsEngine()
59-
defer { emittedDiagnostics = diagnosticsEngine.diagnostics }
60-
61130
do {
62-
args = try ["swiftc"] + Driver.expandResponseFiles(args,
63-
fileSystem: localFileSystem,
64-
diagnosticsEngine: diagnosticsEngine)
131+
args = try [driverPath] + Driver.expandResponseFiles(args,
132+
fileSystem: localFileSystem,
133+
diagnosticsEngine: diagnosticsEngine)
65134

66135
let optionTable = OptionTable()
67136
var parsedOptions = try optionTable.parse(Array(args), for: .batch, delayThrows: true)
@@ -84,7 +153,6 @@ public func getSingleFrontendInvocationFromDriverArguments(argList: [String],
84153
return true
85154
}
86155

87-
88156
let buildPlan = try driver.planBuild()
89157
if diagnosticsEngine.hasErrors {
90158
return true
@@ -98,12 +166,12 @@ public func getSingleFrontendInvocationFromDriverArguments(argList: [String],
98166
diagnosticsEngine.emit(.error_expected_frontend_command())
99167
return true
100168
}
101-
outputFrontendArgs = try executor.description(of: compileJob,
102-
forceResponseFiles: false).components(separatedBy: " ")
169+
singleFrontendTaskCommand = try executor.description(of: compileJob,
170+
forceResponseFiles: false).components(separatedBy: " ")
103171
} catch {
104172
print("Unexpected error: \(error).")
105173
return true
106174
}
107175

108-
return false
176+
return action(singleFrontendTaskCommand)
109177
}

0 commit comments

Comments
 (0)