Skip to content

[Incremental] Next step towards cross-module incremental dependencies #468

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

Merged
merged 12 commits into from
Feb 5, 2021
Merged
18 changes: 18 additions & 0 deletions Sources/SwiftDriver/IncrementalCompilation/DependencyKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ import Foundation
}
}

/// Since the integration surfaces all externalDependencies to be processed later,
/// a combination of the dependency and fingerprint are needed.
public struct FingerprintedExternalDependency: Hashable, Equatable {
let externalDependency: ExternalDependency
let fingerprint: String?
init(_ externalDependency: ExternalDependency, _ fingerprint: String?) {
self.externalDependency = externalDependency
self.fingerprint = fingerprint
}
var isIncremental: Bool { fingerprint != nil }
}

/// A `DependencyKey` carries all of the information necessary to uniquely
/// identify a dependency node in the graph, and serves as a point of identity
Expand Down Expand Up @@ -113,6 +124,13 @@ public struct DependencyKey: Hashable, CustomStringConvertible {
///
/// The `name` of the file is a path to the `swiftdeps` file named in
/// the output file map for a given Swift file.
///
/// If the filename is a swiftmodule, and if it was built by a compilation with
/// `-enable-experimental-cross-module-incremental-build`, the swiftmodule file
/// contains a special section with swiftdeps information for the module
/// in it. In that case the enclosing node should have a fingerprint.
///

case sourceFileProvide(name: String)
/// A "nominal" type that is used, or defined by this file.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,14 +305,6 @@ extension IncrementalCompilationState {
reporter: reporter)

let externallyChangedInputs = computeExternallyChangedInputs(
forIncrementalExternalDependencies: false,
buildTime: outOfDateBuildRecord.buildTime,
fileSystem: fileSystem,
moduleDependencyGraph: moduleDependencyGraph,
reporter: moduleDependencyGraph.reporter)

let incrementallyExternallyChangedInputs = computeExternallyChangedInputs(
forIncrementalExternalDependencies: true,
buildTime: outOfDateBuildRecord.buildTime,
fileSystem: fileSystem,
moduleDependencyGraph: moduleDependencyGraph,
Expand All @@ -327,9 +319,9 @@ extension IncrementalCompilationState {
// Combine to obtain the inputs that definitely must be recompiled.
let definitelyRequiredInputs =
Set(changedInputs.map({ $0.filePath }) +
externallyChangedInputs + incrementallyExternallyChangedInputs +
inputsHavingMalformedDependencySources
+ inputsMissingOutputs)
externallyChangedInputs +
inputsHavingMalformedDependencySources +
inputsMissingOutputs)
if let reporter = reporter {
for scheduledInput in definitelyRequiredInputs.sorted(by: {$0.file.name < $1.file.name}) {
reporter.report("Queuing (initial):", scheduledInput)
Expand Down Expand Up @@ -426,26 +418,23 @@ extension IncrementalCompilationState {

/// Any files dependent on modified files from other modules must be compiled, too.
private static func computeExternallyChangedInputs(
forIncrementalExternalDependencies: Bool,
buildTime: Date,
fileSystem: FileSystem,
moduleDependencyGraph: ModuleDependencyGraph,
reporter: IncrementalCompilationState.Reporter?
) -> [TypedVirtualPath] {
var externalDependencySources = Set<ModuleDependencyGraph.DependencySource>()
let extDeps = forIncrementalExternalDependencies
? moduleDependencyGraph.incrementalExternalDependencies
: moduleDependencyGraph.externalDependencies
for extDep in extDeps {
) -> [TypedVirtualPath] {
var externalDependencySources = Set<DependencySource>()
for extDepAndPrint in moduleDependencyGraph.fingerprintedExternalDependencies {
let extDep = extDepAndPrint.externalDependency
let extModTime = extDep.file.flatMap {try? fileSystem.getFileInfo($0).modTime}
?? Date.distantFuture
if extModTime >= buildTime {
for dependent in moduleDependencyGraph.untracedDependents(of: extDep, isIncremental: forIncrementalExternalDependencies) {
for dependent in moduleDependencyGraph.untracedDependents(of: extDepAndPrint) {
guard let dependencySource = dependent.dependencySource else {
fatalError("Dependent \(dependent) does not have dependencies file!")
}
reporter?.report(
"Queuing because of \(forIncrementalExternalDependencies ? "incremental " : "")external dependency on newer \(extDep.file?.basename ?? "extDep?")",
"Queuing because of external dependency on newer \(extDep.file?.basename ?? "extDep?")",
dependencySource.typedFile)
externalDependencySources.insert(dependencySource)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//===------- ExternalDependencyHolder.swift -------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2020 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

/// Encapsulates the invariant required for anything with a DependencyKey and an fingerprint
protocol KeyAndFingerprintEnforcer {
var key: DependencyKey {get}
var fingerprint: String? {get}
}
extension KeyAndFingerprintEnforcer {
func verifyKeyAndFingerprint() throws {
guard case .externalDepend(let externalDependency) = key.designator
else {
return
}
guard key.aspect == .interface else {
throw KeyAndFingerprintEnforcerError.externalDepsMustBeInterface(externalDependency)
}
guard let file = externalDependency.file else {
throw KeyAndFingerprintEnforcerError.noFile(externalDependency)
}
guard let fingerprint = self.fingerprint,
file.extension == FileType.swiftModule.rawValue
else {
return
}
throw KeyAndFingerprintEnforcerError.onlySwiftModulesHaveFingerprints(externalDependency, fingerprint)
}
}
enum KeyAndFingerprintEnforcerError: LocalizedError {
case externalDepsMustBeInterface(ExternalDependency)
case noFile(ExternalDependency)
case onlySwiftModulesHaveFingerprints(ExternalDependency, String)

var errorDescription: String? {
switch self {
case let .externalDepsMustBeInterface(externalDependency):
return "Aspect of external dependency must be interface: \(externalDependency)"
case let .noFile(externalDependency):
return "External dependency must point to a file: \(externalDependency)"
case let .onlySwiftModulesHaveFingerprints(externalDependency, fingerprint):
return "An external dependency with a fingerprint (\(fingerprint)) must point to a swiftmodule file: \(externalDependency)"
}
}
}
Loading