Skip to content

Commit 6adb223

Browse files
Add project path information to errors & warnings from linker.
rdar://157313332
1 parent 04ca32d commit 6adb223

File tree

6 files changed

+103
-19
lines changed

6 files changed

+103
-19
lines changed

Sources/SWBCore/ProjectModel/Workspace.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,24 @@ public final class Workspace: ProjectModelItem, PIFObject, ReferenceLookupContex
262262
return project
263263
}
264264

265+
public static func projectPath(for target: Target, workspace: Workspace) -> Path {
266+
let project = workspace.project(for: target)
267+
return project.xcodeprojPath
268+
}
269+
270+
public static func projectLocation(for configuredTarget: ConfiguredTarget?, workspaceContext: WorkspaceContext?) -> Diagnostic.Location {
271+
let location: Diagnostic.Location
272+
if let target = configuredTarget?.target,
273+
let workspace = workspaceContext?.workspace {
274+
let path = self.projectPath(for: target, workspace: workspace)
275+
location = Diagnostic.Location.path(path, fileLocation: nil)
276+
}
277+
else {
278+
location = .unknown
279+
}
280+
return location
281+
}
282+
265283
/// Find the projects with the given name.
266284
public func projects(named name: String) -> [Project] {
267285
return projectsByName[name] ?? []

Sources/SWBCore/SpecImplementations/CommandLineToolSpec.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1463,7 +1463,7 @@ open class GenericOutputParser : TaskOutputParser {
14631463
public let toolBasenames: Set<String>
14641464

14651465
/// Buffered output that has not yet been parsed (parsing is line-by-line, so we buffer incomplete lines until we receive more output).
1466-
private var unparsedBytes: ArraySlice<UInt8> = []
1466+
internal var unparsedBytes: ArraySlice<UInt8> = []
14671467

14681468
/// The Diagnostic that is being constructed, possibly across multiple lines of input.
14691469
private var inProgressDiagnostic: Diagnostic?

Sources/SWBCore/SpecImplementations/LinkerSpec.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ open class LinkerSpec : CommandLineToolSpec, @unchecked Sendable {
144144
}
145145

146146
/// Custom entry point for constructing linker tasks.
147-
public func constructLinkerTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, libraries: [LibrarySpecifier], usedTools: [CommandLineToolSpec: Set<FileTypeSpec>]) async {
147+
public func constructLinkerTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, libraries: [LibrarySpecifier], usedTools: [CommandLineToolSpec: Set<FileTypeSpec>], workspaceContext: WorkspaceContext) async {
148148
/// Delegate to the generic machinery.
149149
await delegate.createTask(type: self, ruleInfo: defaultRuleInfo(cbc, delegate), commandLine: commandLineFromTemplate(cbc, delegate, optionContext: discoveredCommandLineToolSpecInfo(cbc.producer, cbc.scope, delegate)).map(\.asString), environment: environmentFromSpec(cbc, delegate), workingDirectory: cbc.producer.defaultWorkingDirectory, inputs: cbc.inputs.map({ $0.absolutePath }), outputs: [cbc.output], action: createTaskAction(cbc, delegate), execDescription: resolveExecutionDescription(cbc, delegate), enableSandboxing: enableSandboxing)
150150
}

Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,60 @@ public struct DiscoveredLdLinkerToolSpecInfo: DiscoveredCommandLineToolSpecInfo
176176
/// Maximum number of undefined symbols to emit. Might be configurable in the future.
177177
let undefinedSymbolCountLimit = 100
178178

179+
override public func write(bytes: ByteString) {
180+
181+
// Split the buffer into slices separated by newlines. The last slice represents the partial last line (there always is one, even if it's empty).
182+
var lines = bytes.split(separator: UInt8(ascii: "\n"), maxSplits: .max, omittingEmptySubsequences: false)
183+
184+
// Any unparsed bytes belong to the first line. We don't want to run `split` over these because it can lead to accidentally quadratic behavior if write is called many times per line.
185+
lines[0] = unparsedBytes + lines[0]
186+
187+
let linesToParse = lines.dropLast()
188+
189+
if let target = self.task.forTarget?.target {
190+
// Linker errors and warnings take more effort to get actionable information out of build logs than those for source files. This is because the linker does not have the path to the project or target name so they are not included in the message.
191+
//
192+
// Prepend the path to the project and target name to any error or warning lines.
193+
// Example input:
194+
// ld: warning: linking with (/System/Library/Frameworks/CoreAudio.framework/Versions/A/CoreAudio) but not using any symbols from it
195+
// Example output:
196+
// /Path/To/ProjectFolder/ProjectName.xcodeproj: TargetName: ld: warning: linking with (/System/Library/Frameworks/CoreAudio.framework/Versions/A/CoreAudio) but not using any symbols from it
197+
198+
let workspace = self.workspaceContext.workspace
199+
let projectPath = Workspace.projectPath(for: target, workspace: workspace)
200+
let targetName = target.name
201+
202+
let processedLines: [ByteString] = linesToParse.map { lineBytes in
203+
let lineString = String(decoding: lineBytes, as: Unicode.UTF8.self)
204+
if lineString.contains(": error:")
205+
|| lineString.contains(": warning:") {
206+
207+
let issueString = "\(projectPath.str): \(targetName): \(lineString)"
208+
return ByteString(encodingAsUTF8: issueString)
209+
}
210+
return ByteString(lineBytes)
211+
}
212+
213+
// Forward the bytes
214+
let processedBytes = ByteString(processedLines.joined(separator: ByteString("\n")))
215+
delegate.emitOutput(processedBytes)
216+
}
217+
else {
218+
// Forward the bytes
219+
let processedBytes = ByteString(linesToParse.joined(separator: ByteString("\n")))
220+
delegate.emitOutput(processedBytes)
221+
}
222+
223+
// Parse any complete lines of output.
224+
for line in linesToParse {
225+
parseLine(line)
226+
}
227+
228+
// Track the last, incomplete line to as the unparsed bytes.
229+
unparsedBytes = lines.last ?? []
230+
}
231+
232+
@discardableResult
179233
override func parseLine<S: Collection>(_ lineBytes: S) -> Bool where S.Element == UInt8 {
180234

181235
// Create a string that we can examine. Use the non-failable constructor, so that we are robust against potentially invalid UTF-8.
@@ -190,11 +244,13 @@ public struct DiscoveredLdLinkerToolSpecInfo: DiscoveredCommandLineToolSpecInfo
190244
}
191245
else if let match = LdLinkerOutputParser.problemMessageRegEx.firstMatch(in: lineString), match[3].hasPrefix("symbol(s) not found") {
192246
// It's time to emit all the symbols. We emit each as a separate message.
247+
let projectLocation = Workspace.projectLocation(for: self.task.forTarget, workspaceContext: self.workspaceContext)
248+
193249
for symbol in undefinedSymbols.prefix(undefinedSymbolCountLimit) {
194-
delegate.diagnosticsEngine.emit(Diagnostic(behavior: .error, location: .unknown, data: DiagnosticData("Undefined symbol: \(symbol)"), appendToOutputStream: false))
250+
delegate.diagnosticsEngine.emit(Diagnostic(behavior: .error, location: projectLocation, data: DiagnosticData("Undefined symbol: \(symbol)"), appendToOutputStream: false))
195251
}
196252
if undefinedSymbols.count > undefinedSymbolCountLimit {
197-
delegate.diagnosticsEngine.emit(Diagnostic(behavior: .note, location: .unknown, data: DiagnosticData("(\(undefinedSymbols.count - undefinedSymbolCountLimit) additional undefined symbols are shown in the transcript"), appendToOutputStream: false))
253+
delegate.diagnosticsEngine.emit(Diagnostic(behavior: .note, location: projectLocation, data: DiagnosticData("(\(undefinedSymbols.count - undefinedSymbolCountLimit) additional undefined symbols are shown in the transcript"), appendToOutputStream: false))
198254
}
199255
collectingUndefinedSymbols = false
200256
undefinedSymbols = []
@@ -213,7 +269,8 @@ public struct DiscoveredLdLinkerToolSpecInfo: DiscoveredCommandLineToolSpecInfo
213269
let severity = match[2].isEmpty ? "error" : match[2]
214270
let behavior = Diagnostic.Behavior(name: severity) ?? .note
215271
let message = match[3].prefix(1).localizedCapitalized + match[3].dropFirst()
216-
let diagnostic = Diagnostic(behavior: behavior, location: .unknown, data: DiagnosticData(message), appendToOutputStream: false)
272+
let projectLocation = Workspace.projectLocation(for: self.task.forTarget, workspaceContext: self.workspaceContext)
273+
let diagnostic = Diagnostic(behavior: behavior, location: projectLocation, data: DiagnosticData(message), appendToOutputStream: false)
217274
delegate.diagnosticsEngine.emit(diagnostic)
218275
}
219276
return true
@@ -320,7 +377,7 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
320377
}
321378
}
322379

323-
override public func constructLinkerTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, libraries: [LibrarySpecifier], usedTools: [CommandLineToolSpec: Set<FileTypeSpec>]) async {
380+
override public func constructLinkerTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, libraries: [LibrarySpecifier], usedTools: [CommandLineToolSpec: Set<FileTypeSpec>], workspaceContext: WorkspaceContext) async {
324381
let resolvedLinkerDriver = Self.resolveLinkerDriver(cbc, usedTools: usedTools)
325382
let linkerDriverLookup: ((MacroDeclaration) -> MacroStringExpression?) = { macro in
326383
switch macro {
@@ -672,7 +729,7 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
672729
await inputs.append(contentsOf: additionalInputDependencies(cbc, delegate, optionContext: discoveredCommandLineToolSpecInfo(cbc.producer, cbc.scope, delegate), lookup: lookup).map(delegate.createNode))
673730

674731
// Add dependencies for any arguments indicating a file path.
675-
Self.addAdditionalDependenciesFromCommandLine(cbc, commandLine, environment, &inputs, &outputs, delegate)
732+
Self.addAdditionalDependenciesFromCommandLine(cbc, commandLine, environment, &inputs, &outputs, delegate, workspaceContext: workspaceContext)
676733

677734
let architecture = cbc.scope.evaluate(BuiltinMacros.arch)
678735
let buildVariant = cbc.scope.evaluate(BuiltinMacros.variant)
@@ -721,15 +778,16 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
721778
delegate.createTask(type: self, dependencyData: dependencyInfo, payload: payload, ruleInfo: defaultRuleInfo(cbc, delegate), commandLine: commandLine, environment: environment, workingDirectory: cbc.producer.defaultWorkingDirectory, inputs: inputs + otherInputs, outputs: outputs, action: delegate.taskActionCreationDelegate.createDeferredExecutionTaskActionIfRequested(userPreferences: cbc.producer.userPreferences), execDescription: resolveExecutionDescription(cbc, delegate), enableSandboxing: enableSandboxing)
722779
}
723780

724-
public static func addAdditionalDependenciesFromCommandLine(_ cbc: CommandBuildContext, _ commandLine: [String], _ environment: EnvironmentBindings, _ inputs: inout [any PlannedNode], _ outputs: inout [any PlannedNode], _ delegate: any TaskGenerationDelegate) {
781+
public static func addAdditionalDependenciesFromCommandLine(_ cbc: CommandBuildContext, _ commandLine: [String], _ environment: EnvironmentBindings, _ inputs: inout [any PlannedNode], _ outputs: inout [any PlannedNode], _ delegate: any TaskGenerationDelegate, workspaceContext: WorkspaceContext) {
725782
guard cbc.scope.evaluate(BuiltinMacros._DISCOVER_COMMAND_LINE_LINKER_INPUTS) else {
726783
return
727784
}
728785

729786
enumerateLinkerCommandLine(arguments: commandLine, handleWl: cbc.scope.evaluate(BuiltinMacros._DISCOVER_COMMAND_LINE_LINKER_INPUTS_INCLUDE_WL)) { arg, value in
730787
func emitDependencyDiagnostic(type: String, node: PlannedPathNode) {
731788
if delegate.userPreferences.enableDebugActivityLogs {
732-
delegate.note("Added \(type) dependency '\(node.path.str)' from command line argument \(arg)", location: .unknown)
789+
let projectLocation = Workspace.projectLocation(for: cbc.producer.configuredTarget, workspaceContext: workspaceContext)
790+
delegate.note("Added \(type) dependency '\(node.path.str)' from command line argument \(arg)", location: projectLocation)
733791
}
734792
}
735793

@@ -1592,7 +1650,7 @@ public final class LibtoolLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @u
15921650
return delegate.taskActionCreationDelegate.createLinkerTaskAction(expandResponseFiles: !useResponseFile)
15931651
}
15941652

1595-
override public func constructLinkerTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, libraries: [LibrarySpecifier], usedTools: [CommandLineToolSpec: Set<FileTypeSpec>]) async {
1653+
override public func constructLinkerTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, libraries: [LibrarySpecifier], usedTools: [CommandLineToolSpec: Set<FileTypeSpec>], workspaceContext: WorkspaceContext) async {
15961654
var inputPaths = cbc.inputs.map({ $0.absolutePath })
15971655
var specialArgs = [String]()
15981656

Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -979,8 +979,10 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F
979979
let linkerInputs = linkerInputNodes.map { FileToBuild(context: context, absolutePath: $0.path) }
980980
let actualOutput = outputPreviewDylib ?? output
981981
let actualCommandOrderingOutputs = !commandOrderingOutputsPreviewDylib.isEmpty ? commandOrderingOutputsPreviewDylib : commandOrderingOutputs
982+
let workspaceContext = context.workspaceContext
982983

983-
await linkerSpec.constructLinkerTasks(CommandBuildContext(producer: context, scope: scope, inputs: linkerInputs, output: actualOutput, commandOrderingInputs: additionalLinkerOrderingInputs, commandOrderingOutputs: actualCommandOrderingOutputs), delegate, libraries: librariesToLink, usedTools: usedTools)
984+
// In SourcesTaskProducer.swift, modify the call:
985+
await linkerSpec.constructLinkerTasks(CommandBuildContext(producer: context, scope: scope, inputs: linkerInputs, output: actualOutput, commandOrderingInputs: additionalLinkerOrderingInputs, commandOrderingOutputs: actualCommandOrderingOutputs), delegate, libraries: librariesToLink, usedTools: usedTools, workspaceContext: workspaceContext)
984986
}
985987

986988
if let outputPreviewDylib, let outputPreviewBlankInjectionDylib {

0 commit comments

Comments
 (0)