Skip to content

[Explicit Module Builds] Add support for libSwiftScan API to query source import details #1928

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
Jun 12, 2025
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
28 changes: 27 additions & 1 deletion Sources/CSwiftScan/include/swiftscan_header.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
#include <stdint.h>

#define SWIFTSCAN_VERSION_MAJOR 2
#define SWIFTSCAN_VERSION_MINOR 1
#define SWIFTSCAN_VERSION_MINOR 2

//=== Public Scanner Data Types -------------------------------------------===//

Expand All @@ -43,6 +43,7 @@ typedef struct swiftscan_dependency_info_s *swiftscan_dependency_info_t;
typedef struct swiftscan_link_library_info_s *swiftscan_link_library_info_t;
typedef struct swiftscan_dependency_graph_s *swiftscan_dependency_graph_t;
typedef struct swiftscan_import_set_s *swiftscan_import_set_t;
typedef struct swiftscan_import_info_s *swiftscan_import_info_t;
typedef struct swiftscan_diagnostic_info_s *swiftscan_diagnostic_info_t;
typedef struct swiftscan_source_location_s *swiftscan_source_location_t;

Expand All @@ -52,6 +53,13 @@ typedef enum {
SWIFTSCAN_DIAGNOSTIC_SEVERITY_NOTE = 2,
SWIFTSCAN_DIAGNOSTIC_SEVERITY_REMARK = 3
} swiftscan_diagnostic_severity_t;
typedef enum {
SWIFTSCAN_ACCESS_LEVEL_PRIVATE = 0,
SWIFTSCAN_ACCESS_LEVEL_FILEPRIVATE = 1,
SWIFTSCAN_ACCESS_LEVEL_INTERNAL = 2,
SWIFTSCAN_ACCESS_LEVEL_PACKAGE = 3,
SWIFTSCAN_ACCESS_LEVEL_PUBLIC = 4
} swiftscan_access_level_t;
typedef struct {
swiftscan_diagnostic_info_t *diagnostics;
size_t count;
Expand All @@ -64,6 +72,14 @@ typedef struct {
swiftscan_link_library_info_t *link_libraries;
size_t count;
} swiftscan_link_library_set_t;
typedef struct {
swiftscan_import_info_t *imports;
size_t count;
} swiftscan_import_info_set_t;
typedef struct {
swiftscan_source_location_t *source_locations;
size_t count;
} swiftscan_source_location_set_t;

//=== Scanner Invocation Specification ------------------------------------===//

Expand Down Expand Up @@ -104,6 +120,8 @@ typedef struct {
(*swiftscan_module_info_get_direct_dependencies)(swiftscan_dependency_info_t);
swiftscan_link_library_set_t *
(*swiftscan_module_info_get_link_libraries)(swiftscan_dependency_graph_t);
swiftscan_import_info_set_t *
(*swiftscan_module_info_get_imports)(swiftscan_dependency_graph_t);
swiftscan_module_details_t
(*swiftscan_module_info_get_details)(swiftscan_dependency_info_t);

Expand All @@ -115,6 +133,14 @@ typedef struct {
bool
(*swiftscan_link_library_info_get_should_force_load)(swiftscan_link_library_info_t);

//=== Import Details Functions -------------------------------------------===//
swiftscan_source_location_set_t *
(*swiftscan_import_info_get_source_locations)(swiftscan_import_info_t info);
swiftscan_string_ref_t
(*swiftscan_import_info_get_identifier)(swiftscan_import_info_t info);
swiftscan_access_level_t
(*swiftscan_import_info_get_access_level)(swiftscan_import_info_t info);

//=== Dependency Module Info Details Functions ----------------------------===//
swiftscan_dependency_info_kind_t
(*swiftscan_module_detail_get_kind)(swiftscan_module_details_t);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,29 @@ public struct LinkLibraryInfo: Codable, Hashable {
public var shouldForceLoad: Bool
}

/// Source 'import'
public struct ImportInfo : Codable, Hashable {
public enum ImportAccessLevel : Codable, Hashable {
case Private
case FilePrivate
case Internal
case Package
case Public
}

public var importIdentifier: String
public var accessLevel: ImportAccessLevel
public var sourceLocations: [ScannerDiagnosticSourceLocation]

@_spi(Testing) public init(importIdentifier: String,
accessLevel: ImportAccessLevel,
sourceLocations: [ScannerDiagnosticSourceLocation]) {
self.importIdentifier = importIdentifier
self.accessLevel = accessLevel
self.sourceLocations = sourceLocations
}
}

/// Details specific to Swift modules.
public struct SwiftModuleDetails: Codable, Hashable {
/// The module interface from which this module was built, if any.
Expand Down Expand Up @@ -195,6 +218,9 @@ public struct ModuleInfo: Codable, Hashable {
/// The set of libraries that need to be linked
public var linkLibraries: [LinkLibraryInfo]?

/// The set of import details of this module
public var importInfos: [ImportInfo]?

/// Specific details of a particular kind of module.
public var details: Details

Expand All @@ -215,11 +241,13 @@ public struct ModuleInfo: Codable, Hashable {
sourceFiles: [String]?,
directDependencies: [ModuleDependencyId]?,
linkLibraries: [LinkLibraryInfo]?,
importInfos: [ImportInfo]?,
details: Details) {
self.modulePath = modulePath
self.sourceFiles = sourceFiles
self.directDependencies = directDependencies
self.linkLibraries = linkLibraries
self.importInfos = importInfos
self.details = details
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ public class InterModuleDependencyOracle {
return swiftScan.supportsLinkLibraries
}

@_spi(Testing) public func supportsImportInfos() throws -> Bool {
guard let swiftScan = swiftScanLibInstance else {
fatalError("Attempting to query supported scanner API with no scanner instance.")
}
return swiftScan.supportsImportInfos
}

@_spi(Testing) public func supportsSeparateImportOnlyDependencise() throws -> Bool {
guard let swiftScan = swiftScanLibInstance else {
fatalError("Attempting to query supported scanner API with no scanner instance.")
Expand Down
52 changes: 52 additions & 0 deletions Sources/SwiftDriver/SwiftScan/DependencyGraphBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,22 @@ private extension SwiftScan {
}
}

var importInfos: [ImportInfo] = []
if supportsImportInfos {
let importInfoSetRefOrNull = api.swiftscan_module_info_get_imports(moduleInfoRef)
guard let importInfoSetRef = importInfoSetRefOrNull else {
throw DependencyScanningError.missingField("dependency_graph.imports")
}
let importInfoRefArray = Array(UnsafeBufferPointer(start: importInfoSetRef.pointee.imports,
count: Int(importInfoSetRef.pointee.count)))
for importInfoRefOrNull in importInfoRefArray {
guard let importInfoRef = importInfoRefOrNull else {
throw DependencyScanningError.missingField("dependency_set_t.imports[_]")
}
importInfos.append(try constructImportInfo(from: importInfoRef))
}
}

guard let moduleDetailsRef = api.swiftscan_module_info_get_details(moduleInfoRef) else {
throw DependencyScanningError.missingField("modules[\(moduleId)].details")
}
Expand All @@ -113,6 +129,7 @@ private extension SwiftScan {
return (moduleId, ModuleInfo(modulePath: modulePath, sourceFiles: sourceFiles,
directDependencies: directDependencies,
linkLibraries: linkLibraries,
importInfos: importInfos,
details: details))
}

Expand All @@ -122,6 +139,41 @@ private extension SwiftScan {
shouldForceLoad: api.swiftscan_link_library_info_get_should_force_load(linkLibraryInfoRef))
}

func constructImportInfo(from importInfoRef: swiftscan_import_info_t) throws -> ImportInfo {
var sourceLocations : [ScannerDiagnosticSourceLocation] = []

let sourceLocationsRefOrNull = api.swiftscan_import_info_get_source_locations(importInfoRef)
guard let sourceLocationsRef = sourceLocationsRefOrNull else {
throw DependencyScanningError.missingField("import_info.source_locations")
}
let sourceLocationsRefArray = Array(UnsafeBufferPointer(start: sourceLocationsRef.pointee.source_locations,
count: Int(sourceLocationsRef.pointee.count)))
for sourceLocationRefOrNull in sourceLocationsRefArray {
guard let sourceLocationRef = sourceLocationRefOrNull else {
throw DependencyScanningError.missingField("import_info.source_locations[_]")
}
sourceLocations.append(try constructSourceLocation(from: sourceLocationRef))
}

let accessLevel = switch api.swiftscan_import_info_get_access_level(importInfoRef) {
case SWIFTSCAN_ACCESS_LEVEL_PRIVATE: ImportInfo.ImportAccessLevel.Private
case SWIFTSCAN_ACCESS_LEVEL_FILEPRIVATE: ImportInfo.ImportAccessLevel.FilePrivate
case SWIFTSCAN_ACCESS_LEVEL_INTERNAL: ImportInfo.ImportAccessLevel.Internal
case SWIFTSCAN_ACCESS_LEVEL_PACKAGE: ImportInfo.ImportAccessLevel.Package
case SWIFTSCAN_ACCESS_LEVEL_PUBLIC: ImportInfo.ImportAccessLevel.Public
default: ImportInfo.ImportAccessLevel.Public
}

return ImportInfo(importIdentifier: try toSwiftString(api.swiftscan_import_info_get_identifier(importInfoRef)),
accessLevel: accessLevel, sourceLocations: sourceLocations)
}

func constructSourceLocation(from sourceLocationRef: swiftscan_source_location_t) throws -> ScannerDiagnosticSourceLocation {
return ScannerDiagnosticSourceLocation(bufferIdentifier: try toSwiftString(api.swiftscan_source_location_get_buffer_identifier(sourceLocationRef)),
lineNumber: Int(api.swiftscan_source_location_get_line_number(sourceLocationRef)),
columnNumber: Int(api.swiftscan_source_location_get_column_number(sourceLocationRef)))
}

/// From a reference to a binary-format module info details object info returned by libSwiftScan,
/// construct an instance of an `ModuleInfo`.Details as used by the driver.
/// The object returned by libSwiftScan is a union so ensure to execute dependency-specific queries.
Expand Down
22 changes: 21 additions & 1 deletion Sources/SwiftDriver/SwiftScan/SwiftScan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,21 @@ public enum DependencyScanningError: LocalizedError, DiagnosticData, Equatable {
}
}

public struct ScannerDiagnosticSourceLocation : DiagnosticLocation {
public struct ScannerDiagnosticSourceLocation : DiagnosticLocation, Codable, Hashable {
public var description: String {
return "\(bufferIdentifier):\(lineNumber):\(columnNumber)"
}
public let bufferIdentifier: String
public let lineNumber: Int
public let columnNumber: Int

@_spi(Testing) public init(bufferIdentifier: String,
lineNumber: Int,
columnNumber: Int) {
self.bufferIdentifier = bufferIdentifier
self.lineNumber = lineNumber
self.columnNumber = columnNumber
}
}

public struct ScannerDiagnosticPayload {
Expand Down Expand Up @@ -318,6 +326,13 @@ private extension String {
api.swiftscan_link_library_info_get_should_force_load != nil
}

@_spi(Testing) public var supportsImportInfos : Bool {
return api.swiftscan_module_info_get_imports != nil &&
api.swiftscan_import_info_get_source_locations != nil &&
api.swiftscan_import_info_get_identifier != nil &&
api.swiftscan_import_info_get_access_level != nil
}

internal func mapToDriverDiagnosticPayload(_ diagnosticSetRef: UnsafeMutablePointer<swiftscan_diagnostic_set_t>) throws -> [ScannerDiagnosticPayload] {
var result: [ScannerDiagnosticPayload] = []
let diagnosticRefArray = Array(UnsafeBufferPointer(start: diagnosticSetRef.pointee.diagnostics,
Expand Down Expand Up @@ -573,6 +588,11 @@ private extension swiftscan_functions_t {
self.swiftscan_link_library_info_get_is_framework = loadOptional("swiftscan_link_library_info_get_is_framework")
self.swiftscan_link_library_info_get_should_force_load = loadOptional("swiftscan_link_library_info_get_should_force_load")

self.swiftscan_module_info_get_imports = loadOptional("swiftscan_module_info_get_imports")
self.swiftscan_import_info_get_source_locations = loadOptional("swiftscan_import_info_get_source_locations")
self.swiftscan_import_info_get_identifier = loadOptional("swiftscan_import_info_get_identifier")
self.swiftscan_import_info_get_access_level = loadOptional("swiftscan_import_info_get_access_level")

// Swift Overlay Dependencies
self.swiftscan_swift_textual_detail_get_swift_overlay_dependencies =
loadOptional("swiftscan_swift_textual_detail_get_swift_overlay_dependencies")
Expand Down
69 changes: 68 additions & 1 deletion Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,74 @@ final class ExplicitModuleBuildTests: XCTestCase {
}
}

func testExplicitImportDetails() throws {
try withTemporaryDirectory { path in
let (_, _, toolchain, _) = try getDriverArtifactsForScanning()

let main = path.appending(component: "testExplicitLinkLibraries.swift")
try localFileSystem.writeFileContents(main, bytes:
"""
public import C;
internal import E;
private import G;
internal import C;
"""
)

let cHeadersPath: AbsolutePath =
try testInputsPath.appending(component: "ExplicitModuleBuilds")
.appending(component: "CHeaders")
let swiftModuleInterfacesPath: AbsolutePath =
try testInputsPath.appending(component: "ExplicitModuleBuilds")
.appending(component: "Swift")
let sdkArgumentsForTesting = (try? Driver.sdkArgumentsForTesting()) ?? []

let dependencyOracle = InterModuleDependencyOracle()
let scanLibPath = try XCTUnwrap(toolchain.lookupSwiftScanLib())
try dependencyOracle.verifyOrCreateScannerInstance(swiftScanLibPath: scanLibPath)
guard try dependencyOracle.supportsImportInfos() else {
throw XCTSkip("libSwiftScan does not support import details reporting.")
}

let args = ["swiftc",
"-I", cHeadersPath.nativePathString(escaped: true),
"-I", swiftModuleInterfacesPath.nativePathString(escaped: true),
"-explicit-module-build",
"-disable-implicit-concurrency-module-import",
"-disable-implicit-string-processing-module-import",
main.nativePathString(escaped: true)] + sdkArgumentsForTesting
var driver = try Driver(args: args)
let _ = try driver.planBuild()
let dependencyGraph = try XCTUnwrap(driver.explicitDependencyBuildPlanner?.dependencyGraph)
let mainModuleImports = try XCTUnwrap(dependencyGraph.mainModule.importInfos)
XCTAssertEqual(mainModuleImports.count, 5)
XCTAssertTrue(mainModuleImports.contains(ImportInfo(importIdentifier: "Swift",
accessLevel: ImportInfo.ImportAccessLevel.Public,
sourceLocations: [])))
XCTAssertTrue(mainModuleImports.contains(ImportInfo(importIdentifier: "SwiftOnoneSupport",
accessLevel: ImportInfo.ImportAccessLevel.Public,
sourceLocations: [])))
XCTAssertTrue(mainModuleImports.contains(ImportInfo(importIdentifier: "C",
accessLevel: ImportInfo.ImportAccessLevel.Public,
sourceLocations: [ScannerDiagnosticSourceLocation(bufferIdentifier: main.nativePathString(escaped: true),
lineNumber: 1,
columnNumber: 8),
ScannerDiagnosticSourceLocation(bufferIdentifier: main.nativePathString(escaped: true),
lineNumber: 4,
columnNumber: 8)])))
XCTAssertTrue(mainModuleImports.contains(ImportInfo(importIdentifier: "E",
accessLevel: ImportInfo.ImportAccessLevel.Internal,
sourceLocations: [ScannerDiagnosticSourceLocation(bufferIdentifier: main.nativePathString(escaped: true),
lineNumber: 2,
columnNumber: 8)])))
XCTAssertTrue(mainModuleImports.contains(ImportInfo(importIdentifier: "G",
accessLevel: ImportInfo.ImportAccessLevel.Private,
sourceLocations: [ScannerDiagnosticSourceLocation(bufferIdentifier: main.nativePathString(escaped: true),
lineNumber: 3,
columnNumber: 8)])))
}
}

func testExplicitLinkLibraries() throws {
try withTemporaryDirectory { path in
let (_, _, toolchain, _) = try getDriverArtifactsForScanning()
Expand All @@ -521,7 +589,6 @@ final class ExplicitModuleBuildTests: XCTestCase {
.appending(component: "Swift")
let sdkArgumentsForTesting = (try? Driver.sdkArgumentsForTesting()) ?? []

// 2. Run a dependency scan to find the just-built module
let dependencyOracle = InterModuleDependencyOracle()
let scanLibPath = try XCTUnwrap(toolchain.lookupSwiftScanLib())
try dependencyOracle.verifyOrCreateScannerInstance(swiftScanLibPath: scanLibPath)
Expand Down