-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Symbol graph support for swiftbuild build system #8923
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
Changes from 4 commits
7807e68
b17042c
6ac1fbc
b7b01f2
c797b79
a386fd7
cf9b61c
d28059b
34c0372
542ddb3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,54 +52,76 @@ struct DumpSymbolGraph: AsyncSwiftCommand { | |
// | ||
// We turn build manifest caching off because we need the build plan. | ||
let buildSystem = try await swiftCommandState.createBuildSystem( | ||
explicitBuildSystem: .native, | ||
// We are enabling all traits for dumping the symbol graph. | ||
traitConfiguration: .init(enableAllTraits: true), | ||
cacheBuildManifest: false | ||
) | ||
try await buildSystem.build() | ||
let buildResult = try await buildSystem.build(subset: .allExcludingTests, buildOutputs: [.symbolGraph, .buildPlan]) | ||
|
||
// Configure the symbol graph extractor. | ||
let symbolGraphExtractor = try SymbolGraphExtract( | ||
fileSystem: swiftCommandState.fileSystem, | ||
tool: swiftCommandState.getTargetToolchain().getSymbolGraphExtract(), | ||
observabilityScope: swiftCommandState.observabilityScope, | ||
skipSynthesizedMembers: skipSynthesizedMembers, | ||
minimumAccessLevel: minimumAccessLevel, | ||
skipInheritedDocs: skipInheritedDocs, | ||
includeSPISymbols: includeSPISymbols, | ||
emitExtensionBlockSymbols: extensionBlockSymbolBehavior == .emitExtensionBlockSymbols, | ||
outputFormat: .json(pretty: prettyPrint) | ||
) | ||
let symbolGraphDirectory = try swiftCommandState.productsBuildParameters.dataPath.appending("symbolgraph") | ||
|
||
// Run the tool once for every library and executable target in the root package. | ||
let buildPlan = try buildSystem.buildPlan | ||
let modulesGraph = try await buildSystem.getPackageGraph() | ||
let symbolGraphDirectory = buildPlan.destinationBuildParameters.dataPath.appending("symbolgraph") | ||
for description in buildPlan.buildModules { | ||
guard description.module.type == .library, | ||
modulesGraph.rootPackages[description.package.id] != nil | ||
else { | ||
continue | ||
} | ||
let fs = swiftCommandState.fileSystem | ||
|
||
try? fs.removeFileTree(symbolGraphDirectory) | ||
try fs.createDirectory(symbolGraphDirectory, recursive: true) | ||
|
||
print("-- Emitting symbol graph for", description.module.name) | ||
let result = try symbolGraphExtractor.extractSymbolGraph( | ||
for: description, | ||
outputRedirection: .collect(redirectStderr: true), | ||
outputDirectory: symbolGraphDirectory, | ||
verboseOutput: swiftCommandState.logLevel <= .info | ||
if buildResult.symbolGraph { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be nice if the BuildResult could include the symbol graph paths themselves so we can compute those at the build system layer instead of in the dump command |
||
// The build system produced symbol graphs for us, one for each target. | ||
let buildPath = try swiftCommandState.productsBuildParameters.buildPath | ||
|
||
// Copy the symbol graphs from the target-specific locations to the single output directory | ||
for rootPackage in try await buildSystem.getPackageGraph().rootPackages { | ||
for module in rootPackage.modules { | ||
if case let sgDir = buildPath.appending(components: "\(try swiftCommandState.productsBuildParameters.triple.archName)", "\(module.name).symbolgraphs"), fs.exists(sgDir) { | ||
for sgFile in try fs.getDirectoryContents(sgDir) { | ||
try fs.copy(from: sgDir.appending(components: sgFile), to: symbolGraphDirectory.appending(sgFile)) | ||
} | ||
} | ||
} | ||
} | ||
} else if let buildPlan = buildResult.buildPlan { | ||
// Otherwise, with a build plan we can run the symbol graph extractor tool on the built targets. | ||
let symbolGraphExtractor = try SymbolGraphExtract( | ||
fileSystem: swiftCommandState.fileSystem, | ||
tool: swiftCommandState.getTargetToolchain().getSymbolGraphExtract(), | ||
observabilityScope: swiftCommandState.observabilityScope, | ||
skipSynthesizedMembers: skipSynthesizedMembers, | ||
minimumAccessLevel: minimumAccessLevel, | ||
skipInheritedDocs: skipInheritedDocs, | ||
includeSPISymbols: includeSPISymbols, | ||
emitExtensionBlockSymbols: extensionBlockSymbolBehavior == .emitExtensionBlockSymbols, | ||
outputFormat: .json(pretty: prettyPrint) | ||
) | ||
|
||
if result.exitStatus != .terminated(code: 0) { | ||
let commandline = "\nUsing commandline: \(result.arguments)" | ||
switch result.output { | ||
case .success(let value): | ||
swiftCommandState.observabilityScope.emit(error: "Failed to emit symbol graph for '\(description.module.c99name)': \(String(decoding: value, as: UTF8.self))\(commandline)") | ||
case .failure(let error): | ||
swiftCommandState.observabilityScope.emit(error: "Internal error while emitting symbol graph for '\(description.module.c99name)': \(error)\(commandline)") | ||
// Run the tool once for every library and executable target in the root package. | ||
let modulesGraph = try await buildSystem.getPackageGraph() | ||
for description in buildPlan.buildModules { | ||
guard description.module.type == .library, | ||
modulesGraph.rootPackages[description.package.id] != nil | ||
else { | ||
continue | ||
} | ||
|
||
print("-- Emitting symbol graph for", description.module.name) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: should we be using ObservabilityScope for emitting message to the console? |
||
let result = try symbolGraphExtractor.extractSymbolGraph( | ||
for: description, | ||
outputRedirection: .collect(redirectStderr: true), | ||
outputDirectory: symbolGraphDirectory, | ||
verboseOutput: swiftCommandState.logLevel <= .info | ||
) | ||
|
||
if result.exitStatus != .terminated(code: 0) { | ||
let commandline = "\nUsing commandline: \(result.arguments)" | ||
switch result.output { | ||
case .success(let value): | ||
swiftCommandState.observabilityScope.emit(error: "Failed to emit symbol graph for '\(description.module.c99name)': \(String(decoding: value, as: UTF8.self))\(commandline)") | ||
case .failure(let error): | ||
swiftCommandState.observabilityScope.emit(error: "Internal error while emitting symbol graph for '\(description.module.c99name)': \(error)\(commandline)") | ||
} | ||
} | ||
} | ||
} else { | ||
throw InternalError("Build system \(buildSystem) cannot produce a symbol graph.") | ||
} | ||
|
||
print("Files written to", symbolGraphDirectory.pathString) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: From my understanding, a build plan is a native build system concept, but I believe the API Diff supports SwiftBuild Build System. Should we be aborting here? Also, should the destinationBuildParameter and
toolsBuildParameters
always be available as "build outputs"?