Skip to content

Commit b10a7d1

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

File tree

5 files changed

+121
-4
lines changed

5 files changed

+121
-4
lines changed

Sources/CSwiftScan/include/swiftscan_header.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,10 @@ typedef struct {
207207
void
208208
(*swiftscan_scan_invocation_dispose)(swiftscan_scan_invocation_t);
209209

210+
//=== Target Info Functions-------- ---------------------------------------===//
211+
swiftscan_string_ref_t
212+
(*swiftscan_compiler_target_info_query)(swiftscan_scan_invocation_t);
213+
210214
//=== Functionality Query Functions ---------------------------------------===//
211215
swiftscan_string_set_t *
212216
(*swiftscan_compiler_supported_arguments_query)(void);

Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift

Lines changed: 2 additions & 2 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+
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.
@@ -485,7 +485,7 @@ public extension Driver {
485485
contents)
486486
}
487487

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

Sources/SwiftDriver/Jobs/PrintTargetInfoJob.swift

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

13+
import protocol TSCBasic.FileSystem
14+
import class Foundation.JSONDecoder
15+
1316
/// Swift versions are major.minor.
1417
struct SwiftVersion {
1518
var major: Int
@@ -173,3 +176,65 @@ extension Toolchain {
173176
)
174177
}
175178
}
179+
180+
extension Driver {
181+
static func queryTargetInfoInProcess(of toolchain: Toolchain,
182+
fileSystem: FileSystem,
183+
invocationCommand: [String]) throws -> FrontendTargetInfo? {
184+
let optionalSwiftScanLibPath = try toolchain.lookupSwiftScanLib()
185+
if let swiftScanLibPath = optionalSwiftScanLibPath,
186+
fileSystem.exists(swiftScanLibPath) {
187+
let libSwiftScanInstance = try SwiftScan(dylib: swiftScanLibPath)
188+
if libSwiftScanInstance.canQueryTargetInfo() {
189+
let targetInfoData = try libSwiftScanInstance.queryTargetInfoJSON(invocationCommand: invocationCommand)
190+
return try JSONDecoder().decode(FrontendTargetInfo.self, from: targetInfoData)
191+
}
192+
}
193+
return nil
194+
}
195+
196+
func computeTargetInfo(target: Triple?,
197+
targetVariant: Triple?,
198+
sdkPath: VirtualPath? = nil,
199+
resourceDirPath: VirtualPath? = nil,
200+
runtimeCompatibilityVersion: String? = nil,
201+
requiresInPlaceExecution: Bool = false,
202+
useStaticResourceDir: Bool = false,
203+
swiftCompilerPrefixArgs: [String],
204+
executor: DriverExecutor) throws -> FrontendTargetInfo {
205+
let frontendTargetInfoJob =
206+
try toolchain.printTargetInfoJob(target: target, targetVariant: targetVariant,
207+
sdkPath: sdkPath, resourceDirPath: resourceDirPath,
208+
runtimeCompatibilityVersion: runtimeCompatibilityVersion,
209+
requiresInPlaceExecution: requiresInPlaceExecution,
210+
useStaticResourceDir: useStaticResourceDir,
211+
swiftCompilerPrefixArgs: swiftCompilerPrefixArgs)
212+
var command = try itemizedJobCommand(of: frontendTargetInfoJob,
213+
useResponseFiles: .disabled,
214+
using: executor.resolver)
215+
sanitizeCommandForLibScanInvocation(&command)
216+
if let targetInfo =
217+
try Self.queryTargetInfoInProcess(of: toolchain, fileSystem: fileSystem,
218+
invocationCommand: command) {
219+
return targetInfo
220+
}
221+
222+
// Fallback: Invoke `swift-frontend -print-target-info` and decode the output
223+
return try executor.execute(
224+
job: frontendTargetInfoJob,
225+
capturingJSONOutputAs: FrontendTargetInfo.self,
226+
forceResponseFiles: false,
227+
recordedInputModificationDates: [:])
228+
}
229+
230+
/// This method exists for testing purposes only
231+
@_spi(Testing) public func verifyBeingAbleToQueryTargetInfoInProcess(invocationCommand: [String]) throws -> Bool {
232+
guard let targetInfo = try Self.queryTargetInfoInProcess(of: toolchain,
233+
fileSystem: fileSystem,
234+
invocationCommand: invocationCommand) else {
235+
return false
236+
}
237+
print("libSwiftScan Compiler: \(targetInfo.compilerVersion)")
238+
return true
239+
}
240+
}

Sources/SwiftDriver/SwiftScan/SwiftScan.swift

Lines changed: 26 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

@@ -311,6 +313,25 @@ internal final class SwiftScan {
311313
throw DependencyScanningError.argumentQueryFailed
312314
}
313315
}
316+
317+
@_spi(Testing) public func canQueryTargetInfo() -> Bool {
318+
return api.swiftscan_compiler_target_info_query != nil &&
319+
api.swiftscan_string_set_dispose != nil
320+
}
321+
322+
func queryTargetInfoJSON(invocationCommand: [String]) throws -> Data {
323+
// Create and configure the scanner invocation
324+
let invocation = api.swiftscan_scan_invocation_create()
325+
defer { api.swiftscan_scan_invocation_dispose(invocation) }
326+
withArrayOfCStrings(invocationCommand) { invocationStringArray in
327+
api.swiftscan_scan_invocation_set_argv(invocation,
328+
Int32(invocationCommand.count),
329+
invocationStringArray)
330+
}
331+
let targetInfoString = try toSwiftString(api.swiftscan_compiler_target_info_query(invocation))
332+
let targetInfoData = Data(targetInfoString.utf8)
333+
return targetInfoData
334+
}
314335
}
315336

316337
// Used for testing purposes only
@@ -347,6 +368,10 @@ private extension swiftscan_functions_t {
347368
self.swiftscan_compiler_supported_features_query =
348369
try loadOptional("swiftscan_compiler_supported_features_query")
349370

371+
// Target Info query
372+
self.swiftscan_compiler_target_info_query =
373+
try loadOptional("swiftscan_compiler_target_info_query")
374+
350375
// Dependency scanner serialization/deserialization features
351376
self.swiftscan_scanner_cache_serialize =
352377
try loadOptional("swiftscan_scanner_cache_serialize")

Tests/SwiftDriverTests/SwiftDriverTests.swift

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4769,9 +4769,14 @@ final class SwiftDriverTests: XCTestCase {
47694769
}
47704770

47714771
func testPrintTargetInfo() throws {
4772+
print("Begin testPrintTargetInfo")
4773+
47724774
do {
4773-
var driver = try Driver(args: ["swift", "-print-target-info", "-target", "arm64-apple-ios12.0", "-sdk", "bar", "-resource-dir", "baz"])
4775+
print("testPrintTargetInfo -> driver 1")
4776+
var driver = try Driver(args: ["swift", "-print-target-info", "-sdk", "bar", "-resource-dir", "baz"])
4777+
print("testPrintTargetInfo -> planBuild 1")
47744778
let plannedJobs = try driver.planBuild()
4779+
print("testPrintTargetInfo -> done 1")
47754780
XCTAssertTrue(plannedJobs.count == 1)
47764781
let job = plannedJobs[0]
47774782
XCTAssertEqual(job.kind, .printTargetInfo)
@@ -4781,6 +4786,24 @@ final class SwiftDriverTests: XCTestCase {
47814786
XCTAssertTrue(job.commandLine.contains(.flag("-resource-dir")))
47824787
}
47834788

4789+
do {
4790+
let targetInfoArgs = ["-print-target-info", "-sdk", "bar", "-resource-dir", "baz"]
4791+
print("testPrintTargetInfo -> driver 2")
4792+
let driver = try Driver(args: ["swift"] + targetInfoArgs)
4793+
print("testPrintTargetInfo -> getScanLibPath")
4794+
let swiftScanLibPath = try XCTUnwrap(driver.toolchain.lookupSwiftScanLib())
4795+
print("Attempting to use scanner library at: \(swiftScanLibPath.debugDescription)")
4796+
if localFileSystem.exists(swiftScanLibPath) {
4797+
print("Library Exists")
4798+
let libSwiftScanInstance = try SwiftScan(dylib: swiftScanLibPath)
4799+
if libSwiftScanInstance.canQueryTargetInfo() {
4800+
print("Library able to query target info")
4801+
XCTAssertTrue(try driver.verifyBeingAbleToQueryTargetInfoInProcess(invocationCommand: targetInfoArgs))
4802+
print("Verified")
4803+
}
4804+
}
4805+
}
4806+
47844807
do {
47854808
struct MockExecutor: DriverExecutor {
47864809
let resolver: ArgsResolver

0 commit comments

Comments
 (0)