Skip to content

Commit 93837bf

Browse files
committed
Add option to explain a given module dependency
Adds support for '-explain-dependency <MODULE_NAME>', which causes the driver to run a dependency scan, and find all dependency paths that cause the main module to depend, directly or transitively, on a given module dependency.
1 parent 1c3cde8 commit 93837bf

File tree

6 files changed

+142
-5
lines changed

6 files changed

+142
-5
lines changed

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1370,6 +1370,37 @@ extension Driver {
13701370
return
13711371
}
13721372

1373+
// If we're only supposed to explain a dependency on a given module, do so now.
1374+
if let explainModuleName = parsedOptions.getLastArgument(.explainModuleDependency) {
1375+
guard let dependencyPlanner = explicitDependencyBuildPlanner else {
1376+
fatalError("Cannot explain dependency without Explicit Build Planner")
1377+
}
1378+
guard let dependencyPaths = try dependencyPlanner.explainDependency(explainModuleName.asSingle) else {
1379+
diagnosticEngine.emit(.remark("No such module dependency found: '\(explainModuleName.asSingle)'"))
1380+
return
1381+
}
1382+
diagnosticEngine.emit(.remark("Module '\(moduleOutputInfo.name)' depends on '\(explainModuleName.asSingle)'"))
1383+
for path in dependencyPaths {
1384+
var pathString:String = ""
1385+
for (index, moduleId) in path.enumerated() {
1386+
switch moduleId {
1387+
case .swift(let moduleName):
1388+
pathString = pathString + "[" + moduleName + "]"
1389+
case .swiftPrebuiltExternal(let moduleName):
1390+
pathString = pathString + "[" + moduleName + "]"
1391+
case .clang(let moduleName):
1392+
pathString = pathString + "[" + moduleName + "](ObjC)"
1393+
case .swiftPlaceholder(_):
1394+
fatalError("Unexpected unresolved Placeholder module")
1395+
}
1396+
if index < path.count - 1 {
1397+
pathString = pathString + " -> "
1398+
}
1399+
}
1400+
diagnosticEngine.emit(.note(pathString))
1401+
}
1402+
}
1403+
13731404
if parsedOptions.contains(.driverPrintOutputFileMap) {
13741405
if let outputFileMap = self.outputFileMap {
13751406
stderrStream <<< outputFileMap.description

Sources/SwiftDriver/ExplicitModuleBuilds/ExplicitDependencyBuildPlanner.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
124124
}
125125
for moduleId in swiftDependencies {
126126
let moduleInfo = try dependencyGraph.moduleInfo(of: moduleId)
127-
let pcmArgs = try dependencyGraph.swiftModulePCMArgs(of: moduleId)
128127
var inputs: [TypedVirtualPath] = []
129128
let outputs: [TypedVirtualPath] = [
130129
TypedVirtualPath(file: moduleInfo.modulePath.path, type: .swiftModule)
@@ -488,6 +487,12 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
488487
}
489488
}
490489

490+
internal extension ExplicitDependencyBuildPlanner {
491+
func explainDependency(_ dependencyModuleName: String) throws -> [[ModuleDependencyId]]? {
492+
return try dependencyGraph.explainDependency(dependencyModuleName: dependencyModuleName)
493+
}
494+
}
495+
491496
// InterModuleDependencyGraph printing, useful for debugging
492497
internal extension InterModuleDependencyGraph {
493498
func prettyPrintString() throws -> String {

Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/CommonDependencyOperations.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,48 @@ extension InterModuleDependencyGraph {
222222
details: .clang(combinedModuleDetails))
223223
}
224224
}
225+
226+
internal extension InterModuleDependencyGraph {
227+
func explainDependency(dependencyModuleName: String) throws -> [[ModuleDependencyId]]? {
228+
guard modules.contains(where: { $0.key.moduleName == dependencyModuleName }) else { return nil }
229+
var results = [[ModuleDependencyId]]()
230+
try findAllPaths(source: .swift(mainModuleName),
231+
to: dependencyModuleName,
232+
pathSoFar: [.swift(mainModuleName)],
233+
results: &results)
234+
return Array(results)
235+
}
236+
237+
238+
private func findAllPaths(source: ModuleDependencyId,
239+
to moduleName: String,
240+
pathSoFar: [ModuleDependencyId],
241+
results: inout [[ModuleDependencyId]]) throws {
242+
let sourceInfo = try moduleInfo(of: source)
243+
// If the source is our target, we are done
244+
guard source.moduleName != moduleName else {
245+
// If the source is a target Swift module, also check if it
246+
// depends on a corresponding Clang module with the same name.
247+
// If it does, add it to the path as well.
248+
var completePath = pathSoFar
249+
if let dependencies = sourceInfo.directDependencies,
250+
dependencies.contains(.clang(moduleName)) {
251+
completePath.append(.clang(moduleName))
252+
}
253+
results.append(completePath)
254+
return
255+
}
256+
257+
// If no more dependencies, this is a leaf node, we are done
258+
guard let dependencies = sourceInfo.directDependencies else {
259+
return
260+
}
261+
262+
for dependency in dependencies {
263+
try findAllPaths(source: dependency,
264+
to: moduleName,
265+
pathSoFar: pathSoFar + [dependency],
266+
results: &results)
267+
}
268+
}
269+
}

Sources/SwiftDriver/Jobs/Planning.swift

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ extension Driver {
140140
IncrementalCompilationState.InitialStateForPlanning?)
141141
throws -> InterModuleDependencyGraph? {
142142
let interModuleDependencyGraph: InterModuleDependencyGraph?
143-
if parsedOptions.contains(.driverExplicitModuleBuild) {
143+
if parsedOptions.contains(.driverExplicitModuleBuild) ||
144+
parsedOptions.contains(.explainModuleDependency) {
144145
interModuleDependencyGraph =
145146
try initialIncrementalState?.maybeUpToDatePriorInterModuleDependencyGraph ??
146147
gatherModuleDependencies()
@@ -195,13 +196,15 @@ extension Driver {
195196
dependencyGraph: InterModuleDependencyGraph?,
196197
addJob: (Job) -> Void)
197198
throws {
198-
// If asked, add jobs to precompile module dependencies
199-
guard parsedOptions.contains(.driverExplicitModuleBuild) else { return }
200199
guard let resolvedDependencyGraph = dependencyGraph else {
201-
fatalError("Attempting to plan explicit dependency build without a dependency graph")
200+
return
202201
}
203202
let modulePrebuildJobs =
204203
try generateExplicitModuleDependenciesJobs(dependencyGraph: resolvedDependencyGraph)
204+
// If asked, add jobs to precompile module dependencies. Otherwise exit.
205+
// We may have a dependency graph but not be required to add pre-compile jobs to the build plan,
206+
// for example when `-explain-dependency` is being used.
207+
guard parsedOptions.contains(.driverExplicitModuleBuild) else { return }
205208
modulePrebuildJobs.forEach(addJob)
206209
}
207210

Sources/SwiftOptions/Options.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
//===----------------------------------------------------------------------===//
1919

2020
extension Option {
21+
// ACTODO before merging: Add in the main repo and re-sync here
22+
public static let explainModuleDependency: Option = Option("-explain-module-dependency", .separate,
23+
attributes: [.helpHidden, .doesNotAffectIncrementalBuild],
24+
helpText: "Show a dependency chain to a specified module",
25+
group: .internalDebug)
2126
public static let INPUT: Option = Option("<input>", .input, attributes: [.argumentIsPath])
2227
public static let HASHHASHHASH: Option = Option("-###", .flag, alias: Option.driverPrintJobs)
2328
public static let abi: Option = Option("-abi", .flag, attributes: [.noDriver], helpText: "Dumping ABI interface")
@@ -791,6 +796,7 @@ extension Option {
791796
extension Option {
792797
public static var allOptions: [Option] {
793798
return [
799+
Option.explainModuleDependency,
794800
Option.INPUT,
795801
Option.HASHHASHHASH,
796802
Option.abi,

Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1613,6 +1613,53 @@ final class ExplicitModuleBuildTests: XCTestCase {
16131613
XCTAssertEqual(moduleMap[1].sourceInfoPath!.path.description, "B.swiftsourceinfo")
16141614
XCTAssertEqual(moduleMap[1].isFramework, false)
16151615
}
1616+
1617+
1618+
func testTraceDependency() throws {
1619+
try withTemporaryDirectory { path in
1620+
try localFileSystem.changeCurrentWorkingDirectory(to: path)
1621+
let moduleCachePath = path.appending(component: "ModuleCache")
1622+
try localFileSystem.createDirectory(moduleCachePath)
1623+
let main = path.appending(component: "testTraceDependency.swift")
1624+
try localFileSystem.writeFileContents(main) {
1625+
$0 <<< "import C;"
1626+
$0 <<< "import E;"
1627+
$0 <<< "import G;"
1628+
}
1629+
1630+
let cHeadersPath: AbsolutePath =
1631+
testInputsPath.appending(component: "ExplicitModuleBuilds")
1632+
.appending(component: "CHeaders")
1633+
let swiftModuleInterfacesPath: AbsolutePath =
1634+
testInputsPath.appending(component: "ExplicitModuleBuilds")
1635+
.appending(component: "Swift")
1636+
let sdkArgumentsForTesting = (try? Driver.sdkArgumentsForTesting()) ?? []
1637+
var driver = try Driver(args: ["swiftc",
1638+
"-I", cHeadersPath.nativePathString(escaped: true),
1639+
"-I", swiftModuleInterfacesPath.nativePathString(escaped: true),
1640+
"-explicit-module-build", "-v",
1641+
"-module-cache-path", moduleCachePath.nativePathString(escaped: true),
1642+
"-working-directory", path.nativePathString(escaped: true),
1643+
"-explain-module-dependency", "A",
1644+
main.nativePathString(escaped: true)] + sdkArgumentsForTesting,
1645+
env: ProcessEnv.vars)
1646+
let jobs = try driver.planBuild()
1647+
try driver.run(jobs: jobs)
1648+
XCTAssertTrue(!driver.diagnosticEngine.diagnostics.isEmpty)
1649+
XCTAssertTrue(driver.diagnosticEngine.diagnostics.contains { $0.behavior == .remark &&
1650+
$0.message.text == "Module 'testTraceDependency' depends on 'A'"})
1651+
1652+
for diag in driver.diagnosticEngine.diagnostics {
1653+
print(diag.behavior)
1654+
print(diag.message)
1655+
}
1656+
XCTAssertTrue(driver.diagnosticEngine.diagnostics.contains { $0.behavior == .note &&
1657+
$0.message.text == "[testTraceDependency] -> [A] -> [A](ObjC)"})
1658+
XCTAssertTrue(driver.diagnosticEngine.diagnostics.contains { $0.behavior == .note &&
1659+
$0.message.text == "[testTraceDependency] -> [C](ObjC) -> [B](ObjC) -> [A](ObjC)"})
1660+
}
1661+
}
1662+
16161663
// We only care about prebuilt modules in macOS.
16171664
#if os(macOS)
16181665
func testPrebuiltModuleGenerationJobs() throws {

0 commit comments

Comments
 (0)