From 7734133cd1fc4f84d73cc78b2f0351b227470e39 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Wed, 1 May 2024 11:38:50 -0400 Subject: [PATCH 01/11] Filter backticks in TestItem IDs Backticks in TestItem IDs should be sanitized. Use the new TokenSyntax.identifier when building up the TestItem IDs. --- .../SupportTypes/TestItem.swift | 2 +- .../Swift/SwiftTestingScanner.swift | 28 ++++++--- .../DocumentTestDiscoveryTests.swift | 61 +++++++++++++++++++ 3 files changed, 82 insertions(+), 9 deletions(-) diff --git a/Sources/LanguageServerProtocol/SupportTypes/TestItem.swift b/Sources/LanguageServerProtocol/SupportTypes/TestItem.swift index a764dabc6..2b6040d66 100644 --- a/Sources/LanguageServerProtocol/SupportTypes/TestItem.swift +++ b/Sources/LanguageServerProtocol/SupportTypes/TestItem.swift @@ -68,7 +68,7 @@ public struct TestItem: ResponseType, Equatable { tags: [TestTag] ) { self.id = id - self.label = label + self.label = id == label ? self.id : label self.description = description self.sortText = sortText self.disabled = disabled diff --git a/Sources/SourceKitLSP/Swift/SwiftTestingScanner.swift b/Sources/SourceKitLSP/Swift/SwiftTestingScanner.swift index 31f640fef..c5d731f28 100644 --- a/Sources/SourceKitLSP/Swift/SwiftTestingScanner.swift +++ b/Sources/SourceKitLSP/Swift/SwiftTestingScanner.swift @@ -269,15 +269,24 @@ final class SyntacticSwiftTestingTestScanner: SyntaxVisitor { } override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind { - return visitTypeOrExtensionDecl(node, typeNames: [node.name.text]) + guard let identifier = node.name.identifier else { + return .skipChildren + } + return visitTypeOrExtensionDecl(node, typeNames: [identifier.name]) } override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { - return visitTypeOrExtensionDecl(node, typeNames: [node.name.text]) + guard let identifier = node.name.identifier else { + return .skipChildren + } + return visitTypeOrExtensionDecl(node, typeNames: [identifier.name]) } override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { - return visitTypeOrExtensionDecl(node, typeNames: [node.name.text]) + guard let identifier = node.name.identifier else { + return .skipChildren + } + return visitTypeOrExtensionDecl(node, typeNames: [identifier.name]) } override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { @@ -289,7 +298,10 @@ final class SyntacticSwiftTestingTestScanner: SyntaxVisitor { } override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { - return visitTypeOrExtensionDecl(node, typeNames: [node.name.text]) + guard let identifier = node.name.identifier else { + return .skipChildren + } + return visitTypeOrExtensionDecl(node, typeNames: [identifier.name]) } override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { @@ -297,7 +309,7 @@ final class SyntacticSwiftTestingTestScanner: SyntaxVisitor { .compactMap { $0.as(AttributeSyntax.self) } .first { $0.isNamed("Test", inModuleNamed: "Testing") } - guard let testAttribute else { + guard let testAttribute, let identifier = node.name.identifier else { return .skipChildren } let attributeData = TestingAttributeData(attribute: testAttribute) @@ -306,7 +318,7 @@ final class SyntacticSwiftTestingTestScanner: SyntaxVisitor { } let name = - node.name.text + "(" + node.signature.parameterClause.parameters.map { "\($0.firstName.text):" }.joined() + ")" + identifier.name + "(" + node.signature.parameterClause.parameters.map { "\($0.firstName.identifier?.name ?? $0.firstName.text):" }.joined() + ")" let range = snapshot.absolutePositionRange( of: node.positionAfterSkippingLeadingTrivia.. Date: Mon, 3 Jun 2024 13:27:39 -0400 Subject: [PATCH 02/11] Cleanup unnecessary code --- Sources/LanguageServerProtocol/SupportTypes/TestItem.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/LanguageServerProtocol/SupportTypes/TestItem.swift b/Sources/LanguageServerProtocol/SupportTypes/TestItem.swift index 2b6040d66..a764dabc6 100644 --- a/Sources/LanguageServerProtocol/SupportTypes/TestItem.swift +++ b/Sources/LanguageServerProtocol/SupportTypes/TestItem.swift @@ -68,7 +68,7 @@ public struct TestItem: ResponseType, Equatable { tags: [TestTag] ) { self.id = id - self.label = id == label ? self.id : label + self.label = label self.description = description self.sortText = sortText self.disabled = disabled From c79f7dc9a7df48e72ba4dd2fce0003fd385cb07c Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Mon, 3 Jun 2024 15:35:45 -0400 Subject: [PATCH 03/11] Fix lint error --- Sources/SourceKitLSP/Swift/SwiftTestingScanner.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/SourceKitLSP/Swift/SwiftTestingScanner.swift b/Sources/SourceKitLSP/Swift/SwiftTestingScanner.swift index c5d731f28..c2d33835c 100644 --- a/Sources/SourceKitLSP/Swift/SwiftTestingScanner.swift +++ b/Sources/SourceKitLSP/Swift/SwiftTestingScanner.swift @@ -317,8 +317,10 @@ final class SyntacticSwiftTestingTestScanner: SyntaxVisitor { return .skipChildren } - let name = - identifier.name + "(" + node.signature.parameterClause.parameters.map { "\($0.firstName.identifier?.name ?? $0.firstName.text):" }.joined() + ")" + let parameters = node.signature.parameterClause.parameters.map { + "\($0.firstName.identifier?.name ?? $0.firstName.text):" + }.joined() + let name = "\(identifier.name)(\(parameters))" let range = snapshot.absolutePositionRange( of: node.positionAfterSkippingLeadingTrivia.. Date: Mon, 3 Jun 2024 19:24:41 -0700 Subject: [PATCH 04/11] Improve markdown files Mostly just removing outdated information, merging files and pointing to the new tools page from https://github.com/apple/swift-org-website/pull/679 with instructions on how to set up editors. --- .devcontainer/Readme.md | 13 -- .../PULL_REQUEST_TEMPLATE/release_branch.md | 7 + CONTRIBUTING.md | 173 ++++++++++++++++- Documentation/Client_Development.md | 16 -- Documentation/Development.md | 136 ------------- Documentation/Editor Integration.md | 57 ++++++ ...iles_To_Reindex.md => Files To Reindex.md} | 0 Editors/README.md | 180 ------------------ README.md | 58 +----- 9 files changed, 236 insertions(+), 404 deletions(-) delete mode 100644 .devcontainer/Readme.md create mode 100644 .github/PULL_REQUEST_TEMPLATE/release_branch.md delete mode 100644 Documentation/Client_Development.md delete mode 100644 Documentation/Development.md create mode 100644 Documentation/Editor Integration.md rename Documentation/{Files_To_Reindex.md => Files To Reindex.md} (100%) delete mode 100644 Editors/README.md diff --git a/.devcontainer/Readme.md b/.devcontainer/Readme.md deleted file mode 100644 index aedd08ae7..000000000 --- a/.devcontainer/Readme.md +++ /dev/null @@ -1,13 +0,0 @@ -## Using VSCode devcontainers - -Official tutorial: https://code.visualstudio.com/docs/devcontainers/tutorial - -### Recommended Settings for macOS - -Some of these are defaults: - - Recommended settings for macOS (some of these are defaults): - - General: - - "Choose file sharing implementation for your containers": VirtioFS (better IO performance) - - Resources: - - CPUs: Allow docker to use most or all of your CPUs - - Memory: Allow docker to use most or all of your memory diff --git a/.github/PULL_REQUEST_TEMPLATE/release_branch.md b/.github/PULL_REQUEST_TEMPLATE/release_branch.md new file mode 100644 index 000000000..b64dadbef --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/release_branch.md @@ -0,0 +1,7 @@ +* **Explanation**: +* **Scope**: +* **Issue**: +* **Original PR**: +* **Risk**: +* **Testing**: +* **Reviewer**: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9f01e1f3b..cfa1e239e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,11 +1,166 @@ -By submitting a pull request, you represent that you have the right to license -your contribution to Apple and the community, and agree by submitting the patch -that your contributions are licensed under the [Swift -license](https://swift.org/LICENSE.txt). +# Contributing ---- +This document contains notes about development and testing of SourceKit-LSP. -Before submitting the pull request, please make sure you have [tested your -changes](https://github.com/apple/swift/blob/main/docs/ContinuousIntegration.md) -and that they follow the Swift project [guidelines for contributing -code](https://swift.org/contributing/#contributing-code). +## Building & Testing + +SourceKit-LSP is a SwiftPM package, so you can build and test it using anything that supports packages - opening in Xcode, Visual Studio Code with [Swift for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=sswg.swift-lang) installed, or through the command line using `swift build` and `swift test`. See below for extra instructions for Linux and Windows + +SourceKit-LSP builds with the latest released Swift version and all its tests pass or, if unsupported by the latest Swift version, are skipped. Using the `main` development branch of SourceKit-LSP with an older Swift versions is not supported. + +> [!TIP] +> SourceKit-LSP’s logging is usually very useful to debug test failures. On macOS these logs are written to the system log by default. To redirect them to stderr, build SourceKit-LSP with the `SOURCEKITLSP_FORCE_NON_DARWIN_LOGGER` environment variable set to `1`: +> - In VS Code: Add the following to your `settings.json`: +> ```json +> "swift.swiftEnvironmentVariables": { "SOURCEKITLSP_FORCE_NON_DARWIN_LOGGER": "1" }, +> ``` +> - In Xcode +> 1. Product -> Scheme -> Edit Scheme… +> 2. Select the Arguments tab in the Run section +> 3. Add a `SOURCEKITLSP_FORCE_NON_DARWIN_LOGGER` environment variable with value `1` +> - On the command line: Set the `SOURCEKITLSP_FORCE_NON_DARWIN_LOGGER` environment variable to `1` when running tests, e.g by running `SOURCEKITLSP_FORCE_NON_DARWIN_LOGGER=1 swift test --parallel` + +> [!TIP] +> Other useful environment variables during test execution are: +> - `SKIP_LONG_TESTS`: Skips tests that usually take longer than 1 second to execute. This significantly speeds up test time, especially with `swift test --parallel` +> - `SOURCEKITLSP_KEEP_TEST_SCRATCH_DIR`: Does not delete the temporary files created during test execution. Allows inspection of the test projects after the test finishes. + +### Linux + +The following dependencies of SourceKit-LSP need to be installed on your system +- libsqlite3-dev libncurses5-dev python3 + +You need to add `/usr/lib/swift` and `/usr/lib/swift/Block` C++ search paths to your `swift build` invocation that SourceKit-LSP’s dependencies build correctly. Assuming that your Swift toolchain is installed to `/`, the build command is + +```sh +$ swift build -Xcxx -I/usr/lib/swift -Xcxx -I/usr/lib/swift/Block +``` + +### Windows + +You must provide the following dependencies for SourceKit-LSP: +- SQLite3 ninja + +```cmd +> swift build -Xcc -I -Xlinker -L -Xcc -I%SDKROOT%\usr\include -Xcc -I%SDKROOT%\usr\include\Block +``` + +The header and library search paths must be passed to the build by absolute path. This allows the clang importer and linker to find the dependencies. + +Additionally, as SourceKit-LSP depends on libdispatch and the Blocks runtime, which are part of the SDK, but not in the default search path, need to be explicitly added. + +### Devcontainer + +You can develop SourceKit-LSP inside a devcontainer, which is essentially a Linux container that has all of SourceKit-LSP’s dependencies pre-installed. The [official tutorial](https://code.visualstudio.com/docs/devcontainers/tutorial) contains information of how to set up devcontainers in VS Code. + +Recommended Docker settings for macOS are: +- General + - "Choose file sharing implementation for your containers": VirtioFS (better IO performance) +- Resources + - CPUs: Allow docker to use most or all of your CPUs + - Memory: Allow docker to use most or all of your memory + +## Using a locally-built sourcekit-lsp in an editor + +If you want test your changes to SourceKit-LSP inside your editor, you can point it to your locally-built `sourcekit-lsp` executable. The exact steps vary by editor. For VS Code, you can add the following to your `settings.json`. + +```json +"swift.sourcekit-lsp.serverPath": "/path/to/sourcekit-lsp/.build/arm64-apple-macosx/debug/sourcekit-lsp", +``` + +> [!TIP] +> The easiest way to debug SourceKit-LSP is usually to write a test case that reproduces the behavior and then debug that. If that’s not possible, you can attach LLDB to the sourcekit-lsp launched by your and set breakpoints to debug. To do so on the command line, run +> ```bash +> $ lldb --wait-for --attach-name sourcekit-lsp +> ``` +> +> If you are developing SourceKit-LSP in Xcode, go to Debug -> Attach to Process by PID or Name. + +## Selecting a Toolchain + +When SourceKit-LSP is installed as part of a toolchain, it finds the Swift version to use relative to itself. When building SourceKit-LSP locally, it picks a default toolchain on your system, which usually corresponds to the toolchain that is used if you invoke `swift` without any specified path. + +To adjust the toolchain that should be used by SourceKit-LSP (eg. because you want to use new `sourcekitd` features that are only available in a Swift open source toolchain snapshot but not your default toolchain), set the `SOURCEKIT_TOOLCHAIN_PATH` environment variable to your toolchain when running SourceKit-LSP. + +## Logging + +SourceKit-LSP has extensive logging to the system log on macOS and to `/var/logs/sourcekit-lsp` or stderr on other platforms. + +To show the logs on macOS, run +```sh +log show --last 1h --predicate 'subsystem CONTAINS "org.swift.sourcekit-lsp"' --info --debug +``` +Or to stream the logs as they are produced: +``` +log stream --predicate 'subsystem CONTAINS "org.swift.sourcekit-lsp"' --level debug +``` + +SourceKit-LSP masks data that may contain private information such as source file names and contents by default. To enable logging of this information, run + +```sh +sudo log config --subsystem org.swift.sourcekit-lsp --mode private_data:on +``` + +To enable more verbose logging on non-macOS platforms, launch sourcekit-lsp with the `SOURCEKITLSP_LOG_LEVEL` environment variable set to `debug`. + + +## Formatting + +SourceKit-LSP is formatted using [swift-format](http://github.com/apple/swift-format) to ensure a consistent style. + +To format your changes run the formatter using the following command +```bash +swift package format-source-code +``` + +If you are developing SourceKit-LSP in VS Code, you can also run the *Run swift-format* task from *Tasks: Run tasks* in the command palette. + +## Authoring commits + +Prefer to squash the commits of your PR (*pull request*) and avoid adding commits like “Address review comments”. This creates a clearer git history, which doesn’t need to record the history of how the PR evolved. + +We prefer to not squash commits when merging a PR because, especially for larger PRs, it sometimes makes sense to split the PR into multiple self-contained chunks of changes. For example, a PR might do a refactoring first before adding a new feature or fixing a bug. This separation is useful for two reasons: +- During review, the commits can be reviewed individually, making each review chunk smaller +- In case this PR introduced a bug that is identified later, it is possible to check if it resulted from the refactoring or the actual change, thereby making it easier find the lines that introduce the issue. + +## Opening a PR + +To submit a PR you don't need permissions on this repo, instead you can fork the repo and create a PR through your forked version. + +For more information and instructions, read the GitHub docs on [forking a repo](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo). + +Once you've pushed your branch, you should see an option on this repository's page to create a PR from a branch in your fork. + +## Opening a PR for Release Branch + +In order for a pull request to be considered for inclusion in a release branch (e.g. `release/6.0`) after it has been cut, it must meet the following requirements: + +1. The title of the PR should start with the tag `[{swift version number}]`. For example, `[6.0]` for the Swift 6.0 release branch. + +1. The PR description must include the following information: + + ```md + * **Explanation**: A description of the issue being fixed or enhancement being made. This can be brief, but it should be clear. + * **Scope**: An assessment of the impact/importance of the change. For example, is the change a source-breaking language change, etc. + * **Issue**: The GitHub Issue link if the change fixes/implements an issue/enhancement. + * **Original PR**: Pull Request link from the `main` branch. + * **Risk**: What is the (specific) risk to the release for taking this change? + * **Testing**: What specific testing has been done or needs to be done to further validate any impact of this change? + * **Reviewer**: One or more code owners for the impacted components should review the change. Technical review can be delegated by a code owner or otherwise requested as deemed appropriate or useful. + ``` + +> [!TIP] +> The PR description can be generated using the [release_branch.md](https://github.com/apple/sourcekit-lsp/blob/main/.github/PULL_REQUEST_TEMPLATE/release_branch.md) [pull request template](https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/about-issue-and-pull-request-templates). To use this template when creating a PR, you need to add the query parameter: +> ``` +> ?expand=1&template=release_branch.md +> ``` +> to the PR URL, as described in the [GitHub documentation on using query parameters to create a pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/using-query-parameters-to-create-a-pull-request). +> This is necessary because GitHub does not currently provide a UI to choose a PR template. + +All changes going into a release branch must go through pull requests that are approved and merged by the corresponding release manager. + +## Review and CI Testing + +After you opened your PR, a maintainer will review it and test your changes in CI (*Continuous Integration*) by adding a `@swift-ci Please test` comment on the pull request. Once your PR is approved and CI has passed, the maintainer will merge your pull request. + +Only contributors with [commit access](https://www.swift.org/contributing/#commit-access) are able to approve pull requests and trigger CI. diff --git a/Documentation/Client_Development.md b/Documentation/Client_Development.md deleted file mode 100644 index fda88644f..000000000 --- a/Documentation/Client_Development.md +++ /dev/null @@ -1,16 +0,0 @@ -# Build a SourceKit-LSP Client - -Here are some critical hints that may help developers of editors or language plugins in adopting SourceKit-LSP: - -* Remember that SourceKit-LSP is at an early stage: - * It is [not yet](https://forums.swift.org/t/what-does-sourcekit-lsp-support/54424) a complete or necessarily accurate reflection of the LSP. - * Logs from `stdErr` may be insufficient for debugging your interaction with `sourcekit-lsp`. - * [Currently](https://github.com/apple/sourcekit-lsp/issues/529), you have to [open a document](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_didOpen) before sending document-based requests. -* You can use `sourcekit-lsp` with Swift packages but [not (yet) with Xcode projects](https://forums.swift.org/t/xcode-project-support/20927). -* [Don't](https://forums.swift.org/t/how-do-you-build-a-sandboxed-editor-that-uses-sourcekit-lsp/40906) attempt to use `sourcekit-lsp` in [a sandboxed context](https://developer.apple.com/documentation/xcode/configuring-the-macos-app-sandbox): - * As with most developer tooling, `sourcekit-lsp` relies on other system- and language tools that it would not be allowed to access from within an app sandbox. -* Strictly adhere to the format specification of LSP packets, including their [header- and content part](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#headerPart). -* Piece LSP packets together from the `stdOut` data: - * `sourcekit-lsp` outputs LSP packets to `stdOut`, but: Single chunks of data output do not correspond to single packets. `stdOut` rather delivers a stream of data. You have to buffer that stream in some form and detect individual LSP packets in it. -* Provide the current system environment variables to `sourcekit-lsp`: - * `sourcekit-lsp` must read some current environment variables of the system, so [don't wipe them all out](https://forums.swift.org/t/making-a-sourcekit-lsp-client-find-references-fails-solved/57426) when providing modified or additional variables. diff --git a/Documentation/Development.md b/Documentation/Development.md deleted file mode 100644 index ce3cfbb96..000000000 --- a/Documentation/Development.md +++ /dev/null @@ -1,136 +0,0 @@ -# Development - -This document contains notes about development and testing of SourceKit-LSP. - -## Table of Contents - -* [Getting Started Developing SourceKit-LSP](#getting-started-developing-sourcekit-lsp) -* [Building SourceKit-LSP](#building-sourcekit-lsp) -* [Toolchains](#toolchains) -* [Debugging](#debugging) -* [Writing Tests](#writing-tests) - -## Getting Started Developing SourceKit-LSP - -For maximum compatibility with toolchain components such as the Swift Package Manager, the only supported way to develop SourceKit-LSP is with the latest toolchain snapshot. We make an effort to keep the build and tests working with the latest release of Swift, but this is not always possible. - -1. Install the latest "Trunk Development (main)" toolchain snapshot from https://swift.org/download/#snapshots. **If you're looking for swift-5.x**, use the `swift-5.x-branch` of SourceKit-LSP with the latest swift-5.x toolchain snapshot. See [Toolchains](#toolchains) for more information. - -2. Build the language server executable `sourcekit-lsp` using `swift build`. See [Building](#building-sourcekit-lsp) for more information. - -3. Configure your editor to use the newly built `sourcekit-lsp` executable and the toolchain snapshot. See [Editors](../Editors) for more information about editor integration. - -4. Build the project you are editing with `swift build` using the toolchain snapshot. The language server depends on the build to provide module dependencies and to update the global index. - -## Building SourceKit-LSP - -Install the latest snapshot from https://swift.org/download/#snapshots. SourceKit-LSP builds with the latest toolchain snapshot of the corresponding branch (e.g. to build the *main* branch, use the latest *main* snapshot of the toolchain). See [Toolchains](#toolchains) for more information about supported toolchains. - -SourceKit-LSP is built using the [Swift Package Manager](https://github.com/apple/swift-package-manager). For a standard debug build on the command line: - -### macOS - -```sh -$ export TOOLCHAINS=swift -$ swift package update -$ swift build -``` - -### Linux - -Install the following dependencies of SourceKit-LSP: - -* libsqlite3-dev libncurses5-dev python3 - -```sh -$ swift package update -$ swift build -Xcxx -I/usr/lib/swift -Xcxx -I/usr/lib/swift/Block -``` - -After building, the server will be located at `.build/debug/sourcekit-lsp`, or a similar path, if you passed any custom options to `swift build`. Editors will generally need to be provided with this path in order to run the newly built server - see [Editors](../Editors) for more information about configuration. - -SourceKit-LSP is designed to build against the latest SwiftPM, so if you run into any issue make sure you have the most up-to-date dependencies by running `swift package update`. - -### Windows - -The user must provide the following dependencies for SourceKit-LSP: -- SQLite3 -- ninja - -```cmd -> swift build -Xcc -I -Xlinker -L -Xcc -I%SDKROOT%\usr\include -Xcc -I%SDKROOT%\usr\include\Block -``` - -The header and library search paths must be passed to the build by absolute -path. This allows the clang importer and linker to find the dependencies. - -Additionally, as SourceKit-LSP depends on libdispatch and the Blocks runtime, -which are part of the SDK, but not in the default search path, need to be -explicitly added. - -### Docker - -SourceKit-LSP should run out of the box using the [Swift official Docker images](https://swift.org/download/#docker). To build `sourcekit-lsp` from source and run its test suite, follow the steps in the *Linux* section. In the official docker images, the toolchain is located at `/`. - -If you are seeing slow compile times, you will most likely need to increase the memory available to the Docker container. - -## Toolchains - -SourceKit-LSP depends on tools such as `sourcekitd` and `clangd`, which it loads at runtime from an installed toolchain. - -### Recommended Toolchain - -Use the latest toolchain snapshot from https://swift.org/download/#snapshots. SourceKit-LSP is designed to be used with the latest toolchain snapshot of the corresponding branch. - -| SourceKit-LSP branch | Toolchain | -|:---------------------|:----------| -| main | Trunk Development (main) | -| swift-5.2-branch | Swift 5.2 Development | -| swift-5.1-branch | Swift 5.1.1+ | - -*Note*: there is no branch of SourceKit-LSP that supports Swift 5.0. - -### Selecting the Toolchain - -After installing the toolchain, SourceKit-LSP needs to know the path to the toolchain. - -* On macOS, the toolchain is installed in `/Library/Developer/Toolchains/` with an `.xctoolchain` extension. The most recently installed toolchain is symlinked as `/Library/Developer/Toolchains/swift-latest.xctoolchain`. If you opted to install for the current user only in the installer, the same paths will be under the home directory, e.g. `~/Library/Developer/Toolchains/`. - -* On Linux, the toolchain is wherever the snapshot's `.tar.gz` file was extracted. - -Your editor may have a way to configure the toolchain path directly via a configuration setting, or it may allow you to override the process environment variables used when launching `sourcekit-lsp`. See [Editors](../Editors) for more information. - -Otherwise, the simplest way to configure the toolchain is to set the following environment variable to the absolute path of the toolchain. - -```sh -SOURCEKIT_TOOLCHAIN_PATH= -``` - -## Debugging - -You can attach LLDB to SourceKit-LSP and set breakpoints to debug. You may want to instruct LLDB to wait for the sourcekit-lsp process to launch and then start your editor, which will typically launch -SourceKit-LSP as soon as you open a Swift file: - -```sh -$ lldb -w -n sourcekit-lsp -``` - -If you are using the Xcode project, go to Debug, Attach to Process by PID or Name. - -### Print SourceKit Logs - -You can configure SourceKit-LSP to print log information from SourceKit to stderr by setting the following environment variable: - -```sh -SOURCEKIT_LOGGING="N" -``` - -Where "N" configures the log verbosity and is one of the following numbers: 0 (error), 1 (warning), 2 (info), or 3 (debug). - -## Writing Tests - -As much as is practical, all code should be covered by tests. New tests can be added under the `Tests` directory and should use `XCTest`. The rest of this section will describe the additional tools available in the `SKTestSupport` module to make it easier to write good and efficient tests. - -### Long tests - -Tests that run longer than approx. 1 second are only executed if the the `SOURCEKIT_LSP_ENABLE_LONG_TESTS` environment variable is set to `YES` or `1`. This, in particular, includes the crash recovery tests. diff --git a/Documentation/Editor Integration.md b/Documentation/Editor Integration.md new file mode 100644 index 000000000..eb7ab1cba --- /dev/null +++ b/Documentation/Editor Integration.md @@ -0,0 +1,57 @@ +# Editor Integration + +Most modern text editors support the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) (LSP) and many have support for Swift through SourceKit-LSP. https://www.swift.org/tools has a list of some popular editors and how to set them up. This page covers any editors not listed there. + + + +## BBEdit + +Support for LSP is built in to BBEdit 14.0 and later. + +If `sourcekit-lsp` is in your `$PATH` or is discoverable by using `xcrun --find sourcekit-lsp`, BBEdit will use it automatically. Otherwise you can manually configure BBEdit to use a suitable `sourcekit-lsp` as needed. + +You can read more about BBEdit's LSP support and configuration hints [here](https://www.barebones.com/support/bbedit/lsp-notes.html). + +## Sublime Text + +Before using SourceKit-LSP with Sublime Text, you will need to install the [LSP](https://packagecontrol.io/packages/LSP), [LSP-SourceKit](https://github.com/sublimelsp/LSP-SourceKit) and [Swift-Next](https://github.com/Swift-Next/Swift-Next) packages from Package Control. Then toggle the server on by typing in command palette `LSP: Enable Language Server Globally` or `LSP: Enable Language Server in Project`. + +## Theia Cloud IDE + +You can use SourceKit-LSP with Theia by using the `theiaide/theia-swift` image. To use the image you need to have [Docker](https://docs.docker.com/get-started/) installed first. + +The following command pulls the image and runs Theia IDE on http://localhost:3000 with the current directory as a workspace. + +```bash +$ docker run -it -p 3000:3000 -v "$(pwd):/home/project:cached" theiaide/theia-swift:next +``` + +You can pass additional arguments to Theia after the image name, for example to enable debugging: + +```bash +$ docker run -it -p 3000:3000 --expose 9229 -p 9229:9229 -v "$(pwd):/home/project:cached" theiaide/theia-swift:next --inspect=0.0.0.0:9229 +``` + +Image Variants +- `theiaide/theia-swift:latest`: This image is based on the latest stable released version. +- `theiaide/theia-swift:next`: This image is based on the nightly published version. + +The `theia-swift-docker` source is located at [theia-apps](https://github.com/theia-ide/theia-apps). + +## Other Editors + +SourceKit-LSP should work with any editor that supports the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) (LSP). Each editor has its own mechanism for configuring an LSP server, so consult your editor's documentation for the specifics. In general, you can configure your editor to use SourceKit-LSP for Swift, C, C++, Objective-C and Objective-C++ files; the editor will need to be configured to find the `sourcekit-lsp` executable from your installed Swift toolchain, which expects to communicate with the editor over `stdin` and `stdout`. + +## Building a new SourceKit-LSP Client + +If you are building a new SourceKit-LSP client for an editor, here are some critical hints that may help you. Some of these are general hints for development of an LSP client. + +- SourceKit-LSP has extensive logging. See the [Logging section in CONTRIBUTING.md](../CONTRIBUTING.md#logging) for more information. +- You have to [open a document](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_didOpen) before sending document-based requests. +- [Don't](https://forums.swift.org/t/how-do-you-build-a-sandboxed-editor-that-uses-sourcekit-lsp/40906) attempt to use Sourcekit-LSP in [a sandboxed context](https://developer.apple.com/documentation/xcode/configuring-the-macos-app-sandbox): + - As with most developer tooling, SourceKit-LSP relies on other system- and language tools that it would not be allowed to access from within an app sandbox. +- Strictly adhere to the format specification of LSP packets, including their [header- and content part](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#headerPart). +- Piece LSP packets together from the stdout data: + - SourceKit-LSP outputs LSP packets to stdout, but: Single chunks of data output do not correspond to single packets. stdout rather delivers a stream of data. You have to buffer that stream in some form and detect individual LSP packets in it. +- Provide the current system environment variables to SourceKit-LSP: + - SourceKit-LSP must read some current environment variables of the system, so [don't wipe them all out](https://forums.swift.org/t/making-a-sourcekit-lsp-client-find-references-fails-solved/57426) when providing modified or additional variables. diff --git a/Documentation/Files_To_Reindex.md b/Documentation/Files To Reindex.md similarity index 100% rename from Documentation/Files_To_Reindex.md rename to Documentation/Files To Reindex.md diff --git a/Editors/README.md b/Editors/README.md deleted file mode 100644 index 5fece4076..000000000 --- a/Editors/README.md +++ /dev/null @@ -1,180 +0,0 @@ -# Editor Integration - -This document contains information about how to configure an editor to use SourceKit-LSP. If your editor is not listed below, but it supports the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) (LSP), see [Other Editors](#other-editors). - -In general, you will need to know where to find the `sourcekit-lsp` server executable. Some examples: - -* With Xcode 11.4+ - * `xcrun sourcekit-lsp` - run the server - * `xcrun --find sourcekit-lsp` - get the full path to the server -* Toolchain from Swift.org - * Linux - * You will find `sourcekit-lsp` in the `bin` directory of the toolchain. - * macOS - * `xcrun --toolchain swift sourcekit-lsp` - run the server - * `xcrun --toolchain swift --find sourcekit-lsp` - get the full path to the server -* Built from source - * `.build///sourcekit-lsp` - -## Visual Studio Code - -The [Swift for Visual Studio Code extension](https://marketplace.visualstudio.com/items?itemName=sswg.swift-lang) uses SourceKit-LSP for code completion, jump to definition and error annotations. Install the extension from the marketplace to add it to your VSCode environment. - -## Sublime Text - -Before using SourceKit-LSP with Sublime Text, you will need to install the [LSP](https://packagecontrol.io/packages/LSP), [LSP-SourceKit](https://github.com/sublimelsp/LSP-SourceKit) and [Swift-Next](https://github.com/Swift-Next/Swift-Next) packages from Package Control. Then toggle the server on by typing in command palette `LSP: Enable Language Server Globally` or `LSP: Enable Language Server in Project`. - -## Emacs - -There is an Emacs client for SourceKit-LSP in the [main Emacs LSP repository](https://github.com/emacs-lsp/lsp-sourcekit). - -## Vim 8 or Neovim - -All methods below assume `sourcekit-lsp` is in your `PATH`. If it's not then replace `sourcekit-lsp` with the absolute path to the sourcekit-lsp executable. - -### vim-lsp - -Install [vim-lsp](https://github.com/prabirshrestha/vim-lsp). In your `.vimrc`, configure vim-lsp to use sourcekit-lsp for Swift source files like so: - -```viml -if executable('sourcekit-lsp') - au User lsp_setup call lsp#register_server({ - \ 'name': 'sourcekit-lsp', - \ 'cmd': {server_info->['sourcekit-lsp']}, - \ 'whitelist': ['swift'], - \ }) -endif -``` - -In order for vim to recognize Swift files, you need to configure the filetype. Otherwise, `:LspStatus` will show that sourcekit-lsp is not running even if a Swift file is open. - -If you are already using a Swift plugin for vim, like [swift.vim](https://github.com/keith/swift.vim), this may be setup already. Otherwise, you can set the filetype manually: - -```viml -augroup filetype - au! BufRead,BufNewFile *.swift set ft=swift -augroup END -``` - -That's it! As a test, open a swift file, put cursor on top of a symbol in normal mode and -run `:LspDefinition`. More commands are documented [here](https://github.com/prabirshrestha/vim-lsp#supported-commands). - -There are many Vim solutions for code completion. For instance, you may want to use LSP for omnifunc: - -```viml -autocmd FileType swift setlocal omnifunc=lsp#complete -``` - -With this added in `.vimrc`, you can use `` in insert mode to trigger sourcekit-lsp completion. - -### coc.nvim - -With [coc.nvim installed](https://github.com/neoclide/coc.nvim#quick-start), the easiest is to use the [coc-sourcekit](https://github.com/klaaspieter/coc-sourcekit) plugin: - -```vim -:CocInstall coc-sourcekit -``` - -Alternatively open your coc config (`:CocConfig` in vim) and add: - -```json - "languageserver": { - "sourcekit-lsp": { - "filetypes": ["swift"], - "command": "sourcekit-lsp", - } - } -``` - -As a test, open a Swift file, put the cursor on top of a symbol in normal mode and run: - -``` -:call CocAction('jumpDefinition') -``` - -### Neovim 0.8 and above -since version 0.8, neovim has native LSP support, which can be used to connect to sourcekit-lsp directly. To do so, add the following to -a .lua config file (such as `lua/swift.lua`): - -```lua -require'lspconfig'.sourcekit.setup{ - cmd = {'$TOOLCHAIN_PATH/usr/bin/sourcekit-lsp'} -} -``` -where `$TOOLCHAIN_PATH` is the path to your active toolchain (for example, `/Library/Developer/Toolchains/swift-latest.xctoolchain`). This should enable -the lsp server directly, and you can test it by opening a swift file and running `:LspInfo`--you should get a window popping up saying "1 client attached to this buffer" and be able to do navigation and such. - -The default LSP commands are not bound to many keys, so it is also useful to create some keybindings to help with various LSP activities. Here are some -of the known lsp commands that work with `sourcekit-lsp`: - -```lua -vim.api.nvim_create_autocmd('LspAttach', { - group = vim.api.nvim_create_augroup('UserLspConfig', {}), - callback = function(ev) - --enable omnifunc completion - vim.bo[ev.buf].omnifunc = 'v:lua.vim.lsp.omnifunc' - - -- buffer local mappings - local opts = { buffer = ev.buf } - -- go to definition - vim.keymap.set('n','gd',vim.lsp.buf.definition,opts) - --puts doc header info into a float page - vim.keymap.set('n','K',vim.lsp.buf.hover,opts) - - -- workspace management. Necessary for multi-module projects - vim.keymap.set('n','wa',vim.lsp.buf.add_workspace_folder, opts) - vim.keymap.set('n','wr',vim.lsp.buf.remove_workspace_folder, opts) - vim.keymap.set('n','wl',function() - print(vim.inspect(vim.lsp.buf.list_workspace_folders())) - end,opts) - - -- add LSP code actions - vim.keymap.set({'n','v'},'ca',vim.lsp.buf.code_action,opts) - - -- find references of a type - vim.keymap.set('n','gr',vim.lsp.buf.references,opts) - end, -}) -``` - -Further information on neovim's LSP integration(including detailed information on configuration) can be found [in neovim's documentation](https://neovim.io/doc/user/lsp.html). - - -## Theia Cloud IDE - -You can use SourceKit-LSP with Theia by using the `theiaide/theia-swift` image. To use the image you need to have [Docker](https://docs.docker.com/get-started/) installed first. - -The following command pulls the image and runs Theia IDE on http://localhost:3000 with the current directory as a workspace. - - docker run -it -p 3000:3000 -v "$(pwd):/home/project:cached" theiaide/theia-swift:next - -You can pass additional arguments to Theia after the image name, for example to enable debugging: - - docker run -it -p 3000:3000 --expose 9229 -p 9229:9229 -v "$(pwd):/home/project:cached" theiaide/theia-swift:next --inspect=0.0.0.0:9229 - -Image Variants - -`theiaide/theia-swift:latest` -This image is based on the latest stable released version. - -`theiaide/theia-swift:next` -This image is based on the nightly published version. - -theia-swift-docker source [theia-apps](https://github.com/theia-ide/theia-apps) - -## BBEdit - -Support for LSP is built in to BBEdit 14.0 and later. - -If `sourcekit-lsp` is in your `$PATH` or is discoverable by using `xcrun --find sourcekit-lsp`, BBEdit will use it automatically. Otherwise you can manually configure BBEdit to use a suitable `sourcekit-lsp` as needed. - -You can read more about BBEdit's LSP support and configuration hints [here](https://www.barebones.com/support/bbedit/lsp-notes.html). - -## Other Editors - -SourceKit-LSP should work with any editor that supports the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) -(LSP). Each editor has its own mechanism for configuring an LSP server, so consult your editor's -documentation for the specifics. In general, you can configure your editor to use SourceKit-LSP for -Swift, C, C++, Objective-C and Objective-C++ files; the editor will need to be configured to find -the `sourcekit-lsp` executable (see the top-level [README](https://github.com/apple/sourcekit-lsp) for build instructions), which -expects to communicate with the editor over `stdin` and `stdout`. diff --git a/README.md b/README.md index e3c8a87c2..2eb17f3fc 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,18 @@ # SourceKit-LSP -SourceKit-LSP is an implementation of the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) (LSP) for Swift and C-based languages. It provides features like code-completion and jump-to-definition to editors that support LSP. SourceKit-LSP is built on top of [sourcekitd](https://github.com/apple/swift/tree/main/tools/SourceKit) and [clangd](https://clang.llvm.org/extra/clangd.html) for high-fidelity language support, and provides a powerful source code index as well as cross-language support. SourceKit-LSP supports projects that use the Swift Package Manager. +SourceKit-LSP is an implementation of the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) (LSP) for Swift and C-based languages. It provides intelligent editor functionality like code-completion and jump-to-definition to editors that support LSP. SourceKit-LSP is built on top of [sourcekitd](https://github.com/apple/swift/tree/main/tools/SourceKit) and [clangd](https://clang.llvm.org/extra/clangd.html) for high-fidelity language support, and provides a powerful source code index as well as cross-language support. SourceKit-LSP supports projects that use the Swift Package Manager and projects that generate a `compile_commands.json` file, such as CMake. ## Getting Started -The SourceKit-LSP server is included with the Swift toolchain. Depending on how you installed Swift, you may already have SourceKit-LSP. Make sure you build your package with the same toolchain as you use sourcekit-lsp from to ensure compatibility. +https://www.swift.org/tools has a list of popular editors that support LSP and can thus be hooked up to SourceKit-LSP to provide intelligent editor functionality as well as set-up guides. -1. Get SourceKit-LSP with a Swift toolchain +## Reporting Issues - 1. If you have installed Xcode 11.4+ or the corresponding Command Line Tools package, the SourceKit-LSP server is included and can be run with `xcrun sourcekit-lsp`. +If you should hit any issues while using SourceKit-LSP, we appreciate bug reports on [GitHub Issue](https://github.com/apple/sourcekit-lsp/issues/new/choose). - 2. If you are using a [toolchain from Swift.org](https://swift.org/download/), the SourceKit-LSP server is included and can be run with `xcrun --toolchain swift sourcekit-lsp` on macOS, or using the full path to the `sourcekit-lsp` executable on Linux. +> [!IMPORTANT] +> SourceKit-LSP does not update its global index in the background or build Swift modules in the background. Thus, a lot of cross-module or global functionality is limited if the project hasn't been built recently. To update the index or rebuild the Swift modules, build the project. - 3. If your toolchain did not come with SourceKit-LSP, see [Development](Documentation/Development.md) for how to build it from source. +## Contributing -2. Configure your editor to use SourceKit-LSP. See [Editors](Editors) for more information about editor integration. - -3. Build the project you are working on with `swift build` using the same toolchain as the SourceKit-LSP server. The language server depends on the build to provide module dependencies and to update the global index. - -## Development - -For more information about developing SourceKit-LSP itself, see [Development](Documentation/Development.md). For information about developing SourceKit-LSP clients (like editors or their language plugins), see [Client Development](Documentation/Client_Development.md). - -## Indexing While Building - -SourceKit-LSP uses a global index called [IndexStoreDB](https://github.com/apple/indexstore-db) to provide features that cross file or module boundaries, such as jump-to-definition or find-references. To efficiently create an index of your source code we use a technique called "indexing while building". When the project is compiled for debugging using `swift build`, the compiler (swiftc or clang) automatically produces additional raw index data that is read by our indexer. Producing this information during compilation saves work and ensures that any time the project is built the index is updated and fully accurate. - -In the future we intend to also provide automatic background indexing so that we can update the index in between builds or to include code that's not always built like unit tests. In the meantime, building your project should bring our index up to date. - -## Status - -SourceKit-LSP is still in early development, so you may run into rough edges with any of the features. The following table shows the status of various features when using the latest development toolchain snapshot. See [Caveats](#caveats) for important known issues you may run into. - -| Feature | Status | Notes | -|---------|:------:|-------| -| Swift | ✅ | | -| C/C++/ObjC | ✅ | Uses [clangd](https://clangd.llvm.org/) | -| Code completion | ✅ | | -| Quick Help (Hover) | ✅ | | -| Diagnostics | ✅ | | -| Fix-its | ✅ | | -| Jump to Definition | ✅ | | -| Find References | ✅ | | -| Background Indexing | ❌ | Build project to update the index using [Indexing While Building](#indexing-while-building) | -| Workspace Symbols | ✅ | | -| Rename | ❌ | | -| Local Refactoring | ✅ | | -| Formatting | ✅ | Whole file only | -| Folding | ✅ | | -| Syntax Highlighting | ✅ | Both syntactic and semantic tokens | -| Document Symbols | ✅ | | -| Call Hierarchy | ✅ | | -| Type Hierarchy | ✅ | | - - -### Caveats - -* SourceKit-LSP does not update its global index in the background, but instead relies on indexing-while-building to provide data. This only affects global queries like find-references and jump-to-definition. - * **Workaround**: build the project to update the index +If you want to contribute code to SourceKit-LSP, see [CONTRIBUTING.md](CONTRIBUTING.md) for more information. From 98149ddd7b07d905fd392775439d4ad19f779e21 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Mon, 3 Jun 2024 20:34:34 -0700 Subject: [PATCH 05/11] Add document that shows how to set up experimental background indexing --- Documentation/Background Indexing.md | 43 ++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 Documentation/Background Indexing.md diff --git a/Documentation/Background Indexing.md b/Documentation/Background Indexing.md new file mode 100644 index 000000000..c5bc53ff5 --- /dev/null +++ b/Documentation/Background Indexing.md @@ -0,0 +1,43 @@ +# Background Indexing + +Background indexing in SourceKit-LSP is available as an experimental feature. This guide shows how to set up background indexing and which caveats to expect. + +> [!WARNING] +> Background indexing is still an experimental feature and still has some non-trivial limitations and bugs (see the known issues). + +## Set Up + +Background indexing is still under active, rapid development, so please build SourceKit-LSP from the `main` branch using the following commands. Things are changing rapidly, so rebuilding daily is not a bad practice. + +```bash +$ git clone https://github.com/apple/sourcekit-lsp.git +$ cd sourcekit-lsp +$ swift package update +$ swift build -c release +``` + +Next, point your editor to use the just-built copy of SourceKit-LSP and enable background indexing by passing `--experimental-feature background-indexing` to sourcekit-lsp. In VS Code, this can be done by adding the following to your settings.json +```json +"swift.sourcekit-lsp.serverPath": "/path/to/sourcekit-lsp/.build/release/sourcekit-lsp", +"swift.sourcekit-lsp.serverArguments": [ "--experimental-feature", "background-indexing" ], +``` + +## Known issues + +- The only supported toolchain for background indexing are currently [Swift 6.0 nightly toolchain snapshots](https://www.swift.org/download/#swift-60-development). Older toolchains are not supported and the nightly toolchains from `main` are having issues because building a target non-deterministically builds for tools or the destination [#1288](https://github.com/apple/sourcekit-lsp/pull/1288#issuecomment-2111400459) [rdar://128100158](rdar://128100158) +- Not really a background indexing related issue but Swift nightly toolchain snapshots are crashing on macOS 14.4 and 14.5 (swift#73327)[https://github.com/apple/swift/issues/73327] + - Workaround: Run the toolchains on an older version of macOS, if possible +- Background Indexing is only supported for SwiftPM projects [#1269](https://github.com/apple/sourcekit-lsp/issues/1269), [#1271](https://github.com/apple/sourcekit-lsp/issues/1271) +- If a module or one of its dependencies has a compilation error, it cannot be properly prepared for indexing because we are running a regular `swift build` to generate its modules [#1254](https://github.com/apple/sourcekit-lsp/issues/1254) rdar://128683404 + - Workaround: Ensure that your files dependencies are in a buildable state to get an up-to-date index and proper cross-module functionality +- If you change a function in a way that changes its USR but keeps it API compatible (such as adding a defaulted parameter), references to it will be lost and not re-indexed automatically [#1264](https://github.com/apple/sourcekit-lsp/issues/1264) + - Workaround: Make some edit to the files that had references to re-index them +- The index build is currently completely separate from the command line build generated using `swift build`. Building *does not* update the index (break your habits of always building!) [#1270](https://github.com/apple/sourcekit-lsp/issues/1270) +- The initial indexing might take 2-3x more time than a regular build [#1254](https://github.com/apple/sourcekit-lsp/issues/1254), [#1262](https://github.com/apple/sourcekit-lsp/issues/1262), [#1268](https://github.com/apple/sourcekit-lsp/issues/1268) +- Spurious re-indexing of ~10-20 source files when `swift build` writes a header to the build directory [rdar://128573306](rdar://128573306) + +## Filing issues + +If you hit any issues that are not mentioned above, please [file a GitHub issue](https://github.com/apple/sourcekit-lsp/issues/new/choose) and attach the following information, if possible: +- A diagnostic bundle generated by running `path/to/sourcekit-lsp diagnose --toolchain path/to/the/toolchain/you/are/using` +- Your project including the `.index-build` folder. From 7d3b44079ac6cc89e065ddb8b21190d15f0e354d Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Mon, 3 Jun 2024 12:56:44 -0700 Subject: [PATCH 06/11] Write log messages to files on non-Darwin platforms Instead of logging to `stderr`, write log messages to files in `.sourcekit-lsp/logs/sourcekit-lsp-..log`. This allows us to retrieve the log messages from `sourcekit-lsp diagnose`. Fixes #1286 rdar://127138318 --- Package.swift | 3 +- Sources/Diagnose/DiagnoseCommand.swift | 28 ++- Sources/LSPLogging/CMakeLists.txt | 2 + Sources/LSPLogging/NonDarwinLogging.swift | 29 ++- .../LSPLogging/SetGlobalLogFileHandler.swift | 202 ++++++++++++++++++ Sources/sourcekit-lsp/SourceKitLSP.swift | 9 + Tests/LSPLoggingTests/LoggingTests.swift | 46 ++-- 7 files changed, 286 insertions(+), 33 deletions(-) create mode 100644 Sources/LSPLogging/SetGlobalLogFileHandler.swift diff --git a/Package.swift b/Package.swift index c9f932632..e9bcd1d88 100644 --- a/Package.swift +++ b/Package.swift @@ -161,7 +161,8 @@ let package = Package( .target( name: "LSPLogging", dependencies: [ - .product(name: "Crypto", package: "swift-crypto") + "SwiftExtensions", + .product(name: "Crypto", package: "swift-crypto"), ], exclude: ["CMakeLists.txt"], swiftSettings: lspLoggingSwiftSettings + [.enableExperimentalFeature("StrictConcurrency")] diff --git a/Sources/Diagnose/DiagnoseCommand.swift b/Sources/Diagnose/DiagnoseCommand.swift index d58f3b289..0cb71757e 100644 --- a/Sources/Diagnose/DiagnoseCommand.swift +++ b/Sources/Diagnose/DiagnoseCommand.swift @@ -232,6 +232,32 @@ public struct DiagnoseCommand: AsyncParsableCommand { #endif } + @MainActor + private func addNonDarwinLogs(toBundle bundlePath: URL) async throws { + reportProgress(.collectingLogMessages(progress: 0), message: "Collecting log files") + + let destinationDir = bundlePath.appendingPathComponent("logs") + try FileManager.default.createDirectory(at: destinationDir, withIntermediateDirectories: true) + + let logFileDirectoryURL = URL(fileURLWithPath: ("~/.sourcekit-lsp/logs" as NSString).expandingTildeInPath) + let enumerator = FileManager.default.enumerator(at: logFileDirectoryURL, includingPropertiesForKeys: nil) + while let fileUrl = enumerator?.nextObject() as? URL { + guard fileUrl.lastPathComponent.hasPrefix("sourcekit-lsp") else { + continue + } + try? FileManager.default.copyItem( + at: fileUrl, + to: destinationDir.appendingPathComponent(fileUrl.lastPathComponent) + ) + } + } + + @MainActor + private func addLogs(toBundle bundlePath: URL) async throws { + try await addNonDarwinLogs(toBundle: bundlePath) + try await addOsLog(toBundle: bundlePath) + } + @MainActor private func addCrashLogs(toBundle bundlePath: URL) throws { #if os(macOS) @@ -328,7 +354,7 @@ public struct DiagnoseCommand: AsyncParsableCommand { await orPrintError { try addCrashLogs(toBundle: bundlePath) } } if components.isEmpty || components.contains(.logs) { - await orPrintError { try await addOsLog(toBundle: bundlePath) } + await orPrintError { try await addLogs(toBundle: bundlePath) } } if components.isEmpty || components.contains(.swiftVersions) { await orPrintError { try await addSwiftVersion(toBundle: bundlePath) } diff --git a/Sources/LSPLogging/CMakeLists.txt b/Sources/LSPLogging/CMakeLists.txt index 615754910..c2313dd91 100644 --- a/Sources/LSPLogging/CMakeLists.txt +++ b/Sources/LSPLogging/CMakeLists.txt @@ -5,10 +5,12 @@ add_library(LSPLogging STATIC LoggingScope.swift NonDarwinLogging.swift OrLog.swift + SetGlobalLogFileHandler.swift SplitLogMessage.swift) set_target_properties(LSPLogging PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) target_link_libraries(LSPLogging PRIVATE + SwiftExtensions $<$>:Foundation>) target_link_libraries(LSPLogging PUBLIC Crypto) diff --git a/Sources/LSPLogging/NonDarwinLogging.swift b/Sources/LSPLogging/NonDarwinLogging.swift index 045fc5b22..2f1301dea 100644 --- a/Sources/LSPLogging/NonDarwinLogging.swift +++ b/Sources/LSPLogging/NonDarwinLogging.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import Foundation +import SwiftExtensions // MARK: - Log settings @@ -261,22 +262,34 @@ private let dateFormatter = { return dateFormatter }() +/// Actor that protects `logHandler` +@globalActor +actor LogHandlerActor { + static var shared: LogHandlerActor = LogHandlerActor() +} + +/// The handler that is called to log a message from `NonDarwinLogger` unless `overrideLogHandler` is set on the logger. +@LogHandlerActor +var logHandler: @Sendable (String) async -> Void = { fputs($0 + "\n", stderr) } + /// The queue on which we log messages. /// /// A global queue since we create and discard loggers all the time. -private let loggingQueue: DispatchQueue = DispatchQueue(label: "loggingQueue", qos: .utility) +private let loggingQueue = AsyncQueue() /// A logger that is designed to be API-compatible with `os.Logger` for all uses /// in sourcekit-lsp. /// /// This logger is used to log messages to stderr on platforms where OSLog is /// not available. +/// +/// `overrideLogHandler` allows capturing of the logged messages for testing purposes. public struct NonDarwinLogger: Sendable { private let subsystem: String private let category: String private let logLevel: NonDarwinLogLevel private let privacyLevel: NonDarwinLogPrivacy - private let logHandler: @Sendable (String) -> Void + private let overrideLogHandler: (@Sendable (String) -> Void)? /// - Parameters: /// - subsystem: See os.Logger @@ -291,13 +304,13 @@ public struct NonDarwinLogger: Sendable { category: String, logLevel: NonDarwinLogLevel? = nil, privacyLevel: NonDarwinLogPrivacy? = nil, - logHandler: @escaping @Sendable (String) -> Void = { fputs($0 + "\n", stderr) } + overrideLogHandler: (@Sendable (String) -> Void)? = nil ) { self.subsystem = subsystem self.category = category self.logLevel = logLevel ?? LogConfig.logLevel self.privacyLevel = privacyLevel ?? LogConfig.privacyLevel - self.logHandler = logHandler + self.overrideLogHandler = overrideLogHandler } /// Logs the given message at the given level. @@ -310,7 +323,7 @@ public struct NonDarwinLogger: Sendable { ) { guard level >= self.logLevel else { return } let date = Date() - loggingQueue.async { + loggingQueue.async(priority: .utility) { @LogHandlerActor in // Truncate log message after 10.000 characters to avoid flooding the log with huge log messages (eg. from a // sourcekitd response). 10.000 characters was chosen because it seems to fit the result of most sourcekitd // responses that are not generated interface or global completion results (which are a lot bigger). @@ -321,7 +334,7 @@ public struct NonDarwinLogger: Sendable { message = message.prefix(10_000) + "..." } // Start each log message with `[org.swift.sourcekit-lsp` so that it’s easy to split the log to the different messages - logHandler( + await (overrideLogHandler ?? logHandler)( """ [\(subsystem):\(category)] \(level) \(dateFormatter.string(from: date)) \(message) @@ -361,8 +374,8 @@ public struct NonDarwinLogger: Sendable { /// Useful for testing to make sure all asynchronous log calls have actually /// written their data. @_spi(Testing) - public static func flush() { - loggingQueue.sync {} + public static func flush() async { + await loggingQueue.async {}.value } public func makeSignposter() -> NonDarwinSignposter { diff --git a/Sources/LSPLogging/SetGlobalLogFileHandler.swift b/Sources/LSPLogging/SetGlobalLogFileHandler.swift new file mode 100644 index 000000000..659365df7 --- /dev/null +++ b/Sources/LSPLogging/SetGlobalLogFileHandler.swift @@ -0,0 +1,202 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Foundation +import RegexBuilder + +#if os(Windows) +import WinSDK +#endif + +#if !canImport(os) || SOURCEKITLSP_FORCE_NON_DARWIN_LOGGER +fileprivate struct FailedToCreateFileError: Error, CustomStringConvertible { + let logFile: URL + + var description: String { + return "Failed to create log file at \(logFile)" + } +} + +/// The number of log file handles that have been created by this process. +/// +/// See comment on `logFileHandle`. +@LogHandlerActor +fileprivate var logRotateIndex = 0 + +/// The file handle to the current log file. When the file managed by this handle reaches its maximum size, we increment +/// the `logRotateIndex` by 1 and set the `logFileHandle` to `nil`. This causes a new log file handle with index +/// `logRotateIndex % logRotateCount` to be created on the next log call. +@LogHandlerActor +fileprivate var logFileHandle: FileHandle? + +@LogHandlerActor +func getOrCreateLogFileHandle(logDirectory: URL, logRotateCount: Int) -> FileHandle { + if let logFileHandle { + return logFileHandle + } + + // Name must match the regex in `cleanOldLogFiles` and the prefix in `DiagnoseCommand.addNonDarwinLogs`. + let logFileUrl = logDirectory.appendingPathComponent( + "sourcekit-lsp-\(ProcessInfo.processInfo.processIdentifier).\(logRotateIndex % logRotateCount).log" + ) + + do { + try FileManager.default.createDirectory(at: logDirectory, withIntermediateDirectories: true) + if !FileManager.default.fileExists(atPath: logFileUrl.path) { + guard FileManager.default.createFile(atPath: logFileUrl.path, contents: nil) else { + throw FailedToCreateFileError(logFile: logFileUrl) + } + } + let newFileHandle = try FileHandle(forWritingTo: logFileUrl) + logFileHandle = newFileHandle + try newFileHandle.truncate(atOffset: 0) + return newFileHandle + } catch { + // If we fail to create a file handle for the log file, log one message about it to stderr and then log to stderr. + // We will try creating a log file again once this section of the log reaches `maxLogFileSize` but that means that + // we'll only log this error every `maxLogFileSize` bytes, which is a lot less spammy than logging it on every log + // call. + fputs("Failed to open file handle for log file at \(logFileUrl.path): \(error)", stderr) + logFileHandle = FileHandle.standardError + return FileHandle.standardError + } +} + +/// Log the given message to a log file in the given log directory. +/// +/// The name of the log file includes the PID of the current process to make sure it is exclusively writing to the file. +/// When a log file reaches `logFileMaxBytes`, it will be rotated, with at most `logRotateCount` different log files +/// being created. +@LogHandlerActor +private func logToFile(message: String, logDirectory: URL, logFileMaxBytes: Int, logRotateCount: Int) throws { + + guard let data = message.data(using: .utf8) else { + fputs( + """ + Failed to convert log message to UTF-8 data + \(message) + + """, + stderr + ) + return + } + let logFileHandleUnwrapped = getOrCreateLogFileHandle(logDirectory: logDirectory, logRotateCount: logRotateCount) + try logFileHandleUnwrapped.write(contentsOf: data) + + // If this log file has exceeded the maximum size, start writing to a new log file. + if try logFileHandleUnwrapped.offset() > logFileMaxBytes { + logRotateIndex += 1 + // Resetting `logFileHandle` will cause a new logFileHandle to be created on the next log call. + logFileHandle = nil + } +} + +/// If the file at the given path is writable, redirect log messages handled by `NonDarwinLogHandler` to the given file. +/// +/// Occasionally checks that the log does not exceed `targetLogSize` (in bytes) and truncates the beginning of the log +/// when it does. +@LogHandlerActor +private func setUpGlobalLogFileHandlerImpl(logFileDirectory: URL, logFileMaxBytes: Int, logRotateCount: Int) { + logHandler = { @LogHandlerActor message in + do { + try logToFile( + message: message, + logDirectory: logFileDirectory, + logFileMaxBytes: logFileMaxBytes, + logRotateCount: logRotateCount + ) + } catch { + fputs( + """ + Failed to write message to log file: \(error) + \(message) + + """, + stderr + ) + } + } +} + +/// Returns `true` if a process with the given PID still exists and is alive. +private func isProcessAlive(pid: Int32) -> Bool { + #if os(Windows) + if let handle = OpenProcess(UInt32(PROCESS_QUERY_INFORMATION), /*bInheritHandle=*/ false, UInt32(pid)) { + CloseHandle(handle) + return true + } + return false + #else + return kill(pid, 0) == 0 + #endif +} + +private func cleanOldLogFilesImpl(logFileDirectory: URL, maxAge: TimeInterval) { + let enumerator = FileManager.default.enumerator(at: logFileDirectory, includingPropertiesForKeys: nil) + while let url = enumerator?.nextObject() as? URL { + let name = url.lastPathComponent + let regex = Regex { + "sourcekit-lsp-" + Capture(ZeroOrMore(.digit)) + "." + ZeroOrMore(.digit) + ".log" + } + guard let match = name.matches(of: regex).only, let pid = Int32(match.1) else { + continue + } + if isProcessAlive(pid: pid) { + // Process that owns this log file is still alive. Don't delete it. + continue + } + guard + let modificationDate = orLog( + "Getting mtime of old log file", + { try FileManager.default.attributesOfItem(atPath: url.path)[.modificationDate] } + ) as? Date, + Date().timeIntervalSince(modificationDate) > maxAge + else { + // File has been modified in the last hour. Don't delete it because it's useful to diagnose issues after + // sourcekit-lsp has exited. + continue + } + orLog("Deleting old log file") { try FileManager.default.removeItem(at: url) } + } +} +#endif + +/// If the file at the given path is writable, redirect log messages handled by `NonDarwinLogHandler` to the given file. +/// +/// Occasionally checks that the log does not exceed `targetLogSize` (in bytes) and truncates the beginning of the log +/// when it does. +/// +/// No-op when using OSLog. +public func setUpGlobalLogFileHandler(logFileDirectory: URL, logFileMaxBytes: Int, logRotateCount: Int) async { + #if !canImport(os) || SOURCEKITLSP_FORCE_NON_DARWIN_LOGGER + await setUpGlobalLogFileHandlerImpl( + logFileDirectory: logFileDirectory, + logFileMaxBytes: logFileMaxBytes, + logRotateCount: logRotateCount + ) + #endif +} + +/// Deletes all sourcekit-lsp log files in `logFilesDirectory` that are not associated with a running process and that +/// haven't been modified within the last hour. +/// +/// No-op when using OSLog. +public func cleanOldLogFiles(logFileDirectory: URL, maxAge: TimeInterval) { + #if !canImport(os) || SOURCEKITLSP_FORCE_NON_DARWIN_LOGGER + cleanOldLogFilesImpl(logFileDirectory: logFileDirectory, maxAge: maxAge) + #endif +} diff --git a/Sources/sourcekit-lsp/SourceKitLSP.swift b/Sources/sourcekit-lsp/SourceKitLSP.swift index 3b89b55b8..22f2291ae 100644 --- a/Sources/sourcekit-lsp/SourceKitLSP.swift +++ b/Sources/sourcekit-lsp/SourceKitLSP.swift @@ -242,6 +242,15 @@ struct SourceKitLSP: AsyncParsableCommand { let realStdoutHandle = FileHandle(fileDescriptor: realStdout, closeOnDealloc: false) + // Directory should match the directory we are searching for logs in `DiagnoseCommand.addNonDarwinLogs`. + let logFileDirectoryURL = URL(fileURLWithPath: ("~/.sourcekit-lsp/logs" as NSString).expandingTildeInPath) + await setUpGlobalLogFileHandler( + logFileDirectory: logFileDirectoryURL, + logFileMaxBytes: 5_000_000, + logRotateCount: 10 + ) + cleanOldLogFiles(logFileDirectory: logFileDirectoryURL, maxAge: 60 * 60 /* 1h */) + let clientConnection = JSONRPCConnection( name: "client", protocol: MessageRegistry.lspProtocol, diff --git a/Tests/LSPLoggingTests/LoggingTests.swift b/Tests/LSPLoggingTests/LoggingTests.swift index 8e596704c..5ce3cc663 100644 --- a/Tests/LSPLoggingTests/LoggingTests.swift +++ b/Tests/LSPLoggingTests/LoggingTests.swift @@ -21,7 +21,7 @@ fileprivate func assertLogging( _ body: (NonDarwinLogger) -> Void, file: StaticString = #filePath, line: UInt = #line -) { +) async { // nonisolated(unsafe) because calls of `assertLogging` do not log to `logHandler` concurrently. nonisolated(unsafe) var messages: [String] = [] let logger = NonDarwinLogger( @@ -29,10 +29,10 @@ fileprivate func assertLogging( category: "test", logLevel: logLevel, privacyLevel: privacyLevel, - logHandler: { messages.append($0) } + overrideLogHandler: { messages.append($0) } ) body(logger) - NonDarwinLogger.flush() + await NonDarwinLogger.flush() guard messages.count == expected.count else { XCTFail( """ @@ -77,7 +77,7 @@ final class LoggingTests: XCTestCase { let logger = NonDarwinLogger( subsystem: LoggingScope.subsystem, category: "test", - logHandler: { + overrideLogHandler: { message = $0 expectation.fulfill() } @@ -91,27 +91,27 @@ final class LoggingTests: XCTestCase { XCTAssert(message.hasSuffix("\nmy message\n---"), "Message did not have expected body. Received \n\(message)") } - func testLoggingBasic() { - assertLogging( + func testLoggingBasic() async { + await assertLogging( expected: ["a"], { $0.log("a") } ) - assertLogging( + await assertLogging( expected: [], { _ in } ) - assertLogging(expected: ["b\n\nc"]) { + await assertLogging(expected: ["b\n\nc"]) { $0.log("b\n\nc") } } - func testLogLevels() { - assertLogging( + func testLogLevels() async { + await assertLogging( logLevel: .default, expected: ["d", "e", "f"] ) { @@ -122,7 +122,7 @@ final class LoggingTests: XCTestCase { $0.debug("h") } - assertLogging( + await assertLogging( logLevel: .error, expected: ["d", "e"] ) { @@ -133,7 +133,7 @@ final class LoggingTests: XCTestCase { $0.debug("h") } - assertLogging( + await assertLogging( logLevel: .fault, expected: ["d"] ) { @@ -145,23 +145,23 @@ final class LoggingTests: XCTestCase { } } - func testPrivacyMaskingLevels() { - assertLogging(expected: ["password is "]) { + func testPrivacyMaskingLevels() async { + await assertLogging(expected: ["password is "]) { let password: String = "1234" $0.log("password is \(password, privacy: .sensitive)") } - assertLogging(expected: ["username is root"]) { + await assertLogging(expected: ["username is root"]) { let username: String = "root" $0.log("username is \(username, privacy: .private)") } - assertLogging(expected: ["username is root"]) { + await assertLogging(expected: ["username is root"]) { let username: String = "root" $0.log("username is \(username)") } - assertLogging( + await assertLogging( privacyLevel: .public, expected: ["username is "] ) { @@ -169,7 +169,7 @@ final class LoggingTests: XCTestCase { $0.log("username is \(username, privacy: .private)") } - assertLogging( + await assertLogging( privacyLevel: .public, expected: ["username is "] ) { @@ -178,15 +178,15 @@ final class LoggingTests: XCTestCase { } } - func testPrivacyMaskingTypes() { - assertLogging( + func testPrivacyMaskingTypes() async { + await assertLogging( privacyLevel: .public, expected: ["logging a static string"] ) { $0.log("logging a \("static string")") } - assertLogging( + await assertLogging( privacyLevel: .public, expected: ["logging from LSPLoggingTests.LoggingTests"] ) { @@ -198,14 +198,14 @@ final class LoggingTests: XCTestCase { var redactedDescription: String = "redacted description" } - assertLogging( + await assertLogging( privacyLevel: .public, expected: ["got redacted description"] ) { $0.log("got \(LogStringConvertible().forLogging)") } - assertLogging( + await assertLogging( privacyLevel: .private, expected: ["got full description"] ) { From 379d282f68468f2ab042fa3f6be2b5c694679894 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 5 Jun 2024 10:16:26 -0700 Subject: [PATCH 07/11] Allow specification of where `sourcekit-lsp diagnose` writes its diagnose bundle This allows VS Code to provide a command in the command palette that gathers sourcekit-lsp diagnostics alongside some VS Code Swift extension diagnostics. --- Sources/Diagnose/DiagnoseCommand.swift | 33 +++++++++++++++++++------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/Sources/Diagnose/DiagnoseCommand.swift b/Sources/Diagnose/DiagnoseCommand.swift index d58f3b289..b9a35c2f4 100644 --- a/Sources/Diagnose/DiagnoseCommand.swift +++ b/Sources/Diagnose/DiagnoseCommand.swift @@ -65,6 +65,14 @@ public struct DiagnoseCommand: AsyncParsableCommand { ) private var components: [BundleComponent] = BundleComponent.allCases + @Option( + help: """ + The directory to which the diagnostic bundle should be written. No file or directory should exist at this path. \ + After sourcekit-lsp diagnose runs, a directory will exist at this path that contains the diagnostic bundle. + """ + ) + var bundleOutputPath: String? = nil + var toolchainRegistry: ToolchainRegistry { get throws { let installPath = try AbsolutePath(validating: Bundle.main.bundlePath) @@ -320,8 +328,12 @@ public struct DiagnoseCommand: AsyncParsableCommand { let dateFormatter = ISO8601DateFormatter() dateFormatter.timeZone = NSTimeZone.local let date = dateFormatter.string(from: Date()).replacingOccurrences(of: ":", with: "-") - let bundlePath = FileManager.default.temporaryDirectory - .appendingPathComponent("sourcekit-lsp-diagnose-\(date)") + let bundlePath = + if let bundleOutputPath = self.bundleOutputPath { + URL(fileURLWithPath: bundleOutputPath) + } else { + FileManager.default.temporaryDirectory.appendingPathComponent("sourcekit-lsp-diagnose-\(date)") + } try FileManager.default.createDirectory(at: bundlePath, withIntermediateDirectories: true) if components.isEmpty || components.contains(.crashReports) { @@ -353,12 +365,17 @@ public struct DiagnoseCommand: AsyncParsableCommand { ) #if os(macOS) - // Reveal the bundle in Finder on macOS - do { - let process = try Process.launch(arguments: ["open", "-R", bundlePath.path], workingDirectory: nil) - try await process.waitUntilExitSendingSigIntOnTaskCancellation() - } catch { - // If revealing the bundle in Finder should fail, we don't care. We still printed the bundle path to stdout. + // Reveal the bundle in Finder on macOS. + // Don't open the bundle in Finder if the user manually specified a log output path. In that case they are running + // `sourcekit-lsp diagnose` as part of a larger logging script (like the Swift for VS Code extension) and the caller + // is responsible for showing the diagnose bundle location to the user + if self.bundleOutputPath == nil { + do { + let process = try Process.launch(arguments: ["open", "-R", bundlePath.path], workingDirectory: nil) + try await process.waitUntilExitSendingSigIntOnTaskCancellation() + } catch { + // If revealing the bundle in Finder should fail, we don't care. We still printed the bundle path to stdout. + } } #endif } From 3fdaa106445da293d84f2a5226d5613fda2d9d17 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 5 Jun 2024 10:58:37 -0700 Subject: [PATCH 08/11] Fix a race condition in `LocalConnection` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The comment on `LocalConnection` assumed that all mutable state was guarded by `queue` but `state` and `handler` actually weren’t. This was found by the thread sanitizer. --- Sources/InProcessClient/LocalConnection.swift | 48 ++++++++++++------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/Sources/InProcessClient/LocalConnection.swift b/Sources/InProcessClient/LocalConnection.swift index 190e7f133..21bf01d10 100644 --- a/Sources/InProcessClient/LocalConnection.swift +++ b/Sources/InProcessClient/LocalConnection.swift @@ -26,10 +26,8 @@ import LanguageServerProtocol /// conn.send(...) // handled by server /// conn.close() /// ``` -/// -/// - Note: Unchecked sendable conformance because shared state is guarded by `queue`. -public final class LocalConnection: Connection, @unchecked Sendable { - enum State { +public final class LocalConnection: Connection, Sendable { + private enum State { case ready, started, closed } @@ -37,36 +35,51 @@ public final class LocalConnection: Connection, @unchecked Sendable { private let name: String /// The queue guarding `_nextRequestID`. - let queue: DispatchQueue = DispatchQueue(label: "local-connection-queue") + private let queue: DispatchQueue = DispatchQueue(label: "local-connection-queue") - var _nextRequestID: Int = 0 + /// - Important: Must only be accessed from `queue` + nonisolated(unsafe) private var _nextRequestID: Int = 0 - var state: State = .ready + /// - Important: Must only be accessed from `queue` + nonisolated(unsafe) private var state: State = .ready - var handler: MessageHandler? = nil + /// - Important: Must only be accessed from `queue` + nonisolated(unsafe) private var handler: MessageHandler? = nil public init(name: String) { self.name = name } deinit { - if state != .closed { - close() + queue.sync { + if state != .closed { + closeAssumingOnQueue() + } } } public func start(handler: MessageHandler) { - precondition(state == .ready) - state = .started - self.handler = handler + queue.sync { + precondition(state == .ready) + state = .started + self.handler = handler + } } - public func close() { + /// - Important: Must only be called from `queue` + private func closeAssumingOnQueue() { + dispatchPrecondition(condition: .onQueue(queue)) precondition(state != .closed) handler = nil state = .closed } + public func close() { + queue.sync { + closeAssumingOnQueue() + } + } + func nextRequestID() -> RequestID { return queue.sync { _nextRequestID += 1 @@ -81,7 +94,10 @@ public final class LocalConnection: Connection, @unchecked Sendable { \(notification.forLogging) """ ) - self.handler?.handle(notification) + guard let handler = queue.sync(execute: { handler }) else { + return + } + handler.handle(notification) } public func send( @@ -97,7 +113,7 @@ public final class LocalConnection: Connection, @unchecked Sendable { """ ) - guard let handler = self.handler else { + guard let handler = queue.sync(execute: { handler }) else { logger.info( """ Replying to request \(id, privacy: .public) with .serverCancelled because no handler is specified in \(self.name, privacy: .public) From 12864077621afa711f530bb0365715d7bce9e56f Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 5 Jun 2024 14:24:09 -0700 Subject: [PATCH 09/11] Remove sourcekitd test hooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Turns out that sourcekitd test hooks were a bad idea because of the following comment that I wrote: ``` `testHooks` are only considered when an instance is being created. If a sourcekitd instance at the given path already exists, its test hooks will be used. ``` During test execution in Xcode, we generate a bunch of `SourceKitServer` instances in the same process that all call `DynamicallyLoadedSourceKitD.getOrCreate`. Now, if `testDontReturnEmptyDiagnosticsIfDiagnosticRequestIsCancelled` is not the first test being executed in the process (which usually it is not), the test hooks in it won’t get used. Switch back to using the preparation hooks, essentially reverting https://github.com/apple/sourcekit-lsp/pull/1412 and keeping the following snippet to fix the underlying issue ```swift // Poll until the `CancelRequestNotification` has been propagated to the request handling. for _ in 0.. Void)? - - public init(sourcekitdRequestDidStart: (@Sendable (SKDRequestDictionary) -> Void)? = nil) { - self.sourcekitdRequestDidStart = sourcekitdRequestDidStart - } -} - /// Wrapper for sourcekitd, taking care of initialization, shutdown, and notification handler /// multiplexing. /// @@ -41,12 +33,10 @@ public struct SourceKitDTestHooks: Sendable { /// `set_notification_handler`, which are global state managed internally by this class. public actor DynamicallyLoadedSourceKitD: SourceKitD { /// The path to the sourcekitd dylib. - private let path: AbsolutePath + public let path: AbsolutePath /// The handle to the dylib. - private let dylib: DLHandle - - public let testHooks: SourceKitDTestHooks + let dylib: DLHandle /// The sourcekitd API functions. public let api: sourcekitd_api_functions_t @@ -65,23 +55,18 @@ public actor DynamicallyLoadedSourceKitD: SourceKitD { /// List of notification handlers that will be called for each notification. private var notificationHandlers: [WeakSKDNotificationHandler] = [] - /// If there is already a `sourcekitd` instance from the given return it, otherwise create a new one. - /// - /// `testHooks` are only considered when an instance is being created. If a sourcekitd instance at the given path - /// already exists, its test hooks will be used. - public static func getOrCreate(dylibPath: AbsolutePath, testHooks: SourceKitDTestHooks) async throws -> SourceKitD { + public static func getOrCreate(dylibPath: AbsolutePath) async throws -> SourceKitD { try await SourceKitDRegistry.shared - .getOrAdd(dylibPath, create: { try DynamicallyLoadedSourceKitD(dylib: dylibPath, testHooks: testHooks) }) + .getOrAdd(dylibPath, create: { try DynamicallyLoadedSourceKitD(dylib: dylibPath) }) } - init(dylib path: AbsolutePath, testHooks: SourceKitDTestHooks) throws { + init(dylib path: AbsolutePath) throws { self.path = path #if os(Windows) self.dylib = try dlopen(path.pathString, mode: []) #else self.dylib = try dlopen(path.pathString, mode: [.lazy, .local, .first]) #endif - self.testHooks = testHooks self.api = try sourcekitd_api_functions_t(self.dylib) self.keys = sourcekitd_api_keys(api: self.api) self.requests = sourcekitd_api_requests(api: self.api) diff --git a/Sources/SourceKitD/SourceKitD.swift b/Sources/SourceKitD/SourceKitD.swift index 05698ac1b..3d0b1f0f5 100644 --- a/Sources/SourceKitD/SourceKitD.swift +++ b/Sources/SourceKitD/SourceKitD.swift @@ -31,8 +31,6 @@ extension sourcekitd_api_request_handle_t: @unchecked Sendable {} /// *Implementors* are expected to handle initialization and shutdown, e.g. during `init` and /// `deinit` or by wrapping an existing sourcekitd session that outlives this object. public protocol SourceKitD: AnyObject, Sendable { - var testHooks: SourceKitDTestHooks { get } - /// The sourcekitd API functions. var api: sourcekitd_api_functions_t { get } @@ -101,8 +99,6 @@ extension SourceKitD { public func send(_ request: SKDRequestDictionary, fileContents: String?) async throws -> SKDResponseDictionary { log(request: request) - testHooks.sourcekitdRequestDidStart?(request) - let sourcekitdResponse: SKDResponse = try await withCancellableCheckedThrowingContinuation { continuation in var handle: sourcekitd_api_request_handle_t? = nil api.send_request(request.dict, &handle) { response in diff --git a/Sources/SourceKitLSP/SourceKitLSPServer+Options.swift b/Sources/SourceKitLSP/SourceKitLSPServer+Options.swift index 6d2acfdb9..dc28b3bb7 100644 --- a/Sources/SourceKitLSP/SourceKitLSPServer+Options.swift +++ b/Sources/SourceKitLSP/SourceKitLSPServer+Options.swift @@ -15,7 +15,6 @@ import LanguageServerProtocol import SKCore import SKSupport import SemanticIndex -import SourceKitD import struct TSCBasic.AbsolutePath import struct TSCBasic.RelativePath @@ -52,8 +51,6 @@ extension SourceKitLSPServer { /// Experimental features that are enabled. public var experimentalFeatures: Set - public var sourcekitdTestHooks: SourceKitDTestHooks - public var indexTestHooks: IndexTestHooks public init( @@ -65,7 +62,6 @@ extension SourceKitLSPServer { generatedInterfacesPath: AbsolutePath = defaultDirectoryForGeneratedInterfaces, swiftPublishDiagnosticsDebounceDuration: TimeInterval = 2, /* 2s */ experimentalFeatures: Set = [], - sourcekitdTestHooks: SourceKitDTestHooks = SourceKitDTestHooks(), indexTestHooks: IndexTestHooks = IndexTestHooks() ) { self.buildSetup = buildSetup @@ -76,7 +72,6 @@ extension SourceKitLSPServer { self.generatedInterfacesPath = generatedInterfacesPath self.swiftPublishDiagnosticsDebounceDuration = swiftPublishDiagnosticsDebounceDuration self.experimentalFeatures = experimentalFeatures - self.sourcekitdTestHooks = sourcekitdTestHooks self.indexTestHooks = indexTestHooks } } diff --git a/Sources/SourceKitLSP/Swift/SwiftLanguageService.swift b/Sources/SourceKitLSP/Swift/SwiftLanguageService.swift index e8fa7bd84..edddc44a5 100644 --- a/Sources/SourceKitLSP/Swift/SwiftLanguageService.swift +++ b/Sources/SourceKitLSP/Swift/SwiftLanguageService.swift @@ -195,10 +195,7 @@ public actor SwiftLanguageService: LanguageService, Sendable { guard let sourcekitd = toolchain.sourcekitd else { return nil } self.sourceKitLSPServer = sourceKitLSPServer self.swiftFormat = toolchain.swiftFormat - self.sourcekitd = try await DynamicallyLoadedSourceKitD.getOrCreate( - dylibPath: sourcekitd, - testHooks: options.sourcekitdTestHooks - ) + self.sourcekitd = try await DynamicallyLoadedSourceKitD.getOrCreate(dylibPath: sourcekitd) self.capabilityRegistry = workspace.capabilityRegistry self.semanticIndexManager = workspace.semanticIndexManager self.serverOptions = options diff --git a/Tests/DiagnoseTests/DiagnoseTests.swift b/Tests/DiagnoseTests/DiagnoseTests.swift index 1776d086f..c1daf1d9c 100644 --- a/Tests/DiagnoseTests/DiagnoseTests.swift +++ b/Tests/DiagnoseTests/DiagnoseTests.swift @@ -318,8 +318,7 @@ private class InProcessSourceKitRequestExecutor: SourceKitRequestExecutor { logger.info("Sending request: \(requestString)") let sourcekitd = try await DynamicallyLoadedSourceKitD.getOrCreate( - dylibPath: try! AbsolutePath(validating: sourcekitd.path), - testHooks: SourceKitDTestHooks() + dylibPath: try! AbsolutePath(validating: sourcekitd.path) ) let response = try await sourcekitd.run(requestYaml: requestString) diff --git a/Tests/SourceKitDTests/SourceKitDRegistryTests.swift b/Tests/SourceKitDTests/SourceKitDRegistryTests.swift index b5dd07026..508cce551 100644 --- a/Tests/SourceKitDTests/SourceKitDRegistryTests.swift +++ b/Tests/SourceKitDTests/SourceKitDRegistryTests.swift @@ -63,7 +63,6 @@ private nonisolated(unsafe) var nextToken = AtomicUInt32(initialValue: 0) final class FakeSourceKitD: SourceKitD { let token: UInt32 - var testHooks: SourceKitDTestHooks { SourceKitDTestHooks() } var api: sourcekitd_api_functions_t { fatalError() } var keys: sourcekitd_api_keys { fatalError() } var requests: sourcekitd_api_requests { fatalError() } diff --git a/Tests/SourceKitDTests/SourceKitDTests.swift b/Tests/SourceKitDTests/SourceKitDTests.swift index 919b95c2f..0d5a3c5bc 100644 --- a/Tests/SourceKitDTests/SourceKitDTests.swift +++ b/Tests/SourceKitDTests/SourceKitDTests.swift @@ -28,10 +28,7 @@ import class TSCBasic.Process final class SourceKitDTests: XCTestCase { func testMultipleNotificationHandlers() async throws { let sourcekitdPath = await ToolchainRegistry.forTesting.default!.sourcekitd! - let sourcekitd = try await DynamicallyLoadedSourceKitD.getOrCreate( - dylibPath: sourcekitdPath, - testHooks: SourceKitDTestHooks() - ) + let sourcekitd = try await DynamicallyLoadedSourceKitD.getOrCreate(dylibPath: sourcekitdPath) let keys = sourcekitd.keys let path = DocumentURI(for: .swift).pseudoPath diff --git a/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift b/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift index 32a58bb9f..be5fe92d2 100644 --- a/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift +++ b/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift @@ -310,15 +310,10 @@ final class PullDiagnosticsTests: XCTestCase { } func testDontReturnEmptyDiagnosticsIfDiagnosticRequestIsCancelled() async throws { - let diagnosticSourcekitdRequestDidStart = self.expectation(description: "diagnostic sourcekitd request did start") let diagnosticRequestCancelled = self.expectation(description: "diagnostic request cancelled") var serverOptions = SourceKitLSPServer.Options.testDefault - serverOptions.sourcekitdTestHooks.sourcekitdRequestDidStart = { request in - guard request.description.contains("source.request.diagnostics") else { - return - } - diagnosticSourcekitdRequestDidStart.fulfill() - self.wait(for: [diagnosticRequestCancelled], timeout: defaultTimeout) + serverOptions.indexTestHooks.preparationTaskDidStart = { _ in + await self.fulfillment(of: [diagnosticRequestCancelled], timeout: defaultTimeout) // Poll until the `CancelRequestNotification` has been propagated to the request handling. for _ in 0.. Date: Wed, 5 Jun 2024 11:18:41 -0700 Subject: [PATCH 10/11] =?UTF-8?q?Only=20report=20log=20collection=20progre?= =?UTF-8?q?ss=20in=20`sourcekit-lsp=20diagnose`=20if=20we=E2=80=99ve=20mad?= =?UTF-8?q?e=20meaningful=20progress?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Especially when we didn’t have a redrawing progress indicator, we would spam the output with out making progress that was visible to the user because the overall progress just shows a percentage without any decimal digits. To fix this, only report progress when we’ve made at least 1% of progress in log collection. --- Sources/Diagnose/DiagnoseCommand.swift | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Sources/Diagnose/DiagnoseCommand.swift b/Sources/Diagnose/DiagnoseCommand.swift index d58f3b289..6fe1b120f 100644 --- a/Sources/Diagnose/DiagnoseCommand.swift +++ b/Sources/Diagnose/DiagnoseCommand.swift @@ -25,6 +25,12 @@ import class TSCUtility.PercentProgressAnimation @MainActor private var progressBar: PercentProgressAnimation? = nil +/// The last progress that was reported on the progress bar. This ensures that when the progress indicator uses the +/// `MultiLinePercentProgressAnimation` (eg. because stderr is redirected to a file) we don't emit status updates +/// without making any real progress. +@MainActor +private var lastProgress: (Int, String)? = nil + /// A component of the diagnostic bundle that's collected in independent stages. fileprivate enum BundleComponent: String, CaseIterable, ExpressibleByArgument { case crashReports = "crash-reports" @@ -292,7 +298,11 @@ public struct DiagnoseCommand: AsyncParsableCommand { @MainActor private func reportProgress(_ state: DiagnoseProgressState, message: String) { - progressBar?.update(step: Int(state.progress * 100), total: 100, text: message) + let progress: (step: Int, message: String) = (Int(state.progress * 100), message) + if lastProgress == nil || progress != lastProgress! { + progressBar?.update(step: Int(state.progress * 100), total: 100, text: message) + lastProgress = progress + } } @MainActor From 556fd333b5995bf965121d7e3042888daff9317c Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 5 Jun 2024 10:45:44 -0700 Subject: [PATCH 11/11] Heap allocate our atomics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We used C atomics but these were allocated as Swift variables. Even thought they were atomic, concurrent accesses to them could violate Swift’s exclusivity laws, raising thread sanitizer errors. Allocate the C atomics using malloc to fix this problem. rdar://129170128 --- Package.swift | 8 +- Sources/CAtomics/include/CAtomics.h | 71 +++------------- Sources/InProcessClient/CMakeLists.txt | 1 - .../InProcessSourceKitLSPClient.swift | 2 +- Sources/SKCore/CMakeLists.txt | 1 - Sources/SKCore/TaskScheduler.swift | 1 - Sources/SKSupport/Atomics.swift | 84 +++++++++++++++++++ Sources/SKSupport/CMakeLists.txt | 4 + .../SwiftPMBuildSystem.swift | 1 - .../TestSourceKitLSPClient.swift | 1 - Sources/SemanticIndex/CMakeLists.txt | 1 + .../PreparationTaskDescription.swift | 2 +- .../UpdateIndexStoreTaskDescription.swift | 1 - Sources/SourceKitLSP/SourceKitLSPServer.swift | 1 - .../SourceKitDRegistryTests.swift | 2 +- .../PullDiagnosticsTests.swift | 2 +- 16 files changed, 107 insertions(+), 76 deletions(-) create mode 100644 Sources/SKSupport/Atomics.swift diff --git a/Package.swift b/Package.swift index c9f932632..54720b892 100644 --- a/Package.swift +++ b/Package.swift @@ -108,7 +108,6 @@ let package = Package( .target( name: "InProcessClient", dependencies: [ - "CAtomics", "LanguageServerProtocol", "LSPLogging", "SKCore", @@ -193,7 +192,6 @@ let package = Package( .target( name: "SemanticIndex", dependencies: [ - "CAtomics", "LanguageServerProtocol", "LSPLogging", "SKCore", @@ -218,7 +216,6 @@ let package = Package( name: "SKCore", dependencies: [ "BuildServerProtocol", - "CAtomics", "LanguageServerProtocol", "LanguageServerProtocolJSONRPC", "LSPLogging", @@ -247,10 +244,11 @@ let package = Package( .target( name: "SKSupport", dependencies: [ - .product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"), + "CAtomics", "LanguageServerProtocol", "LSPLogging", "SwiftExtensions", + .product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"), ], exclude: ["CMakeLists.txt"], swiftSettings: [.enableExperimentalFeature("StrictConcurrency")] @@ -350,7 +348,6 @@ let package = Package( name: "SourceKitLSP", dependencies: [ "BuildServerProtocol", - "CAtomics", "LanguageServerProtocol", "LanguageServerProtocolJSONRPC", "LSPLogging", @@ -378,7 +375,6 @@ let package = Package( name: "SourceKitLSPTests", dependencies: [ "BuildServerProtocol", - "CAtomics", "LSPLogging", "LSPTestSupport", "LanguageServerProtocol", diff --git a/Sources/CAtomics/include/CAtomics.h b/Sources/CAtomics/include/CAtomics.h index 16b797a57..8ec87f35f 100644 --- a/Sources/CAtomics/include/CAtomics.h +++ b/Sources/CAtomics/include/CAtomics.h @@ -16,79 +16,32 @@ #include #include #include - -// MARK: - AtomicBool - -typedef struct { - _Atomic(bool) value; -} AtomicBool; - -__attribute__((swift_name("AtomicBool.init(initialValue:)"))) -static inline AtomicBool atomic_bool_create(bool initialValue) { - AtomicBool atomic; - atomic.value = initialValue; - return atomic; -} - -__attribute__((swift_name("getter:AtomicBool.value(self:)"))) -static inline bool atomic_bool_get(AtomicBool *atomic) { - return atomic->value; -} - -__attribute__((swift_name("setter:AtomicBool.value(self:_:)"))) -static inline void atomic_bool_set(AtomicBool *atomic, bool newValue) { - atomic->value = newValue; -} - -// MARK: - AtomicUInt8 +#include typedef struct { - _Atomic(uint8_t) value; -} AtomicUInt8; + _Atomic(uint32_t) value; +} CAtomicUInt32; -__attribute__((swift_name("AtomicUInt8.init(initialValue:)"))) -static inline AtomicUInt8 atomic_uint8_create(uint8_t initialValue) { - AtomicUInt8 atomic; - atomic.value = initialValue; +static inline CAtomicUInt32 *_Nonnull atomic_uint32_create(uint32_t initialValue) { + CAtomicUInt32 *atomic = malloc(sizeof(CAtomicUInt32)); + atomic->value = initialValue; return atomic; } -__attribute__((swift_name("getter:AtomicUInt8.value(self:)"))) -static inline uint8_t atomic_uint8_get(AtomicUInt8 *atomic) { +static inline uint32_t atomic_uint32_get(CAtomicUInt32 *_Nonnull atomic) { return atomic->value; } -__attribute__((swift_name("setter:AtomicUInt8.value(self:_:)"))) -static inline void atomic_uint8_set(AtomicUInt8 *atomic, uint8_t newValue) { +static inline void atomic_uint32_set(CAtomicUInt32 *_Nonnull atomic, uint32_t newValue) { atomic->value = newValue; } -// MARK: AtomicInt - -typedef struct { - _Atomic(int) value; -} AtomicUInt32; - -__attribute__((swift_name("AtomicUInt32.init(initialValue:)"))) -static inline AtomicUInt32 atomic_int_create(uint32_t initialValue) { - AtomicUInt32 atomic; - atomic.value = initialValue; - return atomic; -} - -__attribute__((swift_name("getter:AtomicUInt32.value(self:)"))) -static inline uint32_t atomic_int_get(AtomicUInt32 *atomic) { - return atomic->value; -} - -__attribute__((swift_name("setter:AtomicUInt32.value(self:_:)"))) -static inline void atomic_uint32_set(AtomicUInt32 *atomic, uint32_t newValue) { - atomic->value = newValue; +static inline uint32_t atomic_uint32_fetch_and_increment(CAtomicUInt32 *_Nonnull atomic) { + return atomic->value++; } -__attribute__((swift_name("AtomicUInt32.fetchAndIncrement(self:)"))) -static inline uint32_t atomic_uint32_fetch_and_increment(AtomicUInt32 *atomic) { - return atomic->value++; +static inline void atomic_uint32_destroy(CAtomicUInt32 *_Nonnull atomic) { + free(atomic); } #endif // SOURCEKITLSP_CATOMICS_H diff --git a/Sources/InProcessClient/CMakeLists.txt b/Sources/InProcessClient/CMakeLists.txt index 023f44d31..7be5da235 100644 --- a/Sources/InProcessClient/CMakeLists.txt +++ b/Sources/InProcessClient/CMakeLists.txt @@ -6,7 +6,6 @@ set_target_properties(InProcessClient PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) target_link_libraries(InProcessClient PUBLIC - CAtomics LanguageServerProtocol LSPLogging SKCore diff --git a/Sources/InProcessClient/InProcessSourceKitLSPClient.swift b/Sources/InProcessClient/InProcessSourceKitLSPClient.swift index 173e99b18..c7e05369d 100644 --- a/Sources/InProcessClient/InProcessSourceKitLSPClient.swift +++ b/Sources/InProcessClient/InProcessSourceKitLSPClient.swift @@ -10,9 +10,9 @@ // //===----------------------------------------------------------------------===// -import CAtomics import LanguageServerProtocol import SKCore +import SKSupport import SourceKitLSP /// Launches a `SourceKitLSPServer` in-process and allows sending messages to it. diff --git a/Sources/SKCore/CMakeLists.txt b/Sources/SKCore/CMakeLists.txt index 0190b23b6..73f8af24f 100644 --- a/Sources/SKCore/CMakeLists.txt +++ b/Sources/SKCore/CMakeLists.txt @@ -23,7 +23,6 @@ set_target_properties(SKCore PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) target_link_libraries(SKCore PUBLIC BuildServerProtocol - CAtomics LanguageServerProtocol LanguageServerProtocolJSONRPC LSPLogging diff --git a/Sources/SKCore/TaskScheduler.swift b/Sources/SKCore/TaskScheduler.swift index fbe8e947a..c9887497e 100644 --- a/Sources/SKCore/TaskScheduler.swift +++ b/Sources/SKCore/TaskScheduler.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import CAtomics import Foundation import LSPLogging import SKSupport diff --git a/Sources/SKSupport/Atomics.swift b/Sources/SKSupport/Atomics.swift new file mode 100644 index 000000000..5a75920e9 --- /dev/null +++ b/Sources/SKSupport/Atomics.swift @@ -0,0 +1,84 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import CAtomics + +#if compiler(>=6.2) +#warning("We should be able to use atomics in the stdlib when we raise the deployment target to require Swift 6") +#endif + +public class AtomicBool { + private let atomic: UnsafeMutablePointer + + public init(initialValue: Bool) { + self.atomic = atomic_uint32_create(initialValue ? 1 : 0) + } + + deinit { + atomic_uint32_destroy(atomic) + } + + public var value: Bool { + get { + atomic_uint32_get(atomic) != 0 + } + set { + atomic_uint32_set(atomic, newValue ? 1 : 0) + } + } +} + +public class AtomicUInt8 { + private let atomic: UnsafeMutablePointer + + public init(initialValue: UInt8) { + self.atomic = atomic_uint32_create(UInt32(initialValue)) + } + + deinit { + atomic_uint32_destroy(atomic) + } + + public var value: UInt8 { + get { + UInt8(atomic_uint32_get(atomic)) + } + set { + atomic_uint32_set(atomic, UInt32(newValue)) + } + } +} + +public class AtomicUInt32 { + private let atomic: UnsafeMutablePointer + + public init(initialValue: UInt32) { + self.atomic = atomic_uint32_create(initialValue) + } + + public var value: UInt32 { + get { + atomic_uint32_get(atomic) + } + set { + atomic_uint32_set(atomic, newValue) + } + } + + deinit { + atomic_uint32_destroy(atomic) + } + + public func fetchAndIncrement() -> UInt32 { + return atomic_uint32_fetch_and_increment(atomic) + } +} diff --git a/Sources/SKSupport/CMakeLists.txt b/Sources/SKSupport/CMakeLists.txt index b5c7d8d19..6474d84bb 100644 --- a/Sources/SKSupport/CMakeLists.txt +++ b/Sources/SKSupport/CMakeLists.txt @@ -1,5 +1,6 @@ add_library(SKSupport STATIC + Atomics.swift BuildConfiguration.swift ByteString.swift Connection+Send.swift @@ -17,6 +18,9 @@ add_library(SKSupport STATIC ) set_target_properties(SKSupport PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) +target_link_libraries(SKSupport PUBLIC + CAtomics +) target_link_libraries(SKSupport PRIVATE LanguageServerProtocol LSPLogging diff --git a/Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift b/Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift index 6f3812fe9..e6ec43218 100644 --- a/Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift +++ b/Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift @@ -13,7 +13,6 @@ import Basics import Build import BuildServerProtocol -import CAtomics import Dispatch import Foundation import LSPLogging diff --git a/Sources/SKTestSupport/TestSourceKitLSPClient.swift b/Sources/SKTestSupport/TestSourceKitLSPClient.swift index cd4e38f1c..859a7b650 100644 --- a/Sources/SKTestSupport/TestSourceKitLSPClient.swift +++ b/Sources/SKTestSupport/TestSourceKitLSPClient.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import CAtomics import Foundation import InProcessClient import LSPTestSupport diff --git a/Sources/SemanticIndex/CMakeLists.txt b/Sources/SemanticIndex/CMakeLists.txt index 4623f2614..83ac62fb4 100644 --- a/Sources/SemanticIndex/CMakeLists.txt +++ b/Sources/SemanticIndex/CMakeLists.txt @@ -12,6 +12,7 @@ add_library(SemanticIndex STATIC set_target_properties(SemanticIndex PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) target_link_libraries(SemanticIndex PRIVATE + LanguageServerProtocol LSPLogging SKCore SwiftExtensions diff --git a/Sources/SemanticIndex/PreparationTaskDescription.swift b/Sources/SemanticIndex/PreparationTaskDescription.swift index db854e923..aacdc8d69 100644 --- a/Sources/SemanticIndex/PreparationTaskDescription.swift +++ b/Sources/SemanticIndex/PreparationTaskDescription.swift @@ -10,11 +10,11 @@ // //===----------------------------------------------------------------------===// -import CAtomics import Foundation import LSPLogging import LanguageServerProtocol import SKCore +import SKSupport import struct TSCBasic.AbsolutePath import class TSCBasic.Process diff --git a/Sources/SemanticIndex/UpdateIndexStoreTaskDescription.swift b/Sources/SemanticIndex/UpdateIndexStoreTaskDescription.swift index 88f5a9e64..d15a0b5c3 100644 --- a/Sources/SemanticIndex/UpdateIndexStoreTaskDescription.swift +++ b/Sources/SemanticIndex/UpdateIndexStoreTaskDescription.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import CAtomics import Foundation import LSPLogging import LanguageServerProtocol diff --git a/Sources/SourceKitLSP/SourceKitLSPServer.swift b/Sources/SourceKitLSP/SourceKitLSPServer.swift index 9f0f6a43e..65016b27c 100644 --- a/Sources/SourceKitLSP/SourceKitLSPServer.swift +++ b/Sources/SourceKitLSP/SourceKitLSPServer.swift @@ -11,7 +11,6 @@ //===----------------------------------------------------------------------===// import BuildServerProtocol -import CAtomics import Dispatch import Foundation import IndexStoreDB diff --git a/Tests/SourceKitDTests/SourceKitDRegistryTests.swift b/Tests/SourceKitDTests/SourceKitDRegistryTests.swift index b5dd07026..5d99002e4 100644 --- a/Tests/SourceKitDTests/SourceKitDRegistryTests.swift +++ b/Tests/SourceKitDTests/SourceKitDRegistryTests.swift @@ -10,8 +10,8 @@ // //===----------------------------------------------------------------------===// -import CAtomics import LSPTestSupport +import SKSupport import SourceKitD import TSCBasic import XCTest diff --git a/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift b/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift index 32a58bb9f..a44b5cc12 100644 --- a/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift +++ b/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift @@ -10,9 +10,9 @@ // //===----------------------------------------------------------------------===// -import CAtomics import LSPTestSupport import LanguageServerProtocol +import SKSupport import SKTestSupport import SourceKitLSP import XCTest