Skip to content

[WIP] Invoke the Swift frontend to prescan for dependencies. #47

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
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
135 changes: 135 additions & 0 deletions Sources/SwiftDriver/Dependency Scanning/ModuleDependencyGraph.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
//===--------------- ModuleDependencyGraph.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 Foundation

enum ModuleDependencyId: Hashable {
case swift(String)
case clang(String)

var moduleName: String {
switch self {
case .swift(let name): return name
case .clang(let name): return name
}
}
}

extension ModuleDependencyId: Codable {
enum CodingKeys: CodingKey {
case swift
case clang
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
let moduleName = try container.decode(String.self, forKey: .swift)
self = .swift(moduleName)
} catch {
let moduleName = try container.decode(String.self, forKey: .clang)
self = .clang(moduleName)
}
}

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .swift(let moduleName):
try container.encode(moduleName, forKey: .swift)
case .clang(let moduleName):
try container.encode(moduleName, forKey: .clang)
}
}
}

/// Details specific to Swift modules.
struct SwiftModuleDetails: Codable {
/// The module interface from which this module was built, if any.
var moduleInterfacePath: String?

/// The bridging header, if any.
var bridgingHeaderPath: String?

/// The source files referenced by the bridging header.
var bridgingSourceFiles: [String] = []
}

/// Details specific to Clang modules.
struct ClangModuleDetails: Codable {
/// The path to the module map used to build this module.
var moduleMapPath: String
}

struct ModuleDependencies: Codable {
/// The path for the module.
var modulePath: String

/// The source files used to build this module.
var sourceFiles: [String] = []

/// The set of direct module dependencies of this module.
var directDependencies: [ModuleDependencyId] = []

/// Specific details of a particular kind of module.
var details: Details

/// Specific details of a particular kind of module.
enum Details {
/// Swift modules may be built from a module interface, and may have
/// a bridging header.
case swift(SwiftModuleDetails)

/// Clang modules are built from a module map file.
case clang(ClangModuleDetails)
}
}

extension ModuleDependencies.Details: Codable {
enum CodingKeys: CodingKey {
case swift
case clang
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
let details = try container.decode(SwiftModuleDetails.self, forKey: .swift)
self = .swift(details)
} catch {
let details = try container.decode(ClangModuleDetails.self, forKey: .clang)
self = .clang(details)
}
}

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .swift(let details):
try container.encode(details, forKey: .swift)
case .clang(let details):
try container.encode(details, forKey: .clang)
}
}
}

/// Describes the complete set of dependencies for a Swift module, including
/// all of the Swift and C modules and source files it depends on.
struct ModuleDependencyGraph: Codable {
/// The name of the main module.
var mainModuleName: String

/// The complete set of modules discovered
var modules: [ModuleDependencyId: ModuleDependencies] = [:]

/// Information about the main module.
var mainModule: ModuleDependencies { modules[.swift(mainModuleName)]! }
}
5 changes: 5 additions & 0 deletions Sources/SwiftDriver/Driver/Driver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ public struct Driver {
/// This will force the driver to first emit the module and then run compile jobs.
public var forceEmitModuleInSingleInvocation: Bool = false

/// The module dependency graph, which is populated during the planning phase
/// only when all modules will be prebuilt and treated as explicit by the
/// various compilation jobs.
var moduleDependencyGraph: ModuleDependencyGraph? = nil

/// Handler for emitting diagnostics to stderr.
public static let stderrDiagnosticsHandler: DiagnosticsEngine.DiagnosticsHandler = { diagnostic in
let stream = stderrStream
Expand Down
81 changes: 81 additions & 0 deletions Sources/SwiftDriver/Driver/ModuleDependencyScanning.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//===--------------- ModuleDependencyScanning.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 Foundation
import TSCBasic

extension Driver {
/// Produce a Swift job to to compute the full module dependency graph
/// in advance, allowing the driver to schedule explicit module builds.
mutating func moduleDependencyGraphJob() throws -> Job {
var commandLine: [Job.ArgTemplate] = swiftCompilerPrefixArgs.map { Job.ArgTemplate.flag($0) }
commandLine.appendFlag("-frontend")
commandLine.appendFlag("-emit-imported-modules")

if parsedOptions.hasArgument(.parseStdlib) {
commandLine.appendFlag(.disableObjcAttrRequiresFoundationModule)
}

try addCommonFrontendOptions(commandLine: &commandLine)
// FIXME: MSVC runtime flags

commandLine.append(contentsOf: inputFiles.map { Job.ArgTemplate.path($0.file)})
return Job(
kind: .moduleDependencyGraph,
tool: swiftCompiler,
commandLine: commandLine,
displayInputs: inputFiles,
inputs: inputFiles,
outputs: []
)
}

private class ModuleDependencyGraphExecutionDelegate : JobExecutorDelegate {
var moduleDependencyGraph: ModuleDependencyGraph? = nil

func jobStarted(job: Job, arguments: [String], pid: Int) {
}

func jobFinished(job: Job, result: ProcessResult, pid: Int) {
switch result.exitStatus {
case .terminated(code: 0):
guard let outputData = try? Data(result.utf8Output().utf8) else {
return
}

let decoder = JSONDecoder()
moduleDependencyGraph = try? decoder.decode(
ModuleDependencyGraph.self, from: outputData)

default:
break;
}
}
}

/// Precompute the dependencies for a given Swift compilation, producing a
/// complete dependency graph including all Swift and C module files and
/// source files.
mutating func computeModuleDependencyGraph() throws
-> ModuleDependencyGraph? {
let job = try moduleDependencyGraphJob()
let resolver = try ArgsResolver()
let executorDelegate = ModuleDependencyGraphExecutionDelegate()
let jobExecutor = JobExecutor(
jobs: [job], resolver: resolver,
executorDelegate: executorDelegate
)
try jobExecutor.execute(env: [:])

// FIXME: Handle errors properly
return executorDelegate.moduleDependencyGraph
}
}
1 change: 1 addition & 0 deletions Sources/SwiftDriver/Jobs/Job.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public struct Job: Codable, Equatable {
case emitModule = "emit-module"
case interpret
case repl
case moduleDependencyGraph = "emit-imported-modules"
}

public enum ArgTemplate: Equatable {
Expand Down
9 changes: 9 additions & 0 deletions Sources/SwiftDriver/Jobs/Planning.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ extension Driver {
private mutating func planStandardCompile() throws -> [Job] {
var jobs = [Job]()

// If we've been asked to prebuild module dependencies, prescan the source
// files to produce a module dependency graph.
if parsedOptions.contains(.driverPrebuildModuleDependencies) {
moduleDependencyGraph = try computeModuleDependencyGraph()
if let graph = moduleDependencyGraph {
print(graph)
}
}

// Keep track of the various outputs we care about from the jobs we build.
var linkerInputs: [TypedVirtualPath] = []
var moduleInputs: [TypedVirtualPath] = []
Expand Down
5 changes: 4 additions & 1 deletion Sources/SwiftDriver/Options/ExtraOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@
extension Option {
public static let driverPrintGraphviz: Option = Option("-driver-print-graphviz", .flag, attributes: [.helpHidden, .doesNotAffectIncrementalBuild], helpText: "Write the job graph as a graphviz file", group: .internalDebug)

public static let driverPrebuildModuleDependencies: Option = Option("-driver-prebuild-module-dependencies", .flag, attributes: [.helpHidden], helpText: "Prebuild module dependencies to make them explicit")

public static var extraOptions: [Option] {
return [
Option.driverPrintGraphviz
Option.driverPrintGraphviz,
Option.driverPrebuildModuleDependencies
]
}
}