Skip to content

Commit 1bc2534

Browse files
committed
[Explicit Module Builds] Simplify '-explain-module-dependency' behavior and introduce '-explain-module-dependency-detailed'
The former will now simply print the first discovered path to the specified dependency module. While the latter will preserve prior behavior of finding *all possible paths* to the specified module
1 parent bc04f85 commit 1bc2534

File tree

6 files changed

+172
-68
lines changed

6 files changed

+172
-68
lines changed

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1474,6 +1474,38 @@ extension Diagnostic.Message {
14741474
}
14751475
}
14761476

1477+
extension Driver {
1478+
func explainModuleDependency(_ explainModuleName: String, allPaths: Bool) throws {
1479+
guard let dependencyPlanner = explicitDependencyBuildPlanner else {
1480+
fatalError("Cannot explain dependency without Explicit Build Planner")
1481+
}
1482+
guard let dependencyPaths = try dependencyPlanner.explainDependency(explainModuleName, allPaths: allPaths) else {
1483+
diagnosticEngine.emit(.remark("No such module dependency found: '\(explainModuleName)'"))
1484+
return
1485+
}
1486+
diagnosticEngine.emit(.remark("Module '\(moduleOutputInfo.name)' depends on '\(explainModuleName)'"))
1487+
for path in dependencyPaths {
1488+
var pathString:String = ""
1489+
for (index, moduleId) in path.enumerated() {
1490+
switch moduleId {
1491+
case .swift(let moduleName):
1492+
pathString = pathString + "[" + moduleName + "]"
1493+
case .swiftPrebuiltExternal(let moduleName):
1494+
pathString = pathString + "[" + moduleName + "]"
1495+
case .clang(let moduleName):
1496+
pathString = pathString + "[" + moduleName + "](ObjC)"
1497+
case .swiftPlaceholder(_):
1498+
fatalError("Unexpected unresolved Placeholder module")
1499+
}
1500+
if index < path.count - 1 {
1501+
pathString = pathString + " -> "
1502+
}
1503+
}
1504+
diagnosticEngine.emit(.note(pathString))
1505+
}
1506+
}
1507+
}
1508+
14771509
extension Driver {
14781510
/// Determine the driver kind based on the command-line arguments, consuming the arguments
14791511
/// conveying this information.
@@ -1520,34 +1552,10 @@ extension Driver {
15201552
}
15211553

15221554
// If we're only supposed to explain a dependency on a given module, do so now.
1523-
if let explainModuleName = parsedOptions.getLastArgument(.explainModuleDependency) {
1524-
guard let dependencyPlanner = explicitDependencyBuildPlanner else {
1525-
fatalError("Cannot explain dependency without Explicit Build Planner")
1526-
}
1527-
guard let dependencyPaths = try dependencyPlanner.explainDependency(explainModuleName.asSingle) else {
1528-
diagnosticEngine.emit(.remark("No such module dependency found: '\(explainModuleName.asSingle)'"))
1529-
return
1530-
}
1531-
diagnosticEngine.emit(.remark("Module '\(moduleOutputInfo.name)' depends on '\(explainModuleName.asSingle)'"))
1532-
for path in dependencyPaths {
1533-
var pathString:String = ""
1534-
for (index, moduleId) in path.enumerated() {
1535-
switch moduleId {
1536-
case .swift(let moduleName):
1537-
pathString = pathString + "[" + moduleName + "]"
1538-
case .swiftPrebuiltExternal(let moduleName):
1539-
pathString = pathString + "[" + moduleName + "]"
1540-
case .clang(let moduleName):
1541-
pathString = pathString + "[" + moduleName + "](ObjC)"
1542-
case .swiftPlaceholder(_):
1543-
fatalError("Unexpected unresolved Placeholder module")
1544-
}
1545-
if index < path.count - 1 {
1546-
pathString = pathString + " -> "
1547-
}
1548-
}
1549-
diagnosticEngine.emit(.note(pathString))
1550-
}
1555+
if let explainModuleName = parsedOptions.getLastArgument(.explainModuleDependencyDetailed) {
1556+
try explainModuleDependency(explainModuleName.asSingle, allPaths: true)
1557+
} else if let explainModuleNameDetailed = parsedOptions.getLastArgument(.explainModuleDependency) {
1558+
try explainModuleDependency(explainModuleNameDetailed.asSingle, allPaths: false)
15511559
}
15521560

15531561
if parsedOptions.contains(.driverPrintOutputFileMap) {

Sources/SwiftDriver/ExplicitModuleBuilds/ExplicitDependencyBuildPlanner.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -618,8 +618,19 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
618618
}
619619

620620
internal extension ExplicitDependencyBuildPlanner {
621-
func explainDependency(_ dependencyModuleName: String) throws -> [[ModuleDependencyId]]? {
622-
return try dependencyGraph.explainDependency(dependencyModuleName: dependencyModuleName)
621+
func explainDependency(_ dependencyModuleName: String, allPaths: Bool) throws -> [[ModuleDependencyId]]? {
622+
return try dependencyGraph.explainDependency(dependencyModuleName: dependencyModuleName, allPaths: allPaths)
623+
}
624+
625+
func findPath(from source: ModuleDependencyId, to destination: ModuleDependencyId) throws -> [ModuleDependencyId]? {
626+
guard dependencyGraph.modules.contains(where: { $0.key == destination }) else { return nil }
627+
var result: [ModuleDependencyId]? = nil
628+
var visited: Set<ModuleDependencyId> = []
629+
try dependencyGraph.findAPath(source: source,
630+
pathSoFar: [source],
631+
visited: &visited,
632+
result: &result) { $0 == destination }
633+
return result
623634
}
624635
}
625636

Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/CommonDependencyOperations.swift

Lines changed: 67 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -415,46 +415,97 @@ internal extension InterModuleDependencyGraph {
415415
}
416416

417417
internal extension InterModuleDependencyGraph {
418-
func explainDependency(dependencyModuleName: String) throws -> [[ModuleDependencyId]]? {
418+
func explainDependency(dependencyModuleName: String, allPaths: Bool) throws -> [[ModuleDependencyId]]? {
419419
guard modules.contains(where: { $0.key.moduleName == dependencyModuleName }) else { return nil }
420-
var results = Set<[ModuleDependencyId]>()
421-
try findAllPaths(source: .swift(mainModuleName),
422-
to: dependencyModuleName,
423-
pathSoFar: [.swift(mainModuleName)],
424-
results: &results)
425-
return results.sorted(by: { $0.count < $1.count })
420+
var result: Set<[ModuleDependencyId]> = []
421+
if allPaths {
422+
try findAllPaths(source: .swift(mainModuleName),
423+
pathSoFar: [.swift(mainModuleName)],
424+
results: &result,
425+
destinationMatch: { $0.moduleName == dependencyModuleName })
426+
} else {
427+
var visited: Set<ModuleDependencyId> = []
428+
var singlePathResult: [ModuleDependencyId]? = nil
429+
if try findAPath(source: .swift(mainModuleName),
430+
pathSoFar: [.swift(mainModuleName)],
431+
visited: &visited,
432+
result: &singlePathResult,
433+
destinationMatch: { $0.moduleName == dependencyModuleName }),
434+
let resultingPath = singlePathResult {
435+
result = [resultingPath]
436+
}
437+
}
438+
return Array(result)
426439
}
427440

441+
@discardableResult
442+
func findAPath(source: ModuleDependencyId,
443+
pathSoFar: [ModuleDependencyId],
444+
visited: inout Set<ModuleDependencyId>,
445+
result: inout [ModuleDependencyId]?,
446+
destinationMatch: (ModuleDependencyId) -> Bool) throws -> Bool {
447+
// Mark this node as visited
448+
visited.insert(source)
449+
let sourceInfo = try moduleInfo(of: source)
450+
if destinationMatch(source) {
451+
// If the source is a target Swift module, also check if it
452+
// depends on a corresponding Clang module with the same name.
453+
// If it does, add it to the path as well.
454+
var completePath = pathSoFar
455+
if let dependencies = sourceInfo.directDependencies,
456+
dependencies.contains(.clang(source.moduleName)) {
457+
completePath.append(.clang(source.moduleName))
458+
}
459+
result = completePath
460+
return true
461+
}
462+
463+
var allDependencies = sourceInfo.directDependencies ?? []
464+
if case .swift(let swiftModuleDetails) = sourceInfo.details,
465+
let overlayDependencies = swiftModuleDetails.swiftOverlayDependencies {
466+
allDependencies.append(contentsOf: overlayDependencies)
467+
}
468+
469+
for dependency in allDependencies {
470+
if try findAPath(source: dependency,
471+
pathSoFar: pathSoFar + [dependency],
472+
visited: &visited,
473+
result: &result,
474+
destinationMatch: destinationMatch) {
475+
return true
476+
}
477+
}
478+
return false
479+
}
428480

429481
private func findAllPaths(source: ModuleDependencyId,
430-
to moduleName: String,
431482
pathSoFar: [ModuleDependencyId],
432-
results: inout Set<[ModuleDependencyId]>) throws {
483+
results: inout Set<[ModuleDependencyId]>,
484+
destinationMatch: (ModuleDependencyId) -> Bool) throws {
433485
let sourceInfo = try moduleInfo(of: source)
434-
// If the source is our target, we are done
435-
if source.moduleName == moduleName {
486+
if destinationMatch(source) {
436487
// If the source is a target Swift module, also check if it
437488
// depends on a corresponding Clang module with the same name.
438489
// If it does, add it to the path as well.
439490
var completePath = pathSoFar
440491
if let dependencies = sourceInfo.directDependencies,
441-
dependencies.contains(.clang(moduleName)) {
442-
completePath.append(.clang(moduleName))
492+
dependencies.contains(.clang(source.moduleName)) {
493+
completePath.append(.clang(source.moduleName))
443494
}
444495
results.insert(completePath)
496+
return
445497
}
446498

447499
var allDependencies = sourceInfo.directDependencies ?? []
448500
if case .swift(let swiftModuleDetails) = sourceInfo.details,
449501
let overlayDependencies = swiftModuleDetails.swiftOverlayDependencies {
450502
allDependencies.append(contentsOf: overlayDependencies)
451503
}
452-
453504
for dependency in allDependencies {
454505
try findAllPaths(source: dependency,
455-
to: moduleName,
456506
pathSoFar: pathSoFar + [dependency],
457-
results: &results)
507+
results: &results,
508+
destinationMatch: destinationMatch)
458509
}
459510
}
460511
}

Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ extension Driver {
5151
/// If the driver is in Explicit Module Build mode, the dependency graph has been computed
5252
case computed
5353
}
54+
5455
/// Add frontend options that are common to different frontend invocations.
5556
mutating func addCommonFrontendOptions(
5657
commandLine: inout [Job.ArgTemplate],

Sources/SwiftOptions/Options.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,7 @@ extension Option {
500500
public static let experimentalSpiOnlyImports: Option = Option("-experimental-spi-only-imports", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Enable use of @_spiOnly imports")
501501
public static let enableExperimentalSwiftBasedClosureSpecialization: Option = Option("-experimental-swift-based-closure-specialization", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Use the experimental Swift based closure-specialization optimization pass instead of the existing C++ one")
502502
public static let explainModuleDependency: Option = Option("-explain-module-dependency", .separate, attributes: [], helpText: "Emit remark/notes describing why compilation may depend on a module with a given name.")
503+
public static let explainModuleDependencyDetailed: Option = Option("-explain-module-dependency-detailed", .separate, attributes: [], helpText: "Emit remark/notes describing why compilation may depend on a module with a given name.")
503504
public static let explicitAutoLinking: Option = Option("-explicit-auto-linking", .flag, attributes: [], helpText: "Instead of linker-load directives, have the driver specify all link dependencies on the linker invocation. Requires '-explicit-module-build'.")
504505
public static let explicitDependencyGraphFormat: Option = Option("-explicit-dependency-graph-format=", .joined, attributes: [.helpHidden, .doesNotAffectIncrementalBuild], helpText: "Specify the explicit dependency graph output format to either 'json' or 'dot'")
505506
public static let explicitInterfaceModuleBuild: Option = Option("-explicit-interface-module-build", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Use the specified command-line to build the module from interface, instead of flags specified in the interface")
@@ -1372,6 +1373,7 @@ extension Option {
13721373
Option.experimentalSpiOnlyImports,
13731374
Option.enableExperimentalSwiftBasedClosureSpecialization,
13741375
Option.explainModuleDependency,
1376+
Option.explainModuleDependencyDetailed,
13751377
Option.explicitAutoLinking,
13761378
Option.explicitDependencyGraphFormat,
13771379
Option.explicitInterfaceModuleBuild,

Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2248,30 +2248,61 @@ final class ExplicitModuleBuildTests: XCTestCase {
22482248
try testInputsPath.appending(component: "ExplicitModuleBuilds")
22492249
.appending(component: "Swift")
22502250
let sdkArgumentsForTesting = (try? Driver.sdkArgumentsForTesting()) ?? []
2251-
var driver = try Driver(args: ["swiftc",
2252-
"-I", cHeadersPath.nativePathString(escaped: true),
2253-
"-I", swiftModuleInterfacesPath.nativePathString(escaped: true),
2254-
"-explicit-module-build", "-v",
2255-
"-module-cache-path", moduleCachePath.nativePathString(escaped: true),
2256-
"-working-directory", path.nativePathString(escaped: true),
2257-
"-explain-module-dependency", "A",
2258-
main.nativePathString(escaped: true)] + sdkArgumentsForTesting,
2259-
env: ProcessEnv.vars)
2260-
let jobs = try driver.planBuild()
2261-
try driver.run(jobs: jobs)
2262-
XCTAssertTrue(!driver.diagnosticEngine.diagnostics.isEmpty)
2263-
XCTAssertTrue(driver.diagnosticEngine.diagnostics.contains { $0.behavior == .remark &&
2264-
$0.message.text == "Module 'testTraceDependency' depends on 'A'"})
22652251

2266-
for diag in driver.diagnosticEngine.diagnostics {
2267-
print(diag.behavior)
2268-
print(diag.message)
2252+
// Detailed explain (all possible paths)
2253+
do {
2254+
var driver = try Driver(args: ["swiftc",
2255+
"-I", cHeadersPath.nativePathString(escaped: true),
2256+
"-I", swiftModuleInterfacesPath.nativePathString(escaped: true),
2257+
"-explicit-module-build", "-v",
2258+
"-module-cache-path", moduleCachePath.nativePathString(escaped: true),
2259+
"-working-directory", path.nativePathString(escaped: true),
2260+
"-explain-module-dependency-detailed", "A",
2261+
main.nativePathString(escaped: true)] + sdkArgumentsForTesting,
2262+
env: ProcessEnv.vars)
2263+
let jobs = try driver.planBuild()
2264+
try driver.run(jobs: jobs)
2265+
XCTAssertTrue(!driver.diagnosticEngine.diagnostics.isEmpty)
2266+
XCTAssertTrue(driver.diagnosticEngine.diagnostics.contains { $0.behavior == .remark &&
2267+
$0.message.text == "Module 'testTraceDependency' depends on 'A'"})
2268+
2269+
for diag in driver.diagnosticEngine.diagnostics {
2270+
print(diag.behavior)
2271+
print(diag.message)
2272+
}
2273+
XCTAssertEqual(driver.diagnosticEngine.diagnostics.filter { $0.behavior == .note}.count, 2)
2274+
XCTAssertTrue(driver.diagnosticEngine.diagnostics.contains { $0.behavior == .note &&
2275+
$0.message.text == "[testTraceDependency] -> [A] -> [A](ObjC)"})
2276+
XCTAssertTrue(driver.diagnosticEngine.diagnostics.contains { $0.behavior == .note &&
2277+
$0.message.text == "[testTraceDependency] -> [C](ObjC) -> [B](ObjC) -> [A](ObjC)"})
2278+
}
2279+
2280+
// Simple explain (first available path)
2281+
do {
2282+
var driver = try Driver(args: ["swiftc",
2283+
"-I", cHeadersPath.nativePathString(escaped: true),
2284+
"-I", swiftModuleInterfacesPath.nativePathString(escaped: true),
2285+
"-explicit-module-build", "-v",
2286+
"-module-cache-path", moduleCachePath.nativePathString(escaped: true),
2287+
"-working-directory", path.nativePathString(escaped: true),
2288+
"-explain-module-dependency", "A",
2289+
main.nativePathString(escaped: true)] + sdkArgumentsForTesting,
2290+
env: ProcessEnv.vars)
2291+
let jobs = try driver.planBuild()
2292+
try driver.run(jobs: jobs)
2293+
XCTAssertTrue(!driver.diagnosticEngine.diagnostics.isEmpty)
2294+
XCTAssertTrue(driver.diagnosticEngine.diagnostics.contains { $0.behavior == .remark &&
2295+
$0.message.text == "Module 'testTraceDependency' depends on 'A'"})
2296+
2297+
for diag in driver.diagnosticEngine.diagnostics {
2298+
print(diag.behavior)
2299+
print(diag.message)
2300+
}
2301+
XCTAssertEqual(driver.diagnosticEngine.diagnostics.filter { $0.behavior == .note}.count, 1)
2302+
XCTAssertTrue(driver.diagnosticEngine.diagnostics.contains { $0.behavior == .note &&
2303+
($0.message.text == "[testTraceDependency] -> [A] -> [A](ObjC)" ||
2304+
$0.message.text == "[testTraceDependency] -> [C](ObjC) -> [B](ObjC) -> [A](ObjC)")})
22692305
}
2270-
XCTAssertEqual(driver.diagnosticEngine.diagnostics.filter { $0.behavior == .note}.count, 2)
2271-
XCTAssertTrue(driver.diagnosticEngine.diagnostics.contains { $0.behavior == .note &&
2272-
$0.message.text == "[testTraceDependency] -> [A] -> [A](ObjC)"})
2273-
XCTAssertTrue(driver.diagnosticEngine.diagnostics.contains { $0.behavior == .note &&
2274-
$0.message.text == "[testTraceDependency] -> [C](ObjC) -> [B](ObjC) -> [A](ObjC)"})
22752306
}
22762307
}
22772308

0 commit comments

Comments
 (0)