Skip to content

Commit af4afcb

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 af4afcb

File tree

12 files changed

+201
-22
lines changed

12 files changed

+201
-22
lines changed

Sources/SwiftDriver/Jobs/Planning.swift

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

499+
500+
public mutating func generatePrebuitModuleGenerationJobs(prebuiltModuleDir: VirtualPath) throws -> [Job] {
501+
guard let sdkPath = frontendTargetInfo.sdkPath?.path else {
502+
// cannot proceed without SDK path
503+
return []
504+
}
505+
// Run the dependency scanner and update the dependency oracle with the results
506+
let dependencyGraph = try gatherModuleDependencies()
507+
var jobs: [Job] = []
508+
var inputPathsCache: [String: [TypedVirtualPath]] = [:]
509+
510+
// Get the .swiftinterface path of the module in SDK
511+
func getInputPaths(_ module: String) throws -> [TypedVirtualPath] {
512+
if let cachedPaths = inputPathsCache[module] {
513+
return cachedPaths
514+
}
515+
func cache(_ results: [TypedVirtualPath]) -> [TypedVirtualPath] {
516+
inputPathsCache[module] = results
517+
return results
518+
}
519+
guard let info = dependencyGraph.modules[.swift(module)] else {
520+
return cache([])
521+
}
522+
if case .swift(let details) = info.details {
523+
if let interfacePath = details.moduleInterfacePath {
524+
var results: [TypedVirtualPath] = []
525+
let currentPath = TypedVirtualPath(file: interfacePath.path, type: .swiftInterface)
526+
try localFileSystem.getDirectoryContents(currentPath.file.parentDirectory.absolutePath!).forEach {
527+
let rawPath = try VirtualPath.init(path: $0)
528+
if rawPath.extension == "swiftinterface" {
529+
results.append(TypedVirtualPath(file: rawPath.intern(), type: .swiftInterface))
530+
}
531+
}
532+
return cache(results)
533+
}
534+
}
535+
return cache([])
536+
}
537+
538+
// Get the .swiftmodule path of the prebuilt module in the toolchain
539+
func getOutputPath(_ module: String, _ inputPath: TypedVirtualPath) -> TypedVirtualPath {
540+
let baseName = inputPath.file.basenameWithoutExt
541+
return TypedVirtualPath(file: prebuiltModuleDir
542+
.appending(component: module + ".swiftmodule")
543+
.appending(component: baseName + ".swiftmodule").intern(),
544+
type: .swiftModule)
545+
}
546+
547+
func getDependenciesPaths(_ module: String, _ inputPath: TypedVirtualPath) throws -> [TypedVirtualPath] {
548+
var results: [TypedVirtualPath] = []
549+
let info = dependencyGraph.modules[.swift(module)]!
550+
guard let dependencies = info.directDependencies else {
551+
return results
552+
}
553+
for dep in dependencies {
554+
let moduleName = dep.moduleName
555+
if let input = try getInputPaths(moduleName).first(where: { $0.file.basename == inputPath.file.basename }) {
556+
results.append(getOutputPath(moduleName, input))
557+
}
558+
}
559+
return results
560+
}
561+
562+
let moduleInfo = dependencyGraph.mainModule
563+
if let dependencies = moduleInfo.directDependencies {
564+
for dep in dependencies {
565+
let moduleName = dep.moduleName
566+
let inputPaths = try getInputPaths(moduleName)
567+
for inputPath in inputPaths {
568+
let outputPath = getOutputPath(moduleName, inputPath)
569+
var commandLine: [Job.ArgTemplate] = []
570+
commandLine.appendFlag(.compileModuleFromInterface)
571+
commandLine.appendFlag(.sdk)
572+
commandLine.append(.path(VirtualPath.lookup(sdkPath)))
573+
commandLine.appendFlag(.prebuiltModuleCachePath)
574+
commandLine.appendPath(prebuiltModuleDir)
575+
commandLine.appendFlag(.moduleName)
576+
commandLine.appendFlag(moduleName)
577+
commandLine.appendFlag(.o)
578+
commandLine.appendPath(outputPath.file)
579+
commandLine.appendPath(inputPath.file)
580+
jobs.append(Job(
581+
moduleName: moduleName,
582+
kind: .compile,
583+
tool: .absolute(try toolchain.getToolPath(.swiftCompiler)),
584+
commandLine: commandLine,
585+
inputs: try getDependenciesPaths(moduleName, inputPath),
586+
primaryInputs: [],
587+
outputs: [outputPath]
588+
))
589+
}
590+
}
591+
}
592+
593+
return jobs
594+
}
595+
499596
private mutating func gatherModuleDependencies()
500597
throws -> InterModuleDependencyGraph {
501598
var dependencyGraph = try performDependencyScan()
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() { }

Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -567,39 +567,38 @@ final class ExplicitModuleBuildTests: XCTestCase {
567567
#endif
568568
}
569569

570-
/// Test the libSwiftScan dependency scanning.
571-
func testDependencyScanning() throws {
572-
// Just instantiating to get at the toolchain path
573-
let driver = try Driver(args: ["swiftc", "-experimental-explicit-module-build",
574-
"-module-name", "testDependencyScanning",
575-
"test.swift"])
570+
func getStdlibShimsPaths(_ driver: Driver) throws -> (AbsolutePath, AbsolutePath) {
576571
let toolchainRootPath: AbsolutePath = try driver.toolchain.getToolPath(.swiftCompiler)
577572
.parentDirectory // bin
578573
.parentDirectory // toolchain root
579-
580-
let stdLibPath: AbsolutePath
581-
let shimsPath: AbsolutePath
582-
// On Darwin, use the SDK's stdlib, to make sure the test is more likely to actually
583-
// find one.
584574
if driver.targetTriple.isDarwin {
585-
let executor = try SwiftDriverExecutor(diagnosticsEngine: DiagnosticsEngine(handlers: [Driver.stderrDiagnosticsHandler]),
575+
let executor = try SwiftDriverExecutor(diagnosticsEngine: DiagnosticsEngine(handlers: [Driver.stderrDiagnosticsHandler]),
586576
processSet: ProcessSet(),
587577
fileSystem: localFileSystem,
588578
env: ProcessEnv.vars)
589579
let sdkPath = try executor.checkNonZeroExit(
590580
args: "xcrun", "-sdk", "macosx", "--show-sdk-path").spm_chomp()
591-
stdLibPath = AbsolutePath(sdkPath).appending(component: "usr")
592-
.appending(component: "lib")
593-
.appending(component: "swift")
594-
shimsPath = stdLibPath.appending(component: "shims")
595-
} else {
596-
stdLibPath = toolchainRootPath.appending(component: "lib")
581+
let stdLibPath = AbsolutePath(sdkPath).appending(component: "usr")
582+
.appending(component: "lib")
597583
.appending(component: "swift")
598-
.appending(component: driver.targetTriple.osNameUnversioned)
599-
shimsPath = toolchainRootPath.appending(component: "lib")
600-
.appending(component: "swift")
601-
.appending(component: "shims")
584+
return (stdLibPath, stdLibPath.appending(component: "shims"))
585+
} else {
586+
return (toolchainRootPath.appending(component: "lib")
587+
.appending(component: "swift")
588+
.appending(component: driver.targetTriple.osNameUnversioned),
589+
toolchainRootPath.appending(component: "lib")
590+
.appending(component: "swift")
591+
.appending(component: "shims"))
602592
}
593+
}
594+
595+
/// Test the libSwiftScan dependency scanning.
596+
func testDependencyScanning() throws {
597+
// Just instantiating to get at the toolchain path
598+
let driver = try Driver(args: ["swiftc", "-experimental-explicit-module-build",
599+
"-module-name", "testDependencyScanning",
600+
"test.swift"])
601+
let (stdLibPath, shimsPath) = try getStdlibShimsPaths(driver)
603602

604603
XCTAssertTrue(localFileSystem.exists(stdLibPath),
605604
"expected Swift StdLib at: \(stdLibPath.description)")
@@ -732,4 +731,35 @@ final class ExplicitModuleBuildTests: XCTestCase {
732731
XCTAssertEqual(moduleMap[1].sourceInfoPath!.path.description, "B.swiftsourceinfo")
733732
XCTAssertEqual(moduleMap[1].isFramework, false)
734733
}
734+
735+
func testPrebuiltModuleGenerationJobs() throws {
736+
try withTemporaryDirectory { path in
737+
let main = path.appending(component: "testPrebuiltModuleGenerationJobs.swift")
738+
try localFileSystem.writeFileContents(main) {
739+
$0 <<< "import A\n"
740+
$0 <<< "import E\n"
741+
$0 <<< "import F\n"
742+
$0 <<< "import G\n"
743+
$0 <<< "import H\n"
744+
}
745+
let packageRootPath = URL(fileURLWithPath: #file).pathComponents
746+
.prefix(while: { $0 != "Tests" }).joined(separator: "/").dropFirst()
747+
let testInputsPath = packageRootPath + "/TestInputs"
748+
let mockSDKPath : String = testInputsPath + "/ExplicitModuleBuilds/Swift/mock-sdk"
749+
let dummyDriver = try Driver(args:["swiftc"])
750+
let (stdLibPath, shimsPath) = try getStdlibShimsPaths(dummyDriver)
751+
752+
var driver = try Driver(args: ["swiftc", main.pathString,
753+
"-sdk", mockSDKPath,
754+
"-I", stdLibPath.description,
755+
"-I", shimsPath.description
756+
])
757+
let jobs = try driver.generatePrebuitModuleGenerationJobs(prebuiltModuleDir: VirtualPath(path: "/tmp/"))
758+
.filter { $0.moduleName != "Swift" && $0.moduleName != "SwiftOnoneSupport"}
759+
XCTAssertTrue(jobs.count == 10)
760+
XCTAssertTrue(jobs.allSatisfy {$0.outputs.count == 1})
761+
XCTAssertTrue(jobs.allSatisfy {$0.kind == .compile})
762+
XCTAssertTrue(jobs.allSatisfy {$0.commandLine.contains(.flag("-compile-module-from-interface"))})
763+
}
764+
}
735765
}

0 commit comments

Comments
 (0)