Skip to content

Emit compiler diagnostics #3867

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

krzyzanowskim
Copy link
Contributor

Check for error,warning,note and remark in a format compatible with clang and swiftc, see: https://github.com/apple/swift/blob/main/docs/Diagnostics.md

Motivation:

Emit compiler diagnostics, normally redirected to output, now consumable by libSwiftPM users. https://bugs.swift.org/browse/SR-15479

Check for error,warning,note and remark in a format compatible with clang and swiftc, see: https://github.com/apple/swift/blob/main/docs/Diagnostics.md
@krzyzanowskim krzyzanowskim force-pushed the marcin/emit-output-diagnostics branch from 411c1c6 to 4fbbeef Compare November 13, 2021 15:20
@tomerd
Copy link
Contributor

tomerd commented Nov 15, 2021

@swift-ci please smoke test

@neonichu
Copy link
Contributor

Could you please add a test for this?

for match in regex.matchGroups(in: output) {
self.errorMessagesByTarget[parser.targetName] = (self.errorMessagesByTarget[parser.targetName] ?? []) + [match[0]]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove deal line

}
}

let regex = try! RegEx(pattern: #"(error:[^\n]*)\n.*"#, options: .dotMatchesLineSeparators)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try! safe?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

safe. not my line though, I just moved it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, with a literal string to a RegEx, try! is fine — it's unfortunate but if we get support for compiler-checked RegEx literals in Swift some day, it will go away.

do {
let regex = try! RegEx(pattern: #"^(.*):(\d+):(\d+):\s*(error|warning|note|remark)\S?\s*(.*)$"#, options: .anchorsMatchLines)
for match in regex.matchGroups(in: output) {
if let filePath = try? AbsolutePath(validating: match[0]) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we guard for length of match? seems safer?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it's necessary in this case, since it comes from a RegEx created from a string literal. There will always be five matches unless there's a bug in matchGroups(in:). Perhaps we can assert that, but it doesn't seem as if there is any runtime condition here that could cause this to fail.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hopefully at some point Swift will have support for regular expressions where the matches can be returned as tuples so the type system knows how many there are.

desc += ":\(line)"
}

if let column = column {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if let column = self.column reads better

}

public var description: String {
"\(self.file)\(self.line?.description.appending(" ") ?? "")"
var desc = self.file.description
if let line = line {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if let line = self.line reads better

@tomerd
Copy link
Contributor

tomerd commented Nov 16, 2021

this is a nice improvement, thanks for putting the PR together @krzyzanowskim. we need a couple of tests, and one thing to confirm is this won't print errors/warnings multiple times now since I think we are printing the compiler output in L609?

cc @abertelrud

@tomerd tomerd self-assigned this Nov 16, 2021
@@ -859,13 +859,24 @@ extension ObservabilityMetadata {
public struct FileLocation: Equatable, CustomStringConvertible {
public let file: AbsolutePath
public let line: Int?
public let column: Int?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eventually I think we will want line/column ranges here (as the .dia format supports) for that's for a later PR. Just a note about the direction I think we'll end up going here.

@abertelrud
Copy link
Contributor

this is a nice improvement, thanks for putting the PR together @krzyzanowskim. we need a couple of tests, and one thing to confirm is this won't print errors/warnings multiple times now since I think we are printing the compiler output in L609?

cc @abertelrud

That's a really good point — I hadn't noticed that. It does look as if the message would be emitted twice. For other IDEs I've worked on the pattern is usually to try to recognize it as a diagnostics and if that fails to just print it.

Either way, a unit test would show for sure whether it's printed once or twice now.

@krzyzanowskim
Copy link
Contributor Author

I think we are printing the compiler output in L609?

writing to output is parallel to diagnostics. emitting diagnostics is derived from the output as we parse the output. I use it as library and it's handy to have it in diagnostics and in the output stream. Does the SPM CLI print diagnostics too?

@abertelrud
Copy link
Contributor

I think we are printing the compiler output in L609?

writing to output is parallel to diagnostics. emitting diagnostics is derived from the output as we parse the output. I use it as library and it's handy to have it in diagnostics and in the output stream. Does the SPM CLI print diagnostics too?

Yes, it looks like the SwiftToolObservability struct prints any emitted diagnostics (and I think all observability events emitted during the build reach up to that level).

@krzyzanowskim
Copy link
Contributor Author

Yes, it looks like the SwiftToolObservability struct prints any emitted diagnostics (and I think all observability events emitted during the build reach up to that level).

An alternative is to either emit diagnostic or write to the output stream, but not both? while it would work for SwiftToolObservability, not so much for everyone else, am I right? If I'd like to get a build output and save it to eg. a file, I need to merge output stream and diagnostics into a single stream to get it all. Thoughts?

@abertelrud
Copy link
Contributor

Yes, it looks like the SwiftToolObservability struct prints any emitted diagnostics (and I think all observability events emitted during the build reach up to that level).

An alternative is to either emit diagnostic or write to the output stream, but not both? while it would work for SwiftToolObservability, not so much for everyone else, am I right? If I'd like to get a build output and save it to eg. a file, I need to merge output stream and diagnostics into a single stream to get it all. Thoughts?

Right, that doesn't seem like a great solution.

Another option would be to use the new ability to associate metadata with diagnostics (this is a new feature of ObservabilityScope) to indicate that it was derived by parsing the output text. Then an observer who is printing text anyway can ignore those diagnostics, while an observer who isn't printing the text can emit them.

@abertelrud
Copy link
Contributor

Yes, it looks like the SwiftToolObservability struct prints any emitted diagnostics (and I think all observability events emitted during the build reach up to that level).

An alternative is to either emit diagnostic or write to the output stream, but not both? while it would work for SwiftToolObservability, not so much for everyone else, am I right? If I'd like to get a build output and save it to eg. a file, I need to merge output stream and diagnostics into a single stream to get it all. Thoughts?

Right, that doesn't seem like a great solution.

Another option would be to use the new ability to associate metadata with diagnostics (this is a new feature of ObservabilityScope) to indicate that it was derived by parsing the output text. Then an observer who is printing text anyway can ignore those diagnostics, while an observer who isn't printing the text can emit them.

It would be clearer in this case if it were the observer that printed regular output text too — I guess in this case the SwiftTool observer will have to rely no line 609 to have already emitted the output, and just not print diagnostics that are tagged as having been parsed from output text.

@tomerd
Copy link
Contributor

tomerd commented Nov 19, 2021

Yes, it looks like the SwiftToolObservability struct prints any emitted diagnostics (and I think all observability events emitted during the build reach up to that level).

An alternative is to either emit diagnostic or write to the output stream, but not both? while it would work for SwiftToolObservability, not so much for everyone else, am I right? If I'd like to get a build output and save it to eg. a file, I need to merge output stream and diagnostics into a single stream to get it all. Thoughts?

Right, that doesn't seem like a great solution.
Another option would be to use the new ability to associate metadata with diagnostics (this is a new feature of ObservabilityScope) to indicate that it was derived by parsing the output text. Then an observer who is printing text anyway can ignore those diagnostics, while an observer who isn't printing the text can emit them.

It would be clearer in this case if it were the observer that printed regular output text too — I guess in this case the SwiftTool observer will have to rely no line 609 to have already emitted the output, and just not print diagnostics that are tagged as having been parsed from output text.

lets take a step back here. @krzyzanowskim I assume in your use case are setting a BuildOperationBuildSystemDelegateHandler and it has an outputStream set? if so, where is that pointing?

@krzyzanowskim
Copy link
Contributor Author

@tomerd I use outputStream as a "raw" build log, and in my case, it goes to the file or to the console, depends on what I need. I treat OutputByteStream as raw data without semantic meaning whatsoever, in contrary to Diagnostics that provide useful information.

@tomerd
Copy link
Contributor

tomerd commented Dec 1, 2021

thanks for the additional info @krzyzanowskim, so in your case it sounds the console output is treated as optional and the diagnostics are the critical part.

@abertelrud @neonichu iiuc the delegate here was designed to pipe the build-system output to the provided stream, to give users information about the build status, etc. is there any important information expected to be included in this beyond what we consider diagnostics? iow, should the entire thing be parsed(*) into diagnostics instead of being printed to the stream as-is?

* with some failure mode protection in which we fall back to dumping it into the stream

@abertelrud
Copy link
Contributor

Yes, it looks to me as if it would be better to just use a single sequence of delegate calls here (and no text stream). Each line of text could really be broken into an info or debug diagnostic if needed — I don't think a fallback is necessary. I don't know how this evolved but I suspect that it started as just a text stream and then some delegate callbacks were added on top to show the animated progress, but that the text stream wasn't removed.

I think the key would be to get rid of the parallel streams, either by making non-issues become info diagnostics or having separate didSeePlainText and didSeeDiagnostic calls (as in the recent plugin changes).

@abertelrud
Copy link
Contributor

I guess I didn't really answer the key question there. Yes, I think the delegate calls about starting and ending certain steps are important because it affects what clients show in terms of status. We can also model those as debug or info diagnostics (perhaps carrying specific metadata) but it seems cleaner to keep a delegate for such build events.

@abertelrud
Copy link
Contributor

So the other approach would then be to have a delegate call to handle the diagnostic in-order with the other calls, but that seems at odds with using ObservabilityScope.emit for diagnostics.

@abertelrud
Copy link
Contributor

Having looked at this a bit more, my specific suggestion for this PR is to:

  • remove the separate outputStream from BuildOperation
  • instead send each line of output through the observabilityScope
    • those that are recognized as errors or warnings have that severity
    • the others get sent as info

That keeps all the output in order, and everything only gets reported once.

I think the only change compared with what you have here would be to send each line of non-matching output as info.

@krzyzanowskim is that something you'd be willing to take on as part of this?

@krzyzanowskim
Copy link
Contributor Author

Having looked at this a bit more, my specific suggestion for this PR

thank you for that. I wasn't sure what's the output of the discussion here was.

@krzyzanowskim is that something you'd be willing to take on as part of this?

yes. I put it on my todo

@abertelrud
Copy link
Contributor

Having looked at this a bit more, my specific suggestion for this PR

thank you for that. I wasn't sure what's the output of the discussion here was.

Yes, sorry about that — I was exploring ideas out loud, but of course that's not very helpful for the PR author.

@krzyzanowskim is that something you'd be willing to take on as part of this?

yes. I put it on my todo

Great, thanks! Please do ping me here if there are problems or ambiguities and I'll try to be more specific with any recommendations.

@neonichu neonichu added the next waiting for next merge window label Apr 11, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
next waiting for next merge window
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants