Skip to content

Add option -explain-module-dependency to explain a given direct or transitive module dependency #1363

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 1 commit into from
May 24, 2023
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
31 changes: 31 additions & 0 deletions Sources/SwiftDriver/Driver/Driver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1370,6 +1370,37 @@ extension Driver {
return
}

// If we're only supposed to explain a dependency on a given module, do so now.
if let explainModuleName = parsedOptions.getLastArgument(.explainModuleDependency) {
guard let dependencyPlanner = explicitDependencyBuildPlanner else {
fatalError("Cannot explain dependency without Explicit Build Planner")
}
guard let dependencyPaths = try dependencyPlanner.explainDependency(explainModuleName.asSingle) else {
diagnosticEngine.emit(.remark("No such module dependency found: '\(explainModuleName.asSingle)'"))
return
}
diagnosticEngine.emit(.remark("Module '\(moduleOutputInfo.name)' depends on '\(explainModuleName.asSingle)'"))
for path in dependencyPaths {
var pathString:String = ""
for (index, moduleId) in path.enumerated() {
switch moduleId {
case .swift(let moduleName):
pathString = pathString + "[" + moduleName + "]"
case .swiftPrebuiltExternal(let moduleName):
pathString = pathString + "[" + moduleName + "]"
case .clang(let moduleName):
pathString = pathString + "[" + moduleName + "](ObjC)"
case .swiftPlaceholder(_):
fatalError("Unexpected unresolved Placeholder module")
}
if index < path.count - 1 {
pathString = pathString + " -> "
}
}
diagnosticEngine.emit(.note(pathString))
}
}

if parsedOptions.contains(.driverPrintOutputFileMap) {
if let outputFileMap = self.outputFileMap {
stderrStream <<< outputFileMap.description
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
}
for moduleId in swiftDependencies {
let moduleInfo = try dependencyGraph.moduleInfo(of: moduleId)
let pcmArgs = try dependencyGraph.swiftModulePCMArgs(of: moduleId)
var inputs: [TypedVirtualPath] = []
let outputs: [TypedVirtualPath] = [
TypedVirtualPath(file: moduleInfo.modulePath.path, type: .swiftModule)
Expand Down Expand Up @@ -488,6 +487,12 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
}
}

internal extension ExplicitDependencyBuildPlanner {
func explainDependency(_ dependencyModuleName: String) throws -> [[ModuleDependencyId]]? {
return try dependencyGraph.explainDependency(dependencyModuleName: dependencyModuleName)
}
}

// InterModuleDependencyGraph printing, useful for debugging
internal extension InterModuleDependencyGraph {
func prettyPrintString() throws -> String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,48 @@ extension InterModuleDependencyGraph {
details: .clang(combinedModuleDetails))
}
}

internal extension InterModuleDependencyGraph {
func explainDependency(dependencyModuleName: String) throws -> [[ModuleDependencyId]]? {
guard modules.contains(where: { $0.key.moduleName == dependencyModuleName }) else { return nil }
var results = [[ModuleDependencyId]]()
try findAllPaths(source: .swift(mainModuleName),
to: dependencyModuleName,
pathSoFar: [.swift(mainModuleName)],
results: &results)
return Array(results)
}


private func findAllPaths(source: ModuleDependencyId,
to moduleName: String,
pathSoFar: [ModuleDependencyId],
results: inout [[ModuleDependencyId]]) throws {
let sourceInfo = try moduleInfo(of: source)
// If the source is our target, we are done
guard source.moduleName != moduleName else {
// If the source is a target Swift module, also check if it
// depends on a corresponding Clang module with the same name.
// If it does, add it to the path as well.
var completePath = pathSoFar
if let dependencies = sourceInfo.directDependencies,
dependencies.contains(.clang(moduleName)) {
completePath.append(.clang(moduleName))
}
results.append(completePath)
return
}

// If no more dependencies, this is a leaf node, we are done
guard let dependencies = sourceInfo.directDependencies else {
return
}

for dependency in dependencies {
try findAllPaths(source: dependency,
to: moduleName,
pathSoFar: pathSoFar + [dependency],
results: &results)
}
}
}
11 changes: 7 additions & 4 deletions Sources/SwiftDriver/Jobs/Planning.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ extension Driver {
IncrementalCompilationState.InitialStateForPlanning?)
throws -> InterModuleDependencyGraph? {
let interModuleDependencyGraph: InterModuleDependencyGraph?
if parsedOptions.contains(.driverExplicitModuleBuild) {
if parsedOptions.contains(.driverExplicitModuleBuild) ||
parsedOptions.contains(.explainModuleDependency) {
interModuleDependencyGraph =
try initialIncrementalState?.maybeUpToDatePriorInterModuleDependencyGraph ??
gatherModuleDependencies()
Expand Down Expand Up @@ -195,13 +196,15 @@ extension Driver {
dependencyGraph: InterModuleDependencyGraph?,
addJob: (Job) -> Void)
throws {
// If asked, add jobs to precompile module dependencies
guard parsedOptions.contains(.driverExplicitModuleBuild) else { return }
guard let resolvedDependencyGraph = dependencyGraph else {
fatalError("Attempting to plan explicit dependency build without a dependency graph")
return
}
let modulePrebuildJobs =
try generateExplicitModuleDependenciesJobs(dependencyGraph: resolvedDependencyGraph)
// If asked, add jobs to precompile module dependencies. Otherwise exit.
// We may have a dependency graph but not be required to add pre-compile jobs to the build plan,
// for example when `-explain-dependency` is being used.
guard parsedOptions.contains(.driverExplicitModuleBuild) else { return }
modulePrebuildJobs.forEach(addJob)
}

Expand Down
14 changes: 12 additions & 2 deletions Sources/SwiftOptions/Options.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ extension Option {
public static let accessNotesPathEQ: Option = Option("-access-notes-path=", .joined, alias: Option.accessNotesPath, attributes: [.frontend, .argumentIsPath])
public static let accessNotesPath: Option = Option("-access-notes-path", .separate, attributes: [.frontend, .argumentIsPath], helpText: "Specify YAML file to override attributes on Swift declarations in this module")
public static let aliasModuleNamesInModuleInterface: Option = Option("-alias-module-names-in-module-interface", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "When emitting a module interface, disambiguate modules using distinct alias names")
public static let allowUnstableCacheKeyForTesting: Option = Option("-allow-unstable-cache-key-for-testing", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Allow compilation caching with unstable inputs for testing purpose")
public static let allowableClient: Option = Option("-allowable-client", .separate, attributes: [.frontend], metaVar: "<vers>", helpText: "Module names that are allowed to import this module")
public static let alwaysCompileOutputFiles: Option = Option("-always-compile-output-files", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Always compile output files even it might not change the results")
public static let analyzeRequirementMachine: Option = Option("-analyze-requirement-machine", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Print out requirement machine statistics at the end of the compilation job")
Expand Down Expand Up @@ -59,6 +60,8 @@ extension Option {
public static let buildModuleFromParseableInterface: Option = Option("-build-module-from-parseable-interface", .flag, alias: Option.compileModuleFromInterface, attributes: [.helpHidden, .frontend, .noDriver], group: .modes)
public static let bypassBatchModeChecks: Option = Option("-bypass-batch-mode-checks", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Bypass checks for batch-mode errors.")
public static let candidateModuleFile: Option = Option("-candidate-module-file", .separate, attributes: [.helpHidden, .frontend, .noDriver], metaVar: "<path>", helpText: "Specify Swift module may be ready to use for an interface")
public static let casFs: Option = Option("-cas-fs", .separate, attributes: [.frontend, .noDriver], metaVar: "<cas-id>", helpText: "Root CASID for CAS FileSystem")
public static let casPath: Option = Option("-cas-path", .separate, attributes: [.frontend], metaVar: "<path>", helpText: "Path to CAS")
public static let checkApiAvailabilityOnly: Option = Option("-check-api-availability-only", .flag, attributes: [.helpHidden, .frontend, .noInteractive], helpText: "Deprecated, has no effect")
public static let checkOnoneCompleteness: Option = Option("-check-onone-completeness", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Print errors if the compile OnoneSupport module is missing symbols")
public static let clangBuildSessionFile: Option = Option("-clang-build-session-file", .separate, attributes: [.frontend, .argumentIsPath], helpText: "Use the last modification time of <file> as the underlying Clang build session timestamp")
Expand All @@ -81,7 +84,7 @@ extension Option {
public static let CrossModuleOptimization: Option = Option("-cross-module-optimization", .flag, attributes: [.helpHidden, .frontend], helpText: "Perform cross-module optimization")
public static let crosscheckUnqualifiedLookup: Option = Option("-crosscheck-unqualified-lookup", .flag, attributes: [.frontend, .noDriver], helpText: "Compare legacy DeclContext- to ASTScope-based unqualified name lookup (for debugging)")
public static let cxxInteropGettersSettersAsProperties: Option = Option("-cxx-interop-getters-setters-as-properties", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Import getters and setters as computed properties in Swift")
public static let cxxInteroperabilityMode: Option = Option("-cxx-interoperability-mode=", .joined, attributes: [.frontend, .moduleInterface], helpText: "Enables C++ interoperability; requires compatbility version to be specified.")
public static let cxxInteroperabilityMode: Option = Option("-cxx-interoperability-mode=", .joined, attributes: [.frontend, .moduleInterface], helpText: "Enables C++ interoperability; pass 'default' to enable or 'off' to disable")
public static let c: Option = Option("-c", .flag, alias: Option.emitObject, attributes: [.frontend, .noInteractive], group: .modes)
public static let debugAssertAfterParse: Option = Option("-debug-assert-after-parse", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Force an assertion failure after parsing", group: .debugCrash)
public static let debugAssertImmediately: Option = Option("-debug-assert-immediately", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Force an assertion failure immediately", group: .debugCrash)
Expand Down Expand Up @@ -336,6 +339,7 @@ extension Option {
public static let enableBatchMode: Option = Option("-enable-batch-mode", .flag, attributes: [.helpHidden, .frontend, .noInteractive], helpText: "Enable combining frontend jobs into batches")
public static let enableBridgingPch: Option = Option("-enable-bridging-pch", .flag, attributes: [.helpHidden], helpText: "Enable automatic generation of bridging PCH files")
public static let enableBuiltinModule: Option = Option("-enable-builtin-module", .flag, attributes: [.frontend, .moduleInterface], helpText: "Enables the explicit import of the Builtin module")
public static let enableCas: Option = Option("-enable-cas", .flag, attributes: [.frontend, .noDriver], helpText: "Enable CAS for swift-frontend")
public static let enableCollocateMetadataFunctions: Option = Option("-enable-collocate-metadata-functions", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Enable collocate metadata functions")
public static let enableColocateTypeDescriptors: Option = Option("-enable-colocate-type-descriptors", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Enable colocate type descriptors")
public static let enableConformanceAvailabilityErrors: Option = Option("-enable-conformance-availability-errors", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Diagnose conformance availability violations as errors")
Expand Down Expand Up @@ -424,13 +428,14 @@ extension Option {
public static let driverExperimentalExplicitModuleBuild: Option = Option("-experimental-explicit-module-build", .flag, alias: Option.driverExplicitModuleBuild, attributes: [.helpHidden], helpText: "Prebuild module dependencies to make them explicit")
public static let experimentalHermeticSealAtLink: Option = Option("-experimental-hermetic-seal-at-link", .flag, attributes: [.helpHidden, .frontend], helpText: "Library code can assume that all clients are visible at linktime, and aggressively strip unused code")
public static let experimentalOneWayClosureParams: Option = Option("-experimental-one-way-closure-params", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Enable experimental support for one-way closure parameters")
public static let ExperimentalPerformanceAnnotations: Option = Option("-experimental-performance-annotations", .flag, attributes: [.helpHidden, .frontend], helpText: "Enable experimental performance annotations")
public static let ExperimentalPerformanceAnnotations: Option = Option("-experimental-performance-annotations", .flag, attributes: [.helpHidden, .frontend], helpText: "Deprecated, has no effect")
public static let experimentalPrintFullConvention: Option = Option("-experimental-print-full-convention", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "When emitting a module interface or SIL, emit additional @convention arguments, regardless of whether they were written in the source. Also requires -use-clang-function-types to be enabled.")
public static let experimentalSkipAllFunctionBodies: Option = Option("-experimental-skip-all-function-bodies", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Skip type-checking function bodies and all SIL generation")
public static let experimentalSkipNonInlinableFunctionBodiesWithoutTypes: Option = Option("-experimental-skip-non-inlinable-function-bodies-without-types", .flag, attributes: [.helpHidden, .frontend], helpText: "Skip work on non-inlinable function bodies that do not declare nested types")
public static let experimentalSkipNonInlinableFunctionBodies: Option = Option("-experimental-skip-non-inlinable-function-bodies", .flag, attributes: [.helpHidden, .frontend], helpText: "Skip type-checking and SIL generation for non-inlinable function bodies")
public static let experimentalSpiImports: Option = Option("-experimental-spi-imports", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Enable experimental support for SPI imports")
public static let experimentalSpiOnlyImports: Option = Option("-experimental-spi-only-imports", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Enable use of @_spiOnly imports")
public static let explainModuleDependency: Option = Option("-explain-module-dependency", .separate, attributes: [], helpText: "Emit remark/notes describing why compilaiton may depend on a module with a given name.")
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'")
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")
public static let driverExplicitModuleBuild: Option = Option("-explicit-module-build", .flag, attributes: [.helpHidden], helpText: "Prebuild module dependencies to make them explicit")
Expand Down Expand Up @@ -800,6 +805,7 @@ extension Option {
Option.accessNotesPathEQ,
Option.accessNotesPath,
Option.aliasModuleNamesInModuleInterface,
Option.allowUnstableCacheKeyForTesting,
Option.allowableClient,
Option.alwaysCompileOutputFiles,
Option.analyzeRequirementMachine,
Expand Down Expand Up @@ -832,6 +838,8 @@ extension Option {
Option.buildModuleFromParseableInterface,
Option.bypassBatchModeChecks,
Option.candidateModuleFile,
Option.casFs,
Option.casPath,
Option.checkApiAvailabilityOnly,
Option.checkOnoneCompleteness,
Option.clangBuildSessionFile,
Expand Down Expand Up @@ -1109,6 +1117,7 @@ extension Option {
Option.enableBatchMode,
Option.enableBridgingPch,
Option.enableBuiltinModule,
Option.enableCas,
Option.enableCollocateMetadataFunctions,
Option.enableColocateTypeDescriptors,
Option.enableConformanceAvailabilityErrors,
Expand Down Expand Up @@ -1204,6 +1213,7 @@ extension Option {
Option.experimentalSkipNonInlinableFunctionBodies,
Option.experimentalSpiImports,
Option.experimentalSpiOnlyImports,
Option.explainModuleDependency,
Option.explicitDependencyGraphFormat,
Option.explicitInterfaceModuleBuild,
Option.driverExplicitModuleBuild,
Expand Down
1 change: 1 addition & 0 deletions Sources/makeOptions/makeOptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ enum SwiftFlags {
SwiftAPIDigesterOption = (1 << 17),
NewDriverOnlyOption = (1 << 18),
ModuleInterfaceOptionIgnorable = (1 << 19),
ModuleInterfaceOptionIgnorablePrivate = (1 << 20)
};

static std::set<std::string> swiftKeywords = { "internal", "static" };
Expand Down
Loading