Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Sources/SwiftDriver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ add_library(SwiftDriver
Toolchains/WindowsToolchain.swift

Utilities/DOTJobGraphSerializer.swift
Utilities/DOTModuleDependencyGraphSerializer.swift
Utilities/DateAdditions.swift
Utilities/Diagnostics.swift
Utilities/FileList.swift
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//===----------- DOTModuleDependencyGraphSerializer.swift - Swift ---------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import TSCBasic

/// Serializes a module dependency graph to a .dot graph
@_spi(Testing) public struct DOTModuleDependencyGraphSerializer {
let graph: InterModuleDependencyGraph

public init(_ interModuleDependencyGraph: InterModuleDependencyGraph) {
self.graph = interModuleDependencyGraph
}

func label(for moduleId: ModuleDependencyId) -> String {
let label: String
switch moduleId {
case .swift(let string):
label = "\(string)"
case .swiftPlaceholder(let string):
label = "\(string) (Placeholder)"
case .swiftPrebuiltExternal(let string):
label = "\(string) (Prebuilt)"
case .clang(let string):
label = "\(string) (C)"
}
return label
}

func quoteName(_ name: String) -> String {
return "\"" + name.replacingOccurrences(of: "\"", with: "\\\"") + "\""
}

func outputNode(for moduleId: ModuleDependencyId) -> String {
let nodeName = quoteName(label(for: moduleId))
let output: String
let font = "fontname=\"Helvetica Bold\""

if moduleId == .swift(graph.mainModuleName) {
output = " \(nodeName) [shape=box, style=bold, color=navy, \(font)];\n"
} else {
switch moduleId {
case .swift(_):
output = " \(nodeName) [style=bold, color=orange, style=filled, \(font)];\n"
case .swiftPlaceholder(_):
output = " \(nodeName) [style=bold, color=gold, style=filled, \(font)];\n"
case .swiftPrebuiltExternal(_):
output = " \(nodeName) [style=bold, color=darkorange3, style=filled, \(font)];\n"
case .clang(_):
output = " \(nodeName) [style=bold, color=lightskyblue, style=filled, \(font)];\n"
}
}
return output
}

public func writeDOT<Stream: TextOutputStream>(to stream: inout Stream) {
stream.write("digraph Modules {\n")
for (moduleId, moduleInfo) in graph.modules {
stream.write(outputNode(for: moduleId))
guard let dependencies = moduleInfo.directDependencies else {
continue
}
for dependencyId in dependencies {
stream.write(" \(quoteName(label(for: moduleId))) -> \(quoteName(label(for: dependencyId))) [color=black];\n")
}
}
stream.write("}\n")
}
}
63 changes: 63 additions & 0 deletions Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1167,6 +1167,69 @@ final class ExplicitModuleBuildTests: XCTestCase {
}
}

func testDependencyGraphDotSerialization() throws {
let (stdlibPath, shimsPath, toolchain, hostTriple) = try getDriverArtifactsForScanning()
let dependencyOracle = InterModuleDependencyOracle()
let scanLibPath = try Driver.getScanLibPath(of: toolchain,
hostTriple: hostTriple,
env: ProcessEnv.vars)
guard try dependencyOracle
.verifyOrCreateScannerInstance(fileSystem: localFileSystem,
swiftScanLibPath: scanLibPath) else {
XCTFail("Dependency scanner library not found")
return
}
// Create a simple test case.
try withTemporaryDirectory { path in
let main = path.appending(component: "testDependencyScanning.swift")
try localFileSystem.writeFileContents(main) {
$0 <<< "import C;"
$0 <<< "import E;"
$0 <<< "import G;"
}

let cHeadersPath: AbsolutePath =
testInputsPath.appending(component: "ExplicitModuleBuilds")
.appending(component: "CHeaders")
let swiftModuleInterfacesPath: AbsolutePath =
testInputsPath.appending(component: "ExplicitModuleBuilds")
.appending(component: "Swift")
let sdkArgumentsForTesting = (try? Driver.sdkArgumentsForTesting()) ?? []
var driver = try Driver(args: ["swiftc",
"-I", cHeadersPath.nativePathString(escaped: true),
"-I", swiftModuleInterfacesPath.nativePathString(escaped: true),
"-I", stdlibPath.nativePathString(escaped: true),
"-I", shimsPath.nativePathString(escaped: true),
"-import-objc-header",
"-explicit-module-build",
"-working-directory", path.nativePathString(escaped: true),
"-disable-clang-target",
main.nativePathString(escaped: true)] + sdkArgumentsForTesting,
env: ProcessEnv.vars)
let resolver = try ArgsResolver(fileSystem: localFileSystem)
var scannerCommand = try driver.dependencyScannerInvocationCommand().1.map { try resolver.resolve($0) }
if scannerCommand.first == "-frontend" {
scannerCommand.removeFirst()
}
let dependencyGraph =
try dependencyOracle.getDependencies(workingDirectory: path,
commandLine: scannerCommand)
let serializer = DOTModuleDependencyGraphSerializer(dependencyGraph)

let outputFile = path.appending(component: "dependency_graph.dot")
var outputStream = try ThreadSafeOutputByteStream(LocalFileOutputByteStream(outputFile))
serializer.writeDOT(to: &outputStream)
outputStream.flush()
let contents = try localFileSystem.readFileContents(outputFile).description
XCTAssertTrue(contents.contains("\"testDependencyScanning\" [shape=box, style=bold, color=navy"))
XCTAssertTrue(contents.contains("\"G\" [style=bold, color=orange"))
XCTAssertTrue(contents.contains("\"E\" [style=bold, color=orange, style=filled"))
XCTAssertTrue(contents.contains("\"C (C)\" [style=bold, color=lightskyblue, style=filled"))
XCTAssertTrue(contents.contains("\"Swift\" [style=bold, color=orange, style=filled"))
XCTAssertTrue(contents.contains("\"SwiftShims (C)\" [style=bold, color=lightskyblue, style=filled"))
XCTAssertTrue(contents.contains("\"Swift\" -> \"SwiftShims (C)\" [color=black];"))
}
}

/// Test the libSwiftScan dependency scanning.
func testDependencyScanReuseCache() throws {
Expand Down