diff --git a/Sources/CSwiftScan/include/swiftscan_header.h b/Sources/CSwiftScan/include/swiftscan_header.h index ab29816ae..36c075baa 100644 --- a/Sources/CSwiftScan/include/swiftscan_header.h +++ b/Sources/CSwiftScan/include/swiftscan_header.h @@ -130,6 +130,8 @@ typedef struct { (*swiftscan_swift_binary_detail_get_module_doc_path)(swiftscan_module_details_t); swiftscan_string_ref_t (*swiftscan_swift_binary_detail_get_module_source_info_path)(swiftscan_module_details_t); + bool + (*swiftscan_swift_binary_detail_get_is_framework)(swiftscan_module_details_t); //=== Swift Placeholder Module Details query APIs -------------------------===// swiftscan_string_ref_t diff --git a/Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/InterModuleDependencyGraph.swift b/Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/InterModuleDependencyGraph.swift index cf4876b6f..937565540 100644 --- a/Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/InterModuleDependencyGraph.swift +++ b/Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/InterModuleDependencyGraph.swift @@ -141,7 +141,7 @@ public struct SwiftPrebuiltExternalModuleDetails: Codable { public init(compiledModulePath: TextualVirtualPath, moduleDocPath: TextualVirtualPath? = nil, moduleSourceInfoPath: TextualVirtualPath? = nil, - isFramework: Bool = false) throws { + isFramework: Bool) throws { self.compiledModulePath = compiledModulePath self.moduleDocPath = moduleDocPath self.moduleSourceInfoPath = moduleSourceInfoPath diff --git a/Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/InterModuleDependencyOracle.swift b/Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/InterModuleDependencyOracle.swift index 39cb02ece..cb7bfa5f5 100644 --- a/Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/InterModuleDependencyOracle.swift +++ b/Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/InterModuleDependencyOracle.swift @@ -128,6 +128,13 @@ public class InterModuleDependencyOracle { swiftScan.resetScannerCache() } } + + @_spi(Testing) public func supportsBinaryFrameworkDependencies() throws -> Bool { + guard let swiftScan = swiftScanLibInstance else { + fatalError("Attempting to query supported scanner API with no scanner instance.") + } + return swiftScan.hasBinarySwiftModuleIsFramework + } @_spi(Testing) public func supportsScannerDiagnostics() throws -> Bool { guard let swiftScan = swiftScanLibInstance else { diff --git a/Sources/SwiftDriver/SwiftScan/DependencyGraphBuilder.swift b/Sources/SwiftDriver/SwiftScan/DependencyGraphBuilder.swift index 88078111e..1d79992d6 100644 --- a/Sources/SwiftDriver/SwiftScan/DependencyGraphBuilder.swift +++ b/Sources/SwiftDriver/SwiftScan/DependencyGraphBuilder.swift @@ -200,9 +200,18 @@ private extension SwiftScan { let moduleSourceInfoPath = try getOptionalPathDetail(from: moduleDetailsRef, using: api.swiftscan_swift_binary_detail_get_module_source_info_path) + + let isFramework: Bool + if hasBinarySwiftModuleIsFramework { + isFramework = api.swiftscan_swift_binary_detail_get_is_framework(moduleDetailsRef) + } else { + isFramework = false + } + return try SwiftPrebuiltExternalModuleDetails(compiledModulePath: compiledModulePath, moduleDocPath: moduleDocPath, - moduleSourceInfoPath: moduleSourceInfoPath) + moduleSourceInfoPath: moduleSourceInfoPath, + isFramework: isFramework) } /// Construct a `SwiftPlaceholderModuleDetails` from a `swiftscan_module_details_t` reference diff --git a/Sources/SwiftDriver/SwiftScan/SwiftScan.swift b/Sources/SwiftDriver/SwiftScan/SwiftScan.swift index 04425c20f..fec28c048 100644 --- a/Sources/SwiftDriver/SwiftScan/SwiftScan.swift +++ b/Sources/SwiftDriver/SwiftScan/SwiftScan.swift @@ -234,6 +234,10 @@ internal final class SwiftScan { return resultGraphMap } + @_spi(Testing) public var hasBinarySwiftModuleIsFramework : Bool { + api.swiftscan_swift_binary_detail_get_is_framework != nil + } + @_spi(Testing) public var canLoadStoreScannerCache : Bool { api.swiftscan_scanner_cache_load != nil && api.swiftscan_scanner_cache_serialize != nil && @@ -370,6 +374,10 @@ private extension swiftscan_functions_t { self.swiftscan_diagnostics_set_dispose = try loadOptional("swiftscan_diagnostics_set_dispose") + // isFramework on binary module dependencies + self.swiftscan_swift_binary_detail_get_is_framework = + try loadOptional("swiftscan_swift_binary_detail_get_is_framework") + // MARK: Required Methods func loadRequired(_ symbol: String) throws -> T { guard let sym: T = Loader.lookup(symbol: symbol, in: swiftscan) else { diff --git a/Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift b/Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift index 56efeffc0..eb1b92287 100644 --- a/Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift +++ b/Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift @@ -789,6 +789,92 @@ final class ExplicitModuleBuildTests: XCTestCase { } } + func testBinaryFrameworkDependencyScan() throws { + try withTemporaryDirectory { path in + let (stdLibPath, shimsPath, toolchain, hostTriple) = try getDriverArtifactsForScanning() + let moduleCachePath = path.appending(component: "ModuleCache") + + // Setup module to be used as dependency + try localFileSystem.createDirectory(moduleCachePath) + let frameworksPath = path.appending(component: "Frameworks") + let frameworkModuleDir = frameworksPath.appending(component: "Foo.framework") + .appending(component: "Modules") + .appending(component: "Foo.swiftmodule") + let frameworkModulePath = + frameworkModuleDir.appending(component: hostTriple.archName + ".swiftmodule") + try localFileSystem.createDirectory(frameworkModuleDir, recursive: true) + let fooSourcePath = path.appending(component: "Foo.swift") + try localFileSystem.writeFileContents(fooSourcePath) { + $0 <<< "public func foo() {}" + } + + // Setup our main test module + let mainSourcePath = path.appending(component: "Foo.swift") + try localFileSystem.writeFileContents(mainSourcePath) { + $0 <<< "import Foo" + } + + // 1. Build Foo module + let sdkArgumentsForTesting = (try? Driver.sdkArgumentsForTesting()) ?? [] + var driverFoo = try Driver(args: ["swiftc", + "-module-cache-path", moduleCachePath.nativePathString(escaped: true), + "-module-name", "Foo", + "-emit-module", + "-emit-module-path", + frameworkModulePath.nativePathString(escaped: true), + "-working-directory", + path.nativePathString(escaped: true), + fooSourcePath.nativePathString(escaped: true)] + sdkArgumentsForTesting, + env: ProcessEnv.vars) + let jobs = try driverFoo.planBuild() + try driverFoo.run(jobs: jobs) + XCTAssertFalse(driverFoo.diagnosticEngine.hasErrors) + + // 2. Run a dependency scan to find the just-built module + let dependencyOracle = InterModuleDependencyOracle() + let scanLibPath = try Driver.getScanLibPath(of: toolchain, + hostTriple: hostTriple, + env: ProcessEnv.vars) + guard try dependencyOracle + .verifyOrCreateScannerInstance(fileSystem: localFileSystem, + swiftScanLibPath: scanLibPath) else { + XCTFail("Dependency scanner library not found") + return + } + guard try dependencyOracle.supportsBinaryFrameworkDependencies() else { + throw XCTSkip("libSwiftScan does not support framework binary dependency reporting.") + } + + var driver = try Driver(args: ["swiftc", + "-I", stdLibPath.nativePathString(escaped: true), + "-I", shimsPath.nativePathString(escaped: true), + "-F", frameworksPath.nativePathString(escaped: true), + "-import-objc-header", + "-explicit-module-build", + "-module-name", "main", + "-working-directory", path.nativePathString(escaped: true), + mainSourcePath.nativePathString(escaped: true)] + sdkArgumentsForTesting, + env: ProcessEnv.vars) + let resolver = try ArgsResolver(fileSystem: localFileSystem) + var scannerCommand = try driver.dependencyScannerInvocationCommand().1.map { try resolver.resolve($0) } + if scannerCommand.first == "-frontend" { + scannerCommand.removeFirst() + } + let dependencyGraph = + try! dependencyOracle.getDependencies(workingDirectory: path, + commandLine: scannerCommand) + + let fooDependencyInfo = try XCTUnwrap(dependencyGraph.modules[.swiftPrebuiltExternal("Foo")]) + guard case .swiftPrebuiltExternal(let fooDetails) = fooDependencyInfo.details else { + XCTFail("Foo dependency module does not have Swift details field") + return + } + + // Ensure the dependency has been reported as a framework + XCTAssertTrue(fooDetails.isFramework) + } + } + func getStdlibShimsPaths(_ driver: Driver) throws -> (AbsolutePath, AbsolutePath) { let toolchainRootPath: AbsolutePath = try driver.toolchain.getToolPath(.swiftCompiler) .parentDirectory // bin