Skip to content

Commit e53435f

Browse files
authored
Merge pull request #1175 from artemcm/DOT_ModuleGraphSerializer
Add a Dot serializer for the inter-module dependency graph.
2 parents f754205 + b35358f commit e53435f

File tree

3 files changed

+140
-0
lines changed

3 files changed

+140
-0
lines changed

Sources/SwiftDriver/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ add_library(SwiftDriver
9898
Toolchains/WindowsToolchain.swift
9999

100100
Utilities/DOTJobGraphSerializer.swift
101+
Utilities/DOTModuleDependencyGraphSerializer.swift
101102
Utilities/DateAdditions.swift
102103
Utilities/Diagnostics.swift
103104
Utilities/FileList.swift
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//===----------- DOTModuleDependencyGraphSerializer.swift - Swift ---------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
import TSCBasic
13+
14+
/// Serializes a module dependency graph to a .dot graph
15+
@_spi(Testing) public struct DOTModuleDependencyGraphSerializer {
16+
let graph: InterModuleDependencyGraph
17+
18+
public init(_ interModuleDependencyGraph: InterModuleDependencyGraph) {
19+
self.graph = interModuleDependencyGraph
20+
}
21+
22+
func label(for moduleId: ModuleDependencyId) -> String {
23+
let label: String
24+
switch moduleId {
25+
case .swift(let string):
26+
label = "\(string)"
27+
case .swiftPlaceholder(let string):
28+
label = "\(string) (Placeholder)"
29+
case .swiftPrebuiltExternal(let string):
30+
label = "\(string) (Prebuilt)"
31+
case .clang(let string):
32+
label = "\(string) (C)"
33+
}
34+
return label
35+
}
36+
37+
func quoteName(_ name: String) -> String {
38+
return "\"" + name.replacingOccurrences(of: "\"", with: "\\\"") + "\""
39+
}
40+
41+
func outputNode(for moduleId: ModuleDependencyId) -> String {
42+
let nodeName = quoteName(label(for: moduleId))
43+
let output: String
44+
let font = "fontname=\"Helvetica Bold\""
45+
46+
if moduleId == .swift(graph.mainModuleName) {
47+
output = " \(nodeName) [shape=box, style=bold, color=navy, \(font)];\n"
48+
} else {
49+
switch moduleId {
50+
case .swift(_):
51+
output = " \(nodeName) [style=bold, color=orange, style=filled, \(font)];\n"
52+
case .swiftPlaceholder(_):
53+
output = " \(nodeName) [style=bold, color=gold, style=filled, \(font)];\n"
54+
case .swiftPrebuiltExternal(_):
55+
output = " \(nodeName) [style=bold, color=darkorange3, style=filled, \(font)];\n"
56+
case .clang(_):
57+
output = " \(nodeName) [style=bold, color=lightskyblue, style=filled, \(font)];\n"
58+
}
59+
}
60+
return output
61+
}
62+
63+
public func writeDOT<Stream: TextOutputStream>(to stream: inout Stream) {
64+
stream.write("digraph Modules {\n")
65+
for (moduleId, moduleInfo) in graph.modules {
66+
stream.write(outputNode(for: moduleId))
67+
guard let dependencies = moduleInfo.directDependencies else {
68+
continue
69+
}
70+
for dependencyId in dependencies {
71+
stream.write(" \(quoteName(label(for: moduleId))) -> \(quoteName(label(for: dependencyId))) [color=black];\n")
72+
}
73+
}
74+
stream.write("}\n")
75+
}
76+
}

Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1167,6 +1167,69 @@ final class ExplicitModuleBuildTests: XCTestCase {
11671167
}
11681168
}
11691169

1170+
func testDependencyGraphDotSerialization() throws {
1171+
let (stdlibPath, shimsPath, toolchain, hostTriple) = try getDriverArtifactsForScanning()
1172+
let dependencyOracle = InterModuleDependencyOracle()
1173+
let scanLibPath = try Driver.getScanLibPath(of: toolchain,
1174+
hostTriple: hostTriple,
1175+
env: ProcessEnv.vars)
1176+
guard try dependencyOracle
1177+
.verifyOrCreateScannerInstance(fileSystem: localFileSystem,
1178+
swiftScanLibPath: scanLibPath) else {
1179+
XCTFail("Dependency scanner library not found")
1180+
return
1181+
}
1182+
// Create a simple test case.
1183+
try withTemporaryDirectory { path in
1184+
let main = path.appending(component: "testDependencyScanning.swift")
1185+
try localFileSystem.writeFileContents(main) {
1186+
$0 <<< "import C;"
1187+
$0 <<< "import E;"
1188+
$0 <<< "import G;"
1189+
}
1190+
1191+
let cHeadersPath: AbsolutePath =
1192+
testInputsPath.appending(component: "ExplicitModuleBuilds")
1193+
.appending(component: "CHeaders")
1194+
let swiftModuleInterfacesPath: AbsolutePath =
1195+
testInputsPath.appending(component: "ExplicitModuleBuilds")
1196+
.appending(component: "Swift")
1197+
let sdkArgumentsForTesting = (try? Driver.sdkArgumentsForTesting()) ?? []
1198+
var driver = try Driver(args: ["swiftc",
1199+
"-I", cHeadersPath.nativePathString(escaped: true),
1200+
"-I", swiftModuleInterfacesPath.nativePathString(escaped: true),
1201+
"-I", stdlibPath.nativePathString(escaped: true),
1202+
"-I", shimsPath.nativePathString(escaped: true),
1203+
"-import-objc-header",
1204+
"-explicit-module-build",
1205+
"-working-directory", path.nativePathString(escaped: true),
1206+
"-disable-clang-target",
1207+
main.nativePathString(escaped: true)] + sdkArgumentsForTesting,
1208+
env: ProcessEnv.vars)
1209+
let resolver = try ArgsResolver(fileSystem: localFileSystem)
1210+
var scannerCommand = try driver.dependencyScannerInvocationCommand().1.map { try resolver.resolve($0) }
1211+
if scannerCommand.first == "-frontend" {
1212+
scannerCommand.removeFirst()
1213+
}
1214+
let dependencyGraph =
1215+
try dependencyOracle.getDependencies(workingDirectory: path,
1216+
commandLine: scannerCommand)
1217+
let serializer = DOTModuleDependencyGraphSerializer(dependencyGraph)
1218+
1219+
let outputFile = path.appending(component: "dependency_graph.dot")
1220+
var outputStream = try ThreadSafeOutputByteStream(LocalFileOutputByteStream(outputFile))
1221+
serializer.writeDOT(to: &outputStream)
1222+
outputStream.flush()
1223+
let contents = try localFileSystem.readFileContents(outputFile).description
1224+
XCTAssertTrue(contents.contains("\"testDependencyScanning\" [shape=box, style=bold, color=navy"))
1225+
XCTAssertTrue(contents.contains("\"G\" [style=bold, color=orange"))
1226+
XCTAssertTrue(contents.contains("\"E\" [style=bold, color=orange, style=filled"))
1227+
XCTAssertTrue(contents.contains("\"C (C)\" [style=bold, color=lightskyblue, style=filled"))
1228+
XCTAssertTrue(contents.contains("\"Swift\" [style=bold, color=orange, style=filled"))
1229+
XCTAssertTrue(contents.contains("\"SwiftShims (C)\" [style=bold, color=lightskyblue, style=filled"))
1230+
XCTAssertTrue(contents.contains("\"Swift\" -> \"SwiftShims (C)\" [color=black];"))
1231+
}
1232+
}
11701233

11711234
/// Test the libSwiftScan dependency scanning.
11721235
func testDependencyScanReuseCache() throws {

0 commit comments

Comments
 (0)