Skip to content

Add capability to query '-print-target-info' in-process using libSwiftScan API #1141

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Sources/CSwiftScan/include/swiftscan_header.h
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ typedef struct {

//=== Cleanup Functions ---------------------------------------------------===//
void
(*swiftscan_string_dispose)(swiftscan_string_ref_t);
void
(*swiftscan_string_set_dispose)(swiftscan_string_set_t *);
void
(*swiftscan_dependency_graph_dispose)(swiftscan_dependency_graph_t);
Expand All @@ -207,6 +209,10 @@ typedef struct {
void
(*swiftscan_scan_invocation_dispose)(swiftscan_scan_invocation_t);

//=== Target Info Functions-------- ---------------------------------------===//
swiftscan_string_ref_t
(*swiftscan_compiler_target_info_query)(swiftscan_scan_invocation_t);

//=== Functionality Query Functions ---------------------------------------===//
swiftscan_string_set_t *
(*swiftscan_compiler_supported_arguments_query)(void);
Expand Down
2 changes: 2 additions & 0 deletions Sources/SwiftDriver/Driver/Driver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,7 @@ public struct Driver {
self.hostTriple =
try Self.computeHostTriple(&self.parsedOptions, diagnosticsEngine: diagnosticEngine,
toolchain: self.toolchain, executor: self.executor,
fileSystem: fileSystem,
swiftCompilerPrefixArgs: self.swiftCompilerPrefixArgs)

// Classify and collect all of the input files.
Expand Down Expand Up @@ -2896,6 +2897,7 @@ extension Driver {
diagnosticsEngine: DiagnosticsEngine,
toolchain: Toolchain,
executor: DriverExecutor,
fileSystem: FileSystem,
swiftCompilerPrefixArgs: [String]) throws -> Triple {

let frontendOverride = try FrontendOverride(&parsedOptions, diagnosticsEngine)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ public extension Driver {
return fallbackToFrontend
}

private func sanitizeCommandForLibScanInvocation(_ command: inout [String]) {
static func sanitizeCommandForLibScanInvocation(_ command: inout [String]) {
// Remove the tool executable to only leave the arguments. When passing the
// command line into libSwiftScan, the library is itself the tool and only
// needs to parse the remaining arguments.
Expand All @@ -257,10 +257,10 @@ public extension Driver {
let isSwiftScanLibAvailable = !(try initSwiftScanLib())
if isSwiftScanLibAvailable {
let cwd = workingDirectory ?? fileSystem.currentWorkingDirectory!
var command = try itemizedJobCommand(of: preScanJob,
useResponseFiles: .disabled,
using: executor.resolver)
sanitizeCommandForLibScanInvocation(&command)
var command = try Self.itemizedJobCommand(of: preScanJob,
useResponseFiles: .disabled,
using: executor.resolver)
Self.sanitizeCommandForLibScanInvocation(&command)
imports =
try interModuleDependencyOracle.getImports(workingDirectory: cwd,
moduleAliases: moduleOutputInfo.aliases,
Expand Down Expand Up @@ -293,10 +293,10 @@ public extension Driver {
let isSwiftScanLibAvailable = !(try initSwiftScanLib())
if isSwiftScanLibAvailable {
let cwd = workingDirectory ?? fileSystem.currentWorkingDirectory!
var command = try itemizedJobCommand(of: scannerJob,
useResponseFiles: .disabled,
using: executor.resolver)
sanitizeCommandForLibScanInvocation(&command)
var command = try Self.itemizedJobCommand(of: scannerJob,
useResponseFiles: .disabled,
using: executor.resolver)
Self.sanitizeCommandForLibScanInvocation(&command)
dependencyGraph =
try interModuleDependencyOracle.getDependencies(workingDirectory: cwd,
moduleAliases: moduleOutputInfo.aliases,
Expand Down Expand Up @@ -339,10 +339,10 @@ public extension Driver {
let isSwiftScanLibAvailable = !(try initSwiftScanLib())
if isSwiftScanLibAvailable {
let cwd = workingDirectory ?? fileSystem.currentWorkingDirectory!
var command = try itemizedJobCommand(of: batchScanningJob,
useResponseFiles: .disabled,
using: executor.resolver)
sanitizeCommandForLibScanInvocation(&command)
var command = try Self.itemizedJobCommand(of: batchScanningJob,
useResponseFiles: .disabled,
using: executor.resolver)
Self.sanitizeCommandForLibScanInvocation(&command)
moduleVersionedGraphMap =
try interModuleDependencyOracle.getBatchDependencies(workingDirectory: cwd,
moduleAliases: moduleOutputInfo.aliases,
Expand Down Expand Up @@ -485,8 +485,8 @@ public extension Driver {
contents)
}

fileprivate func itemizedJobCommand(of job: Job, useResponseFiles: ResponseFileHandling,
using resolver: ArgsResolver) throws -> [String] {
static func itemizedJobCommand(of job: Job, useResponseFiles: ResponseFileHandling,
using resolver: ArgsResolver) throws -> [String] {
let (args, _) = try resolver.resolveArgumentList(for: job,
useResponseFiles: useResponseFiles)
return args
Expand Down
83 changes: 79 additions & 4 deletions Sources/SwiftDriver/Jobs/PrintTargetInfoJob.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
//
//===----------------------------------------------------------------------===//

import protocol TSCBasic.FileSystem
import class Foundation.JSONDecoder

/// Swift versions are major.minor.
struct SwiftVersion {
var major: Int
Expand Down Expand Up @@ -62,7 +65,6 @@ extension SwiftVersion: Codable {
}

/// Describes information about the target as provided by the Swift frontend.
@dynamicMemberLookup
public struct FrontendTargetInfo: Codable {
struct CompatibilityLibrary: Codable {
enum Filter: String, Codable {
Expand Down Expand Up @@ -113,9 +115,10 @@ public struct FrontendTargetInfo: Codable {

// Make members of `FrontendTargetInfo.Paths` accessible on `FrontendTargetInfo`.
extension FrontendTargetInfo {
@_spi(Testing) public subscript<T>(dynamicMember dynamicMember: KeyPath<FrontendTargetInfo.Paths, T>) -> T {
self.paths[keyPath: dynamicMember]
}
public var sdkPath: TextualVirtualPath? { paths.sdkPath }
public var runtimeLibraryPaths: [TextualVirtualPath] { paths.runtimeLibraryPaths }
public var runtimeLibraryImportPaths: [TextualVirtualPath] { paths.runtimeLibraryImportPaths }
public var runtimeResourcePath: TextualVirtualPath { paths.runtimeResourcePath }
}

extension Toolchain {
Expand Down Expand Up @@ -173,3 +176,75 @@ extension Toolchain {
)
}
}

extension Driver {
static func queryTargetInfoInProcess(of toolchain: Toolchain,
fileSystem: FileSystem,
invocationCommand: [String]) throws -> FrontendTargetInfo? {
print("- testPrintTargetInfo - Driver - query target info in-process")
let optionalSwiftScanLibPath = try toolchain.lookupSwiftScanLib()
if let swiftScanLibPath = optionalSwiftScanLibPath,
fileSystem.exists(swiftScanLibPath) {
print("- testPrintTargetInfo - Driver - libSwiftScan\(swiftScanLibPath.pathString)")
let libSwiftScanInstance = try SwiftScan(dylib: swiftScanLibPath)
if libSwiftScanInstance.canQueryTargetInfo(),
libSwiftScanInstance.supportsStringDispose() {
print("- testPrintTargetInfo - Driver - libSwiftScan can query target info")
let targetInfoData = try libSwiftScanInstance.queryTargetInfoJSON(invocationCommand: invocationCommand)
print("- testPrintTargetInfo - Driver - target info data:")
print(targetInfoData)
return try JSONDecoder().decode(FrontendTargetInfo.self, from: targetInfoData)
}
}
return nil
}

static func computeTargetInfo(target: Triple?,
targetVariant: Triple?,
sdkPath: VirtualPath? = nil,
resourceDirPath: VirtualPath? = nil,
runtimeCompatibilityVersion: String? = nil,
requiresInPlaceExecution: Bool = false,
useStaticResourceDir: Bool = false,
swiftCompilerPrefixArgs: [String],
toolchain: Toolchain,
fileSystem: FileSystem,
executor: DriverExecutor) throws -> FrontendTargetInfo {
let frontendTargetInfoJob =
try toolchain.printTargetInfoJob(target: target, targetVariant: targetVariant,
sdkPath: sdkPath, resourceDirPath: resourceDirPath,
runtimeCompatibilityVersion: runtimeCompatibilityVersion,
requiresInPlaceExecution: requiresInPlaceExecution,
useStaticResourceDir: useStaticResourceDir,
swiftCompilerPrefixArgs: swiftCompilerPrefixArgs)
var command = try Self.itemizedJobCommand(of: frontendTargetInfoJob,
useResponseFiles: .disabled,
using: executor.resolver)
Self.sanitizeCommandForLibScanInvocation(&command)
if let targetInfo =
try Self.queryTargetInfoInProcess(of: toolchain, fileSystem: fileSystem,
invocationCommand: command) {
return targetInfo
}

// Fallback: Invoke `swift-frontend -print-target-info` and decode the output
return try executor.execute(
job: frontendTargetInfoJob,
capturingJSONOutputAs: FrontendTargetInfo.self,
forceResponseFiles: false,
recordedInputModificationDates: [:])
}

/// This method exists for testing purposes only
@_spi(Testing) public func verifyBeingAbleToQueryTargetInfoInProcess(invocationCommand: [String]) throws -> Bool {
print("- testPrintTargetInfo - Driver - verifying")
guard let targetInfo = try Self.queryTargetInfoInProcess(of: toolchain,
fileSystem: fileSystem,
invocationCommand: invocationCommand) else {
print("- testPrintTargetInfo - Driver - did not work")
return false
}
print("libSwiftScan Compiler: \(targetInfo.compilerVersion)")
return true
}
}
35 changes: 34 additions & 1 deletion Sources/SwiftDriver/SwiftScan/SwiftScan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

import func Foundation.strdup
import func Foundation.free
import class Foundation.JSONDecoder
import struct Foundation.Data

import protocol TSCBasic.DiagnosticData
import struct TSCBasic.AbsolutePath
Expand Down Expand Up @@ -80,7 +82,7 @@ internal extension swiftscan_diagnostic_severity_t {
}

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

Expand Down Expand Up @@ -269,6 +271,10 @@ internal final class SwiftScan {
api.swiftscan_diagnostic_get_severity != nil &&
api.swiftscan_diagnostics_set_dispose != nil
}

@_spi(Testing) public func supportsStringDispose() -> Bool {
return api.swiftscan_string_dispose != nil
}

@_spi(Testing) public func queryScannerDiagnostics() throws -> [ScannerDiagnosticPayload] {
var result: [ScannerDiagnosticPayload] = []
Expand Down Expand Up @@ -311,6 +317,27 @@ internal final class SwiftScan {
throw DependencyScanningError.argumentQueryFailed
}
}

@_spi(Testing) public func canQueryTargetInfo() -> Bool {
return api.swiftscan_compiler_target_info_query != nil &&
api.swiftscan_string_set_dispose != nil
}

func queryTargetInfoJSON(invocationCommand: [String]) throws -> Data {
// Create and configure the scanner invocation
let invocation = api.swiftscan_scan_invocation_create()
defer { api.swiftscan_scan_invocation_dispose(invocation) }
withArrayOfCStrings(invocationCommand) { invocationStringArray in
api.swiftscan_scan_invocation_set_argv(invocation,
Int32(invocationCommand.count),
invocationStringArray)
}
let targetInfoStringRef = api.swiftscan_compiler_target_info_query(invocation)
defer { api.swiftscan_string_dispose(targetInfoStringRef) }
let targetInfoString = try toSwiftString(targetInfoStringRef)
let targetInfoData = Data(targetInfoString.utf8)
return targetInfoData
}
}

// Used for testing purposes only
Expand Down Expand Up @@ -347,6 +374,10 @@ private extension swiftscan_functions_t {
self.swiftscan_compiler_supported_features_query =
try loadOptional("swiftscan_compiler_supported_features_query")

// Target Info query
self.swiftscan_compiler_target_info_query =
try loadOptional("swiftscan_compiler_target_info_query")

// Dependency scanner serialization/deserialization features
self.swiftscan_scanner_cache_serialize =
try loadOptional("swiftscan_scanner_cache_serialize")
Expand All @@ -370,6 +401,8 @@ private extension swiftscan_functions_t {
try loadOptional("swiftscan_diagnostic_get_severity")
self.swiftscan_diagnostics_set_dispose =
try loadOptional("swiftscan_diagnostics_set_dispose")
self.swiftscan_string_dispose =
try loadOptional("swiftscan_string_dispose")

// isFramework on binary module dependencies
self.swiftscan_swift_binary_detail_get_is_framework =
Expand Down
17 changes: 16 additions & 1 deletion Tests/SwiftDriverTests/SwiftDriverTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4770,7 +4770,7 @@ final class SwiftDriverTests: XCTestCase {

func testPrintTargetInfo() throws {
do {
var driver = try Driver(args: ["swift", "-print-target-info", "-target", "arm64-apple-ios12.0", "-sdk", "bar", "-resource-dir", "baz"])
var driver = try Driver(args: ["swift", "-print-target-info", "-sdk", "bar", "-resource-dir", "baz"])
let plannedJobs = try driver.planBuild()
XCTAssertTrue(plannedJobs.count == 1)
let job = plannedJobs[0]
Expand All @@ -4781,6 +4781,21 @@ final class SwiftDriverTests: XCTestCase {
XCTAssertTrue(job.commandLine.contains(.flag("-resource-dir")))
}

do {
let targetInfoArgs = ["-print-target-info", "-sdk", "bar", "-resource-dir", "baz"]
let driver = try Driver(args: ["swift"] + targetInfoArgs)
let swiftScanLibPath = try XCTUnwrap(driver.toolchain.lookupSwiftScanLib())
if localFileSystem.exists(swiftScanLibPath) {
print("- testPrintTargetInfo - libSwiftScan:\(swiftScanLibPath.pathString)")
let libSwiftScanInstance = try SwiftScan(dylib: swiftScanLibPath)
if libSwiftScanInstance.canQueryTargetInfo() {
print("- testPrintTargetInfo - libSwiftScan can query target info")
XCTAssertTrue(try driver.verifyBeingAbleToQueryTargetInfoInProcess(invocationCommand: targetInfoArgs))
print("- testPrintTargetInfo - libSwiftScan test complete")
}
}
}

do {
struct MockExecutor: DriverExecutor {
let resolver: ArgsResolver
Expand Down