Skip to content

Commit e5d6e9f

Browse files
committed
[Dependency Scanning] Add ability to use the scanner's import-prescan mode
An ability to do a simple import pre-scan was added to the dependency scanner in swiftlang/swift#34028, with a corresponding libSwiftScan API added later. This PR adds the ability to invoke this import pre-scan from the driver.
1 parent faf9128 commit e5d6e9f

File tree

6 files changed

+190
-42
lines changed

6 files changed

+190
-42
lines changed

Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/InterModuleDependencyGraph.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,3 +266,7 @@ public struct InterModuleDependencyGraph: Codable {
266266
/// Information about the main module.
267267
public var mainModule: ModuleInfo { modules[.swift(mainModuleName)]! }
268268
}
269+
270+
public struct InterModuleDependencyImports: Codable {
271+
public var imports: [String]
272+
}

Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/InterModuleDependencyOracle.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@ public class InterModuleDependencyOracle {
5252
}
5353
}
5454

55+
@_spi(Testing) public func getImports(workingDirectory: AbsolutePath,
56+
commandLine: [String])
57+
throws -> InterModuleDependencyImports {
58+
precondition(hasScannerInstance)
59+
return try queue.sync {
60+
return try swiftScanLibInstance!.preScanImports(workingDirectory: workingDirectory,
61+
invocationCommand: commandLine)
62+
}
63+
}
64+
5565
/// Given a specified toolchain path, locate and instantiate an instance of the SwiftScan library
5666
/// Returns True if a library instance exists (either verified or newly-created).
5767
@_spi(Testing) public func verifyOrCreateScannerInstance(fileSystem: FileSystem,

Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift

Lines changed: 91 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ extension Diagnostic.Message {
1919
}
2020
}
2121

22-
internal extension Driver {
22+
public extension Driver {
2323
/// Precompute the dependencies for a given Swift compilation, producing a
2424
/// dependency graph including all Swift and C module files and
2525
/// source files.
26-
mutating func dependencyScanningJob() throws -> Job {
26+
mutating private func dependencyScanningJob() throws -> Job {
2727
let (inputs, commandLine) = try dependencyScannerInvocationCommand()
2828

2929
// Construct the scanning job.
@@ -40,7 +40,7 @@ internal extension Driver {
4040

4141
/// Generate a full command-line invocation to be used for the dependency scanning action
4242
/// on the target module.
43-
mutating func dependencyScannerInvocationCommand()
43+
mutating private func dependencyScannerInvocationCommand()
4444
throws -> ([TypedVirtualPath],[Job.ArgTemplate]) {
4545
// Aggregate the fast dependency scanner arguments
4646
var inputs: [TypedVirtualPath] = []
@@ -66,7 +66,7 @@ internal extension Driver {
6666
}
6767

6868
/// Serialize a map of placeholder (external) dependencies for the dependency scanner.
69-
func serializeExternalDependencyArtifacts(externalBuildArtifacts: ExternalBuildArtifacts)
69+
private func serializeExternalDependencyArtifacts(externalBuildArtifacts: ExternalBuildArtifacts)
7070
throws -> VirtualPath {
7171
let (externalTargetModulePathMap, externalModuleInfoMap) = externalBuildArtifacts
7272
var placeholderArtifacts: [SwiftModuleArtifactInfo] = []
@@ -94,11 +94,8 @@ internal extension Driver {
9494
contents)
9595
}
9696

97-
mutating func performDependencyScan() throws -> InterModuleDependencyGraph {
98-
let scannerJob = try dependencyScanningJob()
99-
let forceResponseFiles = parsedOptions.hasArgument(.driverForceResponseFiles)
100-
let dependencyGraph: InterModuleDependencyGraph
101-
97+
/// Returns false if the lib is available and ready to use
98+
private func initSwiftScanLib() throws -> Bool {
10299
// If `-nonlib-dependency-scanner` was specified or the libSwiftScan library cannot be found,
103100
// attempt to fallback to using `swift-frontend -scan-dependencies` invocations for dependency
104101
// scanning.
@@ -110,20 +107,62 @@ internal extension Driver {
110107
fallbackToFrontend = true
111108
diagnosticEngine.emit(.warn_scanner_frontend_fallback())
112109
}
110+
return fallbackToFrontend
111+
}
112+
113+
private func sanitizeCommandForLibScanInvocation(_ command: inout [String]) {
114+
// Remove the tool executable to only leave the arguments. When passing the
115+
// command line into libSwiftScan, the library is itself the tool and only
116+
// needs to parse the remaining arguments.
117+
command.removeFirst()
118+
// We generate full swiftc -frontend -scan-dependencies invocations in order to also be
119+
// able to launch them as standalone jobs. Frontend's argument parser won't recognize
120+
// -frontend when passed directly.
121+
if command.first == "-frontend" {
122+
command.removeFirst()
123+
}
124+
}
113125

114-
if (!fallbackToFrontend) {
126+
mutating func performImportPrescan() throws -> InterModuleDependencyImports {
127+
let preScanJob = try importPreScanningJob()
128+
let forceResponseFiles = parsedOptions.hasArgument(.driverForceResponseFiles)
129+
let imports: InterModuleDependencyImports
130+
131+
let isSwiftScanLibAvailable = !(try initSwiftScanLib())
132+
if isSwiftScanLibAvailable {
133+
let cwd = workingDirectory ?? fileSystem.currentWorkingDirectory!
134+
var command = try itemizedJobCommand(of: preScanJob,
135+
forceResponseFiles: forceResponseFiles,
136+
using: executor.resolver)
137+
sanitizeCommandForLibScanInvocation(&command)
138+
imports =
139+
try interModuleDependencyOracle.getImports(workingDirectory: cwd,
140+
commandLine: command)
141+
142+
} else {
143+
// Fallback to legacy invocation of the dependency scanner with
144+
// `swift-frontend -scan-dependencies -import-prescan`
145+
imports =
146+
try self.executor.execute(job: preScanJob,
147+
capturingJSONOutputAs: InterModuleDependencyImports.self,
148+
forceResponseFiles: forceResponseFiles,
149+
recordedInputModificationDates: recordedInputModificationDates)
150+
}
151+
return imports
152+
}
153+
154+
mutating internal func performDependencyScan() throws -> InterModuleDependencyGraph {
155+
let scannerJob = try dependencyScanningJob()
156+
let forceResponseFiles = parsedOptions.hasArgument(.driverForceResponseFiles)
157+
let dependencyGraph: InterModuleDependencyGraph
158+
159+
let isSwiftScanLibAvailable = !(try initSwiftScanLib())
160+
if isSwiftScanLibAvailable {
115161
let cwd = workingDirectory ?? fileSystem.currentWorkingDirectory!
116162
var command = try itemizedJobCommand(of: scannerJob,
117163
forceResponseFiles: forceResponseFiles,
118164
using: executor.resolver)
119-
// Remove the tool executable to only leave the arguments
120-
command.removeFirst()
121-
// We generate full swiftc -frontend -scan-dependencies invocations in order to also be
122-
// able to launch them as standalone jobs. Frontend's argument parser won't recognize
123-
// -frontend when passed directly.
124-
if command.first == "-frontend" {
125-
command.removeFirst()
126-
}
165+
sanitizeCommandForLibScanInvocation(&command)
127166
dependencyGraph =
128167
try interModuleDependencyOracle.getDependencies(workingDirectory: cwd,
129168
commandLine: command)
@@ -139,37 +178,19 @@ internal extension Driver {
139178
return dependencyGraph
140179
}
141180

142-
mutating func performBatchDependencyScan(moduleInfos: [BatchScanModuleInfo])
181+
mutating internal func performBatchDependencyScan(moduleInfos: [BatchScanModuleInfo])
143182
throws -> [ModuleDependencyId: [InterModuleDependencyGraph]] {
144183
let batchScanningJob = try batchDependencyScanningJob(for: moduleInfos)
145184
let forceResponseFiles = parsedOptions.hasArgument(.driverForceResponseFiles)
146-
147-
// If `-nonlib-dependency-scanner` was specified or the libSwiftScan library cannot be found,
148-
// attempt to fallback to using `swift-frontend -scan-dependencies` invocations for dependency
149-
// scanning.
150-
var fallbackToFrontend = parsedOptions.hasArgument(.driverScanDependenciesNonLib)
151-
let scanLibPath = try Self.getScanLibPath(of: toolchain, hostTriple: hostTriple, env: env)
152-
if try interModuleDependencyOracle
153-
.verifyOrCreateScannerInstance(fileSystem: fileSystem,
154-
swiftScanLibPath: scanLibPath) == false {
155-
fallbackToFrontend = true
156-
diagnosticEngine.emit(.warn_scanner_frontend_fallback())
157-
}
158-
159185
let moduleVersionedGraphMap: [ModuleDependencyId: [InterModuleDependencyGraph]]
160-
if (!fallbackToFrontend) {
186+
187+
let isSwiftScanLibAvailable = !(try initSwiftScanLib())
188+
if isSwiftScanLibAvailable {
161189
let cwd = workingDirectory ?? fileSystem.currentWorkingDirectory!
162190
var command = try itemizedJobCommand(of: batchScanningJob,
163191
forceResponseFiles: forceResponseFiles,
164192
using: executor.resolver)
165-
// Remove the tool executable to only leave the arguments
166-
command.removeFirst()
167-
// We generate full swiftc -frontend -scan-dependencies invocations in order to also be
168-
// able to launch them as standalone jobs. Frontend's argument parser won't recognize
169-
// -frontend when passed directly.
170-
if command.first == "-frontend" {
171-
command.removeFirst()
172-
}
193+
sanitizeCommandForLibScanInvocation(&command)
173194
moduleVersionedGraphMap =
174195
try interModuleDependencyOracle.getBatchDependencies(workingDirectory: cwd,
175196
commandLine: command,
@@ -226,8 +247,36 @@ internal extension Driver {
226247
return moduleVersionedGraphMap
227248
}
228249

250+
/// Precompute the set of module names as imported by the current module
251+
mutating private func importPreScanningJob() throws -> Job {
252+
// Aggregate the fast dependency scanner arguments
253+
var inputs: [TypedVirtualPath] = []
254+
var commandLine: [Job.ArgTemplate] = swiftCompilerPrefixArgs.map { Job.ArgTemplate.flag($0) }
255+
commandLine.appendFlag("-frontend")
256+
commandLine.appendFlag("-scan-dependencies")
257+
commandLine.appendFlag("-import-prescan")
258+
try addCommonFrontendOptions(commandLine: &commandLine, inputs: &inputs,
259+
bridgingHeaderHandling: .precompiled,
260+
moduleDependencyGraphUse: .dependencyScan)
261+
// FIXME: MSVC runtime flags
262+
263+
// Pass on the input files
264+
commandLine.append(contentsOf: inputFiles.map { .path($0.file) })
265+
266+
// Construct the scanning job.
267+
return Job(moduleName: moduleOutputInfo.name,
268+
kind: .scanDependencies,
269+
tool: VirtualPath.absolute(try toolchain.getToolPath(.swiftCompiler)),
270+
commandLine: commandLine,
271+
displayInputs: inputs,
272+
inputs: inputs,
273+
primaryInputs: [],
274+
outputs: [TypedVirtualPath(file: .standardOutput, type: .jsonDependencies)],
275+
supportsResponseFiles: true)
276+
}
277+
229278
/// Precompute the dependencies for a given collection of modules using swift frontend's batch scanning mode
230-
mutating func batchDependencyScanningJob(for moduleInfos: [BatchScanModuleInfo]) throws -> Job {
279+
mutating private func batchDependencyScanningJob(for moduleInfos: [BatchScanModuleInfo]) throws -> Job {
231280
var inputs: [TypedVirtualPath] = []
232281

233282
// Aggregate the fast dependency scanner arguments

Sources/SwiftDriver/SwiftScan/DependencyGraphBuilder.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@ internal extension SwiftScan {
4242
return resultGraph
4343
}
4444

45+
/// From a reference to a binary-format set of module imports return by libSwiftScan pre-scan query,
46+
/// construct an instance of an `InterModuleDependencyImports` set
47+
func constructImportSet(from importSetRef: swiftscan_import_set_t) throws
48+
-> InterModuleDependencyImports {
49+
guard let importsRef = api.swiftscan_import_set_get_imports(importSetRef) else {
50+
throw DependencyScanningError.missingField("import_set.imports")
51+
}
52+
return InterModuleDependencyImports(imports: try toSwiftStringArray(importsRef.pointee))
53+
}
54+
4555
/// From a reference to a binary-format dependency graph collection returned by libSwiftScan batch scan query,
4656
/// corresponding to the specified batch scan input (`BatchScanModuleInfo`), construct instances of
4757
/// `InterModuleDependencyGraph` for each result.

Sources/SwiftDriver/SwiftScan/SwiftScan.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,33 @@ internal final class SwiftScan {
9292
dylib.leak()
9393
}
9494

95+
func preScanImports(workingDirectory: AbsolutePath,
96+
invocationCommand: [String]) throws -> InterModuleDependencyImports {
97+
// Create and configure the scanner invocation
98+
let invocation = api.swiftscan_scan_invocation_create()
99+
defer { api.swiftscan_scan_invocation_dispose(invocation) }
100+
api.swiftscan_scan_invocation_set_working_directory(invocation,
101+
workingDirectory
102+
.description
103+
.cString(using: String.Encoding.utf8))
104+
withArrayOfCStrings(invocationCommand) { invocationStringArray in
105+
api.swiftscan_scan_invocation_set_argv(invocation,
106+
Int32(invocationCommand.count),
107+
invocationStringArray)
108+
}
109+
110+
let importSetRefOrNull = api.swiftscan_import_set_create(scanner, invocation)
111+
guard let importSetRef = importSetRefOrNull else {
112+
throw DependencyScanningError.dependencyScanFailed
113+
}
114+
115+
let importSet = try constructImportSet(from: importSetRef)
116+
// Free the memory allocated for the in-memory representation of the import set
117+
// returned by the scanner, now that we have translated it.
118+
api.swiftscan_import_set_dispose(importSetRef)
119+
return importSet
120+
}
121+
95122
func scanDependencies(workingDirectory: AbsolutePath,
96123
invocationCommand: [String]) throws -> InterModuleDependencyGraph {
97124
// Create and configure the scanner invocation

Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,54 @@ final class ExplicitModuleBuildTests: XCTestCase {
658658
return (stdLibPath, shimsPath, driver.toolchain, driver.hostTriple)
659659
}
660660

661+
/// Test the libSwiftScan dependency scanning (import-prescan).
662+
func testDependencyImportPrescan() throws {
663+
let (stdLibPath, shimsPath, toolchain, hostTriple) = try getDriverArtifactsForScanning()
664+
665+
// The dependency oracle wraps an instance of libSwiftScan and ensures thread safety across
666+
// queries.
667+
let dependencyOracle = InterModuleDependencyOracle()
668+
let scanLibPath = try Driver.getScanLibPath(of: toolchain,
669+
hostTriple: hostTriple,
670+
env: ProcessEnv.vars)
671+
guard try dependencyOracle
672+
.verifyOrCreateScannerInstance(fileSystem: localFileSystem,
673+
swiftScanLibPath: scanLibPath) else {
674+
XCTFail("Dependency scanner library not found")
675+
return
676+
}
677+
678+
// Create a simple test case.
679+
try withTemporaryDirectory { path in
680+
let main = path.appending(component: "testDependencyScanning.swift")
681+
try localFileSystem.writeFileContents(main) {
682+
$0 <<< "import C;"
683+
$0 <<< "import E;"
684+
$0 <<< "import G;"
685+
}
686+
let packageRootPath = URL(fileURLWithPath: #file).pathComponents
687+
.prefix(while: { $0 != "Tests" }).joined(separator: "/").dropFirst()
688+
let testInputsPath = packageRootPath + "/TestInputs"
689+
let cHeadersPath : String = testInputsPath + "/ExplicitModuleBuilds/CHeaders"
690+
let swiftModuleInterfacesPath : String = testInputsPath + "/ExplicitModuleBuilds/Swift"
691+
let scannerCommand = ["-scan-dependencies",
692+
"-import-prescan",
693+
"-I", cHeadersPath,
694+
"-I", swiftModuleInterfacesPath,
695+
"-I", stdLibPath.description,
696+
"-I", shimsPath.description,
697+
main.pathString]
698+
699+
let imports =
700+
try! dependencyOracle.getImports(workingDirectory: path,
701+
commandLine: scannerCommand)
702+
let expectedImports = ["C", "E", "G", "Swift", "SwiftOnoneSupport"]
703+
// Dependnig on how recent the platform we are running on, the Concurrency module may or may not be present.
704+
let expectedImports2 = ["C", "E", "G", "Swift", "SwiftOnoneSupport", "_Concurrency"]
705+
XCTAssertTrue(Set(imports.imports) == Set(expectedImports) || Set(imports.imports) == Set(expectedImports2))
706+
}
707+
}
708+
661709
/// Test the libSwiftScan dependency scanning.
662710
func testDependencyScanning() throws {
663711
let (stdLibPath, shimsPath, toolchain, hostTriple) = try getDriverArtifactsForScanning()

0 commit comments

Comments
 (0)