Skip to content

Commit 15ff8a3

Browse files
authored
Merge pull request #1268 from artemcm/TargetInfoDefinitive
Use libSwiftScan API to query compilation "Target Info"
2 parents 2a9add5 + dc63fac commit 15ff8a3

File tree

7 files changed

+205
-36
lines changed

7 files changed

+205
-36
lines changed

Sources/CSwiftScan/include/swiftscan_header.h

Lines changed: 7 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,11 @@ 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_v2)(swiftscan_scan_invocation_t,
215+
const char *);
216+
210217
//=== Functionality Query Functions ---------------------------------------===//
211218
swiftscan_string_set_t *
212219
(*swiftscan_compiler_supported_arguments_query)(void);

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 22 additions & 20 deletions
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,18 +2899,18 @@ 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)
29022907
frontendOverride.setUpForTargetInfo(toolchain)
29032908
defer { frontendOverride.setUpForCompilation(toolchain) }
2904-
return try executor.execute(
2905-
job: toolchain.printTargetInfoJob(target: nil, targetVariant: nil,
2906-
swiftCompilerPrefixArgs:
2907-
frontendOverride.prefixArgsForTargetInfo),
2908-
capturingJSONOutputAs: FrontendTargetInfo.self,
2909-
forceResponseFiles: false,
2910-
recordedInputModificationDates: [:]).target.triple
2909+
return try Self.computeTargetInfo(target: nil, targetVariant: nil,
2910+
swiftCompilerPrefixArgs: frontendOverride.prefixArgsForTargetInfo,
2911+
toolchain: toolchain, fileSystem: fileSystem,
2912+
workingDirectory: workingDirectory,
2913+
executor: executor).target.triple
29112914
}
29122915

29132916
static func computeToolchain(
@@ -2918,6 +2921,7 @@ extension Driver {
29182921
executor: DriverExecutor,
29192922
fileSystem: FileSystem,
29202923
useStaticResourceDir: Bool,
2924+
workingDirectory: AbsolutePath?,
29212925
compilerExecutableDir: AbsolutePath?
29222926
) throws -> (Toolchain, FrontendTargetInfo, [String]) {
29232927
let explicitTarget = (parsedOptions.getLastArgument(.target)?.asSingle)
@@ -2961,18 +2965,16 @@ extension Driver {
29612965

29622966
// Query the frontend for target information.
29632967
do {
2964-
var info: FrontendTargetInfo = try executor.execute(
2965-
job: toolchain.printTargetInfoJob(
2966-
target: explicitTarget, targetVariant: explicitTargetVariant,
2967-
sdkPath: sdkPath, resourceDirPath: resourceDirPath,
2968-
runtimeCompatibilityVersion:
2969-
parsedOptions.getLastArgument(.runtimeCompatibilityVersion)?.asSingle,
2970-
useStaticResourceDir: useStaticResourceDir,
2971-
swiftCompilerPrefixArgs: frontendOverride.prefixArgsForTargetInfo
2972-
),
2973-
capturingJSONOutputAs: FrontendTargetInfo.self,
2974-
forceResponseFiles: false,
2975-
recordedInputModificationDates: [:])
2968+
var info: FrontendTargetInfo =
2969+
try Self.computeTargetInfo(target: explicitTarget, targetVariant: explicitTargetVariant,
2970+
sdkPath: sdkPath, resourceDirPath: resourceDirPath,
2971+
runtimeCompatibilityVersion:
2972+
parsedOptions.getLastArgument(.runtimeCompatibilityVersion)?.asSingle,
2973+
useStaticResourceDir: useStaticResourceDir,
2974+
swiftCompilerPrefixArgs: frontendOverride.prefixArgsForTargetInfo,
2975+
toolchain: toolchain, fileSystem: fileSystem,
2976+
workingDirectory: workingDirectory,
2977+
executor: executor)
29762978

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

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: 86 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,85 @@ 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 = try workingDirectory ?? fileSystem.currentWorkingDirectory ?? fileSystem.tempDirectory
192+
let compilerExecutablePath = try toolchain.resolvedTool(.swiftCompiler).path
193+
let targetInfoData =
194+
try libSwiftScanInstance.queryTargetInfoJSON(workingDirectory: cwd,
195+
compilerExecutablePath: compilerExecutablePath,
196+
invocationCommand: invocationCommand)
197+
do {
198+
return try JSONDecoder().decode(FrontendTargetInfo.self, from: targetInfoData)
199+
} catch let decodingError as DecodingError {
200+
let stringToDecode = String(data: targetInfoData, encoding: .utf8)
201+
let errorDesc: String
202+
switch decodingError {
203+
case let .typeMismatch(type, context):
204+
errorDesc = "type mismatch: \(type), path: \(context.codingPath)"
205+
case let .valueNotFound(type, context):
206+
errorDesc = "value missing: \(type), path: \(context.codingPath)"
207+
case let .keyNotFound(key, context):
208+
errorDesc = "key missing: \(key), path: \(context.codingPath)"
209+
case let .dataCorrupted(context):
210+
errorDesc = "data corrupted at path: \(context.codingPath)"
211+
@unknown default:
212+
errorDesc = "unknown decoding error"
213+
}
214+
throw Error.unableToDecodeFrontendTargetInfo(
215+
stringToDecode,
216+
invocationCommand,
217+
errorDesc)
218+
}
219+
}
220+
}
221+
return nil
222+
}
223+
224+
static func computeTargetInfo(target: Triple?,
225+
targetVariant: Triple?,
226+
sdkPath: VirtualPath? = nil,
227+
resourceDirPath: VirtualPath? = nil,
228+
runtimeCompatibilityVersion: String? = nil,
229+
requiresInPlaceExecution: Bool = false,
230+
useStaticResourceDir: Bool = false,
231+
swiftCompilerPrefixArgs: [String],
232+
toolchain: Toolchain,
233+
fileSystem: FileSystem,
234+
workingDirectory: AbsolutePath?,
235+
executor: DriverExecutor) throws -> FrontendTargetInfo {
236+
let frontendTargetInfoJob =
237+
try toolchain.printTargetInfoJob(target: target, targetVariant: targetVariant,
238+
sdkPath: sdkPath, resourceDirPath: resourceDirPath,
239+
runtimeCompatibilityVersion: runtimeCompatibilityVersion,
240+
requiresInPlaceExecution: requiresInPlaceExecution,
241+
useStaticResourceDir: useStaticResourceDir,
242+
swiftCompilerPrefixArgs: swiftCompilerPrefixArgs)
243+
var command = try Self.itemizedJobCommand(of: frontendTargetInfoJob,
244+
useResponseFiles: .disabled,
245+
using: executor.resolver)
246+
Self.sanitizeCommandForLibScanInvocation(&command)
247+
if let targetInfo =
248+
try Self.queryTargetInfoInProcess(of: toolchain, fileSystem: fileSystem,
249+
workingDirectory: workingDirectory,
250+
invocationCommand: command) {
251+
return targetInfo
252+
}
253+
254+
// Fallback: Invoke `swift-frontend -print-target-info` and decode the output
255+
return try executor.execute(
256+
job: frontendTargetInfoJob,
257+
capturingJSONOutputAs: FrontendTargetInfo.self,
258+
forceResponseFiles: false,
259+
recordedInputModificationDates: [:])
260+
}
261+
}

Sources/SwiftDriver/SwiftScan/SwiftScan.swift

Lines changed: 44 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,37 @@ 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_v2 != nil &&
323+
api.swiftscan_string_set_dispose != nil
324+
}
325+
326+
func queryTargetInfoJSON(workingDirectory: AbsolutePath,
327+
compilerExecutablePath: AbsolutePath,
328+
invocationCommand: [String]) throws -> Data {
329+
// Create and configure the scanner invocation
330+
let invocation = api.swiftscan_scan_invocation_create()
331+
defer { api.swiftscan_scan_invocation_dispose(invocation) }
332+
api.swiftscan_scan_invocation_set_working_directory(invocation,
333+
workingDirectory
334+
.description
335+
.cString(using: String.Encoding.utf8))
336+
withArrayOfCStrings(invocationCommand) { invocationStringArray in
337+
api.swiftscan_scan_invocation_set_argv(invocation,
338+
Int32(invocationCommand.count),
339+
invocationStringArray)
340+
}
341+
342+
let targetInfoString: String = try compilerExecutablePath.description.withCString { cstring in
343+
let targetInfoStringRef = api.swiftscan_compiler_target_info_query_v2(invocation, cstring)
344+
defer { api.swiftscan_string_dispose(targetInfoStringRef) }
345+
return try toSwiftString(targetInfoStringRef)
346+
}
347+
348+
let targetInfoData = Data(targetInfoString.utf8)
349+
return targetInfoData
350+
}
314351
}
315352

316353
// Used for testing purposes only
@@ -347,6 +384,10 @@ private extension swiftscan_functions_t {
347384
self.swiftscan_compiler_supported_features_query =
348385
try loadOptional("swiftscan_compiler_supported_features_query")
349386

387+
// Target Info query
388+
self.swiftscan_compiler_target_info_query_v2 =
389+
try loadOptional("swiftscan_compiler_target_info_query_v2")
390+
350391
// Dependency scanner serialization/deserialization features
351392
self.swiftscan_scanner_cache_serialize =
352393
try loadOptional("swiftscan_scanner_cache_serialize")
@@ -370,6 +411,8 @@ private extension swiftscan_functions_t {
370411
try loadOptional("swiftscan_diagnostic_get_severity")
371412
self.swiftscan_diagnostics_set_dispose =
372413
try loadOptional("swiftscan_diagnostics_set_dispose")
414+
self.swiftscan_string_dispose =
415+
try loadOptional("swiftscan_string_dispose")
373416

374417
// isFramework on binary module dependencies
375418
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 {}

0 commit comments

Comments
 (0)