-
Notifications
You must be signed in to change notification settings - Fork 267
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
Reload Swift Package when new file creation is indicated by DidChangeWatchedFileNotification
#443
Reload Swift Package when new file creation is indicated by DidChangeWatchedFileNotification
#443
Conversation
|
@swift-ci Please test |
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.
reloadPackages needs to be taught to notify the build system delegate of the settings changes using delegate.fileBuildSettingsChanged for all the registered URLs (and we'll need to start keeping a list of the registered URLs in the SwiftPM build system). That will fix the settings in files that were already open before the change.
Excited to see this happening!
| @@ -125,6 +125,11 @@ public final class BuildSystemManager { | |||
| self.fallbackSettingsTimeout = fallbackSettingsTimeout | |||
| self.buildSystem?.delegate = self | |||
| } | |||
|
|
|||
| public func filesDidChange(_ events: [FileEvent]) { | |||
| buildSystem?.filesDidChange(events) | |||
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.
This should probably be wrapped in queue.async. Otherwise we are doing settings queries on the build system manager queue, but triggering file changes on the main server queue, which is racy. Alternatively we could make the SwiftPM build system itself thread-safe, but that seems like more boilerplate. Hopefully we can switch to swift concurrency and it won't be so annoying.
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.
Also running the BuildSystem.filesDidChange code async is beneficial I think so we don’t block the queue that receives the LSP messages.
|
Also, don't forget to remove this from the README:
😸 |
|
|
||
| let newFile = ws.testLoc("newFile:call") | ||
|
|
||
| // Check that we don't get cross-file code completion befor we sent a `DidChangeWatchedFilesNotification` to make sure we didn't include the file in the initial retrieval of build settings. |
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.
before we send
Yep, this is great! :D Thanks for working on it |
8503a3f
to
ee5dbf4
Compare
|
Thanks for the feedback @benlangmuir and @DavidGoldman. I addressed the inline comments by squashing the first commit and introduced notification of the build system delegate on package reloading in a second commit. The points still open are:
|
ee5dbf4
to
b156d6f
Compare
|
@swift-ci Please test |
| private func isFileEventRelevant(event: FileEvent) -> Bool { | ||
| // FIXME: fileRules should also include self.workspace.additionalFileRules and toolsVersion should be self.workspace.currentToolsVersion | ||
| let fileRules = FileRuleDescription.builtinRules | ||
| let toolsVersion = ToolsVersion.currentToolsVersion | ||
|
|
||
| guard let fileURL = event.uri.fileURL else { | ||
| return false | ||
| } | ||
| let filePath = AbsolutePath(fileURL.path) | ||
| switch event.type { | ||
| case .created, .deleted: | ||
| return fileRules.contains(where: { $0.match(path: filePath, toolsVersion: toolsVersion) }) | ||
| case .changed: | ||
| return false | ||
| default: // Unknown file change type | ||
| return false | ||
| } | ||
| } |
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.
@neonichu Could you take a look whether this code makes sense? The intention is to match only files which might impact the build settings computed by SwiftPM. If you think this is correct, I’ll open a PR on SwiftPM to make Workspace.additionalFileRules and Workspace.currentToolsVersion public.
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.
I think it might be better to find a way to refactor TargetSourcesBuilder in SwiftPM to provide this kind of information instead of interacting with file rules directly.
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.
Do you have any particular path in mind how Workspace should expose the TargetSourcesBuilder? AFAICT it only creates an ad-hoc PackageBuilder, which in turn creates an ad-hoc TargetSourcesBuilder.
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.
That's a good question, do you have an existing package graph at this point from which you could get a TargetDescription to pass down? If yes, maybe just a new function on Workspace that gets package identity etc and the target description and returns a configured TargetSourcesBuilder? Additionally, I think target sources builder would need a function that would decide whether a singular file path is part of the given package or not.
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.
I do, but I don’t seem to be able to get a TargetDescription from a PackageGraph. AFAICT it only stores ResolvedTargets and Targets but there’s no way to go back to a TargetDescription. Also, I couldn’t really figure out where TargetDescriptions are usually getting created. The places I found where the TargetDescription constructor is being called were the following and none of these seemed to match general-purpose target creation:
ManifestJSONParser.parseTarget(I don’t know why target creation should go through JSON serialization)ManifestLoader.loadFile(deals with legacy system packages)PackageBuilder.createSnippetTargets(I don’t exactly know what snippets are, but they don’t seem to be the general target creation mechanism either)
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.
Ah you're right, I wonder whether we could change TargetSourcesBuilder to take a ResolvedTarget instead. This is turning out to be a bit of a bigger change than I originally thought.
I guess another option could be that we add a new function to Workspace to which you pass all the info you have and for now it would just do a simple check using file rules, but we could later refine it to be more accurate. That way we can decouple your PR from making bigger changes to SwiftPM.
BTW, ManifestJSONParser.parseTarget is the general purpose target creation, because it is just done as part of manifest loading. We run the compiled manifest, it produces JSON and then we load that JSON.
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.
After some offline discussion with @neonichu, we have decided to only watch files matching extensions specified in FileRuleDescription.builtinRules in the client. Additionally, I’m adding a function to SwiftPM that will allow more granular filtering based on e.g. the directory structure of a package in the future: swiftlang/swift-package-manager#4016
| if events.contains(where: { isFileEventRelevant(event: $0) }) { | ||
| // TODO: It should not be necessary to reload the entire package just to get build settings for one file. | ||
| orLog { | ||
| try self.reloadPackage() |
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.
Do we need to do this on self.queue as well?
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.
It doesn’t need to be, because BuildSystemManager is already guarding against race conditions in BuildSystemManager.filesDidChange by informing the build systems about file changes serially on its own queue.
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.
I think it does since registerForChangeNotifications (on queue) could be called right before filesDidChange, and then reloadPackage (off queue) could mutate packageGraph/fileToTarget/sourceDirToTarget while registerForChangeNotifications is reading it on the queue, right? So we need some way to synchronize that.
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.
Oh, yes, we also need to guard packageGraph, fileToTarget and sourceDirToTarget against race conditions. I only thought of watchedFiles and delegate before. Added the guards in 2e3b783.
533d3b3
to
49b2518
Compare
|
swiftlang/swift-package-manager#4016 @swift-ci Please test |
| /// - `packageGraph` | ||
| /// - `fileToTarget` | ||
| /// - `sourceDirToTarget` | ||
| let queue: DispatchQueue = .init(label: "SwiftPMWorkspace.queue", qos: .background) |
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.
Just noticed this: we probably want utility not background. Settings queries block language features, but they're done asyncrhonously so there is no qos donation.
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.
I wonder how much of a difference it makes in practice. But yes, the description of .utility does seem to match more closely to what we are doing.
| @@ -56,4 +56,88 @@ final class SwiftPMIntegrationTests: XCTestCase { | |||
| deprecated: false), | |||
| ]) | |||
| } | |||
|
|
|||
| func testAddFile() throws { | |||
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.
Need to update Linux tests. Incidentally, I wonder if we could switch to automatic test discovery on Linux at this point - it's been around for a while now.
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.
Thanks for catching this. I’ll try to switch to automatic test discovery in a follow-up PR.
49b2518
to
7d957e6
Compare
|
swiftlang/swift-package-manager#4016 @swift-ci Please test |
| @@ -21,6 +21,7 @@ import SKSupport | |||
| import TSCBasic | |||
| import TSCLibc | |||
| import TSCUtility | |||
| import PackageLoading | |||
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.
It would be nice if we could contain all imports of SwiftPM modules to the SwiftPM build system file to keep things contained. No need to change it in this PR though.
…WatchedFileNotification Implement rudementary support for `DidChangeWatchedFileNotification` for SwiftPM projects: When a file is added, reload the Swift package to compute build settings for it. This enables proper semantic functionality added to the project after the LSP server was started. Resolves SR-15633
…kage is reloaded
…space by queue These properties might be modified by `reloadPackage` (which can be called asynchronously) and thus need to be guareded by a queue.
7d957e6
to
9fb40da
Compare
|
swiftlang/swift-package-manager#4016 @swift-ci Please test |
Implement rudementary support for
DidChangeWatchedFileNotificationfor SwiftPM projects: When a file is added, reload the Swift package to compute build settings for the new file.This enables proper semantic functionality added to the project after the LSP server was started.
Resolves rdar://87149128 SR-15633.