diff --git a/Sources/CSwiftScan/include/swiftscan_header.h b/Sources/CSwiftScan/include/swiftscan_header.h index 36c075baa..2cc8316bd 100644 --- a/Sources/CSwiftScan/include/swiftscan_header.h +++ b/Sources/CSwiftScan/include/swiftscan_header.h @@ -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); @@ -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); diff --git a/Sources/SwiftDriver/Driver/Driver.swift b/Sources/SwiftDriver/Driver/Driver.swift index 07e77a9d8..1aa465f14 100644 --- a/Sources/SwiftDriver/Driver/Driver.swift +++ b/Sources/SwiftDriver/Driver/Driver.swift @@ -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. @@ -2896,6 +2897,7 @@ extension Driver { diagnosticsEngine: DiagnosticsEngine, toolchain: Toolchain, executor: DriverExecutor, + fileSystem: FileSystem, swiftCompilerPrefixArgs: [String]) throws -> Triple { let frontendOverride = try FrontendOverride(&parsedOptions, diagnosticsEngine) diff --git a/Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift b/Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift index 622c7e079..9d1e5681e 100644 --- a/Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift +++ b/Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift @@ -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. @@ -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, @@ -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, @@ -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, @@ -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 diff --git a/Sources/SwiftDriver/Jobs/PrintTargetInfoJob.swift b/Sources/SwiftDriver/Jobs/PrintTargetInfoJob.swift index a5ff3723b..6c8adcf99 100644 --- a/Sources/SwiftDriver/Jobs/PrintTargetInfoJob.swift +++ b/Sources/SwiftDriver/Jobs/PrintTargetInfoJob.swift @@ -10,6 +10,9 @@ // //===----------------------------------------------------------------------===// +import protocol TSCBasic.FileSystem +import class Foundation.JSONDecoder + /// Swift versions are major.minor. struct SwiftVersion { var major: Int @@ -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 { @@ -113,9 +115,10 @@ public struct FrontendTargetInfo: Codable { // Make members of `FrontendTargetInfo.Paths` accessible on `FrontendTargetInfo`. extension FrontendTargetInfo { - @_spi(Testing) public subscript(dynamicMember dynamicMember: KeyPath) -> 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 { @@ -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 + } +} diff --git a/Sources/SwiftDriver/SwiftScan/SwiftScan.swift b/Sources/SwiftDriver/SwiftScan/SwiftScan.swift index f68479469..013fca9b0 100644 --- a/Sources/SwiftDriver/SwiftScan/SwiftScan.swift +++ b/Sources/SwiftDriver/SwiftScan/SwiftScan.swift @@ -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 @@ -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 @@ -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] = [] @@ -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 @@ -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") @@ -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 = diff --git a/Tests/SwiftDriverTests/SwiftDriverTests.swift b/Tests/SwiftDriverTests/SwiftDriverTests.swift index b5018dd58..64e2d924c 100644 --- a/Tests/SwiftDriverTests/SwiftDriverTests.swift +++ b/Tests/SwiftDriverTests/SwiftDriverTests.swift @@ -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] @@ -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