Skip to content

Commit 816d5b9

Browse files
committed
Teach Driver to generate swift-frontend invocations to build prebuilt module cache
We should be able to generate prebuilt module cache by reusing the infrastructure libSwiftDriver already provides. More specifically, we could (1) write a dummy Swift file imports every frameworks/libraries in an SDK, (2) use module dependency scanner in the driver to generate module building commands, and (3) use MultiJobExecutor to execute those commands topologically. This PR is for (2).
1 parent 1430a1f commit 816d5b9

File tree

14 files changed

+333
-22
lines changed

14 files changed

+333
-22
lines changed

Package.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ let package = Package(
2121
.executable(
2222
name: "swift-help",
2323
targets: ["swift-help"]),
24+
.executable(
25+
name: "swift-build-sdk-interfaces",
26+
targets: ["swift-build-sdk-interfaces"]),
2427
.library(
2528
name: "SwiftDriver",
2629
targets: ["SwiftDriver"]),
@@ -93,6 +96,11 @@ let package = Package(
9396
name: "swift-help",
9497
dependencies: ["SwiftOptions", "ArgumentParser", "SwiftToolsSupport-auto"]),
9598

99+
/// The help executable.
100+
.target(
101+
name: "swift-build-sdk-interfaces",
102+
dependencies: ["SwiftDriver", "SwiftDriverExecution"]),
103+
96104
/// The `makeOptions` utility (for importing option definitions).
97105
.target(
98106
name: "makeOptions",

Sources/SwiftDriver/Jobs/Planning.swift

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,91 @@ extension Driver {
496496
return try explicitDependencyBuildPlanner!.generateExplicitModuleDependenciesBuildJobs()
497497
}
498498

499+
500+
public mutating func generatePrebuitModuleGenerationJobs(_ inputMap: [String: [TypedVirtualPath]],
501+
_ prebuiltModuleDir: AbsolutePath) throws -> [Job] {
502+
guard let sdkPath = frontendTargetInfo.sdkPath?.path else {
503+
// cannot proceed without SDK path
504+
return []
505+
}
506+
// Run the dependency scanner and update the dependency oracle with the results
507+
let dependencyGraph = try gatherModuleDependencies()
508+
var jobs: [Job] = []
509+
510+
// Create directories for each Swift module
511+
try inputMap.forEach {
512+
try localFileSystem.createDirectory(prebuiltModuleDir
513+
.appending(RelativePath($0.key + ".swiftmodule")))
514+
}
515+
516+
let outputMap: [String: [TypedVirtualPath]] =
517+
Dictionary.init(uniqueKeysWithValues: inputMap.map { key, value in
518+
let outputPaths: [TypedVirtualPath] = value.map {
519+
let path = prebuiltModuleDir.appending(RelativePath(key + ".swiftmodule"))
520+
.appending(RelativePath($0.file.basenameWithoutExt + ".swiftmodule"))
521+
return TypedVirtualPath(file: VirtualPath.absolute(path).intern(),
522+
type: .swiftModule)
523+
}
524+
return (key, outputPaths)
525+
})
526+
527+
func getDependenciesPaths(_ module: String) throws -> [TypedVirtualPath] {
528+
var results: [TypedVirtualPath] = []
529+
let info = dependencyGraph.modules[.swift(module)]!
530+
guard let dependencies = info.directDependencies else {
531+
return results
532+
}
533+
534+
for dep in dependencies {
535+
if case let .swift(moduleName) = dep {
536+
if let outputs = outputMap[moduleName] {
537+
results.append(contentsOf: outputs)
538+
}
539+
}
540+
}
541+
return results
542+
}
543+
544+
let moduleInfo = dependencyGraph.mainModule
545+
if let dependencies = moduleInfo.directDependencies {
546+
for dep in dependencies {
547+
let moduleName = dep.moduleName
548+
if let inputPaths = inputMap[moduleName] {
549+
let outputPaths = outputMap[moduleName]!
550+
assert(inputPaths.count == outputPaths.count)
551+
assert(!inputPaths.isEmpty)
552+
let dependencies = try getDependenciesPaths(moduleName)
553+
for i in 0..<inputPaths.count {
554+
let inputPath = inputPaths[i]
555+
let outputPath = outputPaths[i]
556+
var commandLine: [Job.ArgTemplate] = []
557+
commandLine.appendFlag(.compileModuleFromInterface)
558+
commandLine.appendFlag(.sdk)
559+
commandLine.append(.path(VirtualPath.lookup(sdkPath)))
560+
commandLine.appendFlag(.prebuiltModuleCachePath)
561+
commandLine.appendPath(prebuiltModuleDir)
562+
commandLine.appendFlag(.moduleName)
563+
commandLine.appendFlag(moduleName)
564+
commandLine.appendFlag(.o)
565+
commandLine.appendPath(outputPath.file)
566+
commandLine.appendPath(inputPath.file)
567+
jobs.append(Job(
568+
moduleName: moduleName,
569+
kind: .compile,
570+
tool: .absolute(try toolchain.getToolPath(.swiftCompiler)),
571+
commandLine: commandLine,
572+
inputs: dependencies,
573+
primaryInputs: [],
574+
outputs: [outputPath]
575+
))
576+
}
577+
}
578+
}
579+
}
580+
581+
return jobs
582+
}
583+
499584
private mutating func gatherModuleDependencies()
500585
throws -> InterModuleDependencyGraph {
501586
var dependencyGraph = try performDependencyScan()
@@ -551,6 +636,72 @@ extension Driver {
551636

552637
}
553638

639+
public struct SDKPrebuiltModuleInputsCollector {
640+
let sdkPath: AbsolutePath
641+
let nonFrameworkDirs = [RelativePath("usr/lib/swift"),
642+
RelativePath("System/iOSSupport/usr/lib/swift")]
643+
let frameworkDirs = [RelativePath("System/Library/Frameworks"),
644+
RelativePath("System/iOSSupport/System/Library/Frameworks")]
645+
public init(_ sdkPath: AbsolutePath) {
646+
self.sdkPath = sdkPath
647+
}
648+
649+
public func collectSwiftInterfaceMap() throws -> [String: [TypedVirtualPath]] {
650+
var results: [String: [TypedVirtualPath]] = [:]
651+
652+
func updateResults(_ dir: AbsolutePath) throws {
653+
if !localFileSystem.exists(dir) {
654+
return
655+
}
656+
let moduleName = dir.basenameWithoutExt
657+
if results[moduleName] == nil {
658+
results[moduleName] = []
659+
}
660+
try localFileSystem.getDirectoryContents(dir).forEach {
661+
let currentFile = try VirtualPath(path: $0)
662+
if currentFile.extension == "swiftinterface" {
663+
let interfacePath = TypedVirtualPath(file: currentFile.intern(),
664+
type: .swiftInterface)
665+
if !results[moduleName]!.contains(interfacePath) {
666+
results[moduleName]!.append(interfacePath)
667+
}
668+
}
669+
}
670+
}
671+
for dir in frameworkDirs {
672+
let frameDir = AbsolutePath(sdkPath, dir)
673+
if !localFileSystem.exists(frameDir) {
674+
continue
675+
}
676+
try localFileSystem.getDirectoryContents(frameDir).forEach {
677+
let frameworkPath = try VirtualPath(path: $0)
678+
if frameworkPath.extension != "framework" {
679+
return
680+
}
681+
let moduleName = frameworkPath.basenameWithoutExt
682+
let swiftModulePath = frameworkPath
683+
.appending(component: "Modules")
684+
.appending(component: moduleName + ".swiftmodule").relativePath!
685+
try updateResults(AbsolutePath(frameDir, swiftModulePath))
686+
}
687+
}
688+
for dir in nonFrameworkDirs {
689+
let swiftModuleDir = AbsolutePath(sdkPath, dir)
690+
if !localFileSystem.exists(swiftModuleDir) {
691+
continue
692+
}
693+
try localFileSystem.getDirectoryContents(swiftModuleDir).forEach {
694+
let swiftModulePath = try VirtualPath(path: $0).relativePath!
695+
if swiftModulePath.extension != "swiftmodule" {
696+
return
697+
}
698+
try updateResults(AbsolutePath(swiftModuleDir, swiftModulePath))
699+
}
700+
}
701+
return results
702+
}
703+
}
704+
554705
/// MARK: Planning
555706
extension Driver {
556707
/// Create a job if needed for simple requests that can be immediately
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import SwiftDriverExecution
2+
import SwiftDriver
3+
import TSCLibc
4+
import TSCBasic
5+
import TSCUtility
6+
7+
let diagnosticsEngine = DiagnosticsEngine(handlers: [Driver.stderrDiagnosticsHandler])
8+
9+
guard let sdkPath = ProcessEnv.vars["SDKROOT"] else {
10+
diagnosticsEngine.emit(.error("need to set SDKROOT"))
11+
exit(1)
12+
}
13+
guard let swiftcPath = ProcessEnv.vars["SWIFT_EXEC"] else {
14+
diagnosticsEngine.emit(.error("need to set SWIFT_EXEC"))
15+
exit(1)
16+
}
17+
class PrebuitGenDelegate: JobExecutionDelegate {
18+
func jobStarted(job: Job, arguments: [String], pid: Int) {
19+
diagnosticsEngine.emit(.remark("\(job.moduleName) started"))
20+
}
21+
22+
func jobFinished(job: Job, result: ProcessResult, pid: Int) {
23+
diagnosticsEngine.emit(.remark("\(job.moduleName) finished"))
24+
}
25+
26+
func jobSkipped(job: Job) {}
27+
}
28+
do {
29+
let processSet = ProcessSet()
30+
let collector = try SDKPrebuiltModuleInputsCollector(VirtualPath(path: sdkPath).absolutePath!)
31+
let inputMap = try collector.collectSwiftInterfaceMap()
32+
let allModules = inputMap.keys
33+
try withTemporaryFile(suffix: ".swift") {
34+
let tempPath = $0.path
35+
try localFileSystem.writeFileContents(tempPath, body: {
36+
for module in allModules {
37+
if module == "MediaPlayer" || module == "PhotosUI" {
38+
continue
39+
}
40+
$0 <<< "import " <<< module <<< "\n"
41+
}
42+
})
43+
print(try localFileSystem.readFileContents(tempPath))
44+
let executor = try SwiftDriverExecutor(diagnosticsEngine: diagnosticsEngine,
45+
processSet: processSet,
46+
fileSystem: localFileSystem,
47+
env: ProcessEnv.vars)
48+
var driver = try Driver(args: ["swiftc", tempPath.description, "-sdk", sdkPath],
49+
diagnosticsEngine: diagnosticsEngine,
50+
executor: executor,
51+
compilerExecutableDir: VirtualPath.init(path: swiftcPath).parentDirectory.absolutePath!)
52+
let jobs = try driver.generatePrebuitModuleGenerationJobs(inputMap,
53+
VirtualPath(path: "/tmp/t").absolutePath!)
54+
print(jobs.count)
55+
try executor.execute(workload: .all(jobs), delegate: PrebuitGenDelegate(), numParallelJobs: 10)
56+
}
57+
} catch {
58+
print("error: \(error)")
59+
exit(1)
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// swift-interface-format-version: 1.0
2+
// swift-module-flags: -module-name A
3+
import Swift
4+
public func FuncA() { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// swift-interface-format-version: 1.0
2+
// swift-module-flags: -module-name A
3+
import Swift
4+
public func FuncA() { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// swift-interface-format-version: 1.0
2+
// swift-module-flags: -module-name E
3+
import Swift
4+
public func FuncE() { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// swift-interface-format-version: 1.0
2+
// swift-module-flags: -module-name E
3+
import Swift
4+
public func FuncE() { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// swift-interface-format-version: 1.0
2+
// swift-module-flags: -module-name F
3+
import Swift
4+
import A
5+
public func FuncF() { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// swift-interface-format-version: 1.0
2+
// swift-module-flags: -module-name F
3+
import Swift
4+
import A
5+
public func FuncF() { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// swift-interface-format-version: 1.0
2+
// swift-module-flags: -module-name G
3+
import Swift
4+
import E
5+
public func FuncG() { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// swift-interface-format-version: 1.0
2+
// swift-module-flags: -module-name G
3+
import Swift
4+
import E
5+
public func FuncG() { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// swift-interface-format-version: 1.0
2+
// swift-module-flags: -module-name H
3+
import Swift
4+
import A
5+
import E
6+
import F
7+
import G
8+
public func FuncH() { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// swift-interface-format-version: 1.0
2+
// swift-module-flags: -module-name H
3+
import Swift
4+
import A
5+
import E
6+
import F
7+
import G
8+
public func FuncH() { }

0 commit comments

Comments
 (0)