Skip to content

Commit f9e905d

Browse files
committed
Add capability to query '-print-target-info' in-process using libSwiftScan API
1 parent 2a9add5 commit f9e905d

File tree

8 files changed

+200
-17
lines changed

8 files changed

+200
-17
lines changed

Sources/CSwiftScan/include/swiftscan_header.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ typedef struct {
193193

194194
//=== Cleanup Functions ---------------------------------------------------===//
195195
void
196+
(*swiftscan_string_dispose)(swiftscan_string_ref_t);
197+
void
196198
(*swiftscan_string_set_dispose)(swiftscan_string_set_t *);
197199
void
198200
(*swiftscan_dependency_graph_dispose)(swiftscan_dependency_graph_t);
@@ -207,6 +209,10 @@ typedef struct {
207209
void
208210
(*swiftscan_scan_invocation_dispose)(swiftscan_scan_invocation_t);
209211

212+
//=== Target Info Functions-------- ---------------------------------------===//
213+
swiftscan_string_ref_t
214+
(*swiftscan_compiler_target_info_query)(swiftscan_scan_invocation_t);
215+
210216
//=== Functionality Query Functions ---------------------------------------===//
211217
swiftscan_string_set_t *
212218
(*swiftscan_compiler_supported_arguments_query)(void);

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ public struct Driver {
137137
public let integratedDriver: Bool
138138

139139
/// The file system which we should interact with.
140-
let fileSystem: FileSystem
140+
@_spi(Testing) public let fileSystem: FileSystem
141141

142142
/// Diagnostic engine for emitting warnings, errors, etc.
143143
public let diagnosticEngine: DiagnosticsEngine
@@ -522,12 +522,15 @@ public struct Driver {
522522
compilerMode: self.compilerMode, env: env,
523523
executor: self.executor, fileSystem: fileSystem,
524524
useStaticResourceDir: self.useStaticResourceDir,
525+
workingDirectory: self.workingDirectory,
525526
compilerExecutableDir: compilerExecutableDir)
526527

527528
// Compute the host machine's triple
528529
self.hostTriple =
529530
try Self.computeHostTriple(&self.parsedOptions, diagnosticsEngine: diagnosticEngine,
530531
toolchain: self.toolchain, executor: self.executor,
532+
fileSystem: fileSystem,
533+
workingDirectory: self.workingDirectory,
531534
swiftCompilerPrefixArgs: self.swiftCompilerPrefixArgs)
532535

533536
// Classify and collect all of the input files.
@@ -2896,6 +2899,8 @@ extension Driver {
28962899
diagnosticsEngine: DiagnosticsEngine,
28972900
toolchain: Toolchain,
28982901
executor: DriverExecutor,
2902+
fileSystem: FileSystem,
2903+
workingDirectory: AbsolutePath?,
28992904
swiftCompilerPrefixArgs: [String]) throws -> Triple {
29002905

29012906
let frontendOverride = try FrontendOverride(&parsedOptions, diagnosticsEngine)
@@ -2908,6 +2913,11 @@ extension Driver {
29082913
capturingJSONOutputAs: FrontendTargetInfo.self,
29092914
forceResponseFiles: false,
29102915
recordedInputModificationDates: [:]).target.triple
2916+
// return try Self.computeTargetInfo(target: nil, targetVariant: nil,
2917+
// swiftCompilerPrefixArgs: frontendOverride.prefixArgsForTargetInfo,
2918+
// toolchain: toolchain, fileSystem: fileSystem,
2919+
// workingDirectory: workingDirectory,
2920+
// executor: executor).target.triple
29112921
}
29122922

29132923
static func computeToolchain(
@@ -2918,6 +2928,7 @@ extension Driver {
29182928
executor: DriverExecutor,
29192929
fileSystem: FileSystem,
29202930
useStaticResourceDir: Bool,
2931+
workingDirectory: AbsolutePath?,
29212932
compilerExecutableDir: AbsolutePath?
29222933
) throws -> (Toolchain, FrontendTargetInfo, [String]) {
29232934
let explicitTarget = (parsedOptions.getLastArgument(.target)?.asSingle)
@@ -2973,6 +2984,16 @@ extension Driver {
29732984
capturingJSONOutputAs: FrontendTargetInfo.self,
29742985
forceResponseFiles: false,
29752986
recordedInputModificationDates: [:])
2987+
// var info: FrontendTargetInfo =
2988+
// try Self.computeTargetInfo(target: explicitTarget, targetVariant: explicitTargetVariant,
2989+
// sdkPath: sdkPath, resourceDirPath: resourceDirPath,
2990+
// runtimeCompatibilityVersion:
2991+
// parsedOptions.getLastArgument(.runtimeCompatibilityVersion)?.asSingle,
2992+
// useStaticResourceDir: useStaticResourceDir,
2993+
// swiftCompilerPrefixArgs: frontendOverride.prefixArgsForTargetInfo,
2994+
// toolchain: toolchain, fileSystem: fileSystem,
2995+
// workingDirectory: workingDirectory,
2996+
// executor: executor)
29762997

29772998
// Parse the runtime compatibility version. If present, it will override
29782999
// what is reported by the frontend.

Sources/SwiftDriver/Execution/DriverExecutor.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ extension DriverExecutor {
8787
capturingJSONOutputAs outputType: T.Type,
8888
forceResponseFiles: Bool,
8989
recordedInputModificationDates: [TypedVirtualPath: TimePoint]) throws -> T {
90+
//print(">>> EXEC: \(try self.description(of: job, forceResponseFiles: false))")
9091
let result = try execute(job: job,
9192
forceResponseFiles: forceResponseFiles,
9293
recordedInputModificationDates: recordedInputModificationDates)

Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ public extension Driver {
236236
return fallbackToFrontend
237237
}
238238

239-
private func sanitizeCommandForLibScanInvocation(_ command: inout [String]) {
239+
static func sanitizeCommandForLibScanInvocation(_ command: inout [String]) {
240240
// Remove the tool executable to only leave the arguments. When passing the
241241
// command line into libSwiftScan, the library is itself the tool and only
242242
// needs to parse the remaining arguments.
@@ -257,10 +257,10 @@ public extension Driver {
257257
let isSwiftScanLibAvailable = !(try initSwiftScanLib())
258258
if isSwiftScanLibAvailable {
259259
let cwd = workingDirectory ?? fileSystem.currentWorkingDirectory!
260-
var command = try itemizedJobCommand(of: preScanJob,
261-
useResponseFiles: .disabled,
262-
using: executor.resolver)
263-
sanitizeCommandForLibScanInvocation(&command)
260+
var command = try Self.itemizedJobCommand(of: preScanJob,
261+
useResponseFiles: .disabled,
262+
using: executor.resolver)
263+
Self.sanitizeCommandForLibScanInvocation(&command)
264264
imports =
265265
try interModuleDependencyOracle.getImports(workingDirectory: cwd,
266266
moduleAliases: moduleOutputInfo.aliases,
@@ -293,10 +293,10 @@ public extension Driver {
293293
let isSwiftScanLibAvailable = !(try initSwiftScanLib())
294294
if isSwiftScanLibAvailable {
295295
let cwd = workingDirectory ?? fileSystem.currentWorkingDirectory!
296-
var command = try itemizedJobCommand(of: scannerJob,
297-
useResponseFiles: .disabled,
298-
using: executor.resolver)
299-
sanitizeCommandForLibScanInvocation(&command)
296+
var command = try Self.itemizedJobCommand(of: scannerJob,
297+
useResponseFiles: .disabled,
298+
using: executor.resolver)
299+
Self.sanitizeCommandForLibScanInvocation(&command)
300300
dependencyGraph =
301301
try interModuleDependencyOracle.getDependencies(workingDirectory: cwd,
302302
moduleAliases: moduleOutputInfo.aliases,
@@ -339,10 +339,10 @@ public extension Driver {
339339
let isSwiftScanLibAvailable = !(try initSwiftScanLib())
340340
if isSwiftScanLibAvailable {
341341
let cwd = workingDirectory ?? fileSystem.currentWorkingDirectory!
342-
var command = try itemizedJobCommand(of: batchScanningJob,
343-
useResponseFiles: .disabled,
344-
using: executor.resolver)
345-
sanitizeCommandForLibScanInvocation(&command)
342+
var command = try Self.itemizedJobCommand(of: batchScanningJob,
343+
useResponseFiles: .disabled,
344+
using: executor.resolver)
345+
Self.sanitizeCommandForLibScanInvocation(&command)
346346
moduleVersionedGraphMap =
347347
try interModuleDependencyOracle.getBatchDependencies(workingDirectory: cwd,
348348
moduleAliases: moduleOutputInfo.aliases,
@@ -485,8 +485,8 @@ public extension Driver {
485485
contents)
486486
}
487487

488-
fileprivate func itemizedJobCommand(of job: Job, useResponseFiles: ResponseFileHandling,
489-
using resolver: ArgsResolver) throws -> [String] {
488+
static func itemizedJobCommand(of job: Job, useResponseFiles: ResponseFileHandling,
489+
using resolver: ArgsResolver) throws -> [String] {
490490
let (args, _) = try resolver.resolveArgumentList(for: job,
491491
useResponseFiles: useResponseFiles)
492492
return args

Sources/SwiftDriver/Jobs/PrintTargetInfoJob.swift

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
import protocol TSCBasic.FileSystem
14+
import class Foundation.JSONDecoder
15+
import struct TSCBasic.AbsolutePath
16+
1317
/// Swift versions are major.minor.
1418
struct SwiftVersion {
1519
var major: Int
@@ -173,3 +177,83 @@ extension Toolchain {
173177
)
174178
}
175179
}
180+
181+
extension Driver {
182+
@_spi(Testing) public static func queryTargetInfoInProcess(of toolchain: Toolchain,
183+
fileSystem: FileSystem,
184+
workingDirectory: AbsolutePath?,
185+
invocationCommand: [String]) throws -> FrontendTargetInfo? {
186+
let optionalSwiftScanLibPath = try toolchain.lookupSwiftScanLib()
187+
if let swiftScanLibPath = optionalSwiftScanLibPath,
188+
fileSystem.exists(swiftScanLibPath) {
189+
let libSwiftScanInstance = try SwiftScan(dylib: swiftScanLibPath)
190+
if libSwiftScanInstance.canQueryTargetInfo() {
191+
let cwd = workingDirectory ?? fileSystem.currentWorkingDirectory!
192+
let targetInfoData =
193+
try libSwiftScanInstance.queryTargetInfoJSON(workingDirectory: cwd,
194+
invocationCommand: invocationCommand)
195+
do {
196+
return try JSONDecoder().decode(FrontendTargetInfo.self, from: targetInfoData)
197+
} catch let decodingError as DecodingError {
198+
let stringToDecode = String(data: targetInfoData, encoding: .utf8)
199+
let errorDesc: String
200+
switch decodingError {
201+
case let .typeMismatch(type, context):
202+
errorDesc = "type mismatch: \(type), path: \(context.codingPath)"
203+
case let .valueNotFound(type, context):
204+
errorDesc = "value missing: \(type), path: \(context.codingPath)"
205+
case let .keyNotFound(key, context):
206+
errorDesc = "key missing: \(key), path: \(context.codingPath)"
207+
case let .dataCorrupted(context):
208+
errorDesc = "data corrupted at path: \(context.codingPath)"
209+
@unknown default:
210+
errorDesc = "unknown decoding error"
211+
}
212+
throw Error.unableToDecodeFrontendTargetInfo(
213+
stringToDecode,
214+
invocationCommand,
215+
errorDesc)
216+
}
217+
}
218+
}
219+
return nil
220+
}
221+
222+
static func computeTargetInfo(target: Triple?,
223+
targetVariant: Triple?,
224+
sdkPath: VirtualPath? = nil,
225+
resourceDirPath: VirtualPath? = nil,
226+
runtimeCompatibilityVersion: String? = nil,
227+
requiresInPlaceExecution: Bool = false,
228+
useStaticResourceDir: Bool = false,
229+
swiftCompilerPrefixArgs: [String],
230+
toolchain: Toolchain,
231+
fileSystem: FileSystem,
232+
workingDirectory: AbsolutePath?,
233+
executor: DriverExecutor) throws -> FrontendTargetInfo {
234+
let frontendTargetInfoJob =
235+
try toolchain.printTargetInfoJob(target: target, targetVariant: targetVariant,
236+
sdkPath: sdkPath, resourceDirPath: resourceDirPath,
237+
runtimeCompatibilityVersion: runtimeCompatibilityVersion,
238+
requiresInPlaceExecution: requiresInPlaceExecution,
239+
useStaticResourceDir: useStaticResourceDir,
240+
swiftCompilerPrefixArgs: swiftCompilerPrefixArgs)
241+
var command = try Self.itemizedJobCommand(of: frontendTargetInfoJob,
242+
useResponseFiles: .disabled,
243+
using: executor.resolver)
244+
Self.sanitizeCommandForLibScanInvocation(&command)
245+
if let targetInfo =
246+
try Self.queryTargetInfoInProcess(of: toolchain, fileSystem: fileSystem,
247+
workingDirectory: workingDirectory,
248+
invocationCommand: command) {
249+
return targetInfo
250+
}
251+
252+
// Fallback: Invoke `swift-frontend -print-target-info` and decode the output
253+
return try executor.execute(
254+
job: frontendTargetInfoJob,
255+
capturingJSONOutputAs: FrontendTargetInfo.self,
256+
forceResponseFiles: false,
257+
recordedInputModificationDates: [:])
258+
}
259+
}

Sources/SwiftDriver/SwiftScan/SwiftScan.swift

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
import func Foundation.strdup
1616
import func Foundation.free
17+
import class Foundation.JSONDecoder
18+
import struct Foundation.Data
1719

1820
import protocol TSCBasic.DiagnosticData
1921
import struct TSCBasic.AbsolutePath
@@ -80,7 +82,7 @@ internal extension swiftscan_diagnostic_severity_t {
8082
}
8183

8284
/// Wrapper for libSwiftScan, taking care of initialization, shutdown, and dispatching dependency scanning queries.
83-
internal final class SwiftScan {
85+
@_spi(Testing) public final class SwiftScan {
8486
/// The path to the libSwiftScan dylib.
8587
let path: AbsolutePath
8688

@@ -269,6 +271,10 @@ internal final class SwiftScan {
269271
api.swiftscan_diagnostic_get_severity != nil &&
270272
api.swiftscan_diagnostics_set_dispose != nil
271273
}
274+
275+
@_spi(Testing) public func supportsStringDispose() -> Bool {
276+
return api.swiftscan_string_dispose != nil
277+
}
272278

273279
@_spi(Testing) public func queryScannerDiagnostics() throws -> [ScannerDiagnosticPayload] {
274280
var result: [ScannerDiagnosticPayload] = []
@@ -311,6 +317,34 @@ internal final class SwiftScan {
311317
throw DependencyScanningError.argumentQueryFailed
312318
}
313319
}
320+
321+
@_spi(Testing) public func canQueryTargetInfo() -> Bool {
322+
return api.swiftscan_compiler_target_info_query != nil &&
323+
api.swiftscan_string_set_dispose != nil
324+
}
325+
326+
func queryTargetInfoJSON(workingDirectory: AbsolutePath,
327+
invocationCommand: [String]) throws -> Data {
328+
// Create and configure the scanner invocation
329+
let invocation = api.swiftscan_scan_invocation_create()
330+
defer { api.swiftscan_scan_invocation_dispose(invocation) }
331+
api.swiftscan_scan_invocation_set_working_directory(invocation,
332+
workingDirectory
333+
.description
334+
.cString(using: String.Encoding.utf8))
335+
withArrayOfCStrings(invocationCommand) { invocationStringArray in
336+
api.swiftscan_scan_invocation_set_argv(invocation,
337+
Int32(invocationCommand.count),
338+
invocationStringArray)
339+
}
340+
let targetInfoStringRef = api.swiftscan_compiler_target_info_query(invocation)
341+
let targetInfoString = try toSwiftString(targetInfoStringRef)
342+
if supportsStringDispose() {
343+
api.swiftscan_string_dispose(targetInfoStringRef)
344+
}
345+
let targetInfoData = Data(targetInfoString.utf8)
346+
return targetInfoData
347+
}
314348
}
315349

316350
// Used for testing purposes only
@@ -347,6 +381,10 @@ private extension swiftscan_functions_t {
347381
self.swiftscan_compiler_supported_features_query =
348382
try loadOptional("swiftscan_compiler_supported_features_query")
349383

384+
// Target Info query
385+
self.swiftscan_compiler_target_info_query =
386+
try loadOptional("swiftscan_compiler_target_info_query")
387+
350388
// Dependency scanner serialization/deserialization features
351389
self.swiftscan_scanner_cache_serialize =
352390
try loadOptional("swiftscan_scanner_cache_serialize")
@@ -370,6 +408,8 @@ private extension swiftscan_functions_t {
370408
try loadOptional("swiftscan_diagnostic_get_severity")
371409
self.swiftscan_diagnostics_set_dispose =
372410
try loadOptional("swiftscan_diagnostics_set_dispose")
411+
self.swiftscan_string_dispose =
412+
try loadOptional("swiftscan_string_dispose")
373413

374414
// isFramework on binary module dependencies
375415
self.swiftscan_swift_binary_detail_get_is_framework =

Tests/SwiftDriverTests/SwiftDriverTests.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4781,6 +4781,21 @@ final class SwiftDriverTests: XCTestCase {
47814781
XCTAssertTrue(job.commandLine.contains(.flag("-resource-dir")))
47824782
}
47834783

4784+
// In-process query
4785+
do {
4786+
let targetInfoArgs = ["-print-target-info", "-sdk", "bar", "-resource-dir", "baz"]
4787+
let driver = try Driver(args: ["swift"] + targetInfoArgs)
4788+
let swiftScanLibPath = try XCTUnwrap(driver.toolchain.lookupSwiftScanLib())
4789+
if localFileSystem.exists(swiftScanLibPath) {
4790+
let libSwiftScanInstance = try SwiftScan(dylib: swiftScanLibPath)
4791+
if libSwiftScanInstance.canQueryTargetInfo() {
4792+
XCTAssertTrue(try driver.verifyBeingAbleToQueryTargetInfoInProcess(workingDirectory: localFileSystem.currentWorkingDirectory,
4793+
invocationCommand: targetInfoArgs))
4794+
}
4795+
}
4796+
4797+
}
4798+
47844799
do {
47854800
struct MockExecutor: DriverExecutor {
47864801
let resolver: ArgsResolver
@@ -4803,7 +4818,11 @@ final class SwiftDriverTests: XCTestCase {
48034818
}
48044819
}
48054820

4821+
// Override path to libSwiftScan to force the fallback of using the executor
4822+
var hideSwiftScanEnv = ProcessEnv.vars
4823+
hideSwiftScanEnv["SWIFT_DRIVER_SWIFTSCAN_LIB"] = "/bad/path/lib_InternalSwiftScan.dylib"
48064824
XCTAssertThrowsError(try Driver(args: ["swift", "-print-target-info"],
4825+
env: hideSwiftScanEnv,
48074826
executor: MockExecutor(resolver: ArgsResolver(fileSystem: InMemoryFileSystem())))) {
48084827
error in
48094828
if case .unableToDecodeFrontendTargetInfo = error as? Driver.Error {}

Tests/TestUtilities/DriverExtensions.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,18 @@ extension Driver {
4242
public static func sdkArgumentsForTesting() throws -> [String]? {
4343
try cachedSDKPath.map {["-sdk", try $0.get()]}
4444
}
45+
46+
47+
public func verifyBeingAbleToQueryTargetInfoInProcess(workingDirectory: AbsolutePath?,
48+
invocationCommand: [String]) throws -> Bool {
49+
guard try Self.queryTargetInfoInProcess(of: toolchain,
50+
fileSystem: fileSystem,
51+
workingDirectory: workingDirectory,
52+
invocationCommand: invocationCommand) != nil else {
53+
return false
54+
}
55+
return true
56+
}
4557
}
4658

4759
/// Set to nil if cannot perform on this host

0 commit comments

Comments
 (0)