Skip to content

[Dependency Scanning] Query scanner whether discovered binary Swift modules are frameworks #1240

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

Merged
merged 1 commit into from
Dec 15, 2022
Merged
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
2 changes: 2 additions & 0 deletions Sources/CSwiftScan/include/swiftscan_header.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
11 changes: 10 additions & 1 deletion Sources/SwiftDriver/SwiftScan/DependencyGraphBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions Sources/SwiftDriver/SwiftScan/SwiftScan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand Down Expand Up @@ -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<T>(_ symbol: String) throws -> T {
guard let sym: T = Loader.lookup(symbol: symbol, in: swiftscan) else {
Expand Down
86 changes: 86 additions & 0 deletions Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down