From 9006161fe9fed9f9c5fff16a003e37945fae37c0 Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Mon, 29 Apr 2024 16:55:50 +0800 Subject: [PATCH 01/31] merge Fix buildJars task (#1481) chore(deps): bump protobufjs from 7.2.4 to 7.2.6 in /npm-package (#1483) Bumps [protobufjs](https://github.com/protobufjs/protobuf.js) from 7.2.4 to 7.2.6. - [Release notes](https://github.com/protobufjs/protobuf.js/releases) - [Changelog](https://github.com/protobufjs/protobuf.js/blob/master/CHANGELOG.md) - [Commits](https://github.com/protobufjs/protobuf.js/compare/protobufjs-v7.2.4...protobufjs-v7.2.6) --- updated-dependencies: - dependency-name: protobufjs dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jinbo Wang chore(deps-dev): bump protobufjs from 7.2.4 to 7.2.6 in /extension (#1482) Bumps [protobufjs](https://github.com/protobufjs/protobuf.js) from 7.2.4 to 7.2.6. - [Release notes](https://github.com/protobufjs/protobuf.js/releases) - [Changelog](https://github.com/protobufjs/protobuf.js/blob/master/CHANGELOG.md) - [Commits](https://github.com/protobufjs/protobuf.js/compare/protobufjs-v7.2.4...protobufjs-v7.2.6) --- updated-dependencies: - dependency-name: protobufjs dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jinbo Wang chores: Add build server debug config (#1486) build: Update code sign task to v5 (#1488) refactor: Move Gradle Daemons implementation from Java into Typescript (#1489) Fix: Append the log message to build output if it's from compilation report. (#1490) - Store the id of the compilation task in an LRU cache. - Append the log message to the build output if it's from the compilation task. Signed-off-by: Sheng Chen fix: set GradleExecution jdk use java.import.gradle.java.home (#1491) Refresh the output folders after build tasks (#1493) Signed-off-by: Sheng Chen build: Update telemetry wrapper to 0.14.0 (#1495) chore(deps-dev): bump braces from 3.0.2 to 3.0.3 in /extension (#1496) Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3. - [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3) --- updated-dependencies: - dependency-name: braces dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Extract api scan to a separate pipeline (#1498) build: Prepare for 3.14.0 (#1499) chore(deps-dev): bump braces from 3.0.2 to 3.0.3 in /npm-package (#1500) Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3. - [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3) --- updated-dependencies: - dependency-name: braces dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> gradle server muti-thread fix- Fix the importer version and prepare for 3.14.1 (#1501) build - Update target platform (#1502) init named pipe impl muti-thread and named pipe for buildServer gradle server muti-thread fix- Fix the importer version and prepare for 3.14.1 (#1501) build - Update target platform (#1502) init named pipe test build Server named pipe connection test build Server named pipe connection update build/initialize sender finish forward message demo finish merge and named pipe implement polling solution Only send request after initialization (#1503) fix - Add java 22 to compatibility matrix (#1505) Introduce gson to simplify the object parsing (#1509) Signed-off-by: Sheng Chen fix bug: buildServerHandler --- .azure-pipelines/vscode-gradle-apiscan.yml | 112 ++++++++ .azure-pipelines/vscode-gradle-nightly.yml | 41 ++- .azure-pipelines/vscode-gradle-rc.yml | 40 ++- .vscode/launch.json | 20 ++ .vscode/settings.json | 7 +- CHANGELOG.md | 23 ++ CONTRIBUTING.md | 19 ++ ...com.microsoft.gradle.bs.importer.tp.target | 25 +- .../pom.xml | 2 +- .../META-INF/MANIFEST.MF | 5 +- .../com.microsoft.gradle.bs.importer/pom.xml | 2 +- .../gradle/bs/importer/GradleBuildClient.java | 33 ++- .../GradleBuildServerBuildSupport.java | 65 +++-- .../GradleBuildServerProjectImporter.java | 36 ++- .../bs/importer/ImporterNamedPipeStream.java | 263 ++++++++++++++++++ .../gradle/bs/importer/ImporterPlugin.java | 110 ++++---- .../importer/builder/BuildServerBuilder.java | 60 +++- .../ClasspathJrtWithReleaseOption.java | 3 +- .../java/builder/jdtbuilder/State.java | 3 +- extension/jdtls.ext/pom.xml | 2 +- extension/package-lock.json | 168 +++++++---- extension/package.json | 14 +- extension/src/Extension.ts | 36 ++- extension/src/bs/BuildServerController.ts | 28 +- extension/src/bs/BuildServerHandler.ts | 35 +++ extension/src/bs/ForwardingAgent.ts | 105 +++++++ extension/src/bs/ImporterHandler.ts | 66 +++++ extension/src/commands/Commands.ts | 5 +- extension/src/commands/StopDaemonCommand.ts | 24 +- extension/src/commands/StopDaemonsCommand.ts | 44 ++- extension/src/constant.ts | 1 + extension/src/index.ts | 2 +- extension/src/logger/Logger.ts | 2 +- extension/src/server/GradleServer.ts | 31 ++- extension/src/test/unit/gradleDaemons.test.ts | 249 +++++++++-------- extension/src/util/config.ts | 30 ++ extension/src/util/execAsync.ts | 4 + extension/src/util/generateRandomPipeName.ts | 13 + extension/src/views/constants.ts | 12 +- .../gradleDaemons/GradleDaemonTreeItem.ts | 16 +- .../GradleDaemonsTreeDataProvider.ts | 37 ++- .../views/gradleDaemons/models/DaemonInfo.ts | 29 ++ .../gradleDaemons/models/DaemonStatus.ts | 7 + .../models/GradleConnectionType.ts | 5 + .../gradleDaemons/services/GradleExecution.ts | 3 + .../services/GradleLocalInstallation.ts | 34 +++ .../gradleDaemons/services/GradleStatus.ts | 71 +++++ .../gradleDaemons/services/GradleWrapper.ts | 43 +++ gradle-server/build.gradle | 3 + .../badsyntax/gradle/BuildServerThread.java | 66 +++++ .../github/badsyntax/gradle/GradleServer.java | 33 ++- npm-package/package-lock.json | 34 +-- 52 files changed, 1667 insertions(+), 454 deletions(-) create mode 100644 .azure-pipelines/vscode-gradle-apiscan.yml create mode 100644 extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterNamedPipeStream.java create mode 100644 extension/src/bs/BuildServerHandler.ts create mode 100644 extension/src/bs/ForwardingAgent.ts create mode 100644 extension/src/bs/ImporterHandler.ts create mode 100644 extension/src/util/execAsync.ts create mode 100644 extension/src/util/generateRandomPipeName.ts create mode 100644 extension/src/views/gradleDaemons/models/DaemonInfo.ts create mode 100644 extension/src/views/gradleDaemons/models/DaemonStatus.ts create mode 100644 extension/src/views/gradleDaemons/models/GradleConnectionType.ts create mode 100644 extension/src/views/gradleDaemons/services/GradleExecution.ts create mode 100644 extension/src/views/gradleDaemons/services/GradleLocalInstallation.ts create mode 100644 extension/src/views/gradleDaemons/services/GradleStatus.ts create mode 100644 extension/src/views/gradleDaemons/services/GradleWrapper.ts create mode 100644 gradle-server/src/main/java/com/github/badsyntax/gradle/BuildServerThread.java diff --git a/.azure-pipelines/vscode-gradle-apiscan.yml b/.azure-pipelines/vscode-gradle-apiscan.yml new file mode 100644 index 000000000..245c26afc --- /dev/null +++ b/.azure-pipelines/vscode-gradle-apiscan.yml @@ -0,0 +1,112 @@ +name: $(Date:yyyyMMdd).$(Rev:r) +resources: + repositories: + - repository: self + type: git + ref: refs/heads/main + - repository: 1esPipelines + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release +trigger: none +pr: none +extends: + template: v1/1ES.Official.PipelineTemplate.yml@1esPipelines + parameters: + pool: + name: 1ES_JavaTooling_Pool + image: 1ES_JavaTooling_Windows_2022 + os: windows + sdl: + sourceAnalysisPool: + name: 1ES_JavaTooling_Pool + image: 1ES_JavaTooling_Windows_2022 + os: windows + spotBugs: + enabled: false + stages: + - stage: Build + jobs: + - job: Job_1 + displayName: VSCode-Gradle-API-SCAN + templateContext: + outputs: + - output: pipelineArtifact + artifactName: extension + targetPath: $(Build.ArtifactStagingDirectory) + displayName: "Publish Artifact: extension" + steps: + - checkout: self + fetchTags: true + - task: JavaToolInstaller@0 + displayName: Install Java 11 + inputs: + versionSpec: '11' + jdkArchitectureOption: 'x64' + jdkSourceOption: 'PreInstalled' + - task: NodeTool@0 + displayName: Install Node 16.14.2 + inputs: + versionSpec: '16.14.2' + - task: Gradle@2 + displayName: Build + inputs: + gradleWrapperFile: 'gradlew' + gradleOptions: '-Xmx3072m' + tasks: 'build' + - task: Gradle@2 + displayName: PrepareForRelease + inputs: + gradleWrapperFile: 'gradlew' + gradleOptions: '-Xmx3072m' + tasks: 'prepareForRelease' + - bash: chmod +x gradle-server + workingDirectory: $(Build.SourcesDirectory)/extension/lib + displayName: Set permission + - task: DownloadBuildArtifacts@1 + displayName: 'Download Build Server Artifacts' + inputs: + buildType: specific + project: 'a4d27ce2-a42d-4b71-8eef-78cee9a9728e' + pipeline: 16493 + downloadType: specific + extractTars: false + - task: CopyFiles@2 + displayName: 'Copy Build Server Artifacts' + inputs: + SourceFolder: '$(System.ArtifactsDirectory)/build-server/server/build/libs' + Contents: '**' + TargetFolder: $(Build.SourcesDirectory)/extension/server + - task: JavaToolInstaller@0 + displayName: Install Java 17 + inputs: + versionSpec: '17' + jdkArchitectureOption: 'x64' + jdkSourceOption: 'PreInstalled' + - task: Gradle@2 + displayName: Build + inputs: + gradleWrapperFile: 'gradlew' + gradleOptions: '-Xmx3072m' + tasks: ':extension:copyJdtlsPluginJar' + - bash: npx @vscode/vsce@latest package + workingDirectory: $(Build.SourcesDirectory)/extension + displayName: Package VSIX + - task: CopyFiles@2 + displayName: "Copy Files for APIScan" + inputs: + Contents: "extension/*.vsix" + TargetFolder: $(Agent.TempDirectory)/APIScanFiles + condition: and(succeeded(), ne(variables['DisableAPIScan'], 'true')) + ### Run latest version of APIScan listed at https://www.1eswiki.com/wiki/APIScan_Build_Task + - task: APIScan@2 + displayName: Run APIScan + inputs: + softwareFolder: $(Agent.TempDirectory)/APIScanFiles + softwareName: "vscode-gradle" + softwareVersionNum: "$(Build.BuildId)" + isLargeApp: false + toolVersion: "Latest" + condition: and(succeeded(), ne(variables['DisableAPIScan'], 'true')) + env: + AzureServicesAuthConnectionString: runAs=App;AppId=$(ApiScanClientId);TenantId=$(ApiScanTenant);AppKey=$(ApiScanSecret) diff --git a/.azure-pipelines/vscode-gradle-nightly.yml b/.azure-pipelines/vscode-gradle-nightly.yml index 2b86f0464..72682e475 100644 --- a/.azure-pipelines/vscode-gradle-nightly.yml +++ b/.azure-pipelines/vscode-gradle-nightly.yml @@ -20,8 +20,8 @@ extends: parameters: pool: name: 1ES_JavaTooling_Pool - image: 1ES_JavaTooling_Windows_2022 - os: windows + image: 1ES_JavaTooling_Ubuntu-2004 + os: linux sdl: sourceAnalysisPool: name: 1ES_JavaTooling_Pool @@ -68,10 +68,15 @@ extends: - bash: chmod +x gradle-server workingDirectory: $(Build.SourcesDirectory)/extension/lib displayName: Set permission - - task: EsrpCodeSigning@2 + - task: EsrpCodeSigning@5 displayName: 'ESRP CodeSigning' inputs: - ConnectedServiceName: 'vscjavaci_esrp_codesign' + ConnectedServiceName: 'ESRP-Release-Test' + AppRegistrationClientId: '1992ee18-e9d2-42d6-ab20-94dd947a44b6' + AppRegistrationTenantId: '72f988bf-86f1-41af-91ab-2d7cd011db47' + AuthAKVName: 'vscjavaci' + AuthCertName: 'vscjava-esrprelease-auth' + AuthSignCertName: 'VSCJava-CodeSign' FolderPath: 'extension/lib' Pattern: 'gradle-server.jar' signConfigType: 'inlineSignParams' @@ -143,10 +148,15 @@ extends: gradleWrapperFile: 'gradlew' gradleOptions: '-Xmx3072m' tasks: ':extension:copyJdtlsPluginJar' - - task: EsrpCodeSigning@2 + - task: EsrpCodeSigning@5 displayName: 'ESRP CodeSigning' inputs: - ConnectedServiceName: 'vscjavaci_esrp_codesign' + ConnectedServiceName: 'ESRP-Release-Test' + AppRegistrationClientId: '1992ee18-e9d2-42d6-ab20-94dd947a44b6' + AppRegistrationTenantId: '72f988bf-86f1-41af-91ab-2d7cd011db47' + AuthAKVName: 'vscjavaci' + AuthCertName: 'vscjava-esrprelease-auth' + AuthSignCertName: 'VSCJava-CodeSign' FolderPath: 'extension/server' Pattern: 'com.microsoft.gradle.bs.importer-*.jar' signConfigType: 'inlineSignParams' @@ -173,25 +183,6 @@ extends: - bash: npx @vscode/vsce@latest package --pre-release workingDirectory: $(Build.SourcesDirectory)/extension displayName: Package VSIX - ### Copy files for APIScan - - task: CopyFiles@2 - displayName: "Copy Files for APIScan" - inputs: - Contents: "extension/*.vsix" - TargetFolder: $(Agent.TempDirectory)/APIScanFiles - condition: and(succeeded(), ne(variables['DisableAPIScan'], 'true')) - ### Run latest version of APIScan listed at https://www.1eswiki.com/wiki/APIScan_Build_Task - - task: APIScan@2 - displayName: Run APIScan - inputs: - softwareFolder: $(Agent.TempDirectory)/APIScanFiles - softwareName: "vscode-gradle" - softwareVersionNum: "$(Build.BuildId)" - isLargeApp: false - toolVersion: "Latest" - condition: and(succeeded(), ne(variables['DisableAPIScan'], 'true')) - env: - AzureServicesAuthConnectionString: runAs=App;AppId=$(ApiScanClientId);TenantId=$(ApiScanTenant);AppKey=$(ApiScanSecret) - task: CopyFiles@2 displayName: Copy VSIX inputs: diff --git a/.azure-pipelines/vscode-gradle-rc.yml b/.azure-pipelines/vscode-gradle-rc.yml index b884423ea..bb86b0929 100644 --- a/.azure-pipelines/vscode-gradle-rc.yml +++ b/.azure-pipelines/vscode-gradle-rc.yml @@ -20,8 +20,8 @@ extends: parameters: pool: name: 1ES_JavaTooling_Pool - image: 1ES_JavaTooling_Windows_2022 - os: windows + image: 1ES_JavaTooling_Ubuntu-2004 + os: linux sdl: sourceAnalysisPool: name: 1ES_JavaTooling_Pool @@ -68,10 +68,15 @@ extends: - bash: chmod +x gradle-server workingDirectory: $(Build.SourcesDirectory)/extension/lib displayName: Set permission - - task: EsrpCodeSigning@2 + - task: EsrpCodeSigning@5 displayName: 'ESRP CodeSigning' inputs: - ConnectedServiceName: 'vscjavaci_esrp_codesign' + ConnectedServiceName: 'ESRP-Release-Test' + AppRegistrationClientId: '1992ee18-e9d2-42d6-ab20-94dd947a44b6' + AppRegistrationTenantId: '72f988bf-86f1-41af-91ab-2d7cd011db47' + AuthAKVName: 'vscjavaci' + AuthCertName: 'vscjava-esrprelease-auth' + AuthSignCertName: 'VSCJava-CodeSign' FolderPath: 'extension/lib' Pattern: 'gradle-server.jar' signConfigType: 'inlineSignParams' @@ -138,10 +143,15 @@ extends: gradleWrapperFile: 'gradlew' gradleOptions: '-Xmx3072m' tasks: ':extension:copyJdtlsPluginJar' - - task: EsrpCodeSigning@2 + - task: EsrpCodeSigning@5 displayName: 'ESRP CodeSigning' inputs: - ConnectedServiceName: 'vscjavaci_esrp_codesign' + ConnectedServiceName: 'ESRP-Release-Test' + AppRegistrationClientId: '1992ee18-e9d2-42d6-ab20-94dd947a44b6' + AppRegistrationTenantId: '72f988bf-86f1-41af-91ab-2d7cd011db47' + AuthAKVName: 'vscjavaci' + AuthCertName: 'vscjava-esrprelease-auth' + AuthSignCertName: 'VSCJava-CodeSign' FolderPath: 'extension/server' Pattern: 'com.microsoft.gradle.bs.importer-*.jar' signConfigType: 'inlineSignParams' @@ -168,24 +178,6 @@ extends: - bash: npx @vscode/vsce@latest package workingDirectory: $(Build.SourcesDirectory)/extension displayName: Package VSIX - - task: CopyFiles@2 - displayName: "Copy Files for APIScan" - inputs: - Contents: "extension/*.vsix" - TargetFolder: $(Agent.TempDirectory)/APIScanFiles - condition: and(succeeded(), ne(variables['DisableAPIScan'], 'true')) - ### Run latest version of APIScan listed at https://www.1eswiki.com/wiki/APIScan_Build_Task - - task: APIScan@2 - displayName: Run APIScan - inputs: - softwareFolder: $(Agent.TempDirectory)/APIScanFiles - softwareName: "vscode-gradle" - softwareVersionNum: "$(Build.BuildId)" - isLargeApp: false - toolVersion: "Latest" - condition: and(succeeded(), ne(variables['DisableAPIScan'], 'true')) - env: - AzureServicesAuthConnectionString: runAs=App;AppId=$(ApiScanClientId);TenantId=$(ApiScanTenant);AppKey=$(ApiScanSecret) - task: CopyFiles@2 displayName: Copy VSIX inputs: diff --git a/.vscode/launch.json b/.vscode/launch.json index 5101fbb89..3c3f33f82 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -81,6 +81,26 @@ "order": 2 } }, + { + "name": "Debug Extension & Build Server", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}/extension" + ], + "outFiles": [ + "${workspaceFolder}/extension/dist/**/*.js" + ], + "preLaunchTask": "Gradle: Build", + "presentation": { + "group": "debug", + "order": 3 + }, + "env": { + "DEBUG_GRADLE_BUILD_SERVER":"true" + }, + }, { "name": "Debug Language Server: Launch Extension", "type": "extensionHost", diff --git a/.vscode/settings.json b/.vscode/settings.json index cdb66a421..310ff76a3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,7 +18,7 @@ ], "editor.formatOnSave": false, "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" }, "[markdown]": { "editor.defaultFormatter": "esbenp.prettier-vscode" @@ -32,12 +32,13 @@ "[java]": { "editor.defaultFormatter": "redhat.java", "editor.codeActionsOnSave": { - "source.fixAll.spotlessGradle": true + "source.fixAll.spotlessGradle": "explicit" } }, "[xml]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "files.insertFinalNewline": true, - "files.trimTrailingWhitespace": true + "files.trimTrailingWhitespace": true, + "java.compile.nullAnalysis.mode": "disabled" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 619209bdc..8650edfea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,29 @@ All notable changes to the "vscode-gradle" extension will be documented in this The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +### 3.14.1 +### What's Changed +* fix - Fix the importer version. + +## 3.14.0 +### What's Changed +* enhancement - Bump Gradle Wrapper to v8.5 and change Java 21 min Gradle version by @JoseLion in https://github.com/microsoft/vscode-gradle/pull/1455 +* enhancement - Group the Gradle related menus in the file explorer into a submenu labeled 'Gradle' by @testforstephen in https://github.com/microsoft/vscode-gradle/pull/1474 +* enhancement - Upgrade Build Server for Gradle to 0.2.0 +* fix - Exclude the project itself becoming its project classpath entry by @jdneo in https://github.com/microsoft/vscode-gradle/pull/1464 +* fix - Append the log message to build output if it's from compilation report. by @jdneo in https://github.com/microsoft/vscode-gradle/pull/1490 +* fix - Set GradleExecution jdk use java.import.gradle.java.home by @Jiaaming in https://github.com/microsoft/vscode-gradle/pull/1491 +* fix - Refresh the output folders after build tasks by @jdneo in https://github.com/microsoft/vscode-gradle/pull/1493 +* build - Fix buildJars task by @donat in https://github.com/microsoft/vscode-gradle/pull/1481 +* build - Add Build Server debug config by @Jiaaming in https://github.com/microsoft/vscode-gradle/pull/1486 +* build - Move Gradle Daemons implementation from Java into Typescript by @Jiaaming in https://github.com/microsoft/vscode-gradle/pull/1489 + +## New Contributors +* @donat made their first contribution in https://github.com/microsoft/vscode-gradle/pull/1481 +* @Jiaaming made their first contribution in https://github.com/microsoft/vscode-gradle/pull/1486 + +**Full Changelog**: https://github.com/microsoft/vscode-gradle/compare/3.13.5...3.14.0 + ## 3.13.5 ### Added - Implement onWillUpdate() in GradleBuildServerBuildSupport. [PR#1405](https://github.com/microsoft/vscode-gradle/pull/1405) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ed0a5b509..3ade961ce 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,6 +40,25 @@ The extension uses a Gradle plugin (`com.microsoft.gradle.GradlePlugin`) to get > Note: There is a known issue that when the Gradle project stores in a sub-folder of the root folder, the `Attach to Gradle Plugin` will fail to attach. See [#1237](https://github.com/microsoft/vscode-gradle/issues/1237). +## Debugging Gradle Build Server + +To debug the Extension with the [Gradle Build Server](https://github.com/microsoft/build-server-for-gradle), follow these steps: + +1. Open the `extension/build-server-for-gradle` directory, which you should have [imported previously](#build-gradle-project-importer) as a separate project. +2. In the `.vscode/launch.json` of the build-server-for-gradle project, ensure you have the following configuration to attach the debugger: + ```json + { + "type": "java", + "name": "Attach to Gradle Build Server", + "request": "attach", + "hostName": "localhost", + "port": "8989", + "projectName": "server" + } + ``` +3. In your main project (vscode-gradle), start the `Debug Extension & Build Server` launch configuration. +4. In the build-server-for-gradle project, start the `Attach to Gradle Build Server` launch configuration. + ## Debugging Gradle Language Server (editing feature related) 1. Run vscode launch configuration `Debug Language Server: Launch Extension`. diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer.target/com.microsoft.gradle.bs.importer.tp.target b/extension/jdtls.ext/com.microsoft.gradle.bs.importer.target/com.microsoft.gradle.bs.importer.tp.target index cf5e9dd43..65fafd057 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer.target/com.microsoft.gradle.bs.importer.tp.target +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer.target/com.microsoft.gradle.bs.importer.tp.target @@ -2,27 +2,38 @@ - - - + + + + org.apache.commons + commons-lang3 + 3.14.0 + jar + + + - - + + + + + + - + - + diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer.target/pom.xml b/extension/jdtls.ext/com.microsoft.gradle.bs.importer.target/pom.xml index 1f0577a48..de854971d 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer.target/pom.xml +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer.target/pom.xml @@ -4,7 +4,7 @@ com.microsoft.gradle.jdtls.ext gradle-jdtls-ext-parent - 0.2.0 + 0.3.0 com.microsoft.gradle.bs.importer.tp ${base.name} :: Target Platform diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/META-INF/MANIFEST.MF b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/META-INF/MANIFEST.MF index 0bb519b5d..383bb5b03 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/META-INF/MANIFEST.MF +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Gradle Build Server Importer Bundle-SymbolicName: com.microsoft.gradle.bs.importer;singleton:=true -Bundle-Version: 0.2.0 +Bundle-Version: 0.3.0 Bundle-Activator: com.microsoft.gradle.bs.importer.ImporterPlugin Bundle-RequiredExecutionEnvironment: JavaSE-17 Bundle-ActivationPolicy: lazy @@ -16,6 +16,7 @@ Require-Bundle: org.eclipse.core.runtime, org.eclipse.lsp4j.jsonrpc, org.eclipse.core.resources, org.eclipse.jdt.launching, - org.apache.commons.lang3 + org.apache.commons.lang3, + com.google.gson Bundle-ClassPath: lib/bsp4j-2.1.0-M4.jar, . diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/pom.xml b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/pom.xml index 9fb3246b4..12f22c862 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/pom.xml +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/pom.xml @@ -5,7 +5,7 @@ com.microsoft.gradle.jdtls.ext gradle-jdtls-ext-parent - 0.2.0 + 0.3.0 com.microsoft.gradle.bs.importer eclipse-plugin diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/GradleBuildClient.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/GradleBuildClient.java index fccb741f6..ff5e7d6bd 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/GradleBuildClient.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/GradleBuildClient.java @@ -5,6 +5,7 @@ import java.util.Arrays; import java.util.Date; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -32,6 +33,7 @@ import ch.epfl.scala.bsp4j.MessageType; import ch.epfl.scala.bsp4j.PublishDiagnosticsParams; import ch.epfl.scala.bsp4j.ShowMessageParams; +import ch.epfl.scala.bsp4j.StatusCode; import ch.epfl.scala.bsp4j.TaskDataKind; import ch.epfl.scala.bsp4j.TaskFinishParams; import ch.epfl.scala.bsp4j.TaskProgressParams; @@ -60,6 +62,8 @@ public class GradleBuildClient implements BuildClient { private final JavaLanguageClient lsClient; + private final LruCache failedTaskCache = new LruCache<>(16); + public GradleBuildClient() { this.lsClient = JavaLanguageServerPlugin.getProjectsManager().getConnection(); } @@ -70,8 +74,12 @@ public void onBuildLogMessage(LogMessageParams params) { if (type == MessageType.LOG) { Utils.sendTelemetry(this.lsClient, params.getMessage()); } else { - this.lsClient.sendNotification(new ExecuteCommandParams(CLIENT_BUILD_LOG_CMD, - Arrays.asList(params.getMessage()))); + String command = CLIENT_BUILD_LOG_CMD; + if (type == MessageType.ERROR && failedTaskCache.contains(params.getTask().getId())) { + // append the compilation failure message to the build output channel. + command = CLIENT_APPEND_BUILD_LOG_CMD; + } + this.lsClient.sendNotification(new ExecuteCommandParams(command, Arrays.asList(params.getMessage()))); } } @@ -154,6 +162,9 @@ public void onBuildTaskFinish(TaskFinishParams params) { if (Objects.equals(params.getDataKind(), TaskDataKind.COMPILE_REPORT)) { String msg = params.getMessage() + "\n------\n"; lsClient.sendNotification(new ExecuteCommandParams(CLIENT_APPEND_BUILD_LOG_CMD, Arrays.asList(msg))); + if (params.getStatus() == StatusCode.ERROR) { + failedTaskCache.addAll((params.getTaskId().getParents())); + } } else { Either id = Either.forLeft(params.getTaskId().getId()); WorkDoneProgressEnd workDoneProgressEnd = new WorkDoneProgressEnd(); @@ -162,4 +173,22 @@ public void onBuildTaskFinish(TaskFinishParams params) { lsClient.notifyProgress(new ProgressParams(id, Either.forLeft(workDoneProgressEnd))); } } + + private class LruCache extends LinkedHashSet { + private final int maxSize; + + public LruCache(int maxSize) { + super(maxSize); + this.maxSize = maxSize; + } + + @Override + public boolean add(T element) { + if (size() >= maxSize) { + T oldestElement = iterator().next(); + remove(oldestElement); + } + return super.add(element); + } + } } diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/GradleBuildServerBuildSupport.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/GradleBuildServerBuildSupport.java index eeee66743..de48de606 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/GradleBuildServerBuildSupport.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/GradleBuildServerBuildSupport.java @@ -1,8 +1,5 @@ package com.microsoft.gradle.bs.importer; -import static org.eclipse.jdt.ls.core.internal.handlers.MapFlattener.getString; -import static org.eclipse.jdt.ls.core.internal.handlers.MapFlattener.getValue; - import java.io.File; import java.net.URI; import java.net.URISyntaxException; @@ -40,6 +37,7 @@ import org.eclipse.jdt.internal.core.ClasspathEntry; import org.eclipse.jdt.launching.IVMInstall; import org.eclipse.jdt.launching.JavaRuntime; +import org.eclipse.jdt.ls.core.internal.JSONUtility; import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; import org.eclipse.jdt.ls.core.internal.ProjectUtils; import org.eclipse.jdt.ls.core.internal.ResourceUtils; @@ -60,6 +58,8 @@ import ch.epfl.scala.bsp4j.JavacOptionsItem; import ch.epfl.scala.bsp4j.JavacOptionsParams; import ch.epfl.scala.bsp4j.JavacOptionsResult; +import ch.epfl.scala.bsp4j.MavenDependencyModule; +import ch.epfl.scala.bsp4j.MavenDependencyModuleArtifact; import ch.epfl.scala.bsp4j.OutputPathItem; import ch.epfl.scala.bsp4j.OutputPathsItem; import ch.epfl.scala.bsp4j.OutputPathsParams; @@ -138,6 +138,10 @@ public void onWillUpdate(Collection projects, IProgressMonitor monitor } for (IPath rootPath : roots) { BuildServerConnection connection = ImporterPlugin.getBuildServerConnection(rootPath); + if (connection == null) { + JavaLanguageServerPlugin.logInfo("Skip reloading " + rootPath + " because the connection is not available."); + continue; + } connection.workspaceReload().join(); } } @@ -156,14 +160,18 @@ public void update(IProject project, boolean force, IProgressMonitor monitor) th return; } BuildServerConnection connection = ImporterPlugin.getBuildServerConnection(rootPath); + if (connection == null) { + JavaLanguageServerPlugin.logError("Cannot find build server connection for root: " + rootPath); + return; + } Map> buildTargetMap = Utils.getBuildTargetsMappedByProjectPath(connection); for (URI uri : buildTargetMap.keySet()) { IProject projectFromUri = ProjectUtils.getProjectFromUri(uri.toString()); if (projectFromUri == null || !Utils.isGradleBuildServerProject(projectFromUri)) { continue; } - updateClasspath(projectFromUri, monitor); - updateProjectDependencies(projectFromUri, monitor); + updateClasspath(connection, projectFromUri, monitor); + updateProjectDependencies(connection, projectFromUri, monitor); // TODO: in case that the projects/build targets are created or removed, // we can use the server->client notification: 'buildTarget/didChange' to support this case. } @@ -180,29 +188,28 @@ public String buildToolName() { * add Java nature if necessary. * @throws CoreException */ - public void updateClasspath(IProject project, IProgressMonitor monitor) throws CoreException { + public void updateClasspath(BuildServerConnection connection, IProject project, IProgressMonitor monitor) throws CoreException { IPath rootPath = ProjectUtils.findBelongedWorkspaceRoot(project.getLocation()); if (rootPath == null) { JavaLanguageServerPlugin.logError("Cannot find workspace root for project: " + project.getName()); return; } - BuildServerConnection buildServer = ImporterPlugin.getBuildServerConnection(rootPath); // use map to dedupe the classpath entries having the same path field. Map classpathMap = new LinkedHashMap<>(); - List buildTargets = Utils.getBuildTargetsByProjectUri(buildServer, project.getLocationURI()); + List buildTargets = Utils.getBuildTargetsByProjectUri(connection, project.getLocationURI()); // put test targets to the end of the list moveTestTargetsToEnd(buildTargets); for (BuildTarget buildTarget : buildTargets) { boolean isTest = buildTarget.getTags().contains(BuildTargetTag.TEST); - OutputPathsResult outputResult = buildServer.buildTargetOutputPaths( + OutputPathsResult outputResult = connection.buildTargetOutputPaths( new OutputPathsParams(Arrays.asList(buildTarget.getId()))).join(); String sourceOutputUri = getOutputUriByKind(outputResult.getItems(), OUTPUT_KIND_SOURCE); IPath sourceOutputFullPath = getOutputFullPath(sourceOutputUri, project); if (sourceOutputFullPath == null) { JavaLanguageServerPlugin.logError("Cannot find source output path for build target: " + buildTarget.getId()); } else { - SourcesResult sourcesResult = buildServer.buildTargetSources( + SourcesResult sourcesResult = connection.buildTargetSources( new SourcesParams(Arrays.asList(buildTarget.getId()))).join(); List sourceEntries = getSourceEntries(rootPath, project, sourcesResult, sourceOutputFullPath, isTest, monitor); for (IClasspathEntry entry : sourceEntries) { @@ -214,7 +221,7 @@ public void updateClasspath(IProject project, IProgressMonitor monitor) throws C IPath resourceOutputFullPath = getOutputFullPath(resourceOutputUri, project); // resource output is nullable according to Gradle API definition. if (resourceOutputFullPath != null) { - ResourcesResult resourcesResult = buildServer.buildTargetResources( + ResourcesResult resourcesResult = connection.buildTargetResources( new ResourcesParams(Arrays.asList(buildTarget.getId()))).join(); List resourceEntries = getResourceEntries(project, resourcesResult, resourceOutputFullPath, isTest); for (IClasspathEntry entry : resourceEntries) { @@ -246,7 +253,7 @@ public void updateClasspath(IProject project, IProgressMonitor monitor) throws C for (BuildTarget buildTarget : buildTargets) { boolean isTest = buildTarget.getTags().contains(BuildTargetTag.TEST); - DependencyModulesResult dependencyModuleResult = buildServer.buildTargetDependencyModules( + DependencyModulesResult dependencyModuleResult = connection.buildTargetDependencyModules( new DependencyModulesParams(Arrays.asList(buildTarget.getId()))).join(); List dependencyEntries = getDependencyJars(dependencyModuleResult, isTest, isModular); for (IClasspathEntry entry : dependencyEntries) { @@ -257,7 +264,7 @@ public void updateClasspath(IProject project, IProgressMonitor monitor) throws C javaProject.setRawClasspath(classpathMap.values().toArray(new IClasspathEntry[0]), monitor); // process jpms arguments. - JavacOptionsResult javacOptions = buildServer.buildTargetJavacOptions(new JavacOptionsParams( + JavacOptionsResult javacOptions = connection.buildTargetJavacOptions(new JavacOptionsParams( buildTargets.stream().map(BuildTarget::getId).collect(Collectors.toList()))).join(); List compilerArgs = new LinkedList<>(); for (JavacOptionsItem item : javacOptions.getItems()) { @@ -276,14 +283,13 @@ public void updateClasspath(IProject project, IProgressMonitor monitor) throws C * Update the project dependencies of the project. * @throws CoreException */ - public void updateProjectDependencies(IProject project, IProgressMonitor monitor) throws CoreException { + public void updateProjectDependencies(BuildServerConnection connection, IProject project, IProgressMonitor monitor) throws CoreException { IPath rootPath = ProjectUtils.findBelongedWorkspaceRoot(project.getLocation()); if (rootPath == null) { JavaLanguageServerPlugin.logError("Cannot find workspace root for project: " + project.getName()); return; } - BuildServerConnection buildServer = ImporterPlugin.getBuildServerConnection(rootPath); - List buildTargets = Utils.getBuildTargetsByProjectUri(buildServer, project.getLocationURI()); + List buildTargets = Utils.getBuildTargetsByProjectUri(connection, project.getLocationURI()); Set projectDependencies = new LinkedHashSet<>(); for (BuildTarget buildTarget : buildTargets) { projectDependencies.addAll(buildTarget.getDependencies()); @@ -527,17 +533,17 @@ private JvmBuildTargetEx getJvmTarget(List buildTargets) throws Cor continue; } - String javaHome = getString((Map) buildTarget.getData(), JAVA_HOME); - if (StringUtils.isNotBlank(javaHome) && StringUtils.isBlank(jvmTarget.getJavaHome())) { - jvmTarget.setJavaHome(javaHome); + JvmBuildTargetEx rawJvmTarget = JSONUtility.toModel(buildTarget.getData(), JvmBuildTargetEx.class); + if (StringUtils.isNotBlank(rawJvmTarget.getJavaHome()) && StringUtils.isBlank(jvmTarget.getJavaHome())) { + jvmTarget.setJavaHome(rawJvmTarget.getJavaHome()); } - String gradleVersion = getString((Map) buildTarget.getData(), GRADLE_VERSION); + String gradleVersion = rawJvmTarget.getGradleVersion(); if (StringUtils.isNotBlank(gradleVersion) && StringUtils.isBlank(jvmTarget.getGradleVersion())) { jvmTarget.setGradleVersion(gradleVersion); } - String sourceCompatibility = getString((Map) buildTarget.getData(), SOURCE_COMPATIBILITY); + String sourceCompatibility = rawJvmTarget.getSourceCompatibility(); if (StringUtils.isNotBlank(sourceCompatibility)) { sourceCompatibility = getEclipseCompatibleVersion(sourceCompatibility); if (StringUtils.isBlank(jvmTarget.getSourceCompatibility()) @@ -546,7 +552,7 @@ private JvmBuildTargetEx getJvmTarget(List buildTargets) throws Cor } } - String targetCompatibility = getString((Map) buildTarget.getData(), TARGET_COMPATIBILITY); + String targetCompatibility = rawJvmTarget.getTargetCompatibility(); if (StringUtils.isNotBlank(targetCompatibility)) { targetCompatibility = getEclipseCompatibleVersion(targetCompatibility); if (StringUtils.isBlank(jvmTarget.getTargetCompatibility()) @@ -586,19 +592,21 @@ private List getDependencyJars(DependencyModulesResult dependen if (!"maven".equals(module.getDataKind())) { continue; } - List artifacts = (List) getValue((Map) module.getData(), "artifacts"); + MavenDependencyModule mavenDependencyModule = + JSONUtility.toModel(module.getData(), MavenDependencyModule.class); + List artifacts = mavenDependencyModule.getArtifacts(); if (artifacts == null) { continue; } File artifact = null; File sourceArtifact = null; - for (Map artifactData : artifacts) { - String uri = (String) getValue(artifactData, "uri"); + for (MavenDependencyModuleArtifact artifactData : artifacts) { + String uri = artifactData.getUri(); if (uri == null) { continue; } - String classifier = (String) getValue(artifactData, "classifier"); + String classifier = artifactData.getClassifier(); try { File jarFile = new File(new URI(uri)); if (classifier == null) { @@ -608,7 +616,6 @@ private List getDependencyJars(DependencyModulesResult dependen } } catch (URISyntaxException e) { JavaLanguageServerPlugin.logException(e); - continue; } } @@ -654,7 +661,9 @@ private List getDependencyJars(DependencyModulesResult dependen */ private String getHighestCompatibleJavaVersion(String gradleVersion) { GradleVersion version = GradleVersion.version(gradleVersion); - if (version.compareTo(GradleVersion.version("8.5")) >= 0) { + if (version.compareTo(GradleVersion.version("8.8")) >= 0) { + return JavaCore.VERSION_22; + } else if (version.compareTo(GradleVersion.version("8.5")) >= 0) { return JavaCore.VERSION_21; } else if (version.compareTo(GradleVersion.version("8.3")) >= 0) { return JavaCore.VERSION_20; diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/GradleBuildServerProjectImporter.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/GradleBuildServerProjectImporter.java index 1bee83d70..a0d6537ca 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/GradleBuildServerProjectImporter.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/GradleBuildServerProjectImporter.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.concurrent.CompletionException; import org.eclipse.core.internal.resources.Project; import org.eclipse.core.internal.resources.ProjectDescription; @@ -61,7 +62,6 @@ public boolean applies(IProgressMonitor monitor) throws OperationCanceledExcepti return false; } - if (!Utils.isBuildServerEnabled(getPreferences())) { return false; } @@ -143,7 +143,7 @@ public boolean applies(Collection projectConfigurations, IProgressMonitor @Override public void importToWorkspace(IProgressMonitor monitor) throws OperationCanceledException, CoreException { IPath rootPath = ResourceUtils.filePathFromURI(rootFolder.toURI().toString()); - BuildServerConnection buildServer = ImporterPlugin.getBuildServerConnection(rootPath); + BuildServerConnection buildServer = ImporterPlugin.getBuildServerConnection(rootPath, true); // for all the path in this.directories, find the out most directory which belongs // to rootFolder and use that directory as the root folder for the build server. @@ -168,8 +168,33 @@ public void importToWorkspace(IProgressMonitor monitor) throws OperationCanceled ); BuildServerPreferences data = getBuildServerPreferences(); params.setData(data); + InitializeBuildResult initializeResult = buildServer.buildInitialize(params).join(); buildServer.onBuildInitialized(); + + // InitializeBuildResult initializeResult = null; + // boolean success = false; + // int retries = 0; + // int MAX_RETRIES = 10; + // while (!success && retries < MAX_RETRIES) { + // try { + // initializeResult = buildServer.buildInitialize(params).join(); + // success = true; + // } catch (CompletionException e) { + // System.out.println("Waiting for the TypeScript server to be ready..."); + // try { + // Thread.sleep(1000); // 等待一秒 + // } catch (InterruptedException ie) { + // Thread.currentThread().interrupt(); + // throw new RuntimeException("Thread interrupted while waiting to retry initialization", ie); + // } + // } + // retries++; + // } + // if (!success) { + // throw new RuntimeException("Failed to initialize after " + MAX_RETRIES + " attempts."); + // } + // TODO: save the capabilities of this server if (monitor.isCanceled()) { @@ -183,14 +208,14 @@ public void importToWorkspace(IProgressMonitor monitor) throws OperationCanceled GradleBuildServerBuildSupport buildSupport = new GradleBuildServerBuildSupport(); for (IProject project : projects) { - buildSupport.updateClasspath(project, monitor); + buildSupport.updateClasspath(buildServer, project, monitor); } // We need to add the project dependencies after the Java nature is set to all // the projects, which is done in 'updateClasspath(IProject, IProgressMonitor)', // otherwise JDT will thrown exception when adding projects as dependencies. for (IProject project : projects) { - buildSupport.updateProjectDependencies(project, monitor); + buildSupport.updateProjectDependencies(buildServer, project, monitor); } for (IProject project : projects) { @@ -321,6 +346,9 @@ private void updateProjectDescription(IProject project, IProgressMonitor monitor // because that API will ignore the variable descriptions. if (project instanceof Project internalProject) { ProjectDescription description = internalProject.internalGetDescription(); + if (description == null) { + return; + } VariableDescription variableDescription = new VariableDescription(SCHEMA_VERSION_KEY, SCHEMA_VERSION); boolean changed = description.setVariableDescription(SCHEMA_VERSION_KEY, variableDescription); if (changed) { diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterNamedPipeStream.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterNamedPipeStream.java new file mode 100644 index 000000000..7c7c473fe --- /dev/null +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterNamedPipeStream.java @@ -0,0 +1,263 @@ +/******************************************************************************* + * Copyright (c) 2016-2017 Red Hat Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package com.microsoft.gradle.bs.importer; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.StandardProtocolFamily; +import java.net.UnixDomainSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousFileChannel; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.SocketChannel; +import java.nio.channels.WritableByteChannel; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.nio.file.StandardOpenOption; +import java.security.SecureRandom; + +import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; +import org.eclipse.core.runtime.Platform; + +/** + * A factory for creating the streams for supported transmission methods. + * + * @author Gorkem Ercan + * + */ + +public class ImporterNamedPipeStream { + private String bundleDir; + + private StreamProvider provider; + + interface StreamProvider { + InputStream getInputStream() throws IOException; + + OutputStream getOutputStream() throws IOException; + } + + protected final class PipeStreamProvider implements StreamProvider { + + private InputStream input; + private OutputStream output; + + public PipeStreamProvider() { + initializeNamedPipe(); + } + + @Override + public InputStream getInputStream() throws IOException { + return input; + } + + @Override + public OutputStream getOutputStream() throws IOException { + return output; + } + + private void initializeNamedPipe() { + String pathName = generateRandomPipeName("importer"); + sendImporterPipeName(pathName); + File pipeFile = new File(pathName); + if (isWindows()) { + pipeFile = new File(pathName); + AsynchronousFileChannel channel = null; + try { + channel = AsynchronousFileChannel.open(pipeFile.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE); + input = new NamedPipeInputStream(channel); + output = new NamedPipeOutputStream(channel); + } catch (IOException e) { + e.printStackTrace(); + } + return; + } + + // Need to retry until the pipeName was sent and pipe is created by Extension side + boolean connected = false; + while (!connected) { + try { + UnixDomainSocketAddress socketAddress = UnixDomainSocketAddress.of(pipeFile.toPath()); + SocketChannel channel = SocketChannel.open(StandardProtocolFamily.UNIX); + channel.connect(socketAddress); + input = new NamedPipeInputStream(channel); + output = new NamedPipeOutputStream(channel); + connected = true; + } catch (IOException e) { + try { + Thread.sleep(1000); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Thread interrupted while trying to connect to named pipe", ie); + } + } + } + } + } + + public class NamedPipeInputStream extends InputStream { + + private ReadableByteChannel unixChannel; + private AsynchronousFileChannel winChannel; + private ByteBuffer buffer = ByteBuffer.allocate(1024); + private int readyBytes = 0; + + public NamedPipeInputStream(ReadableByteChannel channel) { + this.unixChannel = channel; + } + + public NamedPipeInputStream(AsynchronousFileChannel channel) { + this.winChannel = channel; + } + + @Override + public int read() throws IOException { + if (buffer.position() < readyBytes) { + return buffer.get() & 0xFF; + } + try { + buffer.clear(); + if (winChannel != null) { + readyBytes = winChannel.read(buffer, 0).get(); + } else { + readyBytes = unixChannel.read(buffer); + } + if (readyBytes == -1) { + return -1; // EOF + } + buffer.flip(); + return buffer.get() & 0xFF; + } catch (InterruptedException | ExecutionException e) { + throw new IOException(e); + } + } + } + + public class NamedPipeOutputStream extends OutputStream { + + private WritableByteChannel unixChannel; + private AsynchronousFileChannel winChannel; + private ByteBuffer buffer = ByteBuffer.allocate(1); + + public NamedPipeOutputStream(WritableByteChannel channel) { + this.unixChannel = channel; + } + + public NamedPipeOutputStream(AsynchronousFileChannel channel) { + this.winChannel = channel; + } + + @Override + public void write(int b) throws IOException { + buffer.clear(); + buffer.put((byte) b); + buffer.position(0); + if (winChannel != null) { + Future result = winChannel.write(buffer, 0); + try { + result.get(); + } catch (Exception e) { + throw new IOException(e); + } + } else { + unixChannel.write(buffer); + } + } + + @Override + public void write(byte[] b) throws IOException { + final int BUFFER_SIZE = 1024; + int blocks = b.length / BUFFER_SIZE; + int writeBytes = 0; + for (int i = 0; i <= blocks; i++) { + int offset = i * BUFFER_SIZE; + int length = Math.min(b.length - writeBytes, BUFFER_SIZE); + if (length <= 0) { + break; + } + writeBytes += length; + ByteBuffer buffer = ByteBuffer.wrap(b, offset, length); + if (winChannel != null) { + Future result = winChannel.write(buffer, 0); + try { + result.get(); + } catch (Exception e) { + throw new IOException(e); + } + } else { + unixChannel.write(buffer); + } + } + } + } + + public ImporterNamedPipeStream(String bundleDir) { + this.bundleDir = bundleDir; + } + + public StreamProvider getSelectedStream() { + if (provider == null) { + provider = createProvider() ; + } + return provider; + } + + private StreamProvider createProvider() { + + return new PipeStreamProvider(); + } + + public InputStream getInputStream() throws IOException { + return getSelectedStream().getInputStream(); + } + + public OutputStream getOutputStream() throws IOException { + return getSelectedStream().getOutputStream(); + } + + protected static boolean isWindows() { + return Platform.OS_WIN32.equals(Platform.getOS()); + } + + private void sendImporterPipeName(String pipeName){ + JavaLanguageServerPlugin.getInstance().getClientConnection() + .sendNotification("gradle.getImporterPipeName", pipeName); + } + + public String generateRandomPipeName(String type) { + SecureRandom random = new SecureRandom(); + byte[] bytes = new byte[11]; + random.nextBytes(bytes); + StringBuilder hexString = new StringBuilder(); + for (byte b : bytes) { + hexString.append(String.format("%02x", b)); + } + String randomSuffix = hexString.toString(); + String path; + if (System.getProperty("os.name").startsWith("Windows")) { + path = "\\\\.\\pipe\\" + randomSuffix + "-" + type + "-sock"; + } else { + //save pipe file to extension folder + path = new File(this.bundleDir).getParent() +'/'+ randomSuffix + "-" + type + ".sock"; + } + + try { + return new File(path).getCanonicalPath(); + } catch (IOException e) { + System.err.println("Error generating the canonical pipe path: " + e.getMessage()); + return null; + } + } +} diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java index c6d915185..8fedc027a 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java @@ -1,8 +1,7 @@ package com.microsoft.gradle.bs.importer; import java.io.File; -import java.io.IOException; -import java.nio.file.Paths; + import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; @@ -20,7 +19,6 @@ import org.osgi.framework.BundleContext; import com.microsoft.java.builder.BuildStateManager; - import ch.epfl.scala.bsp4j.BuildClient; public class ImporterPlugin extends Plugin { @@ -30,16 +28,14 @@ public class ImporterPlugin extends Plugin { private Map> buildServers = new ConcurrentHashMap<>(); private static ImporterPlugin instance; - /** * Digest store for the gradle configuration files. */ private DigestStore digestStore; - private static String bundleDirectory; - private static String bundleVersion = ""; + private static String bundleDirectory; @Override public void start(BundleContext context) throws Exception { BuildStateManager.getBuildStateManager().startup(); @@ -50,7 +46,7 @@ public void start(BundleContext context) throws Exception { if (!bundleFile.isPresent()) { throw new IllegalStateException("Failed to get bundle location."); } - bundleDirectory = bundleFile.get().getParent(); + bundleDirectory = bundleFile.get().getParent(); } @Override @@ -73,28 +69,52 @@ public static DigestStore getDigestStore() { return instance.digestStore; } + /** + * Get the build server connection for the given root path. If the connection doesn't exist, + * returns null. + * @param rootPath + * @throws CoreException + */ public static BuildServerConnection getBuildServerConnection(IPath rootPath) throws CoreException { + return getBuildServerConnection(rootPath, false); + } + + /** + * Get the build server connection for the given root path. + * @param rootPath the root path of the workspace. + * @param createIfMissing whether to create a new build server connection if it doesn't exist. + * @return the build server connection. + * @throws CoreException + */ + public static BuildServerConnection getBuildServerConnection(IPath rootPath, boolean createIfMissing) throws CoreException { Pair pair = instance.buildServers.get(rootPath); if (pair != null) { return pair.getLeft(); } + if (!createIfMissing) { + return null; + } + String javaExecutablePath = getJavaExecutablePath(); String[] classpaths = getBuildServerClasspath(); String pluginPath = getBuildServerPluginPath(); - ProcessBuilder build = new ProcessBuilder( - javaExecutablePath, - "--add-opens=java.base/java.lang=ALL-UNNAMED", - "--add-opens=java.base/java.io=ALL-UNNAMED", - "--add-opens=java.base/java.util=ALL-UNNAMED", - "-Dplugin.dir=" + pluginPath, - "-cp", - String.join(getClasspathSeparator(), classpaths), - "com.microsoft.java.bs.core.Launcher" - ); - + List command = new ArrayList<>(); + command.add(javaExecutablePath); + if (Boolean.parseBoolean(System.getenv("DEBUG_GRADLE_BUILD_SERVER"))) { + command.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8989"); + } + command.add("--add-opens=java.base/java.lang=ALL-UNNAMED"); + command.add("--add-opens=java.base/java.io=ALL-UNNAMED"); + command.add("--add-opens=java.base/java.util=ALL-UNNAMED"); + command.add("-Dplugin.dir=" + pluginPath); + command.add("-cp"); + command.add(String.join(getClasspathSeparator(), classpaths)); + command.add("com.microsoft.java.bs.core.Launcher"); + + ProcessBuilder build = new ProcessBuilder(command); try { Process process = build.start(); BuildClient client = new GradleBuildClient(); @@ -106,47 +126,15 @@ public static BuildServerConnection getBuildServerConnection(IPath rootPath) thr .setRemoteInterface(BuildServerConnection.class) .create(); - launcher.startListening(); - BuildServerConnection server = launcher.getRemoteProxy(); - client.onConnectWithServer(server); - instance.buildServers.put(rootPath, Pair.of(server, client)); - return server; - } catch (IOException e) { - throw new CoreException(new Status(IStatus.ERROR, PLUGIN_ID, - "Failed to start build server.", e)); - } - } - - /** - * Get the Java executable used by JDT.LS, which will be higher than JDK 17. - */ - private static String getJavaExecutablePath() { - Optional command = ProcessHandle.current().info().command(); - if (command.isPresent()) { - return command.get(); - } - - throw new IllegalStateException("Failed to get Java executable path."); - } - - private static String[] getBuildServerClasspath() { - return new String[]{ - Paths.get(bundleDirectory, "server.jar").toString(), - Paths.get(bundleDirectory, "runtime").toString() + File.separatorChar + "*" - }; - } - - private static String getBuildServerPluginPath() { - return Paths.get(bundleDirectory, "plugins").toString(); - } - - private static String getClasspathSeparator() { - String os = System.getProperty("os.name").toLowerCase(); - - if (os.contains("win")) { - return ";"; - } - - return ":"; // Linux or Mac - } + launcher.startListening(); + BuildServerConnection server = launcher.getRemoteProxy(); + client.onConnectWithServer(server); + + instance.buildServers.put(rootPath, Pair.of(server, client)); + return server; + } catch (Exception e) { + e.printStackTrace(); + throw new CoreException(new Status(IStatus.ERROR, PLUGIN_ID, "Failed to start build server.", e)); + } + } } diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/builder/BuildServerBuilder.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/builder/BuildServerBuilder.java index d00122e2c..40ccb6dc6 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/builder/BuildServerBuilder.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/builder/BuildServerBuilder.java @@ -5,6 +5,7 @@ import java.util.Objects; import java.util.stream.Collectors; +import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResourceStatus; import org.eclipse.core.resources.IncrementalProjectBuilder; @@ -13,6 +14,8 @@ import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; import org.eclipse.jdt.ls.core.internal.ProjectUtils; import org.eclipse.jdt.ls.core.internal.preferences.Preferences; @@ -45,18 +48,55 @@ protected IProject[] build(int kind, Map args, IProgressMonitor return null; } BuildServerConnection buildServer = ImporterPlugin.getBuildServerConnection(rootPath); - if (buildServer != null) { - List targets = Utils.getBuildTargetsByProjectUri(buildServer, project.getLocationURI()); - List ids = targets.stream().map(BuildTarget::getId).collect(Collectors.toList()); - if (ids != null) { - // TODO: support clean build - CompileResult result = buildServer.buildTargetCompile(new CompileParams(ids)).join(); - if (Objects.equals(result.getStatusCode(), StatusCode.ERROR)) { - throw new CoreException(new Status(IStatus.ERROR, ImporterPlugin.PLUGIN_ID, - IResourceStatus.BUILD_FAILED, "Build Failed.", null)); - } + if (buildServer == null) { + JavaLanguageServerPlugin.logInfo("Skip building project: " + project.getName() + " because build server is not available."); + return null; + } + List targets = Utils.getBuildTargetsByProjectUri(buildServer, project.getLocationURI()); + List ids = targets.stream().map(BuildTarget::getId).collect(Collectors.toList()); + if (ids != null) { + // TODO: support clean build + CompileResult result = buildServer.buildTargetCompile(new CompileParams(ids)).join(); + if (Objects.equals(result.getStatusCode(), StatusCode.ERROR)) { + throw new CoreException(new Status(IStatus.ERROR, ImporterPlugin.PLUGIN_ID, + IResourceStatus.BUILD_FAILED, "Build Failed.", null)); } + this.refreshOutputs(monitor); } return null; } + + /** + * Trigger .refreshLocal() to all the output folders of the project. + * This is to make sure the changes made by the build server are reflected in the workspace. + */ + private void refreshOutputs(IProgressMonitor monitor) throws CoreException { + IJavaProject javaProject = ProjectUtils.getJavaProject(this.getProject()); + if (javaProject == null) { + return; + } + + boolean needRefreshDefaultOutput = false; + for (IClasspathEntry cp : javaProject.getRawClasspath()) { + if (cp.getEntryKind() != IClasspathEntry.CPE_SOURCE) { + continue; + } + + IPath output = cp.getOutputLocation(); + if (output != null) { + output = output.removeFirstSegments(1); + javaProject.getProject().getFolder(output).refreshLocal(IProject.DEPTH_INFINITE, monitor); + } else { + needRefreshDefaultOutput = true; + } + } + + if (needRefreshDefaultOutput) { + IPath relativeOutputPath = javaProject.getOutputLocation().removeFirstSegments(1); + IFolder defaultOutput = javaProject.getProject().getFolder(relativeOutputPath); + if (defaultOutput.exists()) { + defaultOutput.refreshLocal(IProject.DEPTH_INFINITE, monitor); + } + } + } } diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/java/builder/jdtbuilder/ClasspathJrtWithReleaseOption.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/java/builder/jdtbuilder/ClasspathJrtWithReleaseOption.java index 1bfe6cc41..822fd8a17 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/java/builder/jdtbuilder/ClasspathJrtWithReleaseOption.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/java/builder/jdtbuilder/ClasspathJrtWithReleaseOption.java @@ -21,6 +21,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.Collection; import java.util.Collections; @@ -164,7 +165,7 @@ public void loadModules() { Map newCache = new HashMap<>(); for (Path root : releaseRoots) { try { - Files.walkFileTree(root, Collections.emptySet(), 2, new JRTUtil.AbstractFileVisitor() { + Files.walkFileTree(root, Collections.emptySet(), 2, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path f, BasicFileAttributes attrs) throws IOException { if (attrs.isDirectory() || f.getNameCount() < 3) { diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/java/builder/jdtbuilder/State.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/java/builder/jdtbuilder/State.java index b797921a6..d65392413 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/java/builder/jdtbuilder/State.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/java/builder/jdtbuilder/State.java @@ -56,6 +56,7 @@ import org.eclipse.jdt.internal.compiler.util.SimpleLookupTable; import org.eclipse.jdt.internal.compiler.util.Util; import org.eclipse.jdt.internal.core.JavaModelManager; +import org.eclipse.jdt.internal.core.util.DeduplicationUtil; @SuppressWarnings({"rawtypes", "unchecked"}) public class State { @@ -485,7 +486,7 @@ private static AccessRuleSet readRestriction(CompressedReader in) throws IOExcep int problemId = in.readIntWithHint(PROBLEM_IDS); accessRules[i] = manager.getAccessRuleForProblemId(pattern, problemId); } - return new AccessRuleSet(accessRules, in.readByte(), manager.intern(in.readStringUsingDictionary())); + return new AccessRuleSet(accessRules, in.readByte(), DeduplicationUtil.intern(in.readStringUsingDictionary())); } void tagAsNoopBuild() { diff --git a/extension/jdtls.ext/pom.xml b/extension/jdtls.ext/pom.xml index f4b21205e..e92a24d9c 100644 --- a/extension/jdtls.ext/pom.xml +++ b/extension/jdtls.ext/pom.xml @@ -4,7 +4,7 @@ com.microsoft.gradle.jdtls.ext gradle-jdtls-ext-parent ${base.name} :: Parent - 0.2.0 + 0.3.0 pom Gradle Build Server Client diff --git a/extension/package-lock.json b/extension/package-lock.json index 6c2277ddc..e79d50e2f 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-gradle", - "version": "3.13.5", + "version": "3.14.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vscode-gradle", - "version": "3.13.5", + "version": "3.14.1", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "await-lock": "^2.2.2", @@ -17,7 +17,7 @@ "minimatch": "^5.1.1", "string-argv": "^0.3.1", "tree-kill": "^1.2.2", - "vscode-extension-telemetry-wrapper": "0.13.3", + "vscode-extension-telemetry-wrapper": "0.14.0", "vscode-languageclient": "7.0.0" }, "devDependencies": { @@ -307,46 +307,118 @@ } }, "node_modules/@microsoft/1ds-core-js": { - "version": "3.2.10", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-3.2.10.tgz", - "integrity": "sha512-fYzgrVuAK6HH66QJq5gyB+hmql2OTWwvP2520B7vM/idl8+O0yV9/PPTuwTpCWzGcksq5X3LaUX2MugzaRHLHA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.2.1.tgz", + "integrity": "sha512-fcowL6s42sj4+yU0nko9gkGDCGbeQEGgYxXnwejSAjMw9MKZ0jjYRvxFBR+Aip6aCzf0WV0Jw0j4FA72EXMbIg==", "dependencies": { - "@microsoft/applicationinsights-core-js": "2.8.12", - "@microsoft/applicationinsights-shims": "^2.0.2", - "@microsoft/dynamicproto-js": "^1.1.7" + "@microsoft/applicationinsights-core-js": "3.2.1", + "@microsoft/applicationinsights-shims": "3.0.1", + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.1 < 2.x", + "@nevware21/ts-utils": ">= 0.11.1 < 2.x" } }, "node_modules/@microsoft/1ds-post-js": { - "version": "3.2.10", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-3.2.10.tgz", - "integrity": "sha512-/yUdt1wUQpB0jM3LGwgrkSX2TLc3geHj4RflYYmW20uHN9XDqjSQfPiTm5m1sMcISz7OdQQd/PeqYnybkBOMfQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.2.1.tgz", + "integrity": "sha512-MeGvTxBbADo8um8ylTJST+gDcIpKx0mLtLtcbJtgSdL+SerITf2wvweobE3/NMQiWI8URtCb1XIYKauW7lqVlg==", + "dependencies": { + "@microsoft/1ds-core-js": "4.2.1", + "@microsoft/applicationinsights-shims": "3.0.1", + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.1 < 2.x", + "@nevware21/ts-utils": ">= 0.11.1 < 2.x" + } + }, + "node_modules/@microsoft/applicationinsights-channel-js": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.2.1.tgz", + "integrity": "sha512-+aWBBbIW4/Tf4sLGZmWhd5chktBpKQpnCbkuoTHGe+AWO8Q8fsDa4w2Y89OGuEg9OJ3kr2VKTUU7LgILKFz/cg==", + "dependencies": { + "@microsoft/applicationinsights-common": "3.2.1", + "@microsoft/applicationinsights-core-js": "3.2.1", + "@microsoft/applicationinsights-shims": "3.0.1", + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.1 < 2.x", + "@nevware21/ts-utils": ">= 0.11.1 < 2.x" + }, + "peerDependencies": { + "tslib": "*" + } + }, + "node_modules/@microsoft/applicationinsights-common": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.2.1.tgz", + "integrity": "sha512-vRYQ1SIZJEz1eFbs2AQiLtev5L+zmjZ1Jkj3BWfIxJLd6n0cVR4NZETBSyMuk11KH7MIOrDLvh1CzjBIJIpDAg==", "dependencies": { - "@microsoft/1ds-core-js": "3.2.10", - "@microsoft/applicationinsights-shims": "^2.0.2", - "@microsoft/dynamicproto-js": "^1.1.7" + "@microsoft/applicationinsights-core-js": "3.2.1", + "@microsoft/applicationinsights-shims": "3.0.1", + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-utils": ">= 0.11.1 < 2.x" + }, + "peerDependencies": { + "tslib": "*" } }, "node_modules/@microsoft/applicationinsights-core-js": { - "version": "2.8.12", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.12.tgz", - "integrity": "sha512-lA4epwWPBJ4awx07QQVCkoxygsl0qiTNoSYaR63hRE56ybu4kpp3tpYo/AfOI1DZMgKB8H0EwDz4vVmzUT3p/A==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.2.1.tgz", + "integrity": "sha512-euxkDrF5BroAY7wgviaTVZdMvRAENQtUW4pDTsIjJK26shi1m5fPCc5l+vMn7kO2wQEaEgAOVw+/kSQgXDHN+Q==", "dependencies": { - "@microsoft/applicationinsights-shims": "2.0.2", - "@microsoft/dynamicproto-js": "^1.1.9" + "@microsoft/applicationinsights-shims": "3.0.1", + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.1 < 2.x", + "@nevware21/ts-utils": ">= 0.11.1 < 2.x" }, "peerDependencies": { "tslib": "*" } }, "node_modules/@microsoft/applicationinsights-shims": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.2.tgz", - "integrity": "sha512-PoHEgsnmcqruLNHZ/amACqdJ6YYQpED0KSRe6J7gIJTtpZC1FfFU9b1fmDKDKtFoUSrPzEh1qzO3kmRZP0betg==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", + "integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==", + "dependencies": { + "@nevware21/ts-utils": ">= 0.9.4 < 2.x" + } + }, + "node_modules/@microsoft/applicationinsights-web-basic": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.2.1.tgz", + "integrity": "sha512-aPvZX8VOSwLwnXpLpRnFXUB5Znw+9mcsp4/UMWi6V0SVGKTlyGEDn/xcHAN2uKEcb5aD/w9UYdAsbPWEW6yEpw==", + "dependencies": { + "@microsoft/applicationinsights-channel-js": "3.2.1", + "@microsoft/applicationinsights-common": "3.2.1", + "@microsoft/applicationinsights-core-js": "3.2.1", + "@microsoft/applicationinsights-shims": "3.0.1", + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.1 < 2.x", + "@nevware21/ts-utils": ">= 0.11.1 < 2.x" + }, + "peerDependencies": { + "tslib": "*" + } }, "node_modules/@microsoft/dynamicproto-js": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.9.tgz", - "integrity": "sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.3.tgz", + "integrity": "sha512-JTWTU80rMy3mdxOjjpaiDQsTLZ6YSGGqsjURsY6AUQtIj0udlF/jYmhdLZu8693ZIC0T1IwYnFa0+QeiMnziBA==", + "dependencies": { + "@nevware21/ts-utils": ">= 0.10.4 < 2.x" + } + }, + "node_modules/@nevware21/ts-async": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.1.tgz", + "integrity": "sha512-O2kN8n2HpDWJ7Oji+oTMnhITrCndmrNvrHbGDwAIBydx+FWvLE/vrw4QwnRRMvSCa2AJrcP59Ryklxv30KfkWQ==", + "dependencies": { + "@nevware21/ts-utils": ">= 0.11.2 < 2.x" + } + }, + "node_modules/@nevware21/ts-utils": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.11.2.tgz", + "integrity": "sha512-80W8BkS09kkGuUHJX50Fqq+QqAslxUaOQytH+3JhRacXs1EpEt2JOOkYKytqFZAYir3SeH9fahniEaDzIBxlUw==" }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", @@ -822,15 +894,16 @@ } }, "node_modules/@vscode/extension-telemetry": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz", - "integrity": "sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w==", + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.6.tgz", + "integrity": "sha512-qWK2GNw+b69QRYpjuNM9g3JKToMICoNIdc0rQMtvb4gIG9vKKCZCVCz+ZOx6XM/YlfWAyuPiyxcjIY0xyF+Djg==", "dependencies": { - "@microsoft/1ds-core-js": "^3.2.3", - "@microsoft/1ds-post-js": "^3.2.3" + "@microsoft/1ds-core-js": "^4.1.2", + "@microsoft/1ds-post-js": "^4.1.2", + "@microsoft/applicationinsights-web-basic": "^3.1.2" }, "engines": { - "vscode": "^1.60.0" + "vscode": "^1.75.0" } }, "node_modules/@vscode/test-electron": { @@ -1374,12 +1447,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -2424,9 +2497,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -4275,9 +4348,9 @@ "dev": true }, "node_modules/protobufjs": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz", - "integrity": "sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==", + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.6.tgz", + "integrity": "sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -5338,15 +5411,12 @@ } }, "node_modules/vscode-extension-telemetry-wrapper": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/vscode-extension-telemetry-wrapper/-/vscode-extension-telemetry-wrapper-0.13.3.tgz", - "integrity": "sha512-k/PbUbH9/xqiMXI2g2RXpDg+4/v08t3NzdPc7HuDPF3A1XcYkgYwsPnS/bqsKZNymSQdbLvVuie6STMxbDX9KQ==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/vscode-extension-telemetry-wrapper/-/vscode-extension-telemetry-wrapper-0.14.0.tgz", + "integrity": "sha512-EYr1hqiYVSGfupchDN405zSwuvA8V3tJ62KcLIRDr/4ongOc2AvSZ0BlRq8a0w950tadsMlXTKEheB97fZBttg==", "dependencies": { - "@vscode/extension-telemetry": "^0.6.2", + "@vscode/extension-telemetry": "^0.9.6", "uuid": "^8.3.2" - }, - "engines": { - "vscode": "^1.14.0" } }, "node_modules/vscode-jsonrpc": { diff --git a/extension/package.json b/extension/package.json index f80bfe4c6..e1d9431be 100644 --- a/extension/package.json +++ b/extension/package.json @@ -2,7 +2,7 @@ "name": "vscode-gradle", "displayName": "Gradle for Java", "description": "Manage Gradle Projects, run Gradle tasks and provide better Gradle file authoring experience in VS Code", - "version": "3.13.5", + "version": "3.14.1", "private": true, "publisher": "vscjava", "aiKey": "b4aae7d0-c65b-4819-92bf-1d2f537ae7ce", @@ -43,7 +43,7 @@ "main": "./dist/index.js", "contributes": { "javaExtensions": [ - "./server/com.microsoft.gradle.bs.importer-0.2.0.jar" + "./server/com.microsoft.gradle.bs.importer-0.3.0.jar" ], "languages": [ { @@ -131,6 +131,14 @@ "dark": "resources/dark/run.svg" } }, + { + "command": "gradle.getBundleDirectory", + "title": "Get Build Server Port" + }, + { + "command": "gradle.getBundlePath", + "title": "Get Build Server Port" + }, { "command": "gradle.runBuild", "category": "Gradle", @@ -987,7 +995,7 @@ "minimatch": "^5.1.1", "string-argv": "^0.3.1", "tree-kill": "^1.2.2", - "vscode-extension-telemetry-wrapper": "0.13.3", + "vscode-extension-telemetry-wrapper": "0.14.0", "vscode-languageclient": "7.0.0" } } diff --git a/extension/src/Extension.ts b/extension/src/Extension.ts index fb7da5324..5f069af67 100644 --- a/extension/src/Extension.ts +++ b/extension/src/Extension.ts @@ -31,12 +31,16 @@ import { GRADLE_COMPLETION, GRADLE_PROPERTIES_FILE_CHANGE, VSCODE_TRIGGER_COMPLETION, + GET_EXTENSION_PATH } from "./constant"; import { instrumentOperation, sendInfo } from "vscode-extension-telemetry-wrapper"; import { GradleBuildContentProvider } from "./client/GradleBuildContentProvider"; import { BuildServerController } from "./bs/BuildServerController"; - +import { BuildServerHandler } from "./bs/BuildServerHandler"; +import { ImporterHandler } from "./bs/ImporterHandler"; export class Extension { + private readonly buildServerHandler: BuildServerHandler; + private readonly importerHandler: ImporterHandler; private readonly client: GradleClient; private readonly server: GradleServer; private readonly pinnedTasksStore: PinnedTasksStore; @@ -66,7 +70,6 @@ export class Extension { private readonly onDidTerminalOpen: vscode.Event = this._onDidTerminalOpen.event; private recentTerminal: vscode.Terminal | undefined; private readonly buildServerController: BuildServerController; - public constructor(private readonly context: vscode.ExtensionContext) { const loggingChannel = vscode.window.createOutputChannel("Gradle for Java"); logger.setLoggingChannel(loggingChannel); @@ -82,13 +85,26 @@ export class Extension { } const statusBarItem = vscode.window.createStatusBarItem(); - this.server = new GradleServer({ host: "localhost" }, context, serverLogger); + this.context.subscriptions.push( + vscode.commands.registerCommand(GET_EXTENSION_PATH, () => { + return this.context.extensionPath; + }) + ); + + //this.context.subscriptions.push(new vscode.Disposable(() => this.forwardingAgent.dispose())); + this.buildServerHandler = new BuildServerHandler(serverLogger); + this.context.subscriptions.push(new vscode.Disposable(() => this.buildServerHandler.dispose())); + this.importerHandler = new ImporterHandler(this.context, logger); + this.context.subscriptions.push(new vscode.Disposable(() => this.importerHandler.dispose())); + + this.server = new GradleServer({ host: "localhost" }, context, serverLogger, this.buildServerHandler); this.client = new GradleClient(this.server, statusBarItem, clientLogger); this.pinnedTasksStore = new PinnedTasksStore(context); this.recentTasksStore = new RecentTasksStore(); this.taskTerminalsStore = new TaskTerminalsStore(); this.rootProjectsStore = new RootProjectsStore(); this.gradleBuildContentProvider = new GradleBuildContentProvider(this.client); + this.gradleTaskProvider = new GradleTaskProvider( this.rootProjectsStore, this.client, @@ -111,11 +127,7 @@ export class Extension { treeDataProvider: this.gradleTasksTreeDataProvider, showCollapseAll: true, }); - this.gradleDaemonsTreeDataProvider = new GradleDaemonsTreeDataProvider( - this.context, - this.rootProjectsStore, - this.client - ); + this.gradleDaemonsTreeDataProvider = new GradleDaemonsTreeDataProvider(this.context, this.rootProjectsStore); this.gradleDaemonsTreeView = vscode.window.createTreeView(GRADLE_DAEMONS_VIEW, { treeDataProvider: this.gradleDaemonsTreeDataProvider, showCollapseAll: false, @@ -147,7 +159,6 @@ export class Extension { this.buildFileWatcher = new FileWatcher("**/*.{gradle,gradle.kts}"); this.gradleWrapperWatcher = new FileWatcher("**/gradle/wrapper/gradle-wrapper.properties"); this.api = new Api(this.client, this.gradleTasksTreeDataProvider, this.gradleTaskProvider, this.icons); - this.commands = new Commands( this.context, this.pinnedTasksStore, @@ -160,7 +171,7 @@ export class Extension { this.rootProjectsStore, this.taskTerminalsStore, this.recentTasksStore, - this.gradleTasksTreeView + this.gradleTasksTreeView, ); this.buildServerController = new BuildServerController(context); @@ -199,7 +210,6 @@ export class Extension { }) ) ); - this.client.onDidConnect(() => this.refresh()); void this.activate(); void startLanguageServer(this.context, this.gradleBuildContentProvider, this.rootProjectsStore); @@ -232,9 +242,12 @@ export class Extension { private async activate(): Promise { const activated = !!(await this.rootProjectsStore.getProjectRoots()).length; + await this.buildServerHandler.setupBuildServerHandler(); if (!this.server.isReady()) { await this.server.start(); } + await this.importerHandler.waitForImporterPipePath(); + this.importerHandler.setupImporterHandler(this.buildServerHandler.buildServerConnection!); await vscode.commands.executeCommand("setContext", "gradle:activated", activated); await vscode.commands.executeCommand("setContext", "gradle:defaultView", true); } @@ -359,4 +372,5 @@ export class Extension { public getApi(): Api { return this.api; } + } diff --git a/extension/src/bs/BuildServerController.ts b/extension/src/bs/BuildServerController.ts index d4316a83d..22c33a846 100644 --- a/extension/src/bs/BuildServerController.ts +++ b/extension/src/bs/BuildServerController.ts @@ -59,21 +59,29 @@ export class BuildServerController implements Disposable { this.logOutputChannel.appendLine(msg); } }), - commands.registerCommand(SEND_TELEMETRY_CMD, (data: string | object) => { - let jsonString: string; + commands.registerCommand(SEND_TELEMETRY_CMD, (data: string | object | Error) => { let jsonObj: { [key: string]: any }; if (typeof data === "string") { jsonObj = JSON.parse(data); - jsonString = data; } else { jsonObj = data; - jsonString = JSON.stringify(data); } - sendInfo("", { - kind: jsonObj.kind, - data: jsonString, - ...(jsonObj.schemaVersion && { schemaVersion: jsonObj.schemaVersion }), - }); + + const { kind, trace, rootCauseMessage, schemaVersion, ...rest } = jsonObj; + if (trace || rootCauseMessage) { + sendInfo("", { + kind: "bsp-error", + operationName: jsonObj.operationName, + rootCauseMessage, + trace, + }); + } else { + sendInfo("", { + kind: kind, + data2: JSON.stringify(rest), + ...(schemaVersion && { schemaVersion: schemaVersion }), + }); + } }), workspace.onDidChangeConfiguration((e: ConfigurationChangeEvent) => { if (e.affectsConfiguration("java.gradle.buildServer.enabled")) { @@ -121,7 +129,7 @@ export class BuildServerController implements Disposable { machineStatus.hasProjectAtWorkspaceRoot = (await this.hasProjectAtWorkspaceRoot()).toString(); sendInfo("", { kind: "machineStatus", - data: JSON.stringify(machineStatus), + data2: JSON.stringify(machineStatus), }); } diff --git a/extension/src/bs/BuildServerHandler.ts b/extension/src/bs/BuildServerHandler.ts new file mode 100644 index 000000000..256445188 --- /dev/null +++ b/extension/src/bs/BuildServerHandler.ts @@ -0,0 +1,35 @@ +import * as net from "net"; +import * as rpc from "vscode-jsonrpc/node"; +import * as vscode from "vscode"; +import * as fs from "fs"; +import { generateRandomPipeName } from "../util/generateRandomPipeName"; + +export class BuildServerHandler implements vscode.Disposable { + private buildServerConnection: rpc.MessageConnection | null = null; + private buildServerPipeServer: net.Server; + private serverPipePath: string; + + public async setupBuildServerHandler(): Promise { + this.serverPipePath = await generateRandomPipeName("server"); + this.buildServerPipeServer = net.createServer((socket: net.Socket) => { + this.buildServerConnection = rpc.createMessageConnection( + new rpc.StreamMessageReader(socket), + new rpc.StreamMessageWriter(socket) + ); + this.buildServerConnection.listen(); + }); + this.buildServerPipeServer.listen(this.serverPipePath); + } + + public getBuildServerPipeName(): string { + return this.serverPipePath; + } + public getBuildServerConnection(): rpc.MessageConnection | null { + return this.buildServerConnection; + } + public dispose(): void { + if (fs.existsSync(this.serverPipePath)) { + fs.unlinkSync(this.serverPipePath); + } + } +} diff --git a/extension/src/bs/ForwardingAgent.ts b/extension/src/bs/ForwardingAgent.ts new file mode 100644 index 000000000..792c405a5 --- /dev/null +++ b/extension/src/bs/ForwardingAgent.ts @@ -0,0 +1,105 @@ +import * as net from 'net'; +import { Logger } from "../logger/index"; +import * as rpc from 'vscode-jsonrpc/node'; +import * as fs from 'fs'; +import * as vscode from 'vscode'; +import * as path from 'path'; +import { generateRandomPipeName } from '../util/generateRandomPipeName'; +export const GET_IMPORTER_PIPE_NAME = "gradle.getImporterPipeName"; + +export class ForwardingAgent implements vscode.Disposable { + private importerHandler: net.Server; + private buildServerHandler: net.Server; + private IMPORTER_PIPE_PATH: string = ""; + private SERVER_PIPE_PATH: string = ""; + public importerConnection: rpc.MessageConnection | null = null; + public buildServerConnection: rpc.MessageConnection | null = null; + + constructor(private readonly context: vscode.ExtensionContext, + private readonly logger: Logger) {} + + public async start(): Promise { + this.SERVER_PIPE_PATH = await generateRandomPipeName('server'); + this.setupBuildServerHandler(); + await this.waitForImporterPipePath(); + this.setupImporterHandler(); + } + + private setupBuildServerHandler(): void { + this.buildServerHandler = net.createServer((socket: net.Socket) => { + this.buildServerConnection = rpc.createMessageConnection( + new rpc.StreamMessageReader(socket), + new rpc.StreamMessageWriter(socket) + ); + this.buildServerConnection.listen(); + }); + this.buildServerHandler.listen(this.SERVER_PIPE_PATH); + } + + private async waitForImporterPipePath(): Promise { + return new Promise((resolve) => { + this.context.subscriptions.push( + vscode.commands.registerCommand(GET_IMPORTER_PIPE_NAME, (pipeName: string) => { + this.IMPORTER_PIPE_PATH = path.resolve(pipeName); + this.logger.info("Received Importer PipeName from Java: ", this.IMPORTER_PIPE_PATH); + resolve(); + }) + ); + }); + } + + private setupImporterHandler(): void { + this.importerHandler = net.createServer((socket: net.Socket) => { + this.importerConnection = rpc.createMessageConnection( + new rpc.StreamMessageReader(socket), + new rpc.StreamMessageWriter(socket) + ); + this.importerConnection.listen(); + this.forwardMessages(); + }); + this.importerHandler.listen(this.IMPORTER_PIPE_PATH); + this.logger.info("! Importer Handler is listening on: ", this.IMPORTER_PIPE_PATH); + } + + private forwardMessages(): void { + this.importerConnection?.onRequest((method, params) => { + if (this.buildServerConnection) { + return this.buildServerConnection.sendRequest(method, params); + } + throw new Error('Build server connection is not available.'); + }); + + this.importerConnection?.onNotification((method, params) => { + this.buildServerConnection?.sendNotification(method, params); + }); + + this.buildServerConnection?.onRequest((method, params) => { + if (this.importerConnection) { + return this.importerConnection.sendRequest(method, params); + } + throw new Error('Build server connection is not available.'); + }); + + this.buildServerConnection?.onNotification((method, params) => { + this.importerConnection?.sendNotification(method, params); + }); + } + + public getBuildServerPipeName(): string { + return this.SERVER_PIPE_PATH; + } + + public dispose(): void { + try { + if (fs.existsSync(this.SERVER_PIPE_PATH)) { + fs.unlinkSync(this.SERVER_PIPE_PATH); + } + if (fs.existsSync(this.IMPORTER_PIPE_PATH)) { + fs.unlinkSync(this.IMPORTER_PIPE_PATH); + } + } catch (err) { + this.logger.error('Error cleaning up pipes:', err); + } + } + +} diff --git a/extension/src/bs/ImporterHandler.ts b/extension/src/bs/ImporterHandler.ts new file mode 100644 index 000000000..79d9b00a4 --- /dev/null +++ b/extension/src/bs/ImporterHandler.ts @@ -0,0 +1,66 @@ +import * as net from 'net'; +import * as rpc from 'vscode-jsonrpc/node'; +import { Logger } from "../logger/index"; +import * as fs from 'fs'; +import * as vscode from 'vscode'; +import * as path from 'path'; + +export const GET_IMPORTER_PIPE_NAME = "gradle.getImporterPipeName"; + +export class ImporterHandler implements vscode.Disposable { + public importerConnection: rpc.MessageConnection | null = null; + private importerPipeServer: net.Server; + private importerPipePath: string; + constructor(private readonly context: vscode.ExtensionContext, + private readonly logger: Logger) {} + + public async waitForImporterPipePath(): Promise { + return new Promise((resolve) => { + this.context.subscriptions.push( + vscode.commands.registerCommand(GET_IMPORTER_PIPE_NAME, (pipeName: string) => { + this.importerPipePath = path.resolve(pipeName); + this.logger.info("Received Importer PipeName from Java: ", this.importerPipePath); + resolve(); + }) + ); + }); + } + + public setupImporterHandler(buildServerConnection: rpc.MessageConnection): void { + this.importerPipeServer = net.createServer((socket: net.Socket) => { + this.importerConnection = rpc.createMessageConnection( + new rpc.StreamMessageReader(socket), + new rpc.StreamMessageWriter(socket) + ); + if (!this.importerConnection) { + this.logger.error("Importer Connection is Null"); + return; + } + if (!buildServerConnection) { + this.logger.error("Build Server Connection is Null"); + return; + } + + this.importerConnection.onRequest((method, params) => { + this.logger.info("importerConnection.onRequest: ", method); + return buildServerConnection.sendRequest(method, params); + }); + buildServerConnection.onNotification((method, params) => { + this.logger.info("buildServerConnection.onNotification: ", method); + this.importerConnection!.sendNotification(method, params); + }); + + this.importerConnection.listen(); + }); + + this.importerPipeServer.listen(this.importerPipePath, () => { + this.logger.info("!Importer Pipe Server is listening on: ", this.importerPipePath); + }); + } + + public dispose(): void { + if (fs.existsSync(this.importerPipePath)) { + fs.unlinkSync(this.importerPipePath); + } + } +} diff --git a/extension/src/commands/Commands.ts b/extension/src/commands/Commands.ts index d35fb755b..706591261 100644 --- a/extension/src/commands/Commands.ts +++ b/extension/src/commands/Commands.ts @@ -148,8 +148,8 @@ export class Commands { COMMAND_REFRESH_DAEMON_STATUS, new RefreshDaemonStatusCommand(this.gradleDaemonsTreeDataProvider) ); - this.registerCommand(COMMAND_STOP_DAEMONS, new StopDaemonsCommand(this.client, this.rootProjectsStore)); - this.registerCommand(COMMAND_STOP_DAEMON, new StopDaemonCommand(this.client)); + this.registerCommand(COMMAND_STOP_DAEMONS, new StopDaemonsCommand(this.rootProjectsStore)); + this.registerCommand(COMMAND_STOP_DAEMON, new StopDaemonCommand()); this.registerCommand(COMMAND_EXPLORER_TREE, new ExplorerTreeCommand(this.gradleTasksTreeDataProvider)); this.registerCommand(COMMAND_EXPLORER_FLAT, new ExplorerFlatCommand(this.gradleTasksTreeDataProvider)); this.registerCommand(COMMAND_OPEN_SETTINGS, new OpenSettingsCommand()); @@ -193,3 +193,4 @@ export class Commands { } } } + diff --git a/extension/src/commands/StopDaemonCommand.ts b/extension/src/commands/StopDaemonCommand.ts index 3ce6a2d7b..fdce8581e 100644 --- a/extension/src/commands/StopDaemonCommand.ts +++ b/extension/src/commands/StopDaemonCommand.ts @@ -2,12 +2,14 @@ import { GradleDaemonTreeItem } from "../views"; import { confirmModal } from "../util/input"; import { logger } from "../logger"; import { Command } from "./Command"; -import { GradleClient } from "../client"; +import { execAsync } from "../util/execAsync"; +import * as vscode from "vscode"; +import { COMMAND_REFRESH_DAEMON_STATUS } from "./RefreshDaemonStatusCommand"; export const COMMAND_STOP_DAEMON = "gradle.stopDaemon"; export class StopDaemonCommand extends Command { - constructor(private client: GradleClient) { + constructor() { super(); } async run(treeItem: GradleDaemonTreeItem): Promise { @@ -15,9 +17,21 @@ export class StopDaemonCommand extends Command { return; } const pid = treeItem.pid; - const stopDaemonReply = await this.client.stopDaemon(pid); - if (stopDaemonReply) { - logger.info(stopDaemonReply.getMessage()); + try { + await this.stopDaemon(pid); + logger.info(`Successfully stopped daemon with PID ${pid}.`); + } catch (error) { + logger.error(`Failed to stop daemon with PID ${pid}: ${error.message}.`); } } + + async stopDaemon(pid: string): Promise { + if (!pid) { + throw new Error("PID is required to stop the daemon."); + } + + const command = process.platform === "win32" ? `taskkill /PID ${pid} /F` : `kill ${pid}`; + await execAsync(command); + await vscode.commands.executeCommand(COMMAND_REFRESH_DAEMON_STATUS); + } } diff --git a/extension/src/commands/StopDaemonsCommand.ts b/extension/src/commands/StopDaemonsCommand.ts index ec1a72680..bb9106f83 100644 --- a/extension/src/commands/StopDaemonsCommand.ts +++ b/extension/src/commands/StopDaemonsCommand.ts @@ -1,14 +1,19 @@ import * as vscode from "vscode"; import { confirmModal } from "../util/input"; -import { StopDaemonsReply } from "../proto/gradle_pb"; import { logger } from "../logger"; import { Command } from "./Command"; import { RootProjectsStore } from "../stores"; -import { GradleClient } from "../client"; +import { getGradleConfig } from "../util/config"; +import { GradleStatus } from "../views/gradleDaemons/services/GradleStatus"; +import { GradleConnectionType } from "../views/gradleDaemons/models/GradleConnectionType"; +import { GradleWrapper } from "../views/gradleDaemons/services/GradleWrapper"; +import { GradleLocalInstallation } from "../views/gradleDaemons/services/GradleLocalInstallation"; +import { COMMAND_REFRESH_DAEMON_STATUS } from "./RefreshDaemonStatusCommand"; + export const COMMAND_STOP_DAEMONS = "gradle.stopDaemons"; export class StopDaemonsCommand extends Command { - constructor(private client: GradleClient, private rootProjectsStore: RootProjectsStore) { + constructor(private rootProjectsStore: RootProjectsStore) { super(); } async run(): Promise { @@ -20,14 +25,29 @@ export class StopDaemonsCommand extends Command { return; } const gradleRootFolders = await this.rootProjectsStore.getProjectRootsWithUniqueVersions(); - const promises: Promise[] = gradleRootFolders.map((rootProject) => - this.client.stopDaemons(rootProject.getProjectUri().fsPath) - ); - const replies = await Promise.all(promises); - replies.forEach((reply) => { - if (reply) { - logger.info(reply.getMessage()); - } - }); + try { + const promises: Promise[] = gradleRootFolders.map((rootProject) => + this.stopDaemons(rootProject.getProjectUri().fsPath) + ); + await Promise.all(promises); + logger.info(`Successfully stopped all daemons.`); + await vscode.commands.executeCommand(COMMAND_REFRESH_DAEMON_STATUS); + } catch (error) { + logger.error(`Failed to stop daemons: ${error.message}.`); + } + } + + async stopDaemons(projectFolder: string): Promise { + const gradleConfig = getGradleConfig(); + const connectType = await GradleStatus.getConnectionType(gradleConfig); + if (connectType === GradleConnectionType.WRAPPER) { + const gradleExecution = new GradleWrapper(projectFolder); + await gradleExecution.exec(["--stop"]); + } else if (connectType === GradleConnectionType.LOCALINSTALLATION) { + const gradleExecution = new GradleLocalInstallation(gradleConfig.getGradleHome()); + await gradleExecution.exec(["--stop"]); + } else { + logger.info("No daemons to stop."); + } } } diff --git a/extension/src/constant.ts b/extension/src/constant.ts index 2ff546bb7..a12c44a4e 100644 --- a/extension/src/constant.ts +++ b/extension/src/constant.ts @@ -5,6 +5,7 @@ export namespace Context { export const ACTIVATION_CONTEXT_KEY = "gradle:extensionActivated"; } +export const GET_EXTENSION_PATH = "gradle.getExtensionPath" export const GRADLE_BUILD_FILE_CHANGE = "gradle.buildFileChanged"; diff --git a/extension/src/index.ts b/extension/src/index.ts index 39dd644a9..d6b7863ac 100644 --- a/extension/src/index.ts +++ b/extension/src/index.ts @@ -7,7 +7,7 @@ import { Extension } from "./Extension"; let extension: Extension; export async function activate(context: vscode.ExtensionContext): Promise { - await initializeFromJsonFile(context.asAbsolutePath("./package.json"), { firstParty: true }); + await initializeFromJsonFile(context.asAbsolutePath("./package.json")); return instrumentOperation("activation", activateExtension)(context); } diff --git a/extension/src/logger/Logger.ts b/extension/src/logger/Logger.ts index cabdbfa4c..b9df9eddc 100644 --- a/extension/src/logger/Logger.ts +++ b/extension/src/logger/Logger.ts @@ -56,7 +56,7 @@ export class Logger { this.log(error, LogVerbosity.ERROR); sendInfo("", { kind: "gradleTaskServerError", - data: error, + data2: error, }); } diff --git a/extension/src/server/GradleServer.ts b/extension/src/server/GradleServer.ts index d0e5c8d4a..c52bbeaf8 100644 --- a/extension/src/server/GradleServer.ts +++ b/extension/src/server/GradleServer.ts @@ -6,7 +6,9 @@ import * as kill from "tree-kill"; import { getGradleServerCommand, getGradleServerEnv } from "./serverUtil"; import { isDebuggingServer } from "../util"; import { Logger } from "../logger/index"; -import { NO_JAVA_EXECUTABLE } from "../constant"; +import { NO_JAVA_EXECUTABLE, GET_EXTENSION_PATH } from "../constant"; +import { getRedHatJavaExecutablePath } from "../util/config"; +import { BuildServerHandler } from "../bs/BuildServerHandler"; const SERVER_LOGLEVEL_REGEX = /^\[([A-Z]+)\](.*)$/; const DOWNLOAD_PROGRESS_CHAR = "."; @@ -19,7 +21,7 @@ export class GradleServer { private readonly _onDidStart: vscode.EventEmitter = new vscode.EventEmitter(); private readonly _onDidStop: vscode.EventEmitter = new vscode.EventEmitter(); private ready = false; - private port: number | undefined; + private gradleServerPort: number | undefined; private restarting = false; public readonly onDidStart: vscode.Event = this._onDidStart.event; @@ -29,27 +31,33 @@ export class GradleServer { constructor( private readonly opts: ServerOptions, private readonly context: vscode.ExtensionContext, - private readonly logger: Logger + private readonly logger: Logger, + private buildServerHandler: BuildServerHandler ) {} public async start(): Promise { if (isDebuggingServer()) { - this.port = 8887; this.fireOnStart(); } else { - this.port = await getPort(); + this.gradleServerPort = await getPort(); const cwd = this.context.asAbsolutePath("lib"); const cmd = path.join(cwd, getGradleServerCommand()); const env = await getGradleServerEnv(); + const bundleDirectory = await this.getBundleDirectory(); if (!env) { await vscode.window.showErrorMessage(NO_JAVA_EXECUTABLE); return; } - const args = [String(this.port)]; + let javaExecPath = await getRedHatJavaExecutablePath(); - this.logger.debug("Starting server"); + //Get the Java executable used by JDT.LS, which will be higher than JDK 17. + if (!javaExecPath) { + await vscode.window.showErrorMessage("No Red Hat Java Extension Pack Found"); + return; + } + const serverPipeName = this.buildServerHandler.getBuildServerPipeName(); + const args = [String(this.gradleServerPort), serverPipeName, bundleDirectory, javaExecPath]; this.logger.debug(`Gradle Server cmd: ${cmd} ${args.join(" ")}`); - this.process = cp.spawn(`"${cmd}"`, args, { cwd, env, @@ -80,6 +88,11 @@ export class GradleServer { return this.ready; } + public async getBundleDirectory(): Promise { + const extensionPath = await vscode.commands.executeCommand(GET_EXTENSION_PATH); + return path.join(extensionPath, 'server'); + } + public async showRestartMessage(): Promise { const OPT_RESTART = "Restart Server"; const input = await vscode.window.showErrorMessage( @@ -141,7 +154,7 @@ export class GradleServer { } public getPort(): number | undefined { - return this.port; + return this.gradleServerPort; } public getOpts(): ServerOptions { diff --git a/extension/src/test/unit/gradleDaemons.test.ts b/extension/src/test/unit/gradleDaemons.test.ts index 470d621ce..c61aa835b 100644 --- a/extension/src/test/unit/gradleDaemons.test.ts +++ b/extension/src/test/unit/gradleDaemons.test.ts @@ -1,20 +1,13 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ - import * as assert from "assert"; import * as vscode from "vscode"; import * as sinon from "sinon"; import * as path from "path"; -import { - GetDaemonsStatusReply, - DaemonInfo, - StopDaemonReply, - StopDaemonsReply, - Environment, - GradleEnvironment, -} from "../../proto/gradle_pb"; +import { Environment, GradleEnvironment } from "../../proto/gradle_pb"; +import { DaemonInfo } from "../../views/gradleDaemons/models/DaemonInfo"; +import { DaemonStatus } from "../../views/gradleDaemons/models/DaemonStatus"; import { GradleDaemonsTreeDataProvider, GradleDaemonTreeItem } from "../../views"; -// import { Extension } from '../../extension'; import { SinonStub } from "sinon"; import { logger } from "../../logger"; import { @@ -22,7 +15,6 @@ import { resetObjectStubs, buildMockOutputChannel, buildMockWorkspaceFolder, - buildMockClient, buildMockContext, stubWorkspaceFolders, } from "../testUtil"; @@ -31,9 +23,10 @@ import { ICON_DAEMON_STOPPED, ICON_DAEMON_BUSY, ICON_DAEMON_IDLE } from "../../v import { RootProjectsStore } from "../../stores"; import { RefreshDaemonStatusCommand, StopDaemonCommand, StopDaemonsCommand } from "../../commands"; import { sleep } from "../../util"; +import { GradleStatus } from "../../views/gradleDaemons/services/GradleStatus"; +import { GradleConnectionType } from "../../views/gradleDaemons/models/GradleConnectionType"; const mockContext = buildMockContext(); -const mockClient = buildMockClient(); const mockWorkspaceFolder1 = buildMockWorkspaceFolder(0, "folder1", "folder1"); const mockWorkspaceFolder2 = buildMockWorkspaceFolder(1, "folder2", "folder2"); @@ -46,12 +39,11 @@ describe(getSuiteName("Gradle daemons"), () => { let rootProjectsStore: RootProjectsStore; beforeEach(async () => { rootProjectsStore = new RootProjectsStore(); - gradleDaemonsTreeDataProvider = new GradleDaemonsTreeDataProvider(mockContext, rootProjectsStore, mockClient); + gradleDaemonsTreeDataProvider = new GradleDaemonsTreeDataProvider(mockContext, rootProjectsStore); stubWorkspaceFolders([mockWorkspaceFolder1, mockWorkspaceFolder2, mockWorkspaceFolder3]); await rootProjectsStore.populate(); - // GradleClient.getBuild() sets the gradle versions once it receives the gradle environment const projectRoots = await rootProjectsStore.getProjectRoots(); const gradleEnvironment1 = new GradleEnvironment(); gradleEnvironment1.setGradleVersion("6.3"); @@ -89,31 +81,16 @@ describe(getSuiteName("Gradle daemons"), () => { it("should build the daemon treeitems", async () => { await vscode.workspace.getConfiguration("gradle").update("showStoppedDaemons", true, true); - const mockDaemonInfoBusy = new DaemonInfo(); - mockDaemonInfoBusy.setStatus(DaemonInfo.DaemonStatus.BUSY); - mockDaemonInfoBusy.setPid("41716"); - mockDaemonInfoBusy.setInfo("6.3"); - - const mockDaemonInfoIdle = new DaemonInfo(); - mockDaemonInfoIdle.setStatus(DaemonInfo.DaemonStatus.IDLE); - mockDaemonInfoIdle.setPid("41717"); - mockDaemonInfoIdle.setInfo("6.4"); - - const mockDaemonInfoStopped = new DaemonInfo(); - mockDaemonInfoStopped.setStatus(DaemonInfo.DaemonStatus.STOPPED); - mockDaemonInfoStopped.setPid("41718"); - mockDaemonInfoStopped.setInfo("(by user or operating system)"); - - const mockReply1 = new GetDaemonsStatusReply(); - mockReply1.setDaemonInfoList([mockDaemonInfoBusy, mockDaemonInfoStopped]); + const mockDaemonInfoBusy = new DaemonInfo("41716", DaemonStatus.BUSY, "6.3"); + const mockDaemonInfoIdle = new DaemonInfo("41717", DaemonStatus.IDLE, "6.4"); + const mockDaemonInfoStopped = new DaemonInfo("41718", DaemonStatus.STOPPED, "(by user or operating system)"); - const mockReply2 = new GetDaemonsStatusReply(); - mockReply2.setDaemonInfoList([mockDaemonInfoIdle, mockDaemonInfoStopped]); - - mockClient.getDaemonsStatus.withArgs(mockWorkspaceFolder1.uri.fsPath).resolves(mockReply1); - mockClient.getDaemonsStatus.withArgs(mockWorkspaceFolder2.uri.fsPath).resolves(mockReply2); - // NOTE: no reason to mock reply for mockWorkspaceFolder3 as it should be ignored due to - // dupicate gradle version + sinon + .stub(GradleStatus, "getDaemonsStatusList") + .withArgs(mockWorkspaceFolder1.uri.fsPath) + .resolves([mockDaemonInfoBusy, mockDaemonInfoStopped]) + .withArgs(mockWorkspaceFolder2.uri.fsPath) + .resolves([mockDaemonInfoIdle, mockDaemonInfoStopped]); let children = await gradleDaemonsTreeDataProvider.getChildren(); @@ -158,63 +135,51 @@ describe(getSuiteName("Gradle daemons"), () => { }); it("should stop a daemon", async () => { - const mockReply = new StopDaemonReply(); - mockReply.setMessage("Stopped"); - mockClient.stopDaemon.resolves(mockReply); - - const showWarningMessageStub = (sinon.stub(vscode.window, "showWarningMessage") as SinonStub).resolves("Yes"); - - const mockDaemonInfoBusy = new DaemonInfo(); - mockDaemonInfoBusy.setStatus(DaemonInfo.DaemonStatus.BUSY); - mockDaemonInfoBusy.setPid("41716"); - mockDaemonInfoBusy.setInfo("6.4"); - + const mockDaemonInfoBusy = new DaemonInfo("41716", DaemonStatus.BUSY, "6.3"); const mockGradleDaemonTreeItem = new GradleDaemonTreeItem( mockContext, mockDaemonInfoBusy.getPid(), mockDaemonInfoBusy ); - await new StopDaemonCommand(mockClient).run(mockGradleDaemonTreeItem); + const showWarningMessageStub = (sinon.stub(vscode.window, "showWarningMessage") as SinonStub).resolves("Yes"); + const mockStopDaemonCommand = new StopDaemonCommand(); + sinon.stub(mockStopDaemonCommand, "stopDaemon").withArgs(mockDaemonInfoBusy.getPid()).resolves(); + + await mockStopDaemonCommand.run(mockGradleDaemonTreeItem); assert.ok( showWarningMessageStub.calledWith("Are you sure you want to stop the daemon?"), "Stop daemon confirmation message not shown" ); - assert.strictEqual(showWarningMessageStub.callCount, 1); - assert.ok( - mockClient.stopDaemon.calledWith(mockDaemonInfoBusy.getPid()), - "Client stopDaemon not called with daemon PID" - ); - assert.strictEqual(mockClient.stopDaemon.callCount, 1); + assert.ok( - mockOutputChannel.appendLine.calledWith("[info] Stopped"), + mockOutputChannel.appendLine.calledWith("[info] Successfully stopped daemon with PID 41716."), "Output channel appendLine not called with correct message" ); assert.strictEqual(mockOutputChannel.appendLine.callCount, 1); }); it("should stop all daemons", async () => { - const mockReply1 = new StopDaemonsReply(); - mockReply1.setMessage("Stopped 1"); - const mockReply2 = new StopDaemonsReply(); - mockReply2.setMessage("Stopped 2"); - - mockClient.stopDaemons.withArgs(mockWorkspaceFolder1.uri.fsPath).resolves(mockReply1); - mockClient.stopDaemons.withArgs(mockWorkspaceFolder2.uri.fsPath).resolves(mockReply2); - const showWarningMessageStub = (sinon.stub(vscode.window, "showWarningMessage") as SinonStub).resolves("Yes"); + sinon.stub(GradleStatus, "getConnectionType").withArgs(sinon.match.any).resolves(GradleConnectionType.WRAPPER); + + const mockStopDaemonsCommand = new StopDaemonsCommand(rootProjectsStore); + sinon.stub(mockStopDaemonsCommand, "stopDaemons").resolves(); - await new StopDaemonsCommand(mockClient, rootProjectsStore).run(); + await mockStopDaemonsCommand.run(); assert.ok( showWarningMessageStub.calledWith("Are you sure you want to stop the daemons?"), "Stop daemons confirmation message not shown" ); - assert.strictEqual(mockOutputChannel.appendLine.callCount, 2, "Logger not called expected times"); - assert.ok(mockOutputChannel.appendLine.calledWith("[info] Stopped 1"), "Reply for folder 1 not logged"); - assert.ok(mockOutputChannel.appendLine.calledWith("[info] Stopped 2"), "Reply for folder 2 not logged"); + assert.ok( + mockOutputChannel.appendLine.calledWith("[info] Successfully stopped all daemons."), + "Output channel appendLine not called with correct message" + ); + + showWarningMessageStub.restore(); }); it("should refresh the daemons list", async () => { @@ -225,62 +190,112 @@ describe(getSuiteName("Gradle daemons"), () => { assert.strictEqual(onDidChangeSpy.callCount, 1); }); - it("should prevent queing of daemon status requests", async () => { - const mockReply1 = new GetDaemonsStatusReply(); - const mockDaemonInfoBusy = new DaemonInfo(); - mockDaemonInfoBusy.setStatus(DaemonInfo.DaemonStatus.BUSY); - mockDaemonInfoBusy.setPid("41716"); - mockDaemonInfoBusy.setInfo("6.4"); - mockReply1.setDaemonInfoList([mockDaemonInfoBusy]); - const quickReply = Promise.resolve(mockReply1); - - const mockReply2 = new GetDaemonsStatusReply(); - const mockDaemonInfoIdle = new DaemonInfo(); - mockDaemonInfoIdle.setStatus(DaemonInfo.DaemonStatus.IDLE); - mockDaemonInfoIdle.setPid("41716"); - mockDaemonInfoIdle.setInfo("6.4 f00"); - mockReply2.setDaemonInfoList([mockDaemonInfoIdle]); - const longReply = new Promise((resolve) => { + it("should prevent queuing of daemon status requests", async () => { + const mockDaemonInfoBusy = new DaemonInfo("41716", DaemonStatus.BUSY, "6.4"); + const mockDaemonInfoIdle = new DaemonInfo("41716", DaemonStatus.IDLE, "6.4 f00"); + + const quickReply: Promise = Promise.resolve([mockDaemonInfoBusy]); + + const longReply: Promise = new Promise((resolve) => { setTimeout(() => { - resolve(mockReply2); + resolve([mockDaemonInfoIdle]); }, 1000); }); - const workspaceFolder1: vscode.WorkspaceFolder = { - index: 0, - uri: vscode.Uri.file("folder1"), - name: "folder1", - }; - - sinon.stub(vscode.workspace, "workspaceFolders").value([workspaceFolder1]); - - mockClient.getDaemonsStatus.withArgs(mockWorkspaceFolder1.uri.fsPath).returns(quickReply); + sinon.stub(vscode.workspace, "workspaceFolders").value([mockWorkspaceFolder1]); + + const getDaemonsStatusListStub = sinon + .stub(GradleStatus, "getDaemonsStatusList") + .withArgs(mockWorkspaceFolder2.uri.fsPath) + .resolves([mockDaemonInfoIdle]); + + let callCount = 0; + getDaemonsStatusListStub.withArgs(mockWorkspaceFolder1.uri.fsPath).callsFake(async () => { + callCount++; + if (callCount === 1) { + return quickReply; + } else { + return longReply; + } + }); const children = await gradleDaemonsTreeDataProvider.getChildren(); assert.strictEqual(children[0].description, "BUSY"); - mockClient.getDaemonsStatus.withArgs(mockWorkspaceFolder1.uri.fsPath).returns(longReply); - - await new Promise(async (resolve, reject) => { - // This call will return the previous results (quickReply) as we've cancelled - // the request with the subsequent call to refresh() - gradleDaemonsTreeDataProvider - .getChildren() - .then((_children: vscode.TreeItem[]) => { - assert.strictEqual(_children[0].description, "BUSY"); - }) - .catch(reject); - // This call will return the correct results (longReply) - gradleDaemonsTreeDataProvider.refresh(); - await sleep(1000); - gradleDaemonsTreeDataProvider - .getChildren() - .then((_children: vscode.TreeItem[]) => { - assert.strictEqual(_children[0].description, "IDLE"); - resolve(undefined); - }) - .catch(reject); - }); + gradleDaemonsTreeDataProvider.refresh(); + await sleep(1000); + + const refreshedChildren = await gradleDaemonsTreeDataProvider.getChildren(); + + assert.strictEqual(refreshedChildren[0].description, "IDLE"); + }); + + it("should correctly parse daemonInfos from input with Unix and Windows line endings", () => { + // Windows-style input with \r\n + const windowsOutput = ` + 95141 IDLE 8.6\r\n + 12345 BUSY 7.5\r\n + 67890 STOPPED (by user or operating system)\r\n + malformed line\r\n + `; + + const windowsDaemonInfos = GradleStatus.parseDaemonInfo(windowsOutput); + + assert.strictEqual( + windowsDaemonInfos.length, + 3, + "There should be 3 daemons parsed, ignoring malformed lines (Windows)" + ); + + const windowsDaemon1 = windowsDaemonInfos[0]; + assert.strictEqual(windowsDaemon1.getPid(), "95141"); + assert.strictEqual(windowsDaemon1.getStatus(), DaemonStatus.IDLE); + assert.strictEqual(windowsDaemon1.getInfo(), "8.6"); + + const windowsDaemon2 = windowsDaemonInfos[1]; + assert.strictEqual(windowsDaemon2.getPid(), "12345"); + assert.strictEqual(windowsDaemon2.getStatus(), DaemonStatus.BUSY); + assert.strictEqual(windowsDaemon2.getInfo(), "7.5"); + + const windowsDaemon3 = windowsDaemonInfos[2]; + assert.strictEqual(windowsDaemon3.getPid(), "67890"); + assert.strictEqual(windowsDaemon3.getStatus(), DaemonStatus.STOPPED); + assert.strictEqual(windowsDaemon3.getInfo(), "(by user or operating system)"); + + // Unix/Mac-style input with \n + const unixOutput = ` + 95141 IDLE 8.6\n + 12345 BUSY 7.5\n + 67890 STOPPED (by user or operating system)\n + malformed line\n + `; + + const unixDaemonInfos = GradleStatus.parseDaemonInfo(unixOutput); + + assert.strictEqual( + unixDaemonInfos.length, + 3, + "There should be 3 daemons parsed, ignoring malformed lines (Unix/Mac)" + ); + + const unixDaemon1 = unixDaemonInfos[0]; + assert.strictEqual(unixDaemon1.getPid(), "95141"); + assert.strictEqual(unixDaemon1.getStatus(), DaemonStatus.IDLE); + assert.strictEqual(unixDaemon1.getInfo(), "8.6"); + + const unixDaemon2 = unixDaemonInfos[1]; + assert.strictEqual(unixDaemon2.getPid(), "12345"); + assert.strictEqual(unixDaemon2.getStatus(), DaemonStatus.BUSY); + assert.strictEqual(unixDaemon2.getInfo(), "7.5"); + + const unixDaemon3 = unixDaemonInfos[2]; + assert.strictEqual(unixDaemon3.getPid(), "67890"); + assert.strictEqual(unixDaemon3.getStatus(), DaemonStatus.STOPPED); + assert.strictEqual(unixDaemon3.getInfo(), "(by user or operating system)"); + + const emptyOutput = ""; + const emptyDaemonInfos = GradleStatus.parseDaemonInfo(emptyOutput); + assert.strictEqual(emptyDaemonInfos.length, 0, "There should be no daemons parsed for empty output"); }); }); diff --git a/extension/src/util/config.ts b/extension/src/util/config.ts index 68d5a31aa..db25f0653 100644 --- a/extension/src/util/config.ts +++ b/extension/src/util/config.ts @@ -3,6 +3,8 @@ import { getRuntime } from "jdk-utils"; import * as vscode from "vscode"; import { GradleConfig } from "../proto/gradle_pb"; import { RootProject } from "../rootProject/RootProject"; +import * as fs from 'fs'; +import * as path from 'path'; type AutoDetect = "on" | "off"; @@ -26,6 +28,34 @@ export function getConfigJavaImportGradleJavaHome(): string | null { return vscode.workspace.getConfiguration("java").get("import.gradle.java.home", null); } +export function getRedHatJavaExecutablePath(): string | null { + const javaExtension = vscode.extensions.getExtension("redhat.java"); + if (!javaExtension) { + return null; + } + + const extensionPath = javaExtension.extensionPath; + const jrePath = path.join(extensionPath, 'jre'); + if (!fs.existsSync(jrePath) || !fs.lstatSync(jrePath).isDirectory()) { + return null; + } + + // Read the entries in the jre directory and filter out hidden files + const entries = fs.readdirSync(jrePath).filter(entry => !entry.startsWith('.')); + if (entries.length === 0) { + return null; + } + + const entry = entries[0]; + const javaExec = process.platform === "win32" ? 'java.exe' : 'java'; + const javaExecPath = path.join(jrePath, entry, "bin", javaExec); + if (fs.existsSync(javaExecPath)) { + return javaExecPath; + } + + return null; +} + export function getConfigGradleJavaHome(): string | null { return getConfigJavaImportGradleJavaHome() || getJdtlsConfigJavaHome() || getConfigJavaHome(); } diff --git a/extension/src/util/execAsync.ts b/extension/src/util/execAsync.ts new file mode 100644 index 000000000..2a33f9fab --- /dev/null +++ b/extension/src/util/execAsync.ts @@ -0,0 +1,4 @@ +import { exec } from "child_process"; +import { promisify } from "util"; + +export const execAsync = promisify(exec); diff --git a/extension/src/util/generateRandomPipeName.ts b/extension/src/util/generateRandomPipeName.ts new file mode 100644 index 000000000..83079939e --- /dev/null +++ b/extension/src/util/generateRandomPipeName.ts @@ -0,0 +1,13 @@ +import { randomBytes } from 'crypto'; +import * as vscode from 'vscode'; +import { GET_EXTENSION_PATH } from '../constant'; +export async function generateRandomPipeName(type: string): Promise { + const randomSuffix = randomBytes(11).toString('hex'); + if (process.platform === "win32") { + return `\\\\.\\pipe\\${randomSuffix}-${type}-sock`; + } else { + const extensionPath = await vscode.commands.executeCommand(GET_EXTENSION_PATH) + return `${extensionPath}/${randomSuffix}-${type}.sock`; + } +} + diff --git a/extension/src/views/constants.ts b/extension/src/views/constants.ts index 4bb366ecb..b9d48b3b5 100644 --- a/extension/src/views/constants.ts +++ b/extension/src/views/constants.ts @@ -1,4 +1,4 @@ -import { DaemonInfo } from "../proto/gradle_pb"; +import { DaemonStatus } from "./gradleDaemons/models/DaemonStatus"; export const ICON_LOADING = "loading.svg"; export const ICON_GRADLE_TASK = "script.svg"; @@ -24,9 +24,9 @@ export const TREE_ITEM_STATE_FOLDER = "folder"; export const TASK_STATE_RUNNING_REGEX = new RegExp(`^${TREE_ITEM_STATE_TASK_RUNNING}`); export const DAEMON_ICON_MAP = { - [DaemonInfo.DaemonStatus.BUSY]: ICON_DAEMON_BUSY, - [DaemonInfo.DaemonStatus.IDLE]: ICON_DAEMON_IDLE, - [DaemonInfo.DaemonStatus.STOPPED]: ICON_DAEMON_STOPPED, - [DaemonInfo.DaemonStatus.STOPPING]: ICON_DAEMON_STOPPED, - [DaemonInfo.DaemonStatus.CANCELED]: ICON_DAEMON_STOPPED, + [DaemonStatus.BUSY]: ICON_DAEMON_BUSY, + [DaemonStatus.IDLE]: ICON_DAEMON_IDLE, + [DaemonStatus.STOPPED]: ICON_DAEMON_STOPPED, + [DaemonStatus.STOPPING]: ICON_DAEMON_STOPPED, + [DaemonStatus.CANCELED]: ICON_DAEMON_STOPPED, }; diff --git a/extension/src/views/gradleDaemons/GradleDaemonTreeItem.ts b/extension/src/views/gradleDaemons/GradleDaemonTreeItem.ts index d259cb4f7..5b2295c7a 100644 --- a/extension/src/views/gradleDaemons/GradleDaemonTreeItem.ts +++ b/extension/src/views/gradleDaemons/GradleDaemonTreeItem.ts @@ -1,18 +1,8 @@ import * as vscode from "vscode"; import * as path from "path"; -import { DaemonInfo } from "../../proto/gradle_pb"; import { DAEMON_ICON_MAP } from "../constants"; - -interface StatusEnumMapByValue { - [key: number]: string; -} - -const daemonStatusEnumMapByValue: StatusEnumMapByValue = Object.assign( - {}, - ...Object.entries(DaemonInfo.DaemonStatus).map(([a, b]) => ({ - [b]: a, - })) -); +import { DaemonInfo } from "./models/DaemonInfo"; +import { DaemonStatus } from "./models/DaemonStatus"; export class GradleDaemonTreeItem extends vscode.TreeItem { private status: string; @@ -27,7 +17,7 @@ export class GradleDaemonTreeItem extends vscode.TreeItem { light: this.context.asAbsolutePath(path.join("resources", "light", iconName)), dark: this.context.asAbsolutePath(path.join("resources", "dark", iconName)), }; - this.status = daemonStatusEnumMapByValue[daemonInfo.getStatus()]; + this.status = DaemonStatus[daemonInfo.getStatus()]; this.description = this.status; this.contextValue = this.status.toLowerCase(); this.tooltip = `${this.status} - ${daemonInfo.getInfo()}`; diff --git a/extension/src/views/gradleDaemons/GradleDaemonsTreeDataProvider.ts b/extension/src/views/gradleDaemons/GradleDaemonsTreeDataProvider.ts index e79e3d9c0..3a9450891 100644 --- a/extension/src/views/gradleDaemons/GradleDaemonsTreeDataProvider.ts +++ b/extension/src/views/gradleDaemons/GradleDaemonsTreeDataProvider.ts @@ -1,12 +1,11 @@ import * as vscode from "vscode"; import { GradleDaemonTreeItem } from "."; -import { GradleClient } from "../../client"; -import { DaemonInfo } from "../../proto/gradle_pb"; import { RootProjectsStore } from "../../stores"; import { getShowStoppedDaemons, setShowStoppedDaemons } from "../../util/config"; import { Deferred } from "../../util/Deferred"; import { HintItem } from "../gradleTasks/HintItem"; - +import { GradleStatus } from "./services/GradleStatus"; +import { DaemonStatus } from "./models/DaemonStatus"; export class GradleDaemonsTreeDataProvider implements vscode.TreeDataProvider { private cancelDeferred?: Deferred; private treeItems: vscode.TreeItem[] = []; @@ -17,8 +16,7 @@ export class GradleDaemonsTreeDataProvider implements vscode.TreeDataProvider cancellationToken.cancel()); const projectRootFolders = await this.getProjectRootFolders(); - const promises: Promise[] = projectRootFolders.map((projectRootFolder) => - this.client.getDaemonsStatus(projectRootFolder, cancellationToken.token).then((daemonsStatusReply) => { - if (!daemonsStatusReply) { - return []; - } - let daemonInfoList = daemonsStatusReply.getDaemonInfoList(); - if (!getShowStoppedDaemons()) { - daemonInfoList = daemonInfoList.filter((daemonInfo) => { - return daemonInfo.getStatus() !== DaemonInfo.DaemonStatus.STOPPED; - }); - } - return daemonInfoList.map( - (daemonInfo) => new GradleDaemonTreeItem(this.context, daemonInfo.getPid(), daemonInfo) + const promises: Promise[] = projectRootFolders.map(async (projectRootFolder) => { + const daemonInfos = await GradleStatus.getDaemonsStatusList(projectRootFolder); + + let filteredDaemonInfos = daemonInfos; + if (!getShowStoppedDaemons()) { + filteredDaemonInfos = daemonInfos.filter( + (daemonInfo) => daemonInfo.getStatus() !== DaemonStatus.STOPPED ); - }) - ); + } + + return filteredDaemonInfos.map( + (daemonInfo) => new GradleDaemonTreeItem(this.context, daemonInfo.getPid(), daemonInfo) + ); + }); + this.treeItems = await Promise.race([ Promise.all(promises).then((items) => items.flat()), this.cancelDeferred.promise, diff --git a/extension/src/views/gradleDaemons/models/DaemonInfo.ts b/extension/src/views/gradleDaemons/models/DaemonInfo.ts new file mode 100644 index 000000000..b1974ed56 --- /dev/null +++ b/extension/src/views/gradleDaemons/models/DaemonInfo.ts @@ -0,0 +1,29 @@ +import { DaemonStatus } from "./DaemonStatus"; + +export class DaemonInfo { + constructor(private pid: string, private status: DaemonStatus, private info: string) {} + + public getPid(): string { + return this.pid; + } + + public getStatus(): DaemonStatus { + return this.status; + } + + public getInfo(): string { + return this.info; + } + + public setStatus(status: DaemonStatus): void { + this.status = status; + } + + public setInfo(info: string): void { + this.info = info; + } + + public setPid(pid: string): void { + this.pid = pid; + } +} diff --git a/extension/src/views/gradleDaemons/models/DaemonStatus.ts b/extension/src/views/gradleDaemons/models/DaemonStatus.ts new file mode 100644 index 000000000..56a64c354 --- /dev/null +++ b/extension/src/views/gradleDaemons/models/DaemonStatus.ts @@ -0,0 +1,7 @@ +export enum DaemonStatus { + IDLE = "IDLE", + BUSY = "BUSY", + STOPPED = "STOPPED", + STOPPING = "STOPPING", + CANCELED = "CANCELED", +} diff --git a/extension/src/views/gradleDaemons/models/GradleConnectionType.ts b/extension/src/views/gradleDaemons/models/GradleConnectionType.ts new file mode 100644 index 000000000..2674a24c5 --- /dev/null +++ b/extension/src/views/gradleDaemons/models/GradleConnectionType.ts @@ -0,0 +1,5 @@ +export enum GradleConnectionType { + WRAPPER, + LOCALINSTALLATION, + SPECIFICVERSION, +} diff --git a/extension/src/views/gradleDaemons/services/GradleExecution.ts b/extension/src/views/gradleDaemons/services/GradleExecution.ts new file mode 100644 index 000000000..f9ab54e9f --- /dev/null +++ b/extension/src/views/gradleDaemons/services/GradleExecution.ts @@ -0,0 +1,3 @@ +export interface GradleExecution { + exec(args: string[]): Promise; +} diff --git a/extension/src/views/gradleDaemons/services/GradleLocalInstallation.ts b/extension/src/views/gradleDaemons/services/GradleLocalInstallation.ts new file mode 100644 index 000000000..d682a8dab --- /dev/null +++ b/extension/src/views/gradleDaemons/services/GradleLocalInstallation.ts @@ -0,0 +1,34 @@ +import { GradleExecution } from "./GradleExecution"; +import { execAsync } from "../../../util/execAsync"; +import { getConfigJavaImportGradleJavaHome } from "../../../util/config"; +import { logger } from "../../../logger"; + +export class GradleLocalInstallation implements GradleExecution { + private gradleHomePath: string; + + constructor(gradleHomePath: string) { + this.gradleHomePath = gradleHomePath; + } + + public async exec(args: string[]): Promise { + if (args.length === 0) { + throw new Error("No gradle args supplied"); + } + + const command = `${this.gradleHomePath} ${args.join(" ")}`; + + try { + const jdkPath = getConfigJavaImportGradleJavaHome(); + const env = jdkPath ? { ...process.env, JAVA_HOME: jdkPath } : process.env; + + const { stdout, stderr } = await execAsync(command, { env }); + if (stderr) { + logger.error(stderr); + } + return stdout; + } catch (error) { + logger.error(error.message); + throw new Error(`Error running gradle local installation: ${error.message}`); + } + } +} diff --git a/extension/src/views/gradleDaemons/services/GradleStatus.ts b/extension/src/views/gradleDaemons/services/GradleStatus.ts new file mode 100644 index 000000000..46f7fa46a --- /dev/null +++ b/extension/src/views/gradleDaemons/services/GradleStatus.ts @@ -0,0 +1,71 @@ +import { DaemonInfo } from "../models/DaemonInfo"; +import { DaemonStatus } from "../models/DaemonStatus"; +import { getGradleConfig } from "../../../util/config"; +import { GradleConfig } from "../../../proto/gradle_pb"; +import { GradleWrapper } from "./GradleWrapper"; +import { GradleLocalInstallation } from "./GradleLocalInstallation"; +import { GradleConnectionType } from "../models/GradleConnectionType"; +export class GradleStatus { + public static async getConnectionType(gradleConfig: GradleConfig): Promise { + if (gradleConfig.getWrapperEnabled()) { + return GradleConnectionType.WRAPPER; + } else { + if (gradleConfig.getVersion()) { + return GradleConnectionType.SPECIFICVERSION; + } else if (gradleConfig.getGradleHome()) { + return GradleConnectionType.LOCALINSTALLATION; + } + return GradleConnectionType.WRAPPER; + } + } + + private static async getDaemonsStatusOutput(gradleConfig: GradleConfig, projectRoot: string): Promise { + const connectionType = await this.getConnectionType(gradleConfig); + switch (connectionType) { + case GradleConnectionType.WRAPPER: + if (await GradleWrapper.hasValidWrapper(projectRoot)) { + const wrapper = new GradleWrapper(projectRoot); + return wrapper.exec(["--status", "quiet"]); + } + return ""; + case GradleConnectionType.LOCALINSTALLATION: + const localInstallation = new GradleLocalInstallation(gradleConfig.getGradleHome()); + return localInstallation.exec(["--status", "quiet"]); + case GradleConnectionType.SPECIFICVERSION: + return ""; + default: + throw new Error("Unknown connection type"); + } + } + + public static async getDaemonsStatusList(projectRoot: string): Promise { + const gradleConfig = getGradleConfig(); + const output = await this.getDaemonsStatusOutput(gradleConfig, projectRoot); + + return this.parseDaemonInfo(output); + } + + public static parseDaemonInfo(output: string): DaemonInfo[] { + if (!output) return []; + + const lines = output.split(/\r?\n/); + const daemonInfos: DaemonInfo[] = []; + + const statusRegex = /^\s*([0-9]+)\s+(\w+)\s+(.+)$/; + + lines.forEach((line) => { + const match = line.match(statusRegex); + if (match) { + const pid = match[1]; + const statusString = match[2]; + const info = match[3]; + + const status = statusString as DaemonStatus; + + daemonInfos.push(new DaemonInfo(pid, status, info)); + } + }); + + return daemonInfos; + } +} diff --git a/extension/src/views/gradleDaemons/services/GradleWrapper.ts b/extension/src/views/gradleDaemons/services/GradleWrapper.ts new file mode 100644 index 000000000..285c724d9 --- /dev/null +++ b/extension/src/views/gradleDaemons/services/GradleWrapper.ts @@ -0,0 +1,43 @@ +import * as fse from "fs-extra"; +import { execAsync } from "../../../util/execAsync"; +import { GradleExecution } from "./GradleExecution"; +import * as path from "path"; +import { getConfigJavaImportGradleJavaHome } from "../../../util/config"; +import { logger } from "../../../logger"; + +export class GradleWrapper implements GradleExecution { + private gradleWrapperPath: string; + + constructor(private projectRoot: string) { + const wrapperName = process.platform === "win32" ? "gradlew.bat" : "gradlew"; + this.gradleWrapperPath = path.join(projectRoot, wrapperName); + } + + public async exec(args: string[]): Promise { + if (args.length === 0) { + throw new Error("No wrapper args supplied"); + } + + const command = `${this.gradleWrapperPath} ${args.join(" ")}`; + try { + const jdkPath = getConfigJavaImportGradleJavaHome(); + const env = jdkPath ? { ...process.env, JAVA_HOME: jdkPath } : process.env; + + const { stdout, stderr } = await execAsync(command, { cwd: this.projectRoot, env }); + if (stderr) { + logger.error(stderr); + } + return stdout; + } catch (error) { + logger.error(error.message); + throw new Error(`Error running gradle wrapper: ${error.message}`); + } + } + + public static async hasValidWrapper(projectRoot: string): Promise { + const propertiesPath = path.join(projectRoot, "gradle", "wrapper", "gradle-wrapper.properties"); + + const hasProperties = await fse.pathExists(propertiesPath); + return hasProperties; + } +} diff --git a/gradle-server/build.gradle b/gradle-server/build.gradle index ce5aedabd..c53fcf701 100644 --- a/gradle-server/build.gradle +++ b/gradle-server/build.gradle @@ -12,6 +12,9 @@ java { dependencies { implementation project(":gradle-plugin-api") + //implementation "org.eclipse.lsp4j:org.eclipse.lsp4j:0.12.0" + implementation 'ch.epfl.scala:bsp4j:2.1.0-M4' + implementation "org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.12.0" implementation "org.gradle:gradle-tooling-api:${toolingAPIVersion}" implementation 'javax.annotation:javax.annotation-api:1.3.2' implementation "io.grpc:grpc-protobuf:${grpcVersion}" diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/BuildServerThread.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/BuildServerThread.java new file mode 100644 index 000000000..c1b3709b4 --- /dev/null +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/BuildServerThread.java @@ -0,0 +1,66 @@ +package com.github.badsyntax.gradle; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +public class BuildServerThread implements Runnable { + + private String bundleDirectory; + + private final String pipeName; + + private final String javaHome; + public BuildServerThread(String pipeName, String bundleDirectory, String javaHome) { + this.pipeName = pipeName; + this.bundleDirectory = bundleDirectory; + this.javaHome = javaHome; + } + + @Override + public void run() { + try { + String[] classpaths = getBuildServerClasspath(); + + String pluginPath = getBuildServerPluginPath(); + + List command = new ArrayList<>(); + command.add(this.javaHome); + if (Boolean.parseBoolean(System.getenv("DEBUG_GRADLE_BUILD_SERVER"))) { + command.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8989"); + } + command.add("--add-opens=java.base/java.lang=ALL-UNNAMED"); + command.add("--add-opens=java.base/java.io=ALL-UNNAMED"); + command.add("--add-opens=java.base/java.util=ALL-UNNAMED"); + command.add("-Dplugin.dir=" + pluginPath); + command.add("-cp"); + command.add(String.join(getClasspathSeparator(), classpaths)); + command.add("com.microsoft.java.bs.core.Launcher"); + command.add(this.pipeName); + + ProcessBuilder build = new ProcessBuilder(command); + build.start(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private String[] getBuildServerClasspath() { + return new String[]{Paths.get(bundleDirectory, "server.jar").toString(), + Paths.get(bundleDirectory, "runtime").toString() + File.separatorChar + "*"}; + } + + private String getBuildServerPluginPath() { + return Paths.get(bundleDirectory, "plugins").toString(); + } + + private String getClasspathSeparator() { + String os = System.getProperty("os.name").toLowerCase(); + + if (os.contains("win")) { + return ";"; + } + return ":"; + } +} diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java index 9b897e7dc..46f05f356 100644 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java @@ -25,7 +25,7 @@ public GradleServer(ServerBuilder serverBuilder, int port) { @SuppressWarnings("java:S106") public void start() throws IOException { server.start(); - logger.info("Server started, listening on {}", port); + logger.info("Gradle Server started, listening on {}", port); Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { @@ -54,12 +54,29 @@ private void blockUntilShutdown() throws InterruptedException { } public static void main(String[] args) throws Exception { - int port = 8887; - if (args.length > 0) { - port = Integer.parseInt(args[0]); - } - GradleServer server = new GradleServer(port); - server.start(); - server.blockUntilShutdown(); + int gradleServerPort = Integer.parseInt(args[0]); + String buildServerPipeName = args[1]; + String bundleDirectory = args[2]; + String javaExecutablePath = args[3]; + + GradleServer server = new GradleServer(gradleServerPort); + Thread gradleServerThread = new Thread(() -> { + try { + server.start(); + server.blockUntilShutdown(); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + }); + + BuildServerThread buildServerConnectionThread = new BuildServerThread(buildServerPipeName, bundleDirectory, + javaExecutablePath); + Thread buildServerThread = new Thread(buildServerConnectionThread); + + gradleServerThread.start(); + buildServerThread.start(); + + gradleServerThread.join(); + buildServerThread.join(); } } diff --git a/npm-package/package-lock.json b/npm-package/package-lock.json index 3526df60d..b86213232 100644 --- a/npm-package/package-lock.json +++ b/npm-package/package-lock.json @@ -326,12 +326,23 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" + }, + "dependencies": { + "fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + } } }, "callsites": { @@ -669,15 +680,6 @@ "flat-cache": "^3.0.4" } }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, "flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -1004,9 +1006,9 @@ } }, "protobufjs": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz", - "integrity": "sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==", + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.6.tgz", + "integrity": "sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw==", "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", From bbe05ef747a6869b25ac9bf9278c57fcdfd0e7f7 Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Mon, 1 Jul 2024 17:39:48 +0800 Subject: [PATCH 02/31] merge server --- .../bs/importer/ImporterNamedPipeStream.java | 46 +++----- .../gradle/bs/importer/ImporterPlugin.java | 45 ++------ extension/src/Extension.ts | 9 +- extension/src/bs/ForwardingAgent.ts | 105 ------------------ extension/src/bs/ImporterHandler.ts | 40 ++----- extension/src/constant.ts | 2 +- extension/src/server/GradleServer.ts | 4 +- extension/src/util/config.ts | 10 +- extension/src/util/generateRandomPipeName.ts | 11 +- gradle-server/build.gradle | 1 - 10 files changed, 56 insertions(+), 217 deletions(-) delete mode 100644 extension/src/bs/ForwardingAgent.ts diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterNamedPipeStream.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterNamedPipeStream.java index 7c7c473fe..0018bee6b 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterNamedPipeStream.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterNamedPipeStream.java @@ -1,15 +1,3 @@ -/******************************************************************************* - * Copyright (c) 2016-2017 Red Hat Inc. and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat Inc. - initial API and implementation - *******************************************************************************/ package com.microsoft.gradle.bs.importer; import java.io.File; @@ -71,30 +59,26 @@ public OutputStream getOutputStream() throws IOException { private void initializeNamedPipe() { String pathName = generateRandomPipeName("importer"); sendImporterPipeName(pathName); + File pipeFile = new File(pathName); - if (isWindows()) { - pipeFile = new File(pathName); - AsynchronousFileChannel channel = null; - try { - channel = AsynchronousFileChannel.open(pipeFile.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE); - input = new NamedPipeInputStream(channel); - output = new NamedPipeOutputStream(channel); - } catch (IOException e) { - e.printStackTrace(); - } - return; - } // Need to retry until the pipeName was sent and pipe is created by Extension side boolean connected = false; while (!connected) { - try { - UnixDomainSocketAddress socketAddress = UnixDomainSocketAddress.of(pipeFile.toPath()); - SocketChannel channel = SocketChannel.open(StandardProtocolFamily.UNIX); - channel.connect(socketAddress); - input = new NamedPipeInputStream(channel); - output = new NamedPipeOutputStream(channel); - connected = true; + try{ + if (isWindows()) { + AsynchronousFileChannel channel = AsynchronousFileChannel.open(pipeFile.toPath(), + StandardOpenOption.READ, StandardOpenOption.WRITE); + input = new NamedPipeInputStream(channel); + output = new NamedPipeOutputStream(channel); + } else { + UnixDomainSocketAddress socketAddress = UnixDomainSocketAddress.of(pipeFile.toPath()); + SocketChannel channel = SocketChannel.open(StandardProtocolFamily.UNIX); + channel.connect(socketAddress); + input = new NamedPipeInputStream(channel); + output = new NamedPipeOutputStream(channel); + } + connected = true; } catch (IOException e) { try { Thread.sleep(1000); diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java index 8fedc027a..35f83be94 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java @@ -91,41 +91,20 @@ public static BuildServerConnection getBuildServerConnection(IPath rootPath, boo if (pair != null) { return pair.getLeft(); } - - if (!createIfMissing) { + if (!createIfMissing) { return null; } - - String javaExecutablePath = getJavaExecutablePath(); - String[] classpaths = getBuildServerClasspath(); - - String pluginPath = getBuildServerPluginPath(); - - List command = new ArrayList<>(); - command.add(javaExecutablePath); - if (Boolean.parseBoolean(System.getenv("DEBUG_GRADLE_BUILD_SERVER"))) { - command.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8989"); - } - command.add("--add-opens=java.base/java.lang=ALL-UNNAMED"); - command.add("--add-opens=java.base/java.io=ALL-UNNAMED"); - command.add("--add-opens=java.base/java.util=ALL-UNNAMED"); - command.add("-Dplugin.dir=" + pluginPath); - command.add("-cp"); - command.add(String.join(getClasspathSeparator(), classpaths)); - command.add("com.microsoft.java.bs.core.Launcher"); - - ProcessBuilder build = new ProcessBuilder(command); - try { - Process process = build.start(); - BuildClient client = new GradleBuildClient(); - Launcher launcher = new Launcher.Builder() - .setOutput(process.getOutputStream()) - .setInput(process.getInputStream()) - .setLocalService(client) - .setExecutorService(Executors.newCachedThreadPool()) - .setRemoteInterface(BuildServerConnection.class) - .create(); - + try { + ImporterNamedPipeStream pipeStream = new ImporterNamedPipeStream(bundleDirectory); + + GradleBuildClient client = new GradleBuildClient(); + Launcher launcher = new Launcher.Builder() + .setOutput(pipeStream.getOutputStream()) + .setInput(pipeStream.getInputStream()) + .setLocalService(client) + .setExecutorService(Executors.newCachedThreadPool()) + .setRemoteInterface(BuildServerConnection.class) + .create(); launcher.startListening(); BuildServerConnection server = launcher.getRemoteProxy(); client.onConnectWithServer(server); diff --git a/extension/src/Extension.ts b/extension/src/Extension.ts index 5f069af67..012144385 100644 --- a/extension/src/Extension.ts +++ b/extension/src/Extension.ts @@ -31,7 +31,7 @@ import { GRADLE_COMPLETION, GRADLE_PROPERTIES_FILE_CHANGE, VSCODE_TRIGGER_COMPLETION, - GET_EXTENSION_PATH + GET_EXTENSION_PATH, } from "./constant"; import { instrumentOperation, sendInfo } from "vscode-extension-telemetry-wrapper"; import { GradleBuildContentProvider } from "./client/GradleBuildContentProvider"; @@ -91,10 +91,9 @@ export class Extension { }) ); - //this.context.subscriptions.push(new vscode.Disposable(() => this.forwardingAgent.dispose())); - this.buildServerHandler = new BuildServerHandler(serverLogger); + this.buildServerHandler = new BuildServerHandler(); this.context.subscriptions.push(new vscode.Disposable(() => this.buildServerHandler.dispose())); - this.importerHandler = new ImporterHandler(this.context, logger); + this.importerHandler = new ImporterHandler(this.context); this.context.subscriptions.push(new vscode.Disposable(() => this.importerHandler.dispose())); this.server = new GradleServer({ host: "localhost" }, context, serverLogger, this.buildServerHandler); @@ -247,7 +246,7 @@ export class Extension { await this.server.start(); } await this.importerHandler.waitForImporterPipePath(); - this.importerHandler.setupImporterHandler(this.buildServerHandler.buildServerConnection!); + this.importerHandler.setupImporterHandler(this.buildServerHandler.getBuildServerConnection()); await vscode.commands.executeCommand("setContext", "gradle:activated", activated); await vscode.commands.executeCommand("setContext", "gradle:defaultView", true); } diff --git a/extension/src/bs/ForwardingAgent.ts b/extension/src/bs/ForwardingAgent.ts deleted file mode 100644 index 792c405a5..000000000 --- a/extension/src/bs/ForwardingAgent.ts +++ /dev/null @@ -1,105 +0,0 @@ -import * as net from 'net'; -import { Logger } from "../logger/index"; -import * as rpc from 'vscode-jsonrpc/node'; -import * as fs from 'fs'; -import * as vscode from 'vscode'; -import * as path from 'path'; -import { generateRandomPipeName } from '../util/generateRandomPipeName'; -export const GET_IMPORTER_PIPE_NAME = "gradle.getImporterPipeName"; - -export class ForwardingAgent implements vscode.Disposable { - private importerHandler: net.Server; - private buildServerHandler: net.Server; - private IMPORTER_PIPE_PATH: string = ""; - private SERVER_PIPE_PATH: string = ""; - public importerConnection: rpc.MessageConnection | null = null; - public buildServerConnection: rpc.MessageConnection | null = null; - - constructor(private readonly context: vscode.ExtensionContext, - private readonly logger: Logger) {} - - public async start(): Promise { - this.SERVER_PIPE_PATH = await generateRandomPipeName('server'); - this.setupBuildServerHandler(); - await this.waitForImporterPipePath(); - this.setupImporterHandler(); - } - - private setupBuildServerHandler(): void { - this.buildServerHandler = net.createServer((socket: net.Socket) => { - this.buildServerConnection = rpc.createMessageConnection( - new rpc.StreamMessageReader(socket), - new rpc.StreamMessageWriter(socket) - ); - this.buildServerConnection.listen(); - }); - this.buildServerHandler.listen(this.SERVER_PIPE_PATH); - } - - private async waitForImporterPipePath(): Promise { - return new Promise((resolve) => { - this.context.subscriptions.push( - vscode.commands.registerCommand(GET_IMPORTER_PIPE_NAME, (pipeName: string) => { - this.IMPORTER_PIPE_PATH = path.resolve(pipeName); - this.logger.info("Received Importer PipeName from Java: ", this.IMPORTER_PIPE_PATH); - resolve(); - }) - ); - }); - } - - private setupImporterHandler(): void { - this.importerHandler = net.createServer((socket: net.Socket) => { - this.importerConnection = rpc.createMessageConnection( - new rpc.StreamMessageReader(socket), - new rpc.StreamMessageWriter(socket) - ); - this.importerConnection.listen(); - this.forwardMessages(); - }); - this.importerHandler.listen(this.IMPORTER_PIPE_PATH); - this.logger.info("! Importer Handler is listening on: ", this.IMPORTER_PIPE_PATH); - } - - private forwardMessages(): void { - this.importerConnection?.onRequest((method, params) => { - if (this.buildServerConnection) { - return this.buildServerConnection.sendRequest(method, params); - } - throw new Error('Build server connection is not available.'); - }); - - this.importerConnection?.onNotification((method, params) => { - this.buildServerConnection?.sendNotification(method, params); - }); - - this.buildServerConnection?.onRequest((method, params) => { - if (this.importerConnection) { - return this.importerConnection.sendRequest(method, params); - } - throw new Error('Build server connection is not available.'); - }); - - this.buildServerConnection?.onNotification((method, params) => { - this.importerConnection?.sendNotification(method, params); - }); - } - - public getBuildServerPipeName(): string { - return this.SERVER_PIPE_PATH; - } - - public dispose(): void { - try { - if (fs.existsSync(this.SERVER_PIPE_PATH)) { - fs.unlinkSync(this.SERVER_PIPE_PATH); - } - if (fs.existsSync(this.IMPORTER_PIPE_PATH)) { - fs.unlinkSync(this.IMPORTER_PIPE_PATH); - } - } catch (err) { - this.logger.error('Error cleaning up pipes:', err); - } - } - -} diff --git a/extension/src/bs/ImporterHandler.ts b/extension/src/bs/ImporterHandler.ts index 79d9b00a4..0a3d01f01 100644 --- a/extension/src/bs/ImporterHandler.ts +++ b/extension/src/bs/ImporterHandler.ts @@ -1,9 +1,8 @@ -import * as net from 'net'; -import * as rpc from 'vscode-jsonrpc/node'; -import { Logger } from "../logger/index"; -import * as fs from 'fs'; -import * as vscode from 'vscode'; -import * as path from 'path'; +import * as net from "net"; +import * as rpc from "vscode-jsonrpc/node"; +import * as fs from "fs"; +import * as vscode from "vscode"; +import * as path from "path"; export const GET_IMPORTER_PIPE_NAME = "gradle.getImporterPipeName"; @@ -11,51 +10,36 @@ export class ImporterHandler implements vscode.Disposable { public importerConnection: rpc.MessageConnection | null = null; private importerPipeServer: net.Server; private importerPipePath: string; - constructor(private readonly context: vscode.ExtensionContext, - private readonly logger: Logger) {} + constructor(private readonly context: vscode.ExtensionContext) {} public async waitForImporterPipePath(): Promise { return new Promise((resolve) => { this.context.subscriptions.push( vscode.commands.registerCommand(GET_IMPORTER_PIPE_NAME, (pipeName: string) => { this.importerPipePath = path.resolve(pipeName); - this.logger.info("Received Importer PipeName from Java: ", this.importerPipePath); resolve(); }) ); }); } - public setupImporterHandler(buildServerConnection: rpc.MessageConnection): void { + public setupImporterHandler(buildServerConnection: rpc.MessageConnection | null): void { this.importerPipeServer = net.createServer((socket: net.Socket) => { this.importerConnection = rpc.createMessageConnection( new rpc.StreamMessageReader(socket), new rpc.StreamMessageWriter(socket) ); - if (!this.importerConnection) { - this.logger.error("Importer Connection is Null"); - return; - } - if (!buildServerConnection) { - this.logger.error("Build Server Connection is Null"); - return; - } - - this.importerConnection.onRequest((method, params) => { - this.logger.info("importerConnection.onRequest: ", method); - return buildServerConnection.sendRequest(method, params); + this.importerConnection?.onRequest((method, params) => { + return buildServerConnection?.sendRequest(method, params); }); - buildServerConnection.onNotification((method, params) => { - this.logger.info("buildServerConnection.onNotification: ", method); - this.importerConnection!.sendNotification(method, params); + buildServerConnection?.onNotification((method, params) => { + this.importerConnection?.sendNotification(method, params); }); this.importerConnection.listen(); }); - this.importerPipeServer.listen(this.importerPipePath, () => { - this.logger.info("!Importer Pipe Server is listening on: ", this.importerPipePath); - }); + this.importerPipeServer.listen(this.importerPipePath); } public dispose(): void { diff --git a/extension/src/constant.ts b/extension/src/constant.ts index a12c44a4e..664270687 100644 --- a/extension/src/constant.ts +++ b/extension/src/constant.ts @@ -5,7 +5,7 @@ export namespace Context { export const ACTIVATION_CONTEXT_KEY = "gradle:extensionActivated"; } -export const GET_EXTENSION_PATH = "gradle.getExtensionPath" +export const GET_EXTENSION_PATH = "gradle.getExtensionPath"; export const GRADLE_BUILD_FILE_CHANGE = "gradle.buildFileChanged"; diff --git a/extension/src/server/GradleServer.ts b/extension/src/server/GradleServer.ts index c52bbeaf8..f8313b6c7 100644 --- a/extension/src/server/GradleServer.ts +++ b/extension/src/server/GradleServer.ts @@ -48,7 +48,7 @@ export class GradleServer { await vscode.window.showErrorMessage(NO_JAVA_EXECUTABLE); return; } - let javaExecPath = await getRedHatJavaExecutablePath(); + const javaExecPath = await getRedHatJavaExecutablePath(); //Get the Java executable used by JDT.LS, which will be higher than JDK 17. if (!javaExecPath) { @@ -90,7 +90,7 @@ export class GradleServer { public async getBundleDirectory(): Promise { const extensionPath = await vscode.commands.executeCommand(GET_EXTENSION_PATH); - return path.join(extensionPath, 'server'); + return path.join(extensionPath, "server"); } public async showRestartMessage(): Promise { diff --git a/extension/src/util/config.ts b/extension/src/util/config.ts index db25f0653..163f840ab 100644 --- a/extension/src/util/config.ts +++ b/extension/src/util/config.ts @@ -3,8 +3,8 @@ import { getRuntime } from "jdk-utils"; import * as vscode from "vscode"; import { GradleConfig } from "../proto/gradle_pb"; import { RootProject } from "../rootProject/RootProject"; -import * as fs from 'fs'; -import * as path from 'path'; +import * as fs from "fs"; +import * as path from "path"; type AutoDetect = "on" | "off"; @@ -35,19 +35,19 @@ export function getRedHatJavaExecutablePath(): string | null { } const extensionPath = javaExtension.extensionPath; - const jrePath = path.join(extensionPath, 'jre'); + const jrePath = path.join(extensionPath, "jre"); if (!fs.existsSync(jrePath) || !fs.lstatSync(jrePath).isDirectory()) { return null; } // Read the entries in the jre directory and filter out hidden files - const entries = fs.readdirSync(jrePath).filter(entry => !entry.startsWith('.')); + const entries = fs.readdirSync(jrePath).filter((entry) => !entry.startsWith(".")); if (entries.length === 0) { return null; } const entry = entries[0]; - const javaExec = process.platform === "win32" ? 'java.exe' : 'java'; + const javaExec = process.platform === "win32" ? "java.exe" : "java"; const javaExecPath = path.join(jrePath, entry, "bin", javaExec); if (fs.existsSync(javaExecPath)) { return javaExecPath; diff --git a/extension/src/util/generateRandomPipeName.ts b/extension/src/util/generateRandomPipeName.ts index 83079939e..feaf3e40c 100644 --- a/extension/src/util/generateRandomPipeName.ts +++ b/extension/src/util/generateRandomPipeName.ts @@ -1,13 +1,12 @@ -import { randomBytes } from 'crypto'; -import * as vscode from 'vscode'; -import { GET_EXTENSION_PATH } from '../constant'; +import { randomBytes } from "crypto"; +import * as vscode from "vscode"; +import { GET_EXTENSION_PATH } from "../constant"; export async function generateRandomPipeName(type: string): Promise { - const randomSuffix = randomBytes(11).toString('hex'); + const randomSuffix = randomBytes(11).toString("hex"); if (process.platform === "win32") { return `\\\\.\\pipe\\${randomSuffix}-${type}-sock`; } else { - const extensionPath = await vscode.commands.executeCommand(GET_EXTENSION_PATH) + const extensionPath = await vscode.commands.executeCommand(GET_EXTENSION_PATH); return `${extensionPath}/${randomSuffix}-${type}.sock`; } } - diff --git a/gradle-server/build.gradle b/gradle-server/build.gradle index c53fcf701..ba9908c79 100644 --- a/gradle-server/build.gradle +++ b/gradle-server/build.gradle @@ -12,7 +12,6 @@ java { dependencies { implementation project(":gradle-plugin-api") - //implementation "org.eclipse.lsp4j:org.eclipse.lsp4j:0.12.0" implementation 'ch.epfl.scala:bsp4j:2.1.0-M4' implementation "org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.12.0" implementation "org.gradle:gradle-tooling-api:${toolingAPIVersion}" From bfac71734c67d4222c63348a6aa67f233aff36e6 Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Mon, 1 Jul 2024 18:05:16 +0800 Subject: [PATCH 03/31] fix lint --- .../GradleBuildServerProjectImporter.java | 24 ------------------- .../gradle/bs/importer/ImporterPlugin.java | 11 --------- extension/src/Extension.ts | 3 +-- extension/src/commands/Commands.ts | 1 - 4 files changed, 1 insertion(+), 38 deletions(-) diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/GradleBuildServerProjectImporter.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/GradleBuildServerProjectImporter.java index a0d6537ca..5c433ebe9 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/GradleBuildServerProjectImporter.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/GradleBuildServerProjectImporter.java @@ -171,30 +171,6 @@ public void importToWorkspace(IProgressMonitor monitor) throws OperationCanceled InitializeBuildResult initializeResult = buildServer.buildInitialize(params).join(); buildServer.onBuildInitialized(); - - // InitializeBuildResult initializeResult = null; - // boolean success = false; - // int retries = 0; - // int MAX_RETRIES = 10; - // while (!success && retries < MAX_RETRIES) { - // try { - // initializeResult = buildServer.buildInitialize(params).join(); - // success = true; - // } catch (CompletionException e) { - // System.out.println("Waiting for the TypeScript server to be ready..."); - // try { - // Thread.sleep(1000); // 等待一秒 - // } catch (InterruptedException ie) { - // Thread.currentThread().interrupt(); - // throw new RuntimeException("Thread interrupted while waiting to retry initialization", ie); - // } - // } - // retries++; - // } - // if (!success) { - // throw new RuntimeException("Failed to initialize after " + MAX_RETRIES + " attempts."); - // } - // TODO: save the capabilities of this server if (monitor.isCanceled()) { diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java index 7e27643e3..237c9bbfe 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java @@ -85,17 +85,6 @@ public static BuildServerConnection getBuildServerConnection(IPath rootPath) thr return getBuildServerConnection(rootPath, false); } - /** - * Get the build server connection for the given root path. - * @param rootPath the root path of the workspace. - * @param createIfMissing whether to create a new build server connection if it doesn't exist. - * @return the build server connection. - * @throws CoreException - */ - public static BuildServerConnection getBuildServerConnection(IPath rootPath, boolean createIfMissing) throws CoreException { - return getBuildServerConnection(rootPath, false); - } - /** * Get the build server connection for the given root path. * @param rootPath the root path of the workspace. diff --git a/extension/src/Extension.ts b/extension/src/Extension.ts index 012144385..2a3a9961e 100644 --- a/extension/src/Extension.ts +++ b/extension/src/Extension.ts @@ -170,7 +170,7 @@ export class Extension { this.rootProjectsStore, this.taskTerminalsStore, this.recentTasksStore, - this.gradleTasksTreeView, + this.gradleTasksTreeView ); this.buildServerController = new BuildServerController(context); @@ -371,5 +371,4 @@ export class Extension { public getApi(): Api { return this.api; } - } diff --git a/extension/src/commands/Commands.ts b/extension/src/commands/Commands.ts index 706591261..419d76262 100644 --- a/extension/src/commands/Commands.ts +++ b/extension/src/commands/Commands.ts @@ -193,4 +193,3 @@ export class Commands { } } } - From b1e29625490a5007d51e4c546da20a91e1ca9647 Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Tue, 2 Jul 2024 12:14:54 +0800 Subject: [PATCH 04/31] fix generateRandomPipeName --- .../GradleBuildServerProjectImporter.java | 2 - .../gradle/bs/importer/ImporterPlugin.java | 10 +--- ...edPipeStream.java => NamedPipeStream.java} | 46 +++++++++---------- extension/src/Extension.ts | 4 +- extension/src/bs/BuildServerHandler.ts | 13 ++---- extension/src/bs/ImporterHandler.ts | 10 +--- extension/src/server/GradleServer.ts | 2 +- extension/src/util/generateRandomPipeName.ts | 34 ++++++++++---- .../badsyntax/gradle/BuildServerThread.java | 2 +- 9 files changed, 56 insertions(+), 67 deletions(-) rename extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/{ImporterNamedPipeStream.java => NamedPipeStream.java} (85%) diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/GradleBuildServerProjectImporter.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/GradleBuildServerProjectImporter.java index 5c433ebe9..5ae484d85 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/GradleBuildServerProjectImporter.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/GradleBuildServerProjectImporter.java @@ -8,7 +8,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.concurrent.CompletionException; import org.eclipse.core.internal.resources.Project; import org.eclipse.core.internal.resources.ProjectDescription; @@ -168,7 +167,6 @@ public void importToWorkspace(IProgressMonitor monitor) throws OperationCanceled ); BuildServerPreferences data = getBuildServerPreferences(); params.setData(data); - InitializeBuildResult initializeResult = buildServer.buildInitialize(params).join(); buildServer.onBuildInitialized(); // TODO: save the capabilities of this server diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java index 237c9bbfe..0689fbc6d 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java @@ -35,7 +35,6 @@ public class ImporterPlugin extends Plugin { private static String bundleVersion = ""; - private static String bundleDirectory; @Override public void start(BundleContext context) throws Exception { BuildStateManager.getBuildStateManager().startup(); @@ -46,7 +45,6 @@ public void start(BundleContext context) throws Exception { if (!bundleFile.isPresent()) { throw new IllegalStateException("Failed to get bundle location."); } - bundleDirectory = bundleFile.get().getParent(); } @Override @@ -69,12 +67,6 @@ public static DigestStore getDigestStore() { return instance.digestStore; } - /** - * Get the build server connection for the given root path. If the connection doesn't exist, - * returns null. - * @param rootPath - * @throws CoreException - */ /** * Get the build server connection for the given root path. If the connection doesn't exist, * returns null. @@ -101,7 +93,7 @@ public static BuildServerConnection getBuildServerConnection(IPath rootPath, boo return null; } try { - ImporterNamedPipeStream pipeStream = new ImporterNamedPipeStream(bundleDirectory); + NamedPipeStream pipeStream = new NamedPipeStream(); GradleBuildClient client = new GradleBuildClient(); Launcher launcher = new Launcher.Builder() diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterNamedPipeStream.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java similarity index 85% rename from extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterNamedPipeStream.java rename to extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java index 0018bee6b..470c130cc 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterNamedPipeStream.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java @@ -13,6 +13,7 @@ import java.nio.channels.WritableByteChannel; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.security.SecureRandom; @@ -20,14 +21,9 @@ import org.eclipse.core.runtime.Platform; /** - * A factory for creating the streams for supported transmission methods. - * - * @author Gorkem Ercan - * + * A class to create a named pipe stream for the importer to communicate with the extension. */ - -public class ImporterNamedPipeStream { - private String bundleDir; +public class NamedPipeStream { private StreamProvider provider; @@ -57,7 +53,7 @@ public OutputStream getOutputStream() throws IOException { } private void initializeNamedPipe() { - String pathName = generateRandomPipeName("importer"); + String pathName = generateRandomPipeName(); sendImporterPipeName(pathName); File pipeFile = new File(pathName); @@ -187,9 +183,7 @@ public void write(byte[] b) throws IOException { } } - public ImporterNamedPipeStream(String bundleDir) { - this.bundleDir = bundleDir; - } + public NamedPipeStream() {} public StreamProvider getSelectedStream() { if (provider == null) { @@ -220,28 +214,32 @@ private void sendImporterPipeName(String pipeName){ .sendNotification("gradle.getImporterPipeName", pipeName); } - public String generateRandomPipeName(String type) { + public static String generateRandomHex(int numBytes) { SecureRandom random = new SecureRandom(); - byte[] bytes = new byte[11]; + byte[] bytes = new byte[numBytes]; random.nextBytes(bytes); StringBuilder hexString = new StringBuilder(); for (byte b : bytes) { hexString.append(String.format("%02x", b)); } - String randomSuffix = hexString.toString(); - String path; + return hexString.toString(); + } + + public String generateRandomPipeName() { if (System.getProperty("os.name").startsWith("Windows")) { - path = "\\\\.\\pipe\\" + randomSuffix + "-" + type + "-sock"; - } else { - //save pipe file to extension folder - path = new File(this.bundleDir).getParent() +'/'+ randomSuffix + "-" + type + ".sock"; + return Paths.get("\\\\.\\pipe\\", generateRandomHex(16) + "-sock").toString(); } + String tmpdir = System.getProperty("java.io.tmpdir"); + + int fixedLength = ".sock".length(); + int safeIpcPathLengths = 103; + int availableLength = safeIpcPathLengths - fixedLength - tmpdir.length(); + int randomLength = 32; + int bytesLength = Math.min(availableLength / 2, randomLength); - try { - return new File(path).getCanonicalPath(); - } catch (IOException e) { - System.err.println("Error generating the canonical pipe path: " + e.getMessage()); - return null; + if (bytesLength < 16) { + throw new IllegalArgumentException("Unable to generate a random pipe name with character length less than 16"); } + return Paths.get(tmpdir, generateRandomHex(bytesLength) + ".sock").toString(); } } diff --git a/extension/src/Extension.ts b/extension/src/Extension.ts index 2a3a9961e..56383d2b0 100644 --- a/extension/src/Extension.ts +++ b/extension/src/Extension.ts @@ -92,9 +92,7 @@ export class Extension { ); this.buildServerHandler = new BuildServerHandler(); - this.context.subscriptions.push(new vscode.Disposable(() => this.buildServerHandler.dispose())); this.importerHandler = new ImporterHandler(this.context); - this.context.subscriptions.push(new vscode.Disposable(() => this.importerHandler.dispose())); this.server = new GradleServer({ host: "localhost" }, context, serverLogger, this.buildServerHandler); this.client = new GradleClient(this.server, statusBarItem, clientLogger); @@ -241,7 +239,7 @@ export class Extension { private async activate(): Promise { const activated = !!(await this.rootProjectsStore.getProjectRoots()).length; - await this.buildServerHandler.setupBuildServerHandler(); + this.buildServerHandler.setupBuildServerHandler(); if (!this.server.isReady()) { await this.server.start(); } diff --git a/extension/src/bs/BuildServerHandler.ts b/extension/src/bs/BuildServerHandler.ts index 256445188..b094336ba 100644 --- a/extension/src/bs/BuildServerHandler.ts +++ b/extension/src/bs/BuildServerHandler.ts @@ -1,16 +1,14 @@ import * as net from "net"; import * as rpc from "vscode-jsonrpc/node"; -import * as vscode from "vscode"; -import * as fs from "fs"; import { generateRandomPipeName } from "../util/generateRandomPipeName"; -export class BuildServerHandler implements vscode.Disposable { +export class BuildServerHandler { private buildServerConnection: rpc.MessageConnection | null = null; private buildServerPipeServer: net.Server; private serverPipePath: string; - public async setupBuildServerHandler(): Promise { - this.serverPipePath = await generateRandomPipeName("server"); + public setupBuildServerHandler(): void { + this.serverPipePath = generateRandomPipeName(); this.buildServerPipeServer = net.createServer((socket: net.Socket) => { this.buildServerConnection = rpc.createMessageConnection( new rpc.StreamMessageReader(socket), @@ -27,9 +25,4 @@ export class BuildServerHandler implements vscode.Disposable { public getBuildServerConnection(): rpc.MessageConnection | null { return this.buildServerConnection; } - public dispose(): void { - if (fs.existsSync(this.serverPipePath)) { - fs.unlinkSync(this.serverPipePath); - } - } } diff --git a/extension/src/bs/ImporterHandler.ts b/extension/src/bs/ImporterHandler.ts index 0a3d01f01..21ff4d346 100644 --- a/extension/src/bs/ImporterHandler.ts +++ b/extension/src/bs/ImporterHandler.ts @@ -1,12 +1,11 @@ import * as net from "net"; import * as rpc from "vscode-jsonrpc/node"; -import * as fs from "fs"; import * as vscode from "vscode"; import * as path from "path"; export const GET_IMPORTER_PIPE_NAME = "gradle.getImporterPipeName"; -export class ImporterHandler implements vscode.Disposable { +export class ImporterHandler { public importerConnection: rpc.MessageConnection | null = null; private importerPipeServer: net.Server; private importerPipePath: string; @@ -38,13 +37,6 @@ export class ImporterHandler implements vscode.Disposable { this.importerConnection.listen(); }); - this.importerPipeServer.listen(this.importerPipePath); } - - public dispose(): void { - if (fs.existsSync(this.importerPipePath)) { - fs.unlinkSync(this.importerPipePath); - } - } } diff --git a/extension/src/server/GradleServer.ts b/extension/src/server/GradleServer.ts index f8313b6c7..e8d94e0d0 100644 --- a/extension/src/server/GradleServer.ts +++ b/extension/src/server/GradleServer.ts @@ -48,7 +48,7 @@ export class GradleServer { await vscode.window.showErrorMessage(NO_JAVA_EXECUTABLE); return; } - const javaExecPath = await getRedHatJavaExecutablePath(); + const javaExecPath = getRedHatJavaExecutablePath(); //Get the Java executable used by JDT.LS, which will be higher than JDK 17. if (!javaExecPath) { diff --git a/extension/src/util/generateRandomPipeName.ts b/extension/src/util/generateRandomPipeName.ts index feaf3e40c..c764900b8 100644 --- a/extension/src/util/generateRandomPipeName.ts +++ b/extension/src/util/generateRandomPipeName.ts @@ -1,12 +1,30 @@ import { randomBytes } from "crypto"; -import * as vscode from "vscode"; -import { GET_EXTENSION_PATH } from "../constant"; -export async function generateRandomPipeName(type: string): Promise { - const randomSuffix = randomBytes(11).toString("hex"); +import * as os from "os"; +import * as path from "path"; +import * as fs from "fs"; + +const XDG_RUNTIME_DIR = process.env["XDG_RUNTIME_DIR"]; +const safeIpcPathLengths: Map = new Map([ + ["linux", 107], + ["darwin", 103], +]); + +export function generateRandomPipeName(): string { if (process.platform === "win32") { - return `\\\\.\\pipe\\${randomSuffix}-${type}-sock`; - } else { - const extensionPath = await vscode.commands.executeCommand(GET_EXTENSION_PATH); - return `${extensionPath}/${randomSuffix}-${type}.sock`; + return `\\\\.\\pipe\\${randomBytes(16).toString("hex")}-sock`; } + + let randomLength = 32; + const fixedLength = ".sock".length; + const tmpDir: string = fs.realpathSync(XDG_RUNTIME_DIR ?? os.tmpdir()); + const limit = safeIpcPathLengths.get(process.platform); + if (limit !== undefined) { + randomLength = Math.min(limit - tmpDir.length - fixedLength, randomLength); + } + if (randomLength < 16) { + throw new Error(`Unable to generate a random pipe name with ${randomLength} characters.`); + } + + const randomSuffix = randomBytes(Math.floor(randomLength / 2)).toString("hex"); + return path.join(tmpDir, `${randomSuffix}.sock`); } diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/BuildServerThread.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/BuildServerThread.java index c1b3709b4..e3fced378 100644 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/BuildServerThread.java +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/BuildServerThread.java @@ -37,7 +37,7 @@ public void run() { command.add("-cp"); command.add(String.join(getClasspathSeparator(), classpaths)); command.add("com.microsoft.java.bs.core.Launcher"); - command.add(this.pipeName); + command.add("--pipe=" + this.pipeName); ProcessBuilder build = new ProcessBuilder(command); build.start(); From 42f3a9f790b9ffdad85542ec18957158e8bd35ea Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Tue, 2 Jul 2024 15:31:33 +0800 Subject: [PATCH 05/31] refine named pipe repost design --- .../gradle/bs/importer/ImporterPlugin.java | 47 +-- .../gradle/bs/importer/NamedPipeStream.java | 358 +++++++++--------- extension/src/Extension.ts | 16 +- extension/src/bs/BuildServerHandler.ts | 28 -- extension/src/bs/ImporterHandler.ts | 42 -- extension/src/bs/MessageForwardHandler.ts | 75 ++++ extension/src/server/GradleServer.ts | 6 +- extension/src/util/generateRandomPipeName.ts | 1 + 8 files changed, 288 insertions(+), 285 deletions(-) delete mode 100644 extension/src/bs/BuildServerHandler.ts delete mode 100644 extension/src/bs/ImporterHandler.ts create mode 100644 extension/src/bs/MessageForwardHandler.ts diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java index 0689fbc6d..2cd7b70f5 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java @@ -1,6 +1,7 @@ package com.microsoft.gradle.bs.importer; import java.io.File; +import java.io.IOException; import java.util.Map; import java.util.Optional; @@ -89,29 +90,29 @@ public static BuildServerConnection getBuildServerConnection(IPath rootPath, boo if (pair != null) { return pair.getLeft(); } - if (!createIfMissing) { + if (!createIfMissing) { return null; } - try { - NamedPipeStream pipeStream = new NamedPipeStream(); - - GradleBuildClient client = new GradleBuildClient(); - Launcher launcher = new Launcher.Builder() - .setOutput(pipeStream.getOutputStream()) - .setInput(pipeStream.getInputStream()) - .setLocalService(client) - .setExecutorService(Executors.newCachedThreadPool()) - .setRemoteInterface(BuildServerConnection.class) - .create(); - launcher.startListening(); - BuildServerConnection server = launcher.getRemoteProxy(); - client.onConnectWithServer(server); - - instance.buildServers.put(rootPath, Pair.of(server, client)); - return server; - } catch (Exception e) { - e.printStackTrace(); - throw new CoreException(new Status(IStatus.ERROR, PLUGIN_ID, "Failed to start build server.", e)); - } - } + try { + NamedPipeStream pipeStream = new NamedPipeStream(); + + GradleBuildClient client = new GradleBuildClient(); + Launcher launcher = new Launcher.Builder() + .setOutput(pipeStream.getOutputStream()) + .setInput(pipeStream.getInputStream()) + .setLocalService(client) + .setExecutorService(Executors.newCachedThreadPool()) + .setRemoteInterface(BuildServerConnection.class) + .create(); + launcher.startListening(); + BuildServerConnection server = launcher.getRemoteProxy(); + client.onConnectWithServer(server); + + instance.buildServers.put(rootPath, Pair.of(server, client)); + return server; + } catch (IOException e) { + throw new CoreException(new Status(IStatus.ERROR, PLUGIN_ID, + "Failed to start build server.", e)); + } + } } diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java index 470c130cc..741684923 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java @@ -25,44 +25,44 @@ */ public class NamedPipeStream { - private StreamProvider provider; + private StreamProvider provider; - interface StreamProvider { - InputStream getInputStream() throws IOException; + interface StreamProvider { + InputStream getInputStream() throws IOException; - OutputStream getOutputStream() throws IOException; - } + OutputStream getOutputStream() throws IOException; + } - protected final class PipeStreamProvider implements StreamProvider { + protected final class PipeStreamProvider implements StreamProvider { - private InputStream input; - private OutputStream output; + private InputStream input; + private OutputStream output; - public PipeStreamProvider() { - initializeNamedPipe(); - } + public PipeStreamProvider() { + initializeNamedPipe(); + } - @Override - public InputStream getInputStream() throws IOException { - return input; - } + @Override + public InputStream getInputStream() throws IOException { + return input; + } - @Override - public OutputStream getOutputStream() throws IOException { - return output; - } + @Override + public OutputStream getOutputStream() throws IOException { + return output; + } - private void initializeNamedPipe() { - String pathName = generateRandomPipeName(); - sendImporterPipeName(pathName); + private void initializeNamedPipe() { + String pathName = generateRandomPipeName(); + sendImporterPipeName(pathName); - File pipeFile = new File(pathName); + File pipeFile = new File(pathName); - // Need to retry until the pipeName was sent and pipe is created by Extension side - boolean connected = false; - while (!connected) { - try{ - if (isWindows()) { + // Need to retry until the pipeName was sent and pipe is created by Extension side + boolean connected = false; + while (!connected) { + try{ + if (isWindows()) { AsynchronousFileChannel channel = AsynchronousFileChannel.open(pipeFile.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE); input = new NamedPipeInputStream(channel); @@ -75,146 +75,146 @@ private void initializeNamedPipe() { output = new NamedPipeOutputStream(channel); } connected = true; - } catch (IOException e) { - try { - Thread.sleep(1000); - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - throw new RuntimeException("Thread interrupted while trying to connect to named pipe", ie); - } - } - } - } - } - - public class NamedPipeInputStream extends InputStream { - - private ReadableByteChannel unixChannel; - private AsynchronousFileChannel winChannel; - private ByteBuffer buffer = ByteBuffer.allocate(1024); - private int readyBytes = 0; - - public NamedPipeInputStream(ReadableByteChannel channel) { - this.unixChannel = channel; - } - - public NamedPipeInputStream(AsynchronousFileChannel channel) { - this.winChannel = channel; - } - - @Override - public int read() throws IOException { - if (buffer.position() < readyBytes) { - return buffer.get() & 0xFF; - } - try { - buffer.clear(); - if (winChannel != null) { - readyBytes = winChannel.read(buffer, 0).get(); - } else { - readyBytes = unixChannel.read(buffer); - } - if (readyBytes == -1) { - return -1; // EOF - } - buffer.flip(); - return buffer.get() & 0xFF; - } catch (InterruptedException | ExecutionException e) { - throw new IOException(e); - } - } - } - - public class NamedPipeOutputStream extends OutputStream { - - private WritableByteChannel unixChannel; - private AsynchronousFileChannel winChannel; - private ByteBuffer buffer = ByteBuffer.allocate(1); - - public NamedPipeOutputStream(WritableByteChannel channel) { - this.unixChannel = channel; - } - - public NamedPipeOutputStream(AsynchronousFileChannel channel) { - this.winChannel = channel; - } - - @Override - public void write(int b) throws IOException { - buffer.clear(); - buffer.put((byte) b); - buffer.position(0); - if (winChannel != null) { - Future result = winChannel.write(buffer, 0); - try { - result.get(); - } catch (Exception e) { - throw new IOException(e); - } - } else { - unixChannel.write(buffer); - } - } - - @Override - public void write(byte[] b) throws IOException { - final int BUFFER_SIZE = 1024; - int blocks = b.length / BUFFER_SIZE; - int writeBytes = 0; - for (int i = 0; i <= blocks; i++) { - int offset = i * BUFFER_SIZE; - int length = Math.min(b.length - writeBytes, BUFFER_SIZE); - if (length <= 0) { - break; - } - writeBytes += length; - ByteBuffer buffer = ByteBuffer.wrap(b, offset, length); - if (winChannel != null) { - Future result = winChannel.write(buffer, 0); - try { - result.get(); - } catch (Exception e) { - throw new IOException(e); - } - } else { - unixChannel.write(buffer); - } - } - } - } - - public NamedPipeStream() {} - - public StreamProvider getSelectedStream() { - if (provider == null) { - provider = createProvider() ; - } - return provider; - } - - private StreamProvider createProvider() { - - return new PipeStreamProvider(); - } - - public InputStream getInputStream() throws IOException { - return getSelectedStream().getInputStream(); - } - - public OutputStream getOutputStream() throws IOException { - return getSelectedStream().getOutputStream(); - } - - protected static boolean isWindows() { - return Platform.OS_WIN32.equals(Platform.getOS()); - } - - private void sendImporterPipeName(String pipeName){ - JavaLanguageServerPlugin.getInstance().getClientConnection() - .sendNotification("gradle.getImporterPipeName", pipeName); - } - - public static String generateRandomHex(int numBytes) { + } catch (IOException e) { + try { + Thread.sleep(1000); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Thread interrupted while trying to connect to named pipe", ie); + } + } + } + } + } + + public class NamedPipeInputStream extends InputStream { + + private ReadableByteChannel unixChannel; + private AsynchronousFileChannel winChannel; + private ByteBuffer buffer = ByteBuffer.allocate(1024); + private int readyBytes = 0; + + public NamedPipeInputStream(ReadableByteChannel channel) { + this.unixChannel = channel; + } + + public NamedPipeInputStream(AsynchronousFileChannel channel) { + this.winChannel = channel; + } + + @Override + public int read() throws IOException { + if (buffer.position() < readyBytes) { + return buffer.get() & 0xFF; + } + try { + buffer.clear(); + if (winChannel != null) { + readyBytes = winChannel.read(buffer, 0).get(); + } else { + readyBytes = unixChannel.read(buffer); + } + if (readyBytes == -1) { + return -1; // EOF + } + buffer.flip(); + return buffer.get() & 0xFF; + } catch (InterruptedException | ExecutionException e) { + throw new IOException(e); + } + } + } + + public class NamedPipeOutputStream extends OutputStream { + + private WritableByteChannel unixChannel; + private AsynchronousFileChannel winChannel; + private ByteBuffer buffer = ByteBuffer.allocate(1); + + public NamedPipeOutputStream(WritableByteChannel channel) { + this.unixChannel = channel; + } + + public NamedPipeOutputStream(AsynchronousFileChannel channel) { + this.winChannel = channel; + } + + @Override + public void write(int b) throws IOException { + buffer.clear(); + buffer.put((byte) b); + buffer.position(0); + if (winChannel != null) { + Future result = winChannel.write(buffer, 0); + try { + result.get(); + } catch (Exception e) { + throw new IOException(e); + } + } else { + unixChannel.write(buffer); + } + } + + @Override + public void write(byte[] b) throws IOException { + final int BUFFER_SIZE = 1024; + int blocks = b.length / BUFFER_SIZE; + int writeBytes = 0; + for (int i = 0; i <= blocks; i++) { + int offset = i * BUFFER_SIZE; + int length = Math.min(b.length - writeBytes, BUFFER_SIZE); + if (length <= 0) { + break; + } + writeBytes += length; + ByteBuffer buffer = ByteBuffer.wrap(b, offset, length); + if (winChannel != null) { + Future result = winChannel.write(buffer, 0); + try { + result.get(); + } catch (Exception e) { + throw new IOException(e); + } + } else { + unixChannel.write(buffer); + } + } + } + } + + public NamedPipeStream() {} + + public StreamProvider getSelectedStream() { + if (provider == null) { + provider = createProvider() ; + } + return provider; + } + + private StreamProvider createProvider() { + + return new PipeStreamProvider(); + } + + public InputStream getInputStream() throws IOException { + return getSelectedStream().getInputStream(); + } + + public OutputStream getOutputStream() throws IOException { + return getSelectedStream().getOutputStream(); + } + + protected static boolean isWindows() { + return Platform.OS_WIN32.equals(Platform.getOS()); + } + + private void sendImporterPipeName(String pipeName){ + JavaLanguageServerPlugin.getInstance().getClientConnection() + .sendNotification("gradle.getImporterPipeName", pipeName); + } + + public static String generateRandomHex(int numBytes) { SecureRandom random = new SecureRandom(); byte[] bytes = new byte[numBytes]; random.nextBytes(bytes); @@ -225,21 +225,23 @@ public static String generateRandomHex(int numBytes) { return hexString.toString(); } - public String generateRandomPipeName() { + public String generateRandomPipeName() { if (System.getProperty("os.name").startsWith("Windows")) { return Paths.get("\\\\.\\pipe\\", generateRandomHex(16) + "-sock").toString(); } - String tmpdir = System.getProperty("java.io.tmpdir"); - - int fixedLength = ".sock".length(); - int safeIpcPathLengths = 103; - int availableLength = safeIpcPathLengths - fixedLength - tmpdir.length(); - int randomLength = 32; - int bytesLength = Math.min(availableLength / 2, randomLength); + String tmpDir = System.getenv("XDG_RUNTIME_DIR"); + if (tmpDir == null || tmpDir.isEmpty()) { + tmpDir = System.getProperty("java.io.tmpdir"); + } + int fixedLength = ".sock".length(); + int safeIpcPathLengths = 103; + int availableLength = safeIpcPathLengths - fixedLength - tmpDir.length(); + int randomLength = 32; + int bytesLength = Math.min(availableLength / 2, randomLength); - if (bytesLength < 16) { + if (bytesLength < 16) { throw new IllegalArgumentException("Unable to generate a random pipe name with character length less than 16"); } - return Paths.get(tmpdir, generateRandomHex(bytesLength) + ".sock").toString(); + return Paths.get(tmpDir, generateRandomHex(bytesLength) + ".sock").toString(); } } diff --git a/extension/src/Extension.ts b/extension/src/Extension.ts index 56383d2b0..486bf6a23 100644 --- a/extension/src/Extension.ts +++ b/extension/src/Extension.ts @@ -36,11 +36,9 @@ import { import { instrumentOperation, sendInfo } from "vscode-extension-telemetry-wrapper"; import { GradleBuildContentProvider } from "./client/GradleBuildContentProvider"; import { BuildServerController } from "./bs/BuildServerController"; -import { BuildServerHandler } from "./bs/BuildServerHandler"; -import { ImporterHandler } from "./bs/ImporterHandler"; +import { MessageForwardHandler } from "./bs/MessageForwardHandler"; export class Extension { - private readonly buildServerHandler: BuildServerHandler; - private readonly importerHandler: ImporterHandler; + private readonly messageForwardHandler: MessageForwardHandler; private readonly client: GradleClient; private readonly server: GradleServer; private readonly pinnedTasksStore: PinnedTasksStore; @@ -91,10 +89,8 @@ export class Extension { }) ); - this.buildServerHandler = new BuildServerHandler(); - this.importerHandler = new ImporterHandler(this.context); - - this.server = new GradleServer({ host: "localhost" }, context, serverLogger, this.buildServerHandler); + this.messageForwardHandler = new MessageForwardHandler(this.context); + this.server = new GradleServer({ host: "localhost" }, context, serverLogger, this.messageForwardHandler); this.client = new GradleClient(this.server, statusBarItem, clientLogger); this.pinnedTasksStore = new PinnedTasksStore(context); this.recentTasksStore = new RecentTasksStore(); @@ -239,12 +235,10 @@ export class Extension { private async activate(): Promise { const activated = !!(await this.rootProjectsStore.getProjectRoots()).length; - this.buildServerHandler.setupBuildServerHandler(); if (!this.server.isReady()) { await this.server.start(); } - await this.importerHandler.waitForImporterPipePath(); - this.importerHandler.setupImporterHandler(this.buildServerHandler.getBuildServerConnection()); + await this.messageForwardHandler.startForwarding(); await vscode.commands.executeCommand("setContext", "gradle:activated", activated); await vscode.commands.executeCommand("setContext", "gradle:defaultView", true); } diff --git a/extension/src/bs/BuildServerHandler.ts b/extension/src/bs/BuildServerHandler.ts deleted file mode 100644 index b094336ba..000000000 --- a/extension/src/bs/BuildServerHandler.ts +++ /dev/null @@ -1,28 +0,0 @@ -import * as net from "net"; -import * as rpc from "vscode-jsonrpc/node"; -import { generateRandomPipeName } from "../util/generateRandomPipeName"; - -export class BuildServerHandler { - private buildServerConnection: rpc.MessageConnection | null = null; - private buildServerPipeServer: net.Server; - private serverPipePath: string; - - public setupBuildServerHandler(): void { - this.serverPipePath = generateRandomPipeName(); - this.buildServerPipeServer = net.createServer((socket: net.Socket) => { - this.buildServerConnection = rpc.createMessageConnection( - new rpc.StreamMessageReader(socket), - new rpc.StreamMessageWriter(socket) - ); - this.buildServerConnection.listen(); - }); - this.buildServerPipeServer.listen(this.serverPipePath); - } - - public getBuildServerPipeName(): string { - return this.serverPipePath; - } - public getBuildServerConnection(): rpc.MessageConnection | null { - return this.buildServerConnection; - } -} diff --git a/extension/src/bs/ImporterHandler.ts b/extension/src/bs/ImporterHandler.ts deleted file mode 100644 index 21ff4d346..000000000 --- a/extension/src/bs/ImporterHandler.ts +++ /dev/null @@ -1,42 +0,0 @@ -import * as net from "net"; -import * as rpc from "vscode-jsonrpc/node"; -import * as vscode from "vscode"; -import * as path from "path"; - -export const GET_IMPORTER_PIPE_NAME = "gradle.getImporterPipeName"; - -export class ImporterHandler { - public importerConnection: rpc.MessageConnection | null = null; - private importerPipeServer: net.Server; - private importerPipePath: string; - constructor(private readonly context: vscode.ExtensionContext) {} - - public async waitForImporterPipePath(): Promise { - return new Promise((resolve) => { - this.context.subscriptions.push( - vscode.commands.registerCommand(GET_IMPORTER_PIPE_NAME, (pipeName: string) => { - this.importerPipePath = path.resolve(pipeName); - resolve(); - }) - ); - }); - } - - public setupImporterHandler(buildServerConnection: rpc.MessageConnection | null): void { - this.importerPipeServer = net.createServer((socket: net.Socket) => { - this.importerConnection = rpc.createMessageConnection( - new rpc.StreamMessageReader(socket), - new rpc.StreamMessageWriter(socket) - ); - this.importerConnection?.onRequest((method, params) => { - return buildServerConnection?.sendRequest(method, params); - }); - buildServerConnection?.onNotification((method, params) => { - this.importerConnection?.sendNotification(method, params); - }); - - this.importerConnection.listen(); - }); - this.importerPipeServer.listen(this.importerPipePath); - } -} diff --git a/extension/src/bs/MessageForwardHandler.ts b/extension/src/bs/MessageForwardHandler.ts new file mode 100644 index 000000000..fe4f44cfa --- /dev/null +++ b/extension/src/bs/MessageForwardHandler.ts @@ -0,0 +1,75 @@ +import * as net from "net"; +import * as rpc from "vscode-jsonrpc/node"; +import * as vscode from "vscode"; +import * as path from "path"; +import { generateRandomPipeName } from "../util/generateRandomPipeName"; + +export const GET_IMPORTER_PIPE_NAME = "gradle.getImporterPipeName"; + +export class MessageForwardHandler { + private buildServerConnection: rpc.MessageConnection | null = null; + private importerConnection: rpc.MessageConnection | null = null; + private buildServerPipeServer: net.Server; + private importerPipeServer: net.Server; + private serverPipePath: string; + private importerPipePath: string; + + constructor(private readonly context: vscode.ExtensionContext) { + this.setupBuildServerHandler(); + } + + public async startForwarding(): Promise { + await this.waitForImporterPipePath(); + this.setupImporterHandler(); + } + + private setupBuildServerHandler(): void { + //TODO: using generateRandomPipeName() from vscode-languageclient after upgrading + this.serverPipePath = generateRandomPipeName(); + this.buildServerPipeServer = net.createServer((socket: net.Socket) => { + this.buildServerConnection = rpc.createMessageConnection( + new rpc.StreamMessageReader(socket), + new rpc.StreamMessageWriter(socket) + ); + this.buildServerConnection.listen(); + }); + this.buildServerPipeServer.listen(this.serverPipePath); + } + + private async waitForImporterPipePath(): Promise { + return new Promise((resolve) => { + this.context.subscriptions.push( + vscode.commands.registerCommand(GET_IMPORTER_PIPE_NAME, (pipeName: string) => { + this.importerPipePath = path.resolve(pipeName); + resolve(); + }) + ); + }); + } + + private setupImporterHandler(): void { + this.importerPipeServer = net.createServer((socket: net.Socket) => { + this.importerConnection = rpc.createMessageConnection( + new rpc.StreamMessageReader(socket), + new rpc.StreamMessageWriter(socket) + ); + this.setupMessageForwarding(); + this.importerConnection.listen(); + }); + this.importerPipeServer.listen(this.importerPipePath); + } + + private setupMessageForwarding(): void { + this.importerConnection?.onRequest((method, params) => { + return this.buildServerConnection?.sendRequest(method, params); + }); + + this.buildServerConnection?.onNotification((method, params) => { + this.importerConnection?.sendNotification(method, params); + }); + } + + public getBuildServerPipeName(): string { + return this.serverPipePath; + } +} diff --git a/extension/src/server/GradleServer.ts b/extension/src/server/GradleServer.ts index e8d94e0d0..f7a28eb64 100644 --- a/extension/src/server/GradleServer.ts +++ b/extension/src/server/GradleServer.ts @@ -8,7 +8,7 @@ import { isDebuggingServer } from "../util"; import { Logger } from "../logger/index"; import { NO_JAVA_EXECUTABLE, GET_EXTENSION_PATH } from "../constant"; import { getRedHatJavaExecutablePath } from "../util/config"; -import { BuildServerHandler } from "../bs/BuildServerHandler"; +import { MessageForwardHandler } from "../bs/MessageForwardHandler"; const SERVER_LOGLEVEL_REGEX = /^\[([A-Z]+)\](.*)$/; const DOWNLOAD_PROGRESS_CHAR = "."; @@ -32,7 +32,7 @@ export class GradleServer { private readonly opts: ServerOptions, private readonly context: vscode.ExtensionContext, private readonly logger: Logger, - private buildServerHandler: BuildServerHandler + private messageForwardHandler: MessageForwardHandler ) {} public async start(): Promise { @@ -55,7 +55,7 @@ export class GradleServer { await vscode.window.showErrorMessage("No Red Hat Java Extension Pack Found"); return; } - const serverPipeName = this.buildServerHandler.getBuildServerPipeName(); + const serverPipeName = this.messageForwardHandler.getBuildServerPipeName(); const args = [String(this.gradleServerPort), serverPipeName, bundleDirectory, javaExecPath]; this.logger.debug(`Gradle Server cmd: ${cmd} ${args.join(" ")}`); this.process = cp.spawn(`"${cmd}"`, args, { diff --git a/extension/src/util/generateRandomPipeName.ts b/extension/src/util/generateRandomPipeName.ts index c764900b8..0aa665247 100644 --- a/extension/src/util/generateRandomPipeName.ts +++ b/extension/src/util/generateRandomPipeName.ts @@ -9,6 +9,7 @@ const safeIpcPathLengths: Map = new Map([ ["darwin", 103], ]); +//TODO: remove this function after upgrading vscode-languageclient export function generateRandomPipeName(): string { if (process.platform === "win32") { return `\\\\.\\pipe\\${randomBytes(16).toString("hex")}-sock`; From 38038da6c9e09c5deac2cd9a120639649783b5f6 Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Wed, 3 Jul 2024 12:28:43 +0800 Subject: [PATCH 06/31] refine jdk search logic --- extension/src/server/GradleServer.ts | 22 ++++--- extension/src/util/config.ts | 58 ++++++++++++------- .../github/badsyntax/gradle/GradleServer.java | 41 +++++++++---- 3 files changed, 82 insertions(+), 39 deletions(-) diff --git a/extension/src/server/GradleServer.ts b/extension/src/server/GradleServer.ts index f7a28eb64..d3662918c 100644 --- a/extension/src/server/GradleServer.ts +++ b/extension/src/server/GradleServer.ts @@ -7,7 +7,7 @@ import { getGradleServerCommand, getGradleServerEnv } from "./serverUtil"; import { isDebuggingServer } from "../util"; import { Logger } from "../logger/index"; import { NO_JAVA_EXECUTABLE, GET_EXTENSION_PATH } from "../constant"; -import { getRedHatJavaExecutablePath } from "../util/config"; +import { getRedHatJavaExecutablePath, getJavaExecutablePath, redHatJavaInstalled } from "../util/config"; import { MessageForwardHandler } from "../bs/MessageForwardHandler"; const SERVER_LOGLEVEL_REGEX = /^\[([A-Z]+)\](.*)$/; @@ -48,15 +48,21 @@ export class GradleServer { await vscode.window.showErrorMessage(NO_JAVA_EXECUTABLE); return; } - const javaExecPath = getRedHatJavaExecutablePath(); - - //Get the Java executable used by JDT.LS, which will be higher than JDK 17. - if (!javaExecPath) { - await vscode.window.showErrorMessage("No Red Hat Java Extension Pack Found"); - return; + let javaExecPath: string | null = null; + //Get the Java executable, which will be higher than JDK 17. + if (redHatJavaInstalled()) { + javaExecPath = getRedHatJavaExecutablePath() || (await getJavaExecutablePath()); + if (javaExecPath === null) { + await vscode.window.showErrorMessage(NO_JAVA_EXECUTABLE); + } } const serverPipeName = this.messageForwardHandler.getBuildServerPipeName(); - const args = [String(this.gradleServerPort), serverPipeName, bundleDirectory, javaExecPath]; + const args = [ + `--port=${this.gradleServerPort}`, + `--pipeName=${serverPipeName}`, + `--bundleDir=${bundleDirectory}`, + `--javaExecPath=${javaExecPath}`, + ]; this.logger.debug(`Gradle Server cmd: ${cmd} ${args.join(" ")}`); this.process = cp.spawn(`"${cmd}"`, args, { cwd, diff --git a/extension/src/util/config.ts b/extension/src/util/config.ts index 163f840ab..1001679d6 100644 --- a/extension/src/util/config.ts +++ b/extension/src/util/config.ts @@ -1,9 +1,10 @@ import { execSync } from "child_process"; -import { getRuntime } from "jdk-utils"; +import { getRuntime, JAVA_FILENAME } from "jdk-utils"; import * as vscode from "vscode"; import { GradleConfig } from "../proto/gradle_pb"; import { RootProject } from "../rootProject/RootProject"; import * as fs from "fs"; +import * as fse from "fs-extra"; import * as path from "path"; type AutoDetect = "on" | "off"; @@ -28,31 +29,48 @@ export function getConfigJavaImportGradleJavaHome(): string | null { return vscode.workspace.getConfiguration("java").get("import.gradle.java.home", null); } -export function getRedHatJavaExecutablePath(): string | null { - const javaExtension = vscode.extensions.getExtension("redhat.java"); - if (!javaExtension) { - return null; - } - - const extensionPath = javaExtension.extensionPath; - const jrePath = path.join(extensionPath, "jre"); - if (!fs.existsSync(jrePath) || !fs.lstatSync(jrePath).isDirectory()) { - return null; +export async function getJavaExecutablePath(): Promise { + const javaHomeGetters = [ + getConfigJavaImportGradleJavaHome, + getJdtlsConfigJavaHome, + getConfigJavaHome, + () => process.env.JAVA_HOME, + ]; + + for (const getJavaHome of javaHomeGetters) { + const javaHome = getJavaHome(); + if (javaHome) { + const runtime = await getRuntime(javaHome, { withVersion: true }); + // Ensure the Java version is greater than 17 + if (runtime?.version && runtime.version.major >= 17) { + const javaExecPath = path.join(javaHome, "bin", "java"); + if (fs.existsSync(javaExecPath)) { + return javaExecPath; + } + } + } } + return null; +} +export function redHatJavaInstalled(): boolean { + return !!vscode.extensions.getExtension("redhat.java"); +} - // Read the entries in the jre directory and filter out hidden files - const entries = fs.readdirSync(jrePath).filter((entry) => !entry.startsWith(".")); - if (entries.length === 0) { +export function getRedHatJavaExecutablePath(): string | null { + if (!redHatJavaInstalled()) { return null; } - const entry = entries[0]; - const javaExec = process.platform === "win32" ? "java.exe" : "java"; - const javaExecPath = path.join(jrePath, entry, "bin", javaExec); - if (fs.existsSync(javaExecPath)) { - return javaExecPath; + const jreHome = path.join(vscode.extensions.getExtension("redhat.java")!.extensionPath, "jre"); + if (fse.existsSync(jreHome) && fse.statSync(jreHome).isDirectory()) { + const candidates = fse.readdirSync(jreHome); + for (const candidate of candidates) { + const javaExecutable = path.join(jreHome, candidate, "bin", JAVA_FILENAME); + if (fse.existsSync(javaExecutable)) { + return javaExecutable; + } + } } - return null; } diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java index 46f05f356..24c593b13 100644 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java @@ -3,6 +3,8 @@ import io.grpc.Server; import io.grpc.ServerBuilder; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -54,10 +56,11 @@ private void blockUntilShutdown() throws InterruptedException { } public static void main(String[] args) throws Exception { - int gradleServerPort = Integer.parseInt(args[0]); - String buildServerPipeName = args[1]; - String bundleDirectory = args[2]; - String javaExecutablePath = args[3]; + Map params = parseArgs(args); + int gradleServerPort = Integer.parseInt(params.get("port")); + String buildServerPipeName = params.get("pipeName"); + String bundleDirectory = params.get("bundleDir"); + String javaExecutablePath = params.getOrDefault("javaExecPath", null); GradleServer server = new GradleServer(gradleServerPort); Thread gradleServerThread = new Thread(() -> { @@ -68,15 +71,31 @@ public static void main(String[] args) throws Exception { e.printStackTrace(); } }); - - BuildServerThread buildServerConnectionThread = new BuildServerThread(buildServerPipeName, bundleDirectory, - javaExecutablePath); - Thread buildServerThread = new Thread(buildServerConnectionThread); - gradleServerThread.start(); - buildServerThread.start(); + + if (javaExecutablePath != null) { + BuildServerThread buildServerConnectionThread = new BuildServerThread(buildServerPipeName, bundleDirectory, + javaExecutablePath); + Thread buildServerThread = new Thread(buildServerConnectionThread); + buildServerThread.start(); + buildServerThread.join(); + } gradleServerThread.join(); - buildServerThread.join(); + } + + private static Map parseArgs(String[] args) { + Map paramMap = new HashMap<>(); + for (String arg : args) { + if (arg.startsWith("--")) { + int index = arg.indexOf('='); + if (index != -1) { + String key = arg.substring(2, index); + String value = arg.substring(index + 1); + paramMap.put(key, value); + } + } + } + return paramMap; } } From 1d916bb93fe8dff507d6d171cb8e625d241037ff Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Wed, 3 Jul 2024 15:22:48 +0800 Subject: [PATCH 07/31] update messageProxy, add error handling for task server, fix bugs --- .../gradle/bs/importer/NamedPipeStream.java | 62 +++++++++------ extension/package.json | 8 -- extension/src/Extension.ts | 17 ++--- extension/src/bs/BuildServerConnector.ts | 33 ++++++++ extension/src/bs/JdtlsImporterConnector.ts | 52 +++++++++++++ extension/src/bs/MessageForwardHandler.ts | 75 ------------------- extension/src/bs/MessageProxy.ts | 21 ++++++ extension/src/constant.ts | 1 - extension/src/server/GradleServer.ts | 15 ++-- gradle-server/build.gradle | 2 - .../github/badsyntax/gradle/GradleServer.java | 58 +++++++------- .../github/badsyntax/gradle/utils/Utils.java | 32 ++++++++ 12 files changed, 211 insertions(+), 165 deletions(-) create mode 100644 extension/src/bs/BuildServerConnector.ts create mode 100644 extension/src/bs/JdtlsImporterConnector.ts delete mode 100644 extension/src/bs/MessageForwardHandler.ts create mode 100644 extension/src/bs/MessageProxy.ts diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java index 741684923..7a63e9800 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java @@ -27,6 +27,7 @@ public class NamedPipeStream { private StreamProvider provider; + private final int MAX_ATTEMPTS = 5; interface StreamProvider { InputStream getInputStream() throws IOException; @@ -55,35 +56,47 @@ public OutputStream getOutputStream() throws IOException { private void initializeNamedPipe() { String pathName = generateRandomPipeName(); sendImporterPipeName(pathName); - File pipeFile = new File(pathName); - // Need to retry until the pipeName was sent and pipe is created by Extension side + int attempts = 0; boolean connected = false; - while (!connected) { - try{ - if (isWindows()) { - AsynchronousFileChannel channel = AsynchronousFileChannel.open(pipeFile.toPath(), - StandardOpenOption.READ, StandardOpenOption.WRITE); - input = new NamedPipeInputStream(channel); - output = new NamedPipeOutputStream(channel); - } else { - UnixDomainSocketAddress socketAddress = UnixDomainSocketAddress.of(pipeFile.toPath()); - SocketChannel channel = SocketChannel.open(StandardProtocolFamily.UNIX); - channel.connect(socketAddress); - input = new NamedPipeInputStream(channel); - output = new NamedPipeOutputStream(channel); - } + // Need to retry until the pipeName was sent and pipe is created by Extension side + while (!connected && attempts < MAX_ATTEMPTS) { + try { + attemptConnection(pipeFile); connected = true; } catch (IOException e) { - try { - Thread.sleep(1000); - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - throw new RuntimeException("Thread interrupted while trying to connect to named pipe", ie); - } + handleConnectionFailure(e, attempts); + attempts++; } } + if (!connected) { + throw new RuntimeException("Failed to connect after " + MAX_ATTEMPTS + " attempts."); + } + } + + private void attemptConnection(File pipeFile) throws IOException { + if (isWindows()) { + AsynchronousFileChannel channel = AsynchronousFileChannel.open(pipeFile.toPath(), + StandardOpenOption.READ, StandardOpenOption.WRITE); + input = new NamedPipeInputStream(channel); + output = new NamedPipeOutputStream(channel); + } else { + UnixDomainSocketAddress socketAddress = UnixDomainSocketAddress.of(pipeFile.toPath()); + SocketChannel channel = SocketChannel.open(StandardProtocolFamily.UNIX); + channel.connect(socketAddress); + input = new NamedPipeInputStream(channel); + output = new NamedPipeOutputStream(channel); + } + } + + private void handleConnectionFailure(IOException e, int attempts) { + try { + Thread.sleep(1000); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Thread interrupted while handling connection failure", ie); + } } } @@ -202,6 +215,7 @@ public InputStream getInputStream() throws IOException { } public OutputStream getOutputStream() throws IOException { + return getSelectedStream().getOutputStream(); } @@ -214,7 +228,7 @@ private void sendImporterPipeName(String pipeName){ .sendNotification("gradle.getImporterPipeName", pipeName); } - public static String generateRandomHex(int numBytes) { + private static String generateRandomHex(int numBytes) { SecureRandom random = new SecureRandom(); byte[] bytes = new byte[numBytes]; random.nextBytes(bytes); @@ -225,7 +239,7 @@ public static String generateRandomHex(int numBytes) { return hexString.toString(); } - public String generateRandomPipeName() { + private String generateRandomPipeName() { if (System.getProperty("os.name").startsWith("Windows")) { return Paths.get("\\\\.\\pipe\\", generateRandomHex(16) + "-sock").toString(); } diff --git a/extension/package.json b/extension/package.json index e1d9431be..fa6fe363c 100644 --- a/extension/package.json +++ b/extension/package.json @@ -131,14 +131,6 @@ "dark": "resources/dark/run.svg" } }, - { - "command": "gradle.getBundleDirectory", - "title": "Get Build Server Port" - }, - { - "command": "gradle.getBundlePath", - "title": "Get Build Server Port" - }, { "command": "gradle.runBuild", "category": "Gradle", diff --git a/extension/src/Extension.ts b/extension/src/Extension.ts index 486bf6a23..923e7d931 100644 --- a/extension/src/Extension.ts +++ b/extension/src/Extension.ts @@ -31,14 +31,13 @@ import { GRADLE_COMPLETION, GRADLE_PROPERTIES_FILE_CHANGE, VSCODE_TRIGGER_COMPLETION, - GET_EXTENSION_PATH, } from "./constant"; import { instrumentOperation, sendInfo } from "vscode-extension-telemetry-wrapper"; import { GradleBuildContentProvider } from "./client/GradleBuildContentProvider"; import { BuildServerController } from "./bs/BuildServerController"; -import { MessageForwardHandler } from "./bs/MessageForwardHandler"; +import { MessageProxy } from "./bs/MessageProxy"; export class Extension { - private readonly messageForwardHandler: MessageForwardHandler; + private readonly messageProxy: MessageProxy; private readonly client: GradleClient; private readonly server: GradleServer; private readonly pinnedTasksStore: PinnedTasksStore; @@ -83,14 +82,8 @@ export class Extension { } const statusBarItem = vscode.window.createStatusBarItem(); - this.context.subscriptions.push( - vscode.commands.registerCommand(GET_EXTENSION_PATH, () => { - return this.context.extensionPath; - }) - ); - - this.messageForwardHandler = new MessageForwardHandler(this.context); - this.server = new GradleServer({ host: "localhost" }, context, serverLogger, this.messageForwardHandler); + this.messageProxy = new MessageProxy(this.context); + this.server = new GradleServer({ host: "localhost" }, context, serverLogger, this.messageProxy); this.client = new GradleClient(this.server, statusBarItem, clientLogger); this.pinnedTasksStore = new PinnedTasksStore(context); this.recentTasksStore = new RecentTasksStore(); @@ -238,7 +231,7 @@ export class Extension { if (!this.server.isReady()) { await this.server.start(); } - await this.messageForwardHandler.startForwarding(); + await this.messageProxy.start(); await vscode.commands.executeCommand("setContext", "gradle:activated", activated); await vscode.commands.executeCommand("setContext", "gradle:defaultView", true); } diff --git a/extension/src/bs/BuildServerConnector.ts b/extension/src/bs/BuildServerConnector.ts new file mode 100644 index 000000000..1eb7d96b9 --- /dev/null +++ b/extension/src/bs/BuildServerConnector.ts @@ -0,0 +1,33 @@ +import * as net from "net"; +import * as rpc from "vscode-jsonrpc/node"; +import { generateRandomPipeName } from "../util/generateRandomPipeName"; + +export class BuildServerConnector { + private serverConnection: rpc.MessageConnection | null = null; + private serverPipeServer: net.Server; + private serverPipePath: string; + + constructor() { + this.setupServer(); + } + + private setupServer(): void { + this.serverPipePath = generateRandomPipeName(); + this.serverPipeServer = net.createServer((socket: net.Socket) => { + this.serverConnection = rpc.createMessageConnection( + new rpc.StreamMessageReader(socket), + new rpc.StreamMessageWriter(socket) + ); + this.serverConnection.listen(); + }); + this.serverPipeServer.listen(this.serverPipePath); + } + + public getServerConnection(): rpc.MessageConnection | null { + return this.serverConnection; + } + + public getServerPipePath(): string { + return this.serverPipePath; + } +} diff --git a/extension/src/bs/JdtlsImporterConnector.ts b/extension/src/bs/JdtlsImporterConnector.ts new file mode 100644 index 000000000..2b7fa586a --- /dev/null +++ b/extension/src/bs/JdtlsImporterConnector.ts @@ -0,0 +1,52 @@ +import * as net from "net"; +import * as rpc from "vscode-jsonrpc/node"; +import * as vscode from "vscode"; +import * as path from "path"; + +export const GET_IMPORTER_PIPE_NAME = "gradle.getImporterPipeName"; + +export class JdtlsImporterConnector { + private importerConnection: rpc.MessageConnection | null = null; + private importerPipeServer: net.Server; + private importerPipePath: string; + private readonly context: vscode.ExtensionContext; + + constructor(context: vscode.ExtensionContext) { + this.context = context; + } + + //receive the pipe name from Java jdt.ls importer + public async waitForImporterPipePath(): Promise { + return new Promise((resolve) => { + this.context.subscriptions.push( + vscode.commands.registerCommand(GET_IMPORTER_PIPE_NAME, (pipeName: string) => { + this.importerPipePath = path.resolve(pipeName); + resolve(); + }) + ); + }); + } + + public setupImporterServer(buildServerConnection: rpc.MessageConnection | null): void { + this.importerPipeServer = net.createServer((socket: net.Socket) => { + this.importerConnection = rpc.createMessageConnection( + new rpc.StreamMessageReader(socket), + new rpc.StreamMessageWriter(socket) + ); + this.setupMessageForwarding(buildServerConnection); + this.importerConnection.listen(); + }); + this.importerPipeServer.listen(this.importerPipePath); + } + + private setupMessageForwarding(buildServerConnection: rpc.MessageConnection | null): void { + const importerConnection = this.importerConnection; + + importerConnection?.onRequest((method, params) => { + return buildServerConnection?.sendRequest(method, params); + }); + buildServerConnection?.onNotification((method, params) => { + importerConnection?.sendNotification(method, params); + }); + } +} diff --git a/extension/src/bs/MessageForwardHandler.ts b/extension/src/bs/MessageForwardHandler.ts deleted file mode 100644 index fe4f44cfa..000000000 --- a/extension/src/bs/MessageForwardHandler.ts +++ /dev/null @@ -1,75 +0,0 @@ -import * as net from "net"; -import * as rpc from "vscode-jsonrpc/node"; -import * as vscode from "vscode"; -import * as path from "path"; -import { generateRandomPipeName } from "../util/generateRandomPipeName"; - -export const GET_IMPORTER_PIPE_NAME = "gradle.getImporterPipeName"; - -export class MessageForwardHandler { - private buildServerConnection: rpc.MessageConnection | null = null; - private importerConnection: rpc.MessageConnection | null = null; - private buildServerPipeServer: net.Server; - private importerPipeServer: net.Server; - private serverPipePath: string; - private importerPipePath: string; - - constructor(private readonly context: vscode.ExtensionContext) { - this.setupBuildServerHandler(); - } - - public async startForwarding(): Promise { - await this.waitForImporterPipePath(); - this.setupImporterHandler(); - } - - private setupBuildServerHandler(): void { - //TODO: using generateRandomPipeName() from vscode-languageclient after upgrading - this.serverPipePath = generateRandomPipeName(); - this.buildServerPipeServer = net.createServer((socket: net.Socket) => { - this.buildServerConnection = rpc.createMessageConnection( - new rpc.StreamMessageReader(socket), - new rpc.StreamMessageWriter(socket) - ); - this.buildServerConnection.listen(); - }); - this.buildServerPipeServer.listen(this.serverPipePath); - } - - private async waitForImporterPipePath(): Promise { - return new Promise((resolve) => { - this.context.subscriptions.push( - vscode.commands.registerCommand(GET_IMPORTER_PIPE_NAME, (pipeName: string) => { - this.importerPipePath = path.resolve(pipeName); - resolve(); - }) - ); - }); - } - - private setupImporterHandler(): void { - this.importerPipeServer = net.createServer((socket: net.Socket) => { - this.importerConnection = rpc.createMessageConnection( - new rpc.StreamMessageReader(socket), - new rpc.StreamMessageWriter(socket) - ); - this.setupMessageForwarding(); - this.importerConnection.listen(); - }); - this.importerPipeServer.listen(this.importerPipePath); - } - - private setupMessageForwarding(): void { - this.importerConnection?.onRequest((method, params) => { - return this.buildServerConnection?.sendRequest(method, params); - }); - - this.buildServerConnection?.onNotification((method, params) => { - this.importerConnection?.sendNotification(method, params); - }); - } - - public getBuildServerPipeName(): string { - return this.serverPipePath; - } -} diff --git a/extension/src/bs/MessageProxy.ts b/extension/src/bs/MessageProxy.ts new file mode 100644 index 000000000..6d2b32953 --- /dev/null +++ b/extension/src/bs/MessageProxy.ts @@ -0,0 +1,21 @@ +import { JdtlsImporterConnector } from "./JdtlsImporterConnector"; +import { BuildServerConnector } from "./BuildServerConnector"; +import * as vscode from "vscode"; + +export class MessageProxy { + private buildServerConnector: BuildServerConnector; + private jdtlsImporterConnector: JdtlsImporterConnector; + + constructor(context: vscode.ExtensionContext) { + this.buildServerConnector = new BuildServerConnector(); + this.jdtlsImporterConnector = new JdtlsImporterConnector(context); + } + + public async start(): Promise { + await this.jdtlsImporterConnector.waitForImporterPipePath(); + this.jdtlsImporterConnector.setupImporterServer(this.buildServerConnector.getServerConnection()); + } + public getBuildServerPipeName(): string { + return this.buildServerConnector.getServerPipePath(); + } +} diff --git a/extension/src/constant.ts b/extension/src/constant.ts index 664270687..2ff546bb7 100644 --- a/extension/src/constant.ts +++ b/extension/src/constant.ts @@ -5,7 +5,6 @@ export namespace Context { export const ACTIVATION_CONTEXT_KEY = "gradle:extensionActivated"; } -export const GET_EXTENSION_PATH = "gradle.getExtensionPath"; export const GRADLE_BUILD_FILE_CHANGE = "gradle.buildFileChanged"; diff --git a/extension/src/server/GradleServer.ts b/extension/src/server/GradleServer.ts index d3662918c..56dd7664a 100644 --- a/extension/src/server/GradleServer.ts +++ b/extension/src/server/GradleServer.ts @@ -6,9 +6,9 @@ import * as kill from "tree-kill"; import { getGradleServerCommand, getGradleServerEnv } from "./serverUtil"; import { isDebuggingServer } from "../util"; import { Logger } from "../logger/index"; -import { NO_JAVA_EXECUTABLE, GET_EXTENSION_PATH } from "../constant"; +import { NO_JAVA_EXECUTABLE } from "../constant"; import { getRedHatJavaExecutablePath, getJavaExecutablePath, redHatJavaInstalled } from "../util/config"; -import { MessageForwardHandler } from "../bs/MessageForwardHandler"; +import { MessageProxy } from "../bs/MessageProxy"; const SERVER_LOGLEVEL_REGEX = /^\[([A-Z]+)\](.*)$/; const DOWNLOAD_PROGRESS_CHAR = "."; @@ -32,7 +32,7 @@ export class GradleServer { private readonly opts: ServerOptions, private readonly context: vscode.ExtensionContext, private readonly logger: Logger, - private messageForwardHandler: MessageForwardHandler + private messageProxy: MessageProxy ) {} public async start(): Promise { @@ -43,7 +43,7 @@ export class GradleServer { const cwd = this.context.asAbsolutePath("lib"); const cmd = path.join(cwd, getGradleServerCommand()); const env = await getGradleServerEnv(); - const bundleDirectory = await this.getBundleDirectory(); + const bundleDirectory = this.context.asAbsolutePath("server"); if (!env) { await vscode.window.showErrorMessage(NO_JAVA_EXECUTABLE); return; @@ -56,7 +56,7 @@ export class GradleServer { await vscode.window.showErrorMessage(NO_JAVA_EXECUTABLE); } } - const serverPipeName = this.messageForwardHandler.getBuildServerPipeName(); + const serverPipeName = this.messageProxy.getBuildServerPipeName(); const args = [ `--port=${this.gradleServerPort}`, `--pipeName=${serverPipeName}`, @@ -94,11 +94,6 @@ export class GradleServer { return this.ready; } - public async getBundleDirectory(): Promise { - const extensionPath = await vscode.commands.executeCommand(GET_EXTENSION_PATH); - return path.join(extensionPath, "server"); - } - public async showRestartMessage(): Promise { const OPT_RESTART = "Restart Server"; const input = await vscode.window.showErrorMessage( diff --git a/gradle-server/build.gradle b/gradle-server/build.gradle index ba9908c79..ce5aedabd 100644 --- a/gradle-server/build.gradle +++ b/gradle-server/build.gradle @@ -12,8 +12,6 @@ java { dependencies { implementation project(":gradle-plugin-api") - implementation 'ch.epfl.scala:bsp4j:2.1.0-M4' - implementation "org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.12.0" implementation "org.gradle:gradle-tooling-api:${toolingAPIVersion}" implementation 'javax.annotation:javax.annotation-api:1.3.2' implementation "io.grpc:grpc-protobuf:${grpcVersion}" diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java index 24c593b13..a6460db5a 100644 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java @@ -1,14 +1,13 @@ package com.github.badsyntax.gradle; +import com.github.badsyntax.gradle.utils.Utils; import io.grpc.Server; import io.grpc.ServerBuilder; import java.io.IOException; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - public class GradleServer { private static final Logger logger = LoggerFactory.getLogger(GradleServer.class.getName()); @@ -56,14 +55,24 @@ private void blockUntilShutdown() throws InterruptedException { } public static void main(String[] args) throws Exception { - Map params = parseArgs(args); - int gradleServerPort = Integer.parseInt(params.get("port")); - String buildServerPipeName = params.get("pipeName"); - String bundleDirectory = params.get("bundleDir"); - String javaExecutablePath = params.getOrDefault("javaExecPath", null); + Map params = Utils.parseArgs(args); + + int gradleServerPort = Utils.parseIntegerParam(params, "port"); + String buildServerPipeName = Utils.validateRequiredParam(params, "pipeName"); + String bundleDirectory = Utils.validateRequiredParam(params, "bundleDir"); + // JavaExecutablePath is optional. Null means that the build server will not be + // started. + String javaExecutablePath = params.get("javaExecPath"); - GradleServer server = new GradleServer(gradleServerPort); - Thread gradleServerThread = new Thread(() -> { + startGradleServer(gradleServerPort); + if (javaExecutablePath != null) { + startBuildServerThread(buildServerPipeName, bundleDirectory, javaExecutablePath); + } + } + + private static void startGradleServer(int port) { + GradleServer server = new GradleServer(port); + Thread serverThread = new Thread(() -> { try { server.start(); server.blockUntilShutdown(); @@ -71,31 +80,14 @@ public static void main(String[] args) throws Exception { e.printStackTrace(); } }); - gradleServerThread.start(); - - if (javaExecutablePath != null) { - BuildServerThread buildServerConnectionThread = new BuildServerThread(buildServerPipeName, bundleDirectory, - javaExecutablePath); - Thread buildServerThread = new Thread(buildServerConnectionThread); - buildServerThread.start(); - buildServerThread.join(); - } - - gradleServerThread.join(); + serverThread.start(); } - private static Map parseArgs(String[] args) { - Map paramMap = new HashMap<>(); - for (String arg : args) { - if (arg.startsWith("--")) { - int index = arg.indexOf('='); - if (index != -1) { - String key = arg.substring(2, index); - String value = arg.substring(index + 1); - paramMap.put(key, value); - } - } - } - return paramMap; + private static void startBuildServerThread(String pipeName, String directory, String javaPath) + throws InterruptedException { + BuildServerThread buildServerConnectionThread = new BuildServerThread(pipeName, directory, javaPath); + Thread buildServerThread = new Thread(buildServerConnectionThread); + buildServerThread.start(); + buildServerThread.join(); } } diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/utils/Utils.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/utils/Utils.java index e213544c5..12db8f5cc 100644 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/utils/Utils.java +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/utils/Utils.java @@ -5,7 +5,9 @@ import io.github.g00fy2.versioncompare.Version; import java.io.File; +import java.util.HashMap; import java.util.Locale; +import java.util.Map; public class Utils { public static boolean isValidFile(File file) { @@ -74,4 +76,34 @@ private static String toPackageName(String name) { } return result.toString(); } + public static Map parseArgs(String[] args) { + Map paramMap = new HashMap<>(); + for (String arg : args) { + if (arg.startsWith("--")) { + int index = arg.indexOf('='); + if (index != -1) { + String key = arg.substring(2, index); + String value = arg.substring(index + 1); + paramMap.put(key, value); + } + } + } + return paramMap; + } + + public static int parseIntegerParam(Map params, String key) throws IllegalArgumentException { + String valueStr = params.get(key); + if (valueStr == null || valueStr.isEmpty()) { + throw new IllegalArgumentException(key + " is required and can not be empty"); + } + return Integer.parseInt(valueStr); + } + + public static String validateRequiredParam(Map params, String key) throws IllegalArgumentException { + String value = params.get(key); + if (value == null || value.isEmpty()) { + throw new IllegalArgumentException(key + " is required and can not be empty"); + } + return value; + } } From 29aa9efe7834f7b65e86ddba9a2c303009f1f88d Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Wed, 3 Jul 2024 16:10:32 +0800 Subject: [PATCH 08/31] fix nit --- .../gradle/bs/importer/NamedPipeStream.java | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java index 7a63e9800..775c32ecf 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java @@ -59,19 +59,17 @@ private void initializeNamedPipe() { File pipeFile = new File(pathName); int attempts = 0; - boolean connected = false; // Need to retry until the pipeName was sent and pipe is created by Extension side - while (!connected && attempts < MAX_ATTEMPTS) { + while (attempts < MAX_ATTEMPTS) { try { attemptConnection(pipeFile); - connected = true; } catch (IOException e) { - handleConnectionFailure(e, attempts); + sleep(e, attempts); attempts++; } } - if (!connected) { - throw new RuntimeException("Failed to connect after " + MAX_ATTEMPTS + " attempts."); + if (attempts == MAX_ATTEMPTS) { + throw new RuntimeException("Failed to connect to the named pipe after " + MAX_ATTEMPTS + " attempts"); } } @@ -90,7 +88,7 @@ private void attemptConnection(File pipeFile) throws IOException { } } - private void handleConnectionFailure(IOException e, int attempts) { + private void sleep(IOException e, int attempts) { try { Thread.sleep(1000); } catch (InterruptedException ie) { @@ -196,8 +194,6 @@ public void write(byte[] b) throws IOException { } } - public NamedPipeStream() {} - public StreamProvider getSelectedStream() { if (provider == null) { provider = createProvider() ; @@ -206,7 +202,6 @@ public StreamProvider getSelectedStream() { } private StreamProvider createProvider() { - return new PipeStreamProvider(); } @@ -215,7 +210,6 @@ public InputStream getInputStream() throws IOException { } public OutputStream getOutputStream() throws IOException { - return getSelectedStream().getOutputStream(); } From d60ff9faa223a022fdf0f7f5dc8ed06d63a2022c Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Wed, 3 Jul 2024 16:38:48 +0800 Subject: [PATCH 09/31] fix nit --- .../src/com/microsoft/gradle/bs/importer/ImporterPlugin.java | 5 ++++- extension/src/Extension.ts | 5 ++++- extension/src/server/GradleServer.ts | 1 + extension/src/util/config.ts | 1 + .../main/java/com/github/badsyntax/gradle/GradleServer.java | 1 + 5 files changed, 11 insertions(+), 2 deletions(-) diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java index 2cd7b70f5..c8d20d5e3 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java @@ -20,6 +20,7 @@ import org.osgi.framework.BundleContext; import com.microsoft.java.builder.BuildStateManager; + import ch.epfl.scala.bsp4j.BuildClient; public class ImporterPlugin extends Plugin { @@ -29,6 +30,7 @@ public class ImporterPlugin extends Plugin { private Map> buildServers = new ConcurrentHashMap<>(); private static ImporterPlugin instance; + /** * Digest store for the gradle configuration files. */ @@ -90,6 +92,7 @@ public static BuildServerConnection getBuildServerConnection(IPath rootPath, boo if (pair != null) { return pair.getLeft(); } + if (!createIfMissing) { return null; } @@ -104,10 +107,10 @@ public static BuildServerConnection getBuildServerConnection(IPath rootPath, boo .setExecutorService(Executors.newCachedThreadPool()) .setRemoteInterface(BuildServerConnection.class) .create(); + launcher.startListening(); BuildServerConnection server = launcher.getRemoteProxy(); client.onConnectWithServer(server); - instance.buildServers.put(rootPath, Pair.of(server, client)); return server; } catch (IOException e) { diff --git a/extension/src/Extension.ts b/extension/src/Extension.ts index 923e7d931..87c18e345 100644 --- a/extension/src/Extension.ts +++ b/extension/src/Extension.ts @@ -36,6 +36,7 @@ import { instrumentOperation, sendInfo } from "vscode-extension-telemetry-wrappe import { GradleBuildContentProvider } from "./client/GradleBuildContentProvider"; import { BuildServerController } from "./bs/BuildServerController"; import { MessageProxy } from "./bs/MessageProxy"; + export class Extension { private readonly messageProxy: MessageProxy; private readonly client: GradleClient; @@ -67,6 +68,7 @@ export class Extension { private readonly onDidTerminalOpen: vscode.Event = this._onDidTerminalOpen.event; private recentTerminal: vscode.Terminal | undefined; private readonly buildServerController: BuildServerController; + public constructor(private readonly context: vscode.ExtensionContext) { const loggingChannel = vscode.window.createOutputChannel("Gradle for Java"); logger.setLoggingChannel(loggingChannel); @@ -90,7 +92,6 @@ export class Extension { this.taskTerminalsStore = new TaskTerminalsStore(); this.rootProjectsStore = new RootProjectsStore(); this.gradleBuildContentProvider = new GradleBuildContentProvider(this.client); - this.gradleTaskProvider = new GradleTaskProvider( this.rootProjectsStore, this.client, @@ -145,6 +146,7 @@ export class Extension { this.buildFileWatcher = new FileWatcher("**/*.{gradle,gradle.kts}"); this.gradleWrapperWatcher = new FileWatcher("**/gradle/wrapper/gradle-wrapper.properties"); this.api = new Api(this.client, this.gradleTasksTreeDataProvider, this.gradleTaskProvider, this.icons); + this.commands = new Commands( this.context, this.pinnedTasksStore, @@ -196,6 +198,7 @@ export class Extension { }) ) ); + this.client.onDidConnect(() => this.refresh()); void this.activate(); void startLanguageServer(this.context, this.gradleBuildContentProvider, this.rootProjectsStore); diff --git a/extension/src/server/GradleServer.ts b/extension/src/server/GradleServer.ts index 56dd7664a..11167ddf4 100644 --- a/extension/src/server/GradleServer.ts +++ b/extension/src/server/GradleServer.ts @@ -64,6 +64,7 @@ export class GradleServer { `--javaExecPath=${javaExecPath}`, ]; this.logger.debug(`Gradle Server cmd: ${cmd} ${args.join(" ")}`); + this.process = cp.spawn(`"${cmd}"`, args, { cwd, env, diff --git a/extension/src/util/config.ts b/extension/src/util/config.ts index 1001679d6..91f3c0822 100644 --- a/extension/src/util/config.ts +++ b/extension/src/util/config.ts @@ -52,6 +52,7 @@ export async function getJavaExecutablePath(): Promise { } return null; } + export function redHatJavaInstalled(): boolean { return !!vscode.extensions.getExtension("redhat.java"); } diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java index a6460db5a..8ad4edd9d 100644 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java @@ -8,6 +8,7 @@ import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + public class GradleServer { private static final Logger logger = LoggerFactory.getLogger(GradleServer.class.getName()); From 1f8122f0a63a5111361bd1e2c65377dddc8d5a64 Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Thu, 4 Jul 2024 16:11:20 +0800 Subject: [PATCH 10/31] fix comments --- .../GradleBuildServerProjectImporter.java | 5 ++ .../gradle/bs/importer/ImporterPlugin.java | 8 ++- .../gradle/bs/importer/NamedPipeStream.java | 9 ++- extension/src/bs/BuildServerConnector.ts | 4 ++ extension/src/bs/JdtlsImporterConnector.ts | 59 +++++++++++-------- extension/src/bs/MessageProxy.ts | 23 +++++++- .../github/badsyntax/gradle/GradleServer.java | 5 +- 7 files changed, 79 insertions(+), 34 deletions(-) diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/GradleBuildServerProjectImporter.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/GradleBuildServerProjectImporter.java index 5ae484d85..4f19b409b 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/GradleBuildServerProjectImporter.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/GradleBuildServerProjectImporter.java @@ -61,6 +61,11 @@ public boolean applies(IProgressMonitor monitor) throws OperationCanceledExcepti return false; } + //TODO: support multi-root workspaces + if (getPreferences().getRootPaths().size() != 1) { + return false; + } + if (!Utils.isBuildServerEnabled(getPreferences())) { return false; } diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java index c8d20d5e3..eecb6bb91 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java @@ -96,6 +96,12 @@ public static BuildServerConnection getBuildServerConnection(IPath rootPath, boo if (!createIfMissing) { return null; } + + if (instance.buildServers.size() > 0) { + throw new CoreException(new Status(IStatus.ERROR, PLUGIN_ID, + "Not support multiple workspaces.")); + } + try { NamedPipeStream pipeStream = new NamedPipeStream(); @@ -115,7 +121,7 @@ public static BuildServerConnection getBuildServerConnection(IPath rootPath, boo return server; } catch (IOException e) { throw new CoreException(new Status(IStatus.ERROR, PLUGIN_ID, - "Failed to start build server.", e)); + "Failed to start build server.", e)); } } } diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java index 775c32ecf..5ac315c3c 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java @@ -39,10 +39,6 @@ protected final class PipeStreamProvider implements StreamProvider { private InputStream input; private OutputStream output; - public PipeStreamProvider() { - initializeNamedPipe(); - } - @Override public InputStream getInputStream() throws IOException { return input; @@ -63,6 +59,7 @@ private void initializeNamedPipe() { while (attempts < MAX_ATTEMPTS) { try { attemptConnection(pipeFile); + break; } catch (IOException e) { sleep(e, attempts); attempts++; @@ -202,7 +199,9 @@ public StreamProvider getSelectedStream() { } private StreamProvider createProvider() { - return new PipeStreamProvider(); + PipeStreamProvider pipeStreamProvider = new PipeStreamProvider(); + pipeStreamProvider.initializeNamedPipe(); + return pipeStreamProvider; } public InputStream getInputStream() throws IOException { diff --git a/extension/src/bs/BuildServerConnector.ts b/extension/src/bs/BuildServerConnector.ts index 1eb7d96b9..ba36a3de8 100644 --- a/extension/src/bs/BuildServerConnector.ts +++ b/extension/src/bs/BuildServerConnector.ts @@ -2,6 +2,10 @@ import * as net from "net"; import * as rpc from "vscode-jsonrpc/node"; import { generateRandomPipeName } from "../util/generateRandomPipeName"; +/** + * This class will create named pipe file and setting up a pipe server + * that will be used to communicate with the build server + */ export class BuildServerConnector { private serverConnection: rpc.MessageConnection | null = null; private serverPipeServer: net.Server; diff --git a/extension/src/bs/JdtlsImporterConnector.ts b/extension/src/bs/JdtlsImporterConnector.ts index 2b7fa586a..b6bef98aa 100644 --- a/extension/src/bs/JdtlsImporterConnector.ts +++ b/extension/src/bs/JdtlsImporterConnector.ts @@ -5,48 +5,59 @@ import * as path from "path"; export const GET_IMPORTER_PIPE_NAME = "gradle.getImporterPipeName"; +/** + * This class will receive the pipe name from Java jdt.ls importer, + * generate named pipe file and setting up a pipe server that will be used to + * communicate with the importer + */ export class JdtlsImporterConnector { private importerConnection: rpc.MessageConnection | null = null; private importerPipeServer: net.Server; private importerPipePath: string; private readonly context: vscode.ExtensionContext; + private readonly _onPipePathReady: vscode.EventEmitter = new vscode.EventEmitter(); constructor(context: vscode.ExtensionContext) { this.context = context; + this.registerCommand(); } - //receive the pipe name from Java jdt.ls importer + //Receive the pipe name from Java jdt.ls importer public async waitForImporterPipePath(): Promise { return new Promise((resolve) => { - this.context.subscriptions.push( - vscode.commands.registerCommand(GET_IMPORTER_PIPE_NAME, (pipeName: string) => { - this.importerPipePath = path.resolve(pipeName); - resolve(); - }) - ); + this._onPipePathReady.event((resolvedPath) => { + this.importerPipePath = resolvedPath; + resolve(); + }); }); } - public setupImporterServer(buildServerConnection: rpc.MessageConnection | null): void { - this.importerPipeServer = net.createServer((socket: net.Socket) => { - this.importerConnection = rpc.createMessageConnection( - new rpc.StreamMessageReader(socket), - new rpc.StreamMessageWriter(socket) - ); - this.setupMessageForwarding(buildServerConnection); - this.importerConnection.listen(); + private registerCommand(): void { + this.context.subscriptions.push( + vscode.commands.registerCommand(GET_IMPORTER_PIPE_NAME, (pipeName: string) => { + this._onPipePathReady.fire(path.resolve(pipeName)); + }) + ); + } + + public async setupImporterServer(): Promise { + return new Promise((resolve) => { + this.importerPipeServer = net.createServer((socket: net.Socket) => { + this.importerConnection = rpc.createMessageConnection( + new rpc.StreamMessageReader(socket), + new rpc.StreamMessageWriter(socket) + ); + resolve(); + }); + this.importerPipeServer.listen(this.importerPipePath); }); - this.importerPipeServer.listen(this.importerPipePath); } - private setupMessageForwarding(buildServerConnection: rpc.MessageConnection | null): void { - const importerConnection = this.importerConnection; + public startListening(): void { + this.importerConnection!.listen(); + } - importerConnection?.onRequest((method, params) => { - return buildServerConnection?.sendRequest(method, params); - }); - buildServerConnection?.onNotification((method, params) => { - importerConnection?.sendNotification(method, params); - }); + public getImporterConnection(): rpc.MessageConnection | null { + return this.importerConnection; } } diff --git a/extension/src/bs/MessageProxy.ts b/extension/src/bs/MessageProxy.ts index 6d2b32953..cdc8d47fb 100644 --- a/extension/src/bs/MessageProxy.ts +++ b/extension/src/bs/MessageProxy.ts @@ -1,6 +1,7 @@ import { JdtlsImporterConnector } from "./JdtlsImporterConnector"; import { BuildServerConnector } from "./BuildServerConnector"; import * as vscode from "vscode"; +import * as rpc from "vscode-jsonrpc/node"; export class MessageProxy { private buildServerConnector: BuildServerConnector; @@ -13,9 +14,29 @@ export class MessageProxy { public async start(): Promise { await this.jdtlsImporterConnector.waitForImporterPipePath(); - this.jdtlsImporterConnector.setupImporterServer(this.buildServerConnector.getServerConnection()); + await this.jdtlsImporterConnector.setupImporterServer(); + + this.setupMessageForwarding( + this.jdtlsImporterConnector.getImporterConnection(), + this.buildServerConnector.getServerConnection() + ); + this.jdtlsImporterConnector.startListening(); } + public getBuildServerPipeName(): string { return this.buildServerConnector.getServerPipePath(); } + + private setupMessageForwarding( + importerConnection: rpc.MessageConnection | null, + buildServerConnection: rpc.MessageConnection | null + ): void { + importerConnection?.onRequest((method, params) => { + return buildServerConnection?.sendRequest(method, params); + }); + + buildServerConnection?.onNotification((method, params) => { + importerConnection?.sendNotification(method, params); + }); + } } diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java index 8ad4edd9d..94763d7cd 100644 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java @@ -65,13 +65,13 @@ public static void main(String[] args) throws Exception { // started. String javaExecutablePath = params.get("javaExecPath"); - startGradleServer(gradleServerPort); + startGradleServerThread(gradleServerPort); if (javaExecutablePath != null) { startBuildServerThread(buildServerPipeName, bundleDirectory, javaExecutablePath); } } - private static void startGradleServer(int port) { + private static void startGradleServerThread(int port) throws InterruptedException { GradleServer server = new GradleServer(port); Thread serverThread = new Thread(() -> { try { @@ -89,6 +89,5 @@ private static void startBuildServerThread(String pipeName, String directory, St BuildServerThread buildServerConnectionThread = new BuildServerThread(pipeName, directory, javaPath); Thread buildServerThread = new Thread(buildServerConnectionThread); buildServerThread.start(); - buildServerThread.join(); } } From 58b85f80898f9aee72a92f632c01fe977b62333c Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Fri, 5 Jul 2024 10:35:49 +0800 Subject: [PATCH 11/31] expand jdk search logic and fix comments --- .../gradle/bs/importer/ImporterPlugin.java | 1 - .../gradle/bs/importer/NamedPipeStream.java | 54 +++++------ extension/src/Extension.ts | 1 + extension/src/bs/BuildServerConnector.ts | 6 +- extension/src/bs/MessageProxy.ts | 4 + extension/src/server/GradleServer.ts | 5 +- extension/src/util/config.ts | 52 +++++++--- extension/src/util/jdkUtils.ts | 94 +++++++++++++++++++ 8 files changed, 168 insertions(+), 49 deletions(-) create mode 100644 extension/src/util/jdkUtils.ts diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java index eecb6bb91..15f24fb50 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java @@ -2,7 +2,6 @@ import java.io.File; import java.io.IOException; - import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java index 5ac315c3c..8904fdb0f 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java @@ -28,12 +28,37 @@ public class NamedPipeStream { private StreamProvider provider; private final int MAX_ATTEMPTS = 5; + interface StreamProvider { InputStream getInputStream() throws IOException; - OutputStream getOutputStream() throws IOException; } + public StreamProvider getSelectedStream() { + if (provider == null) { + provider = createProvider(); + } + return provider; + } + + private StreamProvider createProvider() { + PipeStreamProvider pipeStreamProvider = new PipeStreamProvider(); + pipeStreamProvider.initializeNamedPipe(); + return pipeStreamProvider; + } + + public InputStream getInputStream() throws IOException { + return getSelectedStream().getInputStream(); + } + + public OutputStream getOutputStream() throws IOException { + return getSelectedStream().getOutputStream(); + } + + private void sendImporterPipeName(String pipeName) { + JavaLanguageServerPlugin.getInstance().getClientConnection() + .sendNotification("gradle.getImporterPipeName", pipeName); + } protected final class PipeStreamProvider implements StreamProvider { private InputStream input; @@ -191,36 +216,10 @@ public void write(byte[] b) throws IOException { } } - public StreamProvider getSelectedStream() { - if (provider == null) { - provider = createProvider() ; - } - return provider; - } - - private StreamProvider createProvider() { - PipeStreamProvider pipeStreamProvider = new PipeStreamProvider(); - pipeStreamProvider.initializeNamedPipe(); - return pipeStreamProvider; - } - - public InputStream getInputStream() throws IOException { - return getSelectedStream().getInputStream(); - } - - public OutputStream getOutputStream() throws IOException { - return getSelectedStream().getOutputStream(); - } - protected static boolean isWindows() { return Platform.OS_WIN32.equals(Platform.getOS()); } - private void sendImporterPipeName(String pipeName){ - JavaLanguageServerPlugin.getInstance().getClientConnection() - .sendNotification("gradle.getImporterPipeName", pipeName); - } - private static String generateRandomHex(int numBytes) { SecureRandom random = new SecureRandom(); byte[] bytes = new byte[numBytes]; @@ -251,4 +250,5 @@ private String generateRandomPipeName() { } return Paths.get(tmpDir, generateRandomHex(bytesLength) + ".sock").toString(); } + } diff --git a/extension/src/Extension.ts b/extension/src/Extension.ts index 8ca182f9c..160b749fe 100644 --- a/extension/src/Extension.ts +++ b/extension/src/Extension.ts @@ -241,6 +241,7 @@ export class Extension { }); } const activated = !!(await this.rootProjectsStore.getProjectRoots()).length; + this.messageProxy.prepareToStart(); if (!this.server.isReady()) { await this.server.start(); } diff --git a/extension/src/bs/BuildServerConnector.ts b/extension/src/bs/BuildServerConnector.ts index ba36a3de8..87555f721 100644 --- a/extension/src/bs/BuildServerConnector.ts +++ b/extension/src/bs/BuildServerConnector.ts @@ -11,11 +11,7 @@ export class BuildServerConnector { private serverPipeServer: net.Server; private serverPipePath: string; - constructor() { - this.setupServer(); - } - - private setupServer(): void { + public setupServer(): void { this.serverPipePath = generateRandomPipeName(); this.serverPipeServer = net.createServer((socket: net.Socket) => { this.serverConnection = rpc.createMessageConnection( diff --git a/extension/src/bs/MessageProxy.ts b/extension/src/bs/MessageProxy.ts index cdc8d47fb..61759a552 100644 --- a/extension/src/bs/MessageProxy.ts +++ b/extension/src/bs/MessageProxy.ts @@ -12,6 +12,10 @@ export class MessageProxy { this.jdtlsImporterConnector = new JdtlsImporterConnector(context); } + public prepareToStart(): void { + this.buildServerConnector.setupServer(); + } + public async start(): Promise { await this.jdtlsImporterConnector.waitForImporterPipePath(); await this.jdtlsImporterConnector.setupImporterServer(); diff --git a/extension/src/server/GradleServer.ts b/extension/src/server/GradleServer.ts index 11167ddf4..9071d4025 100644 --- a/extension/src/server/GradleServer.ts +++ b/extension/src/server/GradleServer.ts @@ -48,11 +48,10 @@ export class GradleServer { await vscode.window.showErrorMessage(NO_JAVA_EXECUTABLE); return; } - let javaExecPath: string | null = null; - //Get the Java executable, which will be higher than JDK 17. + let javaExecPath: string | undefined = undefined; if (redHatJavaInstalled()) { javaExecPath = getRedHatJavaExecutablePath() || (await getJavaExecutablePath()); - if (javaExecPath === null) { + if (javaExecPath === undefined) { await vscode.window.showErrorMessage(NO_JAVA_EXECUTABLE); } } diff --git a/extension/src/util/config.ts b/extension/src/util/config.ts index 91f3c0822..88479d1fa 100644 --- a/extension/src/util/config.ts +++ b/extension/src/util/config.ts @@ -6,7 +6,13 @@ import { RootProject } from "../rootProject/RootProject"; import * as fs from "fs"; import * as fse from "fs-extra"; import * as path from "path"; - +import { + sortJdksBySource, + findDefaultRuntimeFromSettings, + getMajorVersion, + listJdks, + sortJdksByVersion, +} from "./jdkUtils"; type AutoDetect = "on" | "off"; export function getConfigIsAutoDetectionEnabled(rootProject: RootProject): boolean { @@ -29,28 +35,48 @@ export function getConfigJavaImportGradleJavaHome(): string | null { return vscode.workspace.getConfiguration("java").get("import.gradle.java.home", null); } -export async function getJavaExecutablePath(): Promise { - const javaHomeGetters = [ - getConfigJavaImportGradleJavaHome, - getJdtlsConfigJavaHome, - getConfigJavaHome, - () => process.env.JAVA_HOME, - ]; +export async function getJavaExecutablePath(): Promise { + const REQUIRED_JDK_VERSION = 17; + + // search from jdt.ls.java.home, java.home + const javaHomeGetters = [getJdtlsConfigJavaHome, getConfigJavaHome]; + let javaHome: string | undefined = undefined; + let javaVersion = 0; for (const getJavaHome of javaHomeGetters) { - const javaHome = getJavaHome(); + javaHome = getJavaHome() || undefined; if (javaHome) { - const runtime = await getRuntime(javaHome, { withVersion: true }); + javaVersion = await getMajorVersion(javaHome); // Ensure the Java version is greater than 17 - if (runtime?.version && runtime.version.major >= 17) { - const javaExecPath = path.join(javaHome, "bin", "java"); + if (javaVersion >= REQUIRED_JDK_VERSION) { + const javaExecPath = path.join(javaHome, "bin", JAVA_FILENAME); if (fs.existsSync(javaExecPath)) { return javaExecPath; } } } } - return null; + + // search valid JDKs from env.JAVA_HOME, env.PATH, SDKMAN, jEnv, jabba, Common directories + const javaRuntimes = await listJdks(); + sortJdksByVersion(javaRuntimes); + + const validJdks = javaRuntimes.filter((r) => r.version!.major >= REQUIRED_JDK_VERSION); + if (validJdks.length > 0) { + sortJdksBySource(validJdks); + javaHome = validJdks[0].homedir; + javaVersion = validJdks[0].version!.major; + } + + //search java.configuration.runtimes + if (javaHome) { + javaHome = await findDefaultRuntimeFromSettings(); + javaVersion = await getMajorVersion(javaHome); + } + if (javaHome && javaVersion >= REQUIRED_JDK_VERSION) { + return path.join(javaHome, "bin", JAVA_FILENAME); + } + return undefined; } export function redHatJavaInstalled(): boolean { diff --git a/extension/src/util/jdkUtils.ts b/extension/src/util/jdkUtils.ts new file mode 100644 index 000000000..80672aee5 --- /dev/null +++ b/extension/src/util/jdkUtils.ts @@ -0,0 +1,94 @@ +import { getRuntime, findRuntimes, IJavaRuntime, getSources } from "jdk-utils"; +import * as vscode from "vscode"; +import * as fs from "fs"; +import * as path from "path"; + +let cachedJdks: IJavaRuntime[]; + +export async function getMajorVersion(javaHome: string | undefined): Promise { + if (!javaHome) { + return 0; + } + const runtime = await getRuntime(javaHome, { withVersion: true }); + return runtime?.version?.major || 0; +} + +export async function findDefaultRuntimeFromSettings(): Promise { + const runtimes = vscode.workspace.getConfiguration().get("java.configuration.runtimes"); + if (Array.isArray(runtimes) && runtimes.length) { + let candidate: string | undefined; + for (const runtime of runtimes) { + if (!runtime || typeof runtime !== "object" || !runtime.path) { + continue; + } + const jr = await getRuntime(runtime.path); + if (jr) { + candidate = jr.homedir; + } + if (runtime.default) { + break; + } + } + return candidate; + } + return undefined; +} + +export async function listJdks(force?: boolean): Promise { + if (force || !cachedJdks) { + cachedJdks = await findRuntimes({ checkJavac: true, withVersion: true, withTags: true }).then((jdks) => + jdks.filter((jdk) => { + // Validate if it's a real Java Home + return ( + fs.existsSync(path.join(jdk.homedir, "lib", "rt.jar")) || + fs.existsSync(path.join(jdk.homedir, "jre", "lib", "rt.jar")) || // Java 8 + fs.existsSync(path.join(jdk.homedir, "lib", "jrt-fs.jar")) + ); // Java 9+ + }) + ); + } + return cachedJdks; +} +/** + * Sort by source where JDk is located. + * The order is: + * 1. JDK_HOME, JAVA_HOME, PATH + * 2. JDK manager such as SDKMAN, jEnv, jabba, asdf + * 3. Common places such as /usr/lib/jvm + * 4. Others + */ +export function sortJdksBySource(jdks: IJavaRuntime[]) { + const rankedJdks = jdks as Array; + const env: string[] = ["JDK_HOME", "JAVA_HOME", "PATH"]; + const jdkManagers: string[] = ["SDKMAN", "jEnv", "jabba", "asdf"]; + for (const jdk of rankedJdks) { + const detectedSources: string[] = getSources(jdk); + for (const [index, source] of env.entries()) { + if (detectedSources.includes(source)) { + jdk.rank = index; // jdk from environment variables + break; + } + } + + if (jdk.rank) { + continue; + } + + const fromManager: boolean = detectedSources.some((source) => jdkManagers.includes(source)); + if (fromManager) { + jdk.rank = env.length + 1; // jdk from the jdk managers such as SDKMAN + } else if (!detectedSources.length) { + jdk.rank = env.length + 2; // jdk from common places + } else { + jdk.rank = env.length + 3; // jdk from other source such as ~/.gradle/jdks + } + } + rankedJdks.sort((a, b) => a.rank - b.rank); +} + +/** + * Sort by major version in descend order. + */ +export function sortJdksByVersion(jdks: IJavaRuntime[]) { + jdks.sort((a, b) => (b.version?.major ?? 0) - (a.version?.major ?? 0)); +} From d713497af6b56348bb6eb162482497611a8fb0d6 Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Fri, 5 Jul 2024 10:44:14 +0800 Subject: [PATCH 12/31] fix typo --- extension/src/util/config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extension/src/util/config.ts b/extension/src/util/config.ts index 88479d1fa..e3305ea3e 100644 --- a/extension/src/util/config.ts +++ b/extension/src/util/config.ts @@ -68,8 +68,8 @@ export async function getJavaExecutablePath(): Promise { javaVersion = validJdks[0].version!.major; } - //search java.configuration.runtimes - if (javaHome) { + //search java.configuration.runtimes if still not found + if (!javaHome) { javaHome = await findDefaultRuntimeFromSettings(); javaVersion = await getMajorVersion(javaHome); } From c117618fb9bb99ad21d059a6fd8010bd4bcd077f Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Fri, 5 Jul 2024 10:48:14 +0800 Subject: [PATCH 13/31] fix typo --- extension/src/util/jdkUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extension/src/util/jdkUtils.ts b/extension/src/util/jdkUtils.ts index 80672aee5..92b48fced 100644 --- a/extension/src/util/jdkUtils.ts +++ b/extension/src/util/jdkUtils.ts @@ -42,8 +42,8 @@ export async function listJdks(force?: boolean): Promise { return ( fs.existsSync(path.join(jdk.homedir, "lib", "rt.jar")) || fs.existsSync(path.join(jdk.homedir, "jre", "lib", "rt.jar")) || // Java 8 - fs.existsSync(path.join(jdk.homedir, "lib", "jrt-fs.jar")) - ); // Java 9+ + fs.existsSync(path.join(jdk.homedir, "lib", "jrt-fs.jar")) // Java 9+ + ); }) ); } From d0ec67483004a4f6b8b1c3255f87196c5c7ada1b Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Fri, 5 Jul 2024 13:30:10 +0800 Subject: [PATCH 14/31] fix comments --- .../gradle/bs/importer/NamedPipeStream.java | 81 +++++++++---------- extension/src/Extension.ts | 12 +-- .../src/bs/{MessageProxy.ts => BspProxy.ts} | 24 +++++- extension/src/bs/BuildServerConnector.ts | 8 +- extension/src/bs/JdtlsImporterConnector.ts | 23 ++++-- extension/src/server/GradleServer.ts | 6 +- 6 files changed, 91 insertions(+), 63 deletions(-) rename extension/src/bs/{MessageProxy.ts => BspProxy.ts} (61%) diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java index 8904fdb0f..e55f2063e 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java @@ -54,11 +54,6 @@ public InputStream getInputStream() throws IOException { public OutputStream getOutputStream() throws IOException { return getSelectedStream().getOutputStream(); } - - private void sendImporterPipeName(String pipeName) { - JavaLanguageServerPlugin.getInstance().getClientConnection() - .sendNotification("gradle.getImporterPipeName", pipeName); - } protected final class PipeStreamProvider implements StreamProvider { private InputStream input; @@ -95,6 +90,42 @@ private void initializeNamedPipe() { } } + private static String generateRandomHex(int numBytes) { + SecureRandom random = new SecureRandom(); + byte[] bytes = new byte[numBytes]; + random.nextBytes(bytes); + StringBuilder hexString = new StringBuilder(); + for (byte b : bytes) { + hexString.append(String.format("%02x", b)); + } + return hexString.toString(); + } + + private String generateRandomPipeName() { + if (System.getProperty("os.name").startsWith("Windows")) { + return Paths.get("\\\\.\\pipe\\", generateRandomHex(16) + "-sock").toString(); + } + String tmpDir = System.getenv("XDG_RUNTIME_DIR"); + if (tmpDir == null || tmpDir.isEmpty()) { + tmpDir = System.getProperty("java.io.tmpdir"); + } + int fixedLength = ".sock".length(); + int safeIpcPathLengths = 103; + int availableLength = safeIpcPathLengths - fixedLength - tmpDir.length(); + int randomLength = 32; + int bytesLength = Math.min(availableLength / 2, randomLength); + + if (bytesLength < 16) { + throw new IllegalArgumentException("Unable to generate a random pipe name with character length less than 16"); + } + return Paths.get(tmpDir, generateRandomHex(bytesLength) + ".sock").toString(); + } + + private void sendImporterPipeName(String pipeName) { + JavaLanguageServerPlugin.getInstance().getClientConnection() + .sendNotification("gradle.onWillImporterReady", pipeName); + } + private void attemptConnection(File pipeFile) throws IOException { if (isWindows()) { AsynchronousFileChannel channel = AsynchronousFileChannel.open(pipeFile.toPath(), @@ -118,6 +149,10 @@ private void sleep(IOException e, int attempts) { throw new RuntimeException("Thread interrupted while handling connection failure", ie); } } + + protected static boolean isWindows() { + return Platform.OS_WIN32.equals(Platform.getOS()); + } } public class NamedPipeInputStream extends InputStream { @@ -215,40 +250,4 @@ public void write(byte[] b) throws IOException { } } } - - protected static boolean isWindows() { - return Platform.OS_WIN32.equals(Platform.getOS()); - } - - private static String generateRandomHex(int numBytes) { - SecureRandom random = new SecureRandom(); - byte[] bytes = new byte[numBytes]; - random.nextBytes(bytes); - StringBuilder hexString = new StringBuilder(); - for (byte b : bytes) { - hexString.append(String.format("%02x", b)); - } - return hexString.toString(); - } - - private String generateRandomPipeName() { - if (System.getProperty("os.name").startsWith("Windows")) { - return Paths.get("\\\\.\\pipe\\", generateRandomHex(16) + "-sock").toString(); - } - String tmpDir = System.getenv("XDG_RUNTIME_DIR"); - if (tmpDir == null || tmpDir.isEmpty()) { - tmpDir = System.getProperty("java.io.tmpdir"); - } - int fixedLength = ".sock".length(); - int safeIpcPathLengths = 103; - int availableLength = safeIpcPathLengths - fixedLength - tmpDir.length(); - int randomLength = 32; - int bytesLength = Math.min(availableLength / 2, randomLength); - - if (bytesLength < 16) { - throw new IllegalArgumentException("Unable to generate a random pipe name with character length less than 16"); - } - return Paths.get(tmpDir, generateRandomHex(bytesLength) + ".sock").toString(); - } - } diff --git a/extension/src/Extension.ts b/extension/src/Extension.ts index 160b749fe..324a58b64 100644 --- a/extension/src/Extension.ts +++ b/extension/src/Extension.ts @@ -36,10 +36,10 @@ import { instrumentOperation, sendInfo } from "vscode-extension-telemetry-wrappe import { GradleBuildContentProvider } from "./client/GradleBuildContentProvider"; import { BuildServerController } from "./bs/BuildServerController"; import { GradleTestRunner } from "./bs/GradleTestRunner"; -import { MessageProxy } from "./bs/MessageProxy"; +import { BspProxy } from "./bs/BspProxy"; export class Extension { - private readonly messageProxy: MessageProxy; + private readonly bspProxy: BspProxy; private readonly client: GradleClient; private readonly server: GradleServer; private readonly pinnedTasksStore: PinnedTasksStore; @@ -85,8 +85,8 @@ export class Extension { } const statusBarItem = vscode.window.createStatusBarItem(); - this.messageProxy = new MessageProxy(this.context); - this.server = new GradleServer({ host: "localhost" }, context, serverLogger, this.messageProxy); + this.bspProxy = new BspProxy(this.context); + this.server = new GradleServer({ host: "localhost" }, context, serverLogger, this.bspProxy); this.client = new GradleClient(this.server, statusBarItem, clientLogger); this.pinnedTasksStore = new PinnedTasksStore(context); this.recentTasksStore = new RecentTasksStore(); @@ -241,11 +241,11 @@ export class Extension { }); } const activated = !!(await this.rootProjectsStore.getProjectRoots()).length; - this.messageProxy.prepareToStart(); + this.bspProxy.prepareToStart(); if (!this.server.isReady()) { await this.server.start(); } - await this.messageProxy.start(); + await this.bspProxy.start(); await vscode.commands.executeCommand("setContext", "gradle:activated", activated); await vscode.commands.executeCommand("setContext", "gradle:defaultView", true); } diff --git a/extension/src/bs/MessageProxy.ts b/extension/src/bs/BspProxy.ts similarity index 61% rename from extension/src/bs/MessageProxy.ts rename to extension/src/bs/BspProxy.ts index 61759a552..b9abdf771 100644 --- a/extension/src/bs/MessageProxy.ts +++ b/extension/src/bs/BspProxy.ts @@ -3,7 +3,15 @@ import { BuildServerConnector } from "./BuildServerConnector"; import * as vscode from "vscode"; import * as rpc from "vscode-jsonrpc/node"; -export class MessageProxy { +/** + * Forwards JSON-RPC messages between the build server and the Java JDT LS importer. + * + * This layer is necessary because named pipes are not well supported by Java on Windows, + * but are well supported by Node.js. So Node.js is used to create two named pipe servers. + * + * During the named pipe connecting process, Both the build server and JDT LS importer act as clients connecting to BspProxy. + */ +export class BspProxy { private buildServerConnector: BuildServerConnector; private jdtlsImporterConnector: JdtlsImporterConnector; @@ -11,14 +19,22 @@ export class MessageProxy { this.buildServerConnector = new BuildServerConnector(); this.jdtlsImporterConnector = new JdtlsImporterConnector(context); } - + /** + * This function needs to be called before we start Java Gradle Server. + */ public prepareToStart(): void { - this.buildServerConnector.setupServer(); + this.buildServerConnector.setupBuildServerPipeStream(); } + /** + * The order of the following start steps is important. + * + * We have to start listening after the message forwarding is setup, otherwise the Java importer + * will stop polling and start sending messages before the forwarding is setup and the messages will be lost. + */ public async start(): Promise { await this.jdtlsImporterConnector.waitForImporterPipePath(); - await this.jdtlsImporterConnector.setupImporterServer(); + await this.jdtlsImporterConnector.setupImporterPipeStream(); this.setupMessageForwarding( this.jdtlsImporterConnector.getImporterConnection(), diff --git a/extension/src/bs/BuildServerConnector.ts b/extension/src/bs/BuildServerConnector.ts index 87555f721..521996842 100644 --- a/extension/src/bs/BuildServerConnector.ts +++ b/extension/src/bs/BuildServerConnector.ts @@ -4,14 +4,18 @@ import { generateRandomPipeName } from "../util/generateRandomPipeName"; /** * This class will create named pipe file and setting up a pipe server - * that will be used to communicate with the build server + * that will be used to communicate with the build server. */ export class BuildServerConnector { private serverConnection: rpc.MessageConnection | null = null; private serverPipeServer: net.Server; private serverPipePath: string; - public setupServer(): void { + /** + * It generates a random pipe name, creates a pipe server and + * waiting for the connection from the Java build server. + */ + public setupBuildServerPipeStream(): void { this.serverPipePath = generateRandomPipeName(); this.serverPipeServer = net.createServer((socket: net.Socket) => { this.serverConnection = rpc.createMessageConnection( diff --git a/extension/src/bs/JdtlsImporterConnector.ts b/extension/src/bs/JdtlsImporterConnector.ts index b6bef98aa..6bcba4437 100644 --- a/extension/src/bs/JdtlsImporterConnector.ts +++ b/extension/src/bs/JdtlsImporterConnector.ts @@ -3,7 +3,7 @@ import * as rpc from "vscode-jsonrpc/node"; import * as vscode from "vscode"; import * as path from "path"; -export const GET_IMPORTER_PIPE_NAME = "gradle.getImporterPipeName"; +export const ON_WILL_IMPORTER_READY = "gradle.onWillImporterReady"; /** * This class will receive the pipe name from Java jdt.ls importer, @@ -15,32 +15,41 @@ export class JdtlsImporterConnector { private importerPipeServer: net.Server; private importerPipePath: string; private readonly context: vscode.ExtensionContext; - private readonly _onPipePathReady: vscode.EventEmitter = new vscode.EventEmitter(); + private readonly _onImporterReady: vscode.EventEmitter = new vscode.EventEmitter(); constructor(context: vscode.ExtensionContext) { this.context = context; this.registerCommand(); } - //Receive the pipe name from Java jdt.ls importer + /** + * Waits for the importer pipe path to be ready. + * It listens for the `_onImporterReady` event, and when the event is fired, + * it updates the `importerPipePath` with the resolved path and resolves the Promise. + * + * @returns Promise that resolves when the pipe path is ready + */ public async waitForImporterPipePath(): Promise { return new Promise((resolve) => { - this._onPipePathReady.event((resolvedPath) => { + this._onImporterReady.event((resolvedPath) => { this.importerPipePath = resolvedPath; resolve(); }); }); } + /** + * The `_onPipePathReady` event will be fired when the pipe path is received from Java jdt.ls importer + */ private registerCommand(): void { this.context.subscriptions.push( - vscode.commands.registerCommand(GET_IMPORTER_PIPE_NAME, (pipeName: string) => { - this._onPipePathReady.fire(path.resolve(pipeName)); + vscode.commands.registerCommand(ON_WILL_IMPORTER_READY, (pipeName: string) => { + this._onImporterReady.fire(path.resolve(pipeName)); }) ); } - public async setupImporterServer(): Promise { + public async setupImporterPipeStream(): Promise { return new Promise((resolve) => { this.importerPipeServer = net.createServer((socket: net.Socket) => { this.importerConnection = rpc.createMessageConnection( diff --git a/extension/src/server/GradleServer.ts b/extension/src/server/GradleServer.ts index 9071d4025..89ebda025 100644 --- a/extension/src/server/GradleServer.ts +++ b/extension/src/server/GradleServer.ts @@ -8,7 +8,7 @@ import { isDebuggingServer } from "../util"; import { Logger } from "../logger/index"; import { NO_JAVA_EXECUTABLE } from "../constant"; import { getRedHatJavaExecutablePath, getJavaExecutablePath, redHatJavaInstalled } from "../util/config"; -import { MessageProxy } from "../bs/MessageProxy"; +import { BspProxy } from "../bs/BspProxy"; const SERVER_LOGLEVEL_REGEX = /^\[([A-Z]+)\](.*)$/; const DOWNLOAD_PROGRESS_CHAR = "."; @@ -32,7 +32,7 @@ export class GradleServer { private readonly opts: ServerOptions, private readonly context: vscode.ExtensionContext, private readonly logger: Logger, - private messageProxy: MessageProxy + private bspProxy: BspProxy ) {} public async start(): Promise { @@ -55,7 +55,7 @@ export class GradleServer { await vscode.window.showErrorMessage(NO_JAVA_EXECUTABLE); } } - const serverPipeName = this.messageProxy.getBuildServerPipeName(); + const serverPipeName = this.bspProxy.getBuildServerPipeName(); const args = [ `--port=${this.gradleServerPort}`, `--pipeName=${serverPipeName}`, From a097a87e31488db76ede814f6490cfc0249bfb97 Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Fri, 5 Jul 2024 15:27:42 +0800 Subject: [PATCH 15/31] fix comments --- .../com/microsoft/gradle/bs/importer/NamedPipeStream.java | 2 +- extension/src/bs/BuildServerConnector.ts | 4 ++-- extension/src/bs/JdtlsImporterConnector.ts | 4 ++-- .../java/com/github/badsyntax/gradle/GradleServer.java | 2 +- .../java/com/github/badsyntax/gradle/utils/Utils.java | 8 -------- 5 files changed, 6 insertions(+), 14 deletions(-) diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java index e55f2063e..571533f74 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java @@ -123,7 +123,7 @@ private String generateRandomPipeName() { private void sendImporterPipeName(String pipeName) { JavaLanguageServerPlugin.getInstance().getClientConnection() - .sendNotification("gradle.onWillImporterReady", pipeName); + .sendNotification("gradle.onWillImporterConnect", pipeName); } private void attemptConnection(File pipeFile) throws IOException { diff --git a/extension/src/bs/BuildServerConnector.ts b/extension/src/bs/BuildServerConnector.ts index 521996842..31ff86f21 100644 --- a/extension/src/bs/BuildServerConnector.ts +++ b/extension/src/bs/BuildServerConnector.ts @@ -3,7 +3,7 @@ import * as rpc from "vscode-jsonrpc/node"; import { generateRandomPipeName } from "../util/generateRandomPipeName"; /** - * This class will create named pipe file and setting up a pipe server + * Create named pipe file and setting up a pipe server * that will be used to communicate with the build server. */ export class BuildServerConnector { @@ -12,7 +12,7 @@ export class BuildServerConnector { private serverPipePath: string; /** - * It generates a random pipe name, creates a pipe server and + * Generates a random pipe name, creates a pipe server and * waiting for the connection from the Java build server. */ public setupBuildServerPipeStream(): void { diff --git a/extension/src/bs/JdtlsImporterConnector.ts b/extension/src/bs/JdtlsImporterConnector.ts index 6bcba4437..204072e7d 100644 --- a/extension/src/bs/JdtlsImporterConnector.ts +++ b/extension/src/bs/JdtlsImporterConnector.ts @@ -3,7 +3,7 @@ import * as rpc from "vscode-jsonrpc/node"; import * as vscode from "vscode"; import * as path from "path"; -export const ON_WILL_IMPORTER_READY = "gradle.onWillImporterReady"; +export const ON_WILL_IMPORTER_CONNECT = "gradle.onWillImporterConnect"; /** * This class will receive the pipe name from Java jdt.ls importer, @@ -43,7 +43,7 @@ export class JdtlsImporterConnector { */ private registerCommand(): void { this.context.subscriptions.push( - vscode.commands.registerCommand(ON_WILL_IMPORTER_READY, (pipeName: string) => { + vscode.commands.registerCommand(ON_WILL_IMPORTER_CONNECT, (pipeName: string) => { this._onImporterReady.fire(path.resolve(pipeName)); }) ); diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java index 94763d7cd..29da0f4a0 100644 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java @@ -58,7 +58,7 @@ private void blockUntilShutdown() throws InterruptedException { public static void main(String[] args) throws Exception { Map params = Utils.parseArgs(args); - int gradleServerPort = Utils.parseIntegerParam(params, "port"); + int gradleServerPort = Integer.parseInt(Utils.validateRequiredParam(params, "port")); String buildServerPipeName = Utils.validateRequiredParam(params, "pipeName"); String bundleDirectory = Utils.validateRequiredParam(params, "bundleDir"); // JavaExecutablePath is optional. Null means that the build server will not be diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/utils/Utils.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/utils/Utils.java index 12db8f5cc..827f123f6 100644 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/utils/Utils.java +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/utils/Utils.java @@ -91,14 +91,6 @@ public static Map parseArgs(String[] args) { return paramMap; } - public static int parseIntegerParam(Map params, String key) throws IllegalArgumentException { - String valueStr = params.get(key); - if (valueStr == null || valueStr.isEmpty()) { - throw new IllegalArgumentException(key + " is required and can not be empty"); - } - return Integer.parseInt(valueStr); - } - public static String validateRequiredParam(Map params, String key) throws IllegalArgumentException { String value = params.get(key); if (value == null || value.isEmpty()) { From c2013069aed521995436d10ab20c310385b7ff88 Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Wed, 10 Jul 2024 14:47:56 +0800 Subject: [PATCH 16/31] 1. Add local build-server-for-gradle jar import and related gradle buildJar tasks. 2.Update debug logic. 3.fix typo and comments --- .vscode/launch.json | 61 +++--------- CONTRIBUTING.md | 26 +---- build.gradle | 2 +- extension/build.gradle | 12 ++- extension/src/bs/BspProxy.ts | 4 +- extension/src/bs/BuildServerConnector.ts | 4 +- extension/src/bs/JdtlsImporterConnector.ts | 5 +- extension/src/server/GradleServer.ts | 94 +++++++++---------- extension/src/util/config.ts | 6 +- extension/src/util/generateRandomPipeName.ts | 3 +- extension/src/util/index.ts | 2 - extension/src/util/jdkUtils.ts | 1 + gradle-server/build.gradle | 4 + .../badsyntax/gradle/BuildServerThread.java | 48 ++-------- .../github/badsyntax/gradle/GradleServer.java | 13 +-- 15 files changed, 100 insertions(+), 185 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 3c3f33f82..6b3e0dbe3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -31,19 +31,14 @@ }, { "type": "java", - "name": "Debug Server", - "request": "launch", - "mainClass": "com.github.badsyntax.gradle.GradleServer", - "projectName": "gradle-server", - "cwd": "${workspaceFolder}/gradle-server", - "presentation": { - "group": "debug", - "order": 2, - "hidden": true - } + "name": "Attach to Gradle Server", + "request": "attach", + "hostName": "localhost", + "port": "8089", + "projectName": "com.github.badsyntax.gradle" }, { - "name": "Debug Extension with Debug Server", + "name": "Debug Gradle Server & Extension", "type": "extensionHost", "request": "launch", "runtimeExecutable": "${execPath}", @@ -54,12 +49,13 @@ "${workspaceFolder}/extension/dist/**/*.js" ], "preLaunchTask": "Gradle: Build", + "presentation": { + "group": "debug", + "order": 2 + }, "env": { - "VSCODE_DEBUG_SERVER": "true" + "GRADLE_SERVER_OPTS":"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8089" }, - "presentation": { - "hidden": true - } }, { "name": "Debug Extension & Gradle Plugin", @@ -76,30 +72,10 @@ "env": { "VSCODE_DEBUG_PLUGIN": "true" }, - "presentation": { - "group": "debug", - "order": 2 - } - }, - { - "name": "Debug Extension & Build Server", - "type": "extensionHost", - "request": "launch", - "runtimeExecutable": "${execPath}", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}/extension" - ], - "outFiles": [ - "${workspaceFolder}/extension/dist/**/*.js" - ], - "preLaunchTask": "Gradle: Build", "presentation": { "group": "debug", "order": 3 - }, - "env": { - "DEBUG_GRADLE_BUILD_SERVER":"true" - }, + } }, { "name": "Debug Language Server: Launch Extension", @@ -372,17 +348,4 @@ } }, ], - "compounds": [ - { - "name": "Debug Server & Extension", - "configurations": [ - "Debug Server", - "Debug Extension with Debug Server" - ], - "presentation": { - "group": "debug", - "order": 3 - } - } - ] } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3ade961ce..9a73ee54a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,34 +40,16 @@ The extension uses a Gradle plugin (`com.microsoft.gradle.GradlePlugin`) to get > Note: There is a known issue that when the Gradle project stores in a sub-folder of the root folder, the `Attach to Gradle Plugin` will fail to attach. See [#1237](https://github.com/microsoft/vscode-gradle/issues/1237). -## Debugging Gradle Build Server - -To debug the Extension with the [Gradle Build Server](https://github.com/microsoft/build-server-for-gradle), follow these steps: - -1. Open the `extension/build-server-for-gradle` directory, which you should have [imported previously](#build-gradle-project-importer) as a separate project. -2. In the `.vscode/launch.json` of the build-server-for-gradle project, ensure you have the following configuration to attach the debugger: - ```json - { - "type": "java", - "name": "Attach to Gradle Build Server", - "request": "attach", - "hostName": "localhost", - "port": "8989", - "projectName": "server" - } - ``` -3. In your main project (vscode-gradle), start the `Debug Extension & Build Server` launch configuration. -4. In the build-server-for-gradle project, start the `Attach to Gradle Build Server` launch configuration. +## Debugging Gradle Server + +1. Run vscode launch configuration `Debug Gradle Server & Extension`. +2. Run vscode launch configuration `Attach to Gradle Server`. ## Debugging Gradle Language Server (editing feature related) 1. Run vscode launch configuration `Debug Language Server: Launch Extension`. 2. Run vscode launch configuration `Debug Language Server: Launch Language Server`. -## Debugging Gradle Server (work with Gradle daemon) - -Run vscode launch configuration `Debug Server & Extension`. - ## Development Workflow Open the root of the project in VS Code. diff --git a/build.gradle b/build.gradle index d492b25ab..e976f182f 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { project.ext.set('grpcVersion', '1.53.0') project.ext.set('protobufVersion', '3.12.0') project.ext.set('protocVersion', project.protobufVersion) -project.ext.set('toolingAPIVersion', '8.0.2') +project.ext.set('toolingAPIVersion', '8.8') allprojects { group = 'vscode-gradle' diff --git a/extension/build.gradle b/extension/build.gradle index 9b8dce80c..919aa1ff2 100644 --- a/extension/build.gradle +++ b/extension/build.gradle @@ -235,8 +235,18 @@ task copyBuildServerJars(type: Copy) { mustRunAfter copyDocs } +task copyBuildServerJarsToGradleServer(type: Copy) { + dependsOn ':extension:buildBuildServer' + from('./build-server-for-gradle/server/build/libs/') { + include '**/*.jar' + include '**/init.gradle' + } + into '../gradle-server/build/libs/' + mustRunAfter copyDocs +} + task buildJars() { - dependsOn copyJdtlsPluginJar, copyBuildServerJars + dependsOn copyJdtlsPluginJar, copyBuildServerJars, copyBuildServerJarsToGradleServer } build.finalizedBy buildProd, buildTest diff --git a/extension/src/bs/BspProxy.ts b/extension/src/bs/BspProxy.ts index b9abdf771..cad50c184 100644 --- a/extension/src/bs/BspProxy.ts +++ b/extension/src/bs/BspProxy.ts @@ -52,11 +52,11 @@ export class BspProxy { buildServerConnection: rpc.MessageConnection | null ): void { importerConnection?.onRequest((method, params) => { - return buildServerConnection?.sendRequest(method, params); + return buildServerConnection?.sendRequest(method, params ?? {}); }); buildServerConnection?.onNotification((method, params) => { - importerConnection?.sendNotification(method, params); + importerConnection?.sendNotification(method, params ?? {}); }); } } diff --git a/extension/src/bs/BuildServerConnector.ts b/extension/src/bs/BuildServerConnector.ts index 31ff86f21..0c04a0f6b 100644 --- a/extension/src/bs/BuildServerConnector.ts +++ b/extension/src/bs/BuildServerConnector.ts @@ -3,8 +3,8 @@ import * as rpc from "vscode-jsonrpc/node"; import { generateRandomPipeName } from "../util/generateRandomPipeName"; /** - * Create named pipe file and setting up a pipe server - * that will be used to communicate with the build server. + * Creates a named pipe file and sets up a pipe server + * for communication with the build server. */ export class BuildServerConnector { private serverConnection: rpc.MessageConnection | null = null; diff --git a/extension/src/bs/JdtlsImporterConnector.ts b/extension/src/bs/JdtlsImporterConnector.ts index 204072e7d..29b6f2fa4 100644 --- a/extension/src/bs/JdtlsImporterConnector.ts +++ b/extension/src/bs/JdtlsImporterConnector.ts @@ -6,9 +6,8 @@ import * as path from "path"; export const ON_WILL_IMPORTER_CONNECT = "gradle.onWillImporterConnect"; /** - * This class will receive the pipe name from Java jdt.ls importer, - * generate named pipe file and setting up a pipe server that will be used to - * communicate with the importer + * Receive the pipe name from Java jdt.ls importer, generate named pipe file and + * setting up a pipe server that will be used to communicate with the importer */ export class JdtlsImporterConnector { private importerConnection: rpc.MessageConnection | null = null; diff --git a/extension/src/server/GradleServer.ts b/extension/src/server/GradleServer.ts index 89ebda025..a8e273368 100644 --- a/extension/src/server/GradleServer.ts +++ b/extension/src/server/GradleServer.ts @@ -4,7 +4,6 @@ import * as cp from "child_process"; import * as getPort from "get-port"; import * as kill from "tree-kill"; import { getGradleServerCommand, getGradleServerEnv } from "./serverUtil"; -import { isDebuggingServer } from "../util"; import { Logger } from "../logger/index"; import { NO_JAVA_EXECUTABLE } from "../constant"; import { getRedHatJavaExecutablePath, getJavaExecutablePath, redHatJavaInstalled } from "../util/config"; @@ -36,58 +35,55 @@ export class GradleServer { ) {} public async start(): Promise { - if (isDebuggingServer()) { - this.fireOnStart(); - } else { - this.gradleServerPort = await getPort(); - const cwd = this.context.asAbsolutePath("lib"); - const cmd = path.join(cwd, getGradleServerCommand()); - const env = await getGradleServerEnv(); - const bundleDirectory = this.context.asAbsolutePath("server"); - if (!env) { + this.gradleServerPort = await getPort(); + const cwd = this.context.asAbsolutePath("lib"); + const cmd = path.join(cwd, getGradleServerCommand()); + const env = await getGradleServerEnv(); + const bundleDirectory = this.context.asAbsolutePath("server"); + if (!env) { + await vscode.window.showErrorMessage(NO_JAVA_EXECUTABLE); + return; + } + let startBuildServer = "false"; + if (redHatJavaInstalled()) { + const javaExecPath = getRedHatJavaExecutablePath() || (await getJavaExecutablePath()); + if (javaExecPath === undefined) { await vscode.window.showErrorMessage(NO_JAVA_EXECUTABLE); - return; } - let javaExecPath: string | undefined = undefined; - if (redHatJavaInstalled()) { - javaExecPath = getRedHatJavaExecutablePath() || (await getJavaExecutablePath()); - if (javaExecPath === undefined) { - await vscode.window.showErrorMessage(NO_JAVA_EXECUTABLE); + startBuildServer = "true"; + } + const buildServerPipeName = this.bspProxy.getBuildServerPipeName(); + const args = [ + `--port=${this.gradleServerPort}`, + `--pipeName=${buildServerPipeName}`, + `--bundleDir=${bundleDirectory}`, + `--startBuildServer=${startBuildServer}`, + ]; + this.logger.debug(`Gradle Server cmd: ${cmd} ${args.join(" ")}`); + + this.process = cp.spawn(`"${cmd}"`, args, { + cwd, + env, + shell: true, + }); + this.process.stdout.on("data", this.logOutput); + this.process.stderr.on("data", this.logOutput); + this.process + .on("error", (err: Error) => this.logger.error(err.message)) + .on("exit", async (code) => { + this.logger.warn("Gradle server stopped"); + this._onDidStop.fire(null); + this.ready = false; + this.process?.removeAllListeners(); + if (this.restarting) { + this.restarting = false; + await this.start(); + } else if (code !== 0) { + await this.handleServerStartError(); } - } - const serverPipeName = this.bspProxy.getBuildServerPipeName(); - const args = [ - `--port=${this.gradleServerPort}`, - `--pipeName=${serverPipeName}`, - `--bundleDir=${bundleDirectory}`, - `--javaExecPath=${javaExecPath}`, - ]; - this.logger.debug(`Gradle Server cmd: ${cmd} ${args.join(" ")}`); - - this.process = cp.spawn(`"${cmd}"`, args, { - cwd, - env, - shell: true, }); - this.process.stdout.on("data", this.logOutput); - this.process.stderr.on("data", this.logOutput); - this.process - .on("error", (err: Error) => this.logger.error(err.message)) - .on("exit", async (code) => { - this.logger.warn("Gradle server stopped"); - this._onDidStop.fire(null); - this.ready = false; - this.process?.removeAllListeners(); - if (this.restarting) { - this.restarting = false; - await this.start(); - } else if (code !== 0) { - await this.handleServerStartError(); - } - }); - - this.fireOnStart(); - } + + this.fireOnStart(); } public isReady(): boolean { diff --git a/extension/src/util/config.ts b/extension/src/util/config.ts index e3305ea3e..522c06fce 100644 --- a/extension/src/util/config.ts +++ b/extension/src/util/config.ts @@ -83,9 +83,9 @@ export function redHatJavaInstalled(): boolean { return !!vscode.extensions.getExtension("redhat.java"); } -export function getRedHatJavaExecutablePath(): string | null { +export function getRedHatJavaExecutablePath(): string | undefined { if (!redHatJavaInstalled()) { - return null; + return undefined; } const jreHome = path.join(vscode.extensions.getExtension("redhat.java")!.extensionPath, "jre"); @@ -98,7 +98,7 @@ export function getRedHatJavaExecutablePath(): string | null { } } } - return null; + return undefined; } export function getConfigGradleJavaHome(): string | null { diff --git a/extension/src/util/generateRandomPipeName.ts b/extension/src/util/generateRandomPipeName.ts index 0aa665247..f4d052504 100644 --- a/extension/src/util/generateRandomPipeName.ts +++ b/extension/src/util/generateRandomPipeName.ts @@ -1,3 +1,4 @@ +// See: https://github.com/microsoft/vscode-languageserver-node/blob/6d0454dca7fba8529ba3fc6d930642f134291d3d/jsonrpc/src/node/main.ts#L176 import { randomBytes } from "crypto"; import * as os from "os"; import * as path from "path"; @@ -9,7 +10,7 @@ const safeIpcPathLengths: Map = new Map([ ["darwin", 103], ]); -//TODO: remove this function after upgrading vscode-languageclient +// TODO: remove this function after upgrading vscode-languageclient export function generateRandomPipeName(): string { if (process.platform === "win32") { return `\\\\.\\pipe\\${randomBytes(16).toString("hex")}-sock`; diff --git a/extension/src/util/index.ts b/extension/src/util/index.ts index f95b5c2e3..27d9ba0ef 100644 --- a/extension/src/util/index.ts +++ b/extension/src/util/index.ts @@ -6,8 +6,6 @@ import { RootProject } from "../rootProject"; export const isTest = (): boolean => process.env.VSCODE_TEST?.toLowerCase() === "true"; -export const isDebuggingServer = (): boolean => process.env.VSCODE_DEBUG_SERVER?.toLowerCase() === "true"; - // some run application tasks require a lot of time to start. So we should set a loose timeout. const maximumTimeout = 60000; // ms const tcpTimeout = 300; // ms diff --git a/extension/src/util/jdkUtils.ts b/extension/src/util/jdkUtils.ts index 92b48fced..f4f9c5cf0 100644 --- a/extension/src/util/jdkUtils.ts +++ b/extension/src/util/jdkUtils.ts @@ -1,3 +1,4 @@ +// See: https://github.com/redhat-developer/vscode-java/blob/2015139c5773c0107f75d2289e3656f45cb38c98/src/jdkUtils.ts import { getRuntime, findRuntimes, IJavaRuntime, getSources } from "jdk-utils"; import * as vscode from "vscode"; import * as fs from "fs"; diff --git a/gradle-server/build.gradle b/gradle-server/build.gradle index ce5aedabd..9aa7e2327 100644 --- a/gradle-server/build.gradle +++ b/gradle-server/build.gradle @@ -12,6 +12,9 @@ java { dependencies { implementation project(":gradle-plugin-api") + implementation files('build/libs/server.jar') + implementation fileTree(dir: 'build/libs/runtime', include: ['*.jar'], exclude: ['gradle-tooling-api-*.jar']) + implementation fileTree(dir: 'build/libs/plugins', include: ['*.jar']) implementation "org.gradle:gradle-tooling-api:${toolingAPIVersion}" implementation 'javax.annotation:javax.annotation-api:1.3.2' implementation "io.grpc:grpc-protobuf:${grpcVersion}" @@ -124,6 +127,7 @@ task serverStartScripts(type: CreateStartScripts) { task copyRuntimeLibs(type: Copy) { into "../extension/lib" from configurations.runtimeClasspath + duplicatesStrategy = 'exclude' } project.tasks.named("processResources") { diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/BuildServerThread.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/BuildServerThread.java index e3fced378..b16b31972 100644 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/BuildServerThread.java +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/BuildServerThread.java @@ -1,66 +1,30 @@ package com.github.badsyntax.gradle; -import java.io.File; -import java.io.IOException; +import com.microsoft.java.bs.core.Launcher; import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; public class BuildServerThread implements Runnable { private String bundleDirectory; private final String pipeName; - private final String javaHome; - public BuildServerThread(String pipeName, String bundleDirectory, String javaHome) { + public BuildServerThread(String pipeName, String bundleDirectory) { this.pipeName = pipeName; this.bundleDirectory = bundleDirectory; - this.javaHome = javaHome; } @Override public void run() { try { - String[] classpaths = getBuildServerClasspath(); - - String pluginPath = getBuildServerPluginPath(); - - List command = new ArrayList<>(); - command.add(this.javaHome); - if (Boolean.parseBoolean(System.getenv("DEBUG_GRADLE_BUILD_SERVER"))) { - command.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8989"); - } - command.add("--add-opens=java.base/java.lang=ALL-UNNAMED"); - command.add("--add-opens=java.base/java.io=ALL-UNNAMED"); - command.add("--add-opens=java.base/java.util=ALL-UNNAMED"); - command.add("-Dplugin.dir=" + pluginPath); - command.add("-cp"); - command.add(String.join(getClasspathSeparator(), classpaths)); - command.add("com.microsoft.java.bs.core.Launcher"); - command.add("--pipe=" + this.pipeName); - - ProcessBuilder build = new ProcessBuilder(command); - build.start(); - } catch (IOException e) { + System.setProperty("plugin.dir", getBuildServerPluginPath()); + String[] args = {"--pipe=" + this.pipeName}; + Launcher.main(args); + } catch (Exception e) { e.printStackTrace(); } } - private String[] getBuildServerClasspath() { - return new String[]{Paths.get(bundleDirectory, "server.jar").toString(), - Paths.get(bundleDirectory, "runtime").toString() + File.separatorChar + "*"}; - } - private String getBuildServerPluginPath() { return Paths.get(bundleDirectory, "plugins").toString(); } - - private String getClasspathSeparator() { - String os = System.getProperty("os.name").toLowerCase(); - - if (os.contains("win")) { - return ";"; - } - return ":"; - } } diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java index 29da0f4a0..b15acba99 100644 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java @@ -61,13 +61,11 @@ public static void main(String[] args) throws Exception { int gradleServerPort = Integer.parseInt(Utils.validateRequiredParam(params, "port")); String buildServerPipeName = Utils.validateRequiredParam(params, "pipeName"); String bundleDirectory = Utils.validateRequiredParam(params, "bundleDir"); - // JavaExecutablePath is optional. Null means that the build server will not be - // started. - String javaExecutablePath = params.get("javaExecPath"); + boolean startBuildServer = Boolean.parseBoolean(Utils.validateRequiredParam(params, "startBuildServer")); startGradleServerThread(gradleServerPort); - if (javaExecutablePath != null) { - startBuildServerThread(buildServerPipeName, bundleDirectory, javaExecutablePath); + if (startBuildServer) { + startBuildServerThread(buildServerPipeName, bundleDirectory); } } @@ -84,9 +82,8 @@ private static void startGradleServerThread(int port) throws InterruptedExceptio serverThread.start(); } - private static void startBuildServerThread(String pipeName, String directory, String javaPath) - throws InterruptedException { - BuildServerThread buildServerConnectionThread = new BuildServerThread(pipeName, directory, javaPath); + private static void startBuildServerThread(String pipeName, String directory) throws InterruptedException { + BuildServerThread buildServerConnectionThread = new BuildServerThread(pipeName, directory); Thread buildServerThread = new Thread(buildServerConnectionThread); buildServerThread.start(); } From 609a7812619359cc1925bac422b3193ec6ca491a Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Wed, 10 Jul 2024 16:07:18 +0800 Subject: [PATCH 17/31] Fix: handle empty param in bspProxy repost --- extension/src/bs/BspProxy.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/extension/src/bs/BspProxy.ts b/extension/src/bs/BspProxy.ts index cad50c184..c7d4088fd 100644 --- a/extension/src/bs/BspProxy.ts +++ b/extension/src/bs/BspProxy.ts @@ -52,11 +52,17 @@ export class BspProxy { buildServerConnection: rpc.MessageConnection | null ): void { importerConnection?.onRequest((method, params) => { - return buildServerConnection?.sendRequest(method, params ?? {}); + if (params !== null) { + return buildServerConnection?.sendRequest(method, params); + } + return buildServerConnection?.sendRequest(method); }); buildServerConnection?.onNotification((method, params) => { - importerConnection?.sendNotification(method, params ?? {}); + if (params !== null) { + return importerConnection?.sendNotification(method, params); + } + importerConnection?.sendNotification(method); }); } } From f5826683c6833fc0777e622bb2790c1427567d4c Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Thu, 11 Jul 2024 16:14:10 +0800 Subject: [PATCH 18/31] Fix: comments --- .../gradle/bs/importer/NamedPipeStream.java | 4 ++ extension/src/Extension.ts | 17 ++++++- extension/src/util/config.ts | 11 +---- extension/src/util/jdkUtils.ts | 45 +------------------ gradle-server/build.gradle | 4 +- .../badsyntax/gradle/BuildServerThread.java | 10 ++--- 6 files changed, 27 insertions(+), 64 deletions(-) diff --git a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java index 571533f74..862de589e 100644 --- a/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java +++ b/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/NamedPipeStream.java @@ -19,6 +19,7 @@ import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; import org.eclipse.core.runtime.Platform; +import com.microsoft.gradle.bs.importer.model.Telemetry; /** * A class to create a named pipe stream for the importer to communicate with the extension. @@ -85,6 +86,9 @@ private void initializeNamedPipe() { attempts++; } } + Telemetry telemetry = new Telemetry("importerConnectAttempts", attempts); + Utils.sendTelemetry(JavaLanguageServerPlugin.getProjectsManager().getConnection(), + telemetry); if (attempts == MAX_ATTEMPTS) { throw new RuntimeException("Failed to connect to the named pipe after " + MAX_ATTEMPTS + " attempts"); } diff --git a/extension/src/Extension.ts b/extension/src/Extension.ts index 324a58b64..13a8d25cc 100644 --- a/extension/src/Extension.ts +++ b/extension/src/Extension.ts @@ -1,4 +1,5 @@ import * as vscode from "vscode"; +import { commands, window } from "vscode"; import { logger, LogVerbosity, Logger } from "./logger"; import { Api } from "./api"; import { GradleClient } from "./client"; @@ -317,7 +318,21 @@ export class Extension { private async restartServer(): Promise { if (this.server.isReady()) { await this.client.cancelBuilds(); - await this.server.restart(); + // TODO: find a better way to restart task server separately + const msg = "Please reload to make the change take effect. Reload now?"; + const action = "Reload"; + window.showWarningMessage(msg, action).then((selection) => { + if (selection === action) { + sendInfo("", { + kind: "acceptServerRestart", + }); + commands.executeCommand("workbench.action.reloadWindow"); + } else { + sendInfo("", { + kind: "rejectServerRestart", + }); + } + }); } } diff --git a/extension/src/util/config.ts b/extension/src/util/config.ts index 522c06fce..db3c115ec 100644 --- a/extension/src/util/config.ts +++ b/extension/src/util/config.ts @@ -6,13 +6,7 @@ import { RootProject } from "../rootProject/RootProject"; import * as fs from "fs"; import * as fse from "fs-extra"; import * as path from "path"; -import { - sortJdksBySource, - findDefaultRuntimeFromSettings, - getMajorVersion, - listJdks, - sortJdksByVersion, -} from "./jdkUtils"; +import { findDefaultRuntimeFromSettings, getMajorVersion, listJdks } from "./jdkUtils"; type AutoDetect = "on" | "off"; export function getConfigIsAutoDetectionEnabled(rootProject: RootProject): boolean { @@ -47,7 +41,6 @@ export async function getJavaExecutablePath(): Promise { javaHome = getJavaHome() || undefined; if (javaHome) { javaVersion = await getMajorVersion(javaHome); - // Ensure the Java version is greater than 17 if (javaVersion >= REQUIRED_JDK_VERSION) { const javaExecPath = path.join(javaHome, "bin", JAVA_FILENAME); if (fs.existsSync(javaExecPath)) { @@ -59,11 +52,9 @@ export async function getJavaExecutablePath(): Promise { // search valid JDKs from env.JAVA_HOME, env.PATH, SDKMAN, jEnv, jabba, Common directories const javaRuntimes = await listJdks(); - sortJdksByVersion(javaRuntimes); const validJdks = javaRuntimes.filter((r) => r.version!.major >= REQUIRED_JDK_VERSION); if (validJdks.length > 0) { - sortJdksBySource(validJdks); javaHome = validJdks[0].homedir; javaVersion = validJdks[0].version!.major; } diff --git a/extension/src/util/jdkUtils.ts b/extension/src/util/jdkUtils.ts index f4f9c5cf0..fed3af316 100644 --- a/extension/src/util/jdkUtils.ts +++ b/extension/src/util/jdkUtils.ts @@ -1,5 +1,5 @@ // See: https://github.com/redhat-developer/vscode-java/blob/2015139c5773c0107f75d2289e3656f45cb38c98/src/jdkUtils.ts -import { getRuntime, findRuntimes, IJavaRuntime, getSources } from "jdk-utils"; +import { getRuntime, findRuntimes, IJavaRuntime } from "jdk-utils"; import * as vscode from "vscode"; import * as fs from "fs"; import * as path from "path"; @@ -50,46 +50,3 @@ export async function listJdks(force?: boolean): Promise { } return cachedJdks; } -/** - * Sort by source where JDk is located. - * The order is: - * 1. JDK_HOME, JAVA_HOME, PATH - * 2. JDK manager such as SDKMAN, jEnv, jabba, asdf - * 3. Common places such as /usr/lib/jvm - * 4. Others - */ -export function sortJdksBySource(jdks: IJavaRuntime[]) { - const rankedJdks = jdks as Array; - const env: string[] = ["JDK_HOME", "JAVA_HOME", "PATH"]; - const jdkManagers: string[] = ["SDKMAN", "jEnv", "jabba", "asdf"]; - for (const jdk of rankedJdks) { - const detectedSources: string[] = getSources(jdk); - for (const [index, source] of env.entries()) { - if (detectedSources.includes(source)) { - jdk.rank = index; // jdk from environment variables - break; - } - } - - if (jdk.rank) { - continue; - } - - const fromManager: boolean = detectedSources.some((source) => jdkManagers.includes(source)); - if (fromManager) { - jdk.rank = env.length + 1; // jdk from the jdk managers such as SDKMAN - } else if (!detectedSources.length) { - jdk.rank = env.length + 2; // jdk from common places - } else { - jdk.rank = env.length + 3; // jdk from other source such as ~/.gradle/jdks - } - } - rankedJdks.sort((a, b) => a.rank - b.rank); -} - -/** - * Sort by major version in descend order. - */ -export function sortJdksByVersion(jdks: IJavaRuntime[]) { - jdks.sort((a, b) => (b.version?.major ?? 0) - (a.version?.major ?? 0)); -} diff --git a/gradle-server/build.gradle b/gradle-server/build.gradle index 9aa7e2327..95b5730aa 100644 --- a/gradle-server/build.gradle +++ b/gradle-server/build.gradle @@ -13,9 +13,9 @@ java { dependencies { implementation project(":gradle-plugin-api") implementation files('build/libs/server.jar') - implementation fileTree(dir: 'build/libs/runtime', include: ['*.jar'], exclude: ['gradle-tooling-api-*.jar']) + implementation fileTree(dir: 'build/libs/runtime', include: ['*.jar'], exclude: ['gradle-tooling-api-*.jar', 'slf4j-api-*.jar']) implementation fileTree(dir: 'build/libs/plugins', include: ['*.jar']) - implementation "org.gradle:gradle-tooling-api:${toolingAPIVersion}" + implementation("org.gradle:gradle-tooling-api:${toolingAPIVersion}") implementation 'javax.annotation:javax.annotation-api:1.3.2' implementation "io.grpc:grpc-protobuf:${grpcVersion}" implementation "io.grpc:grpc-stub:${grpcVersion}" diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/BuildServerThread.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/BuildServerThread.java index b16b31972..88c072958 100644 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/BuildServerThread.java +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/BuildServerThread.java @@ -15,13 +15,9 @@ public BuildServerThread(String pipeName, String bundleDirectory) { @Override public void run() { - try { - System.setProperty("plugin.dir", getBuildServerPluginPath()); - String[] args = {"--pipe=" + this.pipeName}; - Launcher.main(args); - } catch (Exception e) { - e.printStackTrace(); - } + System.setProperty("plugin.dir", getBuildServerPluginPath()); + String[] args = {"--pipe=" + this.pipeName}; + Launcher.main(args); } private String getBuildServerPluginPath() { From 92149572cf59c0f087e0b0769570af3e471b1509 Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Fri, 12 Jul 2024 12:46:18 +0800 Subject: [PATCH 19/31] 1.Fix: comment 2.Refine debug setting logic 3.Rename: gradle service -> task server for better understanding --- .vscode/launch.json | 2 +- CONTRIBUTING.md | 4 +- extension/build.gradle | 1 + extension/src/Extension.ts | 51 ++++++++++++------- extension/src/server/GradleServer.ts | 13 ++--- .../github/badsyntax/gradle/GradleServer.java | 20 ++++---- .../{GradleService.java => TaskService.java} | 2 +- 7 files changed, 57 insertions(+), 36 deletions(-) rename gradle-server/src/main/java/com/github/badsyntax/gradle/{GradleService.java => TaskService.java} (97%) diff --git a/.vscode/launch.json b/.vscode/launch.json index 6b3e0dbe3..97080c446 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -54,7 +54,7 @@ "order": 2 }, "env": { - "GRADLE_SERVER_OPTS":"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8089" + "GRADLE_SERVER_OPTS":"-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8089" }, }, { diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9a73ee54a..cea07c049 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,7 +43,9 @@ The extension uses a Gradle plugin (`com.microsoft.gradle.GradlePlugin`) to get ## Debugging Gradle Server 1. Run vscode launch configuration `Debug Gradle Server & Extension`. -2. Run vscode launch configuration `Attach to Gradle Server`. +2. Run vscode launch configuration `Attach to Gradle Server` when you notice the `Gradle: Connecting...` message in the bottom status bar. + +> Note: If `Java: Error` message appear in the bottom status bar, it indicates that the connection attempt in step 2 was too slow. [GradleBuildClient](/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java#L107) requires the Gradle Server to be active to establish a connection. If this happens, retry the attachment more swiftly. ## Debugging Gradle Language Server (editing feature related) diff --git a/extension/build.gradle b/extension/build.gradle index 919aa1ff2..e7f533005 100644 --- a/extension/build.gradle +++ b/extension/build.gradle @@ -235,6 +235,7 @@ task copyBuildServerJars(type: Copy) { mustRunAfter copyDocs } +// TODO: This task can be removed once the build server is published. task copyBuildServerJarsToGradleServer(type: Copy) { dependsOn ':extension:buildBuildServer' from('./build-server-for-gradle/server/build/libs/') { diff --git a/extension/src/Extension.ts b/extension/src/Extension.ts index 13a8d25cc..dd7e911a8 100644 --- a/extension/src/Extension.ts +++ b/extension/src/Extension.ts @@ -307,7 +307,19 @@ export class Extension { this.gradleWrapperWatcher.onDidChange( instrumentOperation(GRADLE_PROPERTIES_FILE_CHANGE, async (_operationId: string, uri: vscode.Uri) => { logger.info("Gradle wrapper properties changed:", uri.fsPath); - await this.restartServer(); + const selection = await this.showRestartWindow(); + if (selection === "Reload") { + sendInfo("", { + kind: "wrapperPropertiesChangedReloadRequest", + data2: "true", + }); + await this.restartServer(); + } else { + sendInfo("", { + kind: "wrapperPropertiesChangedReloadRequest", + data2: "false", + }); + } if (isLanguageServerStarted) { void vscode.commands.executeCommand("gradle.distributionChanged"); } @@ -318,24 +330,17 @@ export class Extension { private async restartServer(): Promise { if (this.server.isReady()) { await this.client.cancelBuilds(); - // TODO: find a better way to restart task server separately - const msg = "Please reload to make the change take effect. Reload now?"; - const action = "Reload"; - window.showWarningMessage(msg, action).then((selection) => { - if (selection === action) { - sendInfo("", { - kind: "acceptServerRestart", - }); - commands.executeCommand("workbench.action.reloadWindow"); - } else { - sendInfo("", { - kind: "rejectServerRestart", - }); - } - }); + await commands.executeCommand("workbench.action.reloadWindow"); } } + private async showRestartWindow(): Promise { + const msg = "Please reload to make the change take effect. Reload now?"; + const action = "Reload"; + const selection = await window.showWarningMessage(msg, action); + return selection; + } + private refresh(): Thenable { return vscode.commands.executeCommand(COMMAND_REFRESH); } @@ -348,7 +353,19 @@ export class Extension { event.affectsConfiguration("java.jdt.ls.java.home") || event.affectsConfiguration("java.import.gradle.java.home") ) { - await this.restartServer(); + const selection = await this.showRestartWindow(); + if (selection === "Reload") { + sendInfo("", { + kind: "javaHomeChangedReloadRequest", + data2: "true", + }); + await this.restartServer(); + } else { + sendInfo("", { + kind: "javaHomeChangedReloadRequest", + data2: "false", + }); + } } else if ( event.affectsConfiguration("gradle.javaDebug.cleanOutput") || event.affectsConfiguration("gradle.nestedProjects") diff --git a/extension/src/server/GradleServer.ts b/extension/src/server/GradleServer.ts index a8e273368..2a4ee9ec2 100644 --- a/extension/src/server/GradleServer.ts +++ b/extension/src/server/GradleServer.ts @@ -20,7 +20,7 @@ export class GradleServer { private readonly _onDidStart: vscode.EventEmitter = new vscode.EventEmitter(); private readonly _onDidStop: vscode.EventEmitter = new vscode.EventEmitter(); private ready = false; - private gradleServerPort: number | undefined; + private taskServerPort: number | undefined; private restarting = false; public readonly onDidStart: vscode.Event = this._onDidStart.event; @@ -35,7 +35,7 @@ export class GradleServer { ) {} public async start(): Promise { - this.gradleServerPort = await getPort(); + this.taskServerPort = await getPort(); const cwd = this.context.asAbsolutePath("lib"); const cmd = path.join(cwd, getGradleServerCommand()); const env = await getGradleServerEnv(); @@ -47,14 +47,15 @@ export class GradleServer { let startBuildServer = "false"; if (redHatJavaInstalled()) { const javaExecPath = getRedHatJavaExecutablePath() || (await getJavaExecutablePath()); - if (javaExecPath === undefined) { + if (!javaExecPath) { await vscode.window.showErrorMessage(NO_JAVA_EXECUTABLE); + } else { + startBuildServer = "true"; } - startBuildServer = "true"; } const buildServerPipeName = this.bspProxy.getBuildServerPipeName(); const args = [ - `--port=${this.gradleServerPort}`, + `--port=${this.taskServerPort}`, `--pipeName=${buildServerPipeName}`, `--bundleDir=${bundleDirectory}`, `--startBuildServer=${startBuildServer}`, @@ -151,7 +152,7 @@ export class GradleServer { } public getPort(): number | undefined { - return this.gradleServerPort; + return this.taskServerPort; } public getOpts(): ServerOptions { diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java index b15acba99..69b06843b 100644 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java @@ -13,7 +13,7 @@ public class GradleServer { private static final Logger logger = LoggerFactory.getLogger(GradleServer.class.getName()); private final int port; - private final Server server; + private final Server taskServer; public GradleServer(int port) { this(ServerBuilder.forPort(port), port); @@ -21,12 +21,12 @@ public GradleServer(int port) { public GradleServer(ServerBuilder serverBuilder, int port) { this.port = port; - server = serverBuilder.addService(new GradleService()).build(); + taskServer = serverBuilder.addService(new TaskService()).build(); } @SuppressWarnings("java:S106") public void start() throws IOException { - server.start(); + taskServer.start(); logger.info("Gradle Server started, listening on {}", port); Runtime.getRuntime().addShutdownHook(new Thread() { @Override @@ -44,32 +44,32 @@ public void run() { } public void stop() throws InterruptedException { - if (server != null) { - server.shutdown().awaitTermination(30, TimeUnit.SECONDS); + if (taskServer != null) { + taskServer.shutdown().awaitTermination(30, TimeUnit.SECONDS); } } private void blockUntilShutdown() throws InterruptedException { - if (server != null) { - server.awaitTermination(); + if (taskServer != null) { + taskServer.awaitTermination(); } } public static void main(String[] args) throws Exception { Map params = Utils.parseArgs(args); - int gradleServerPort = Integer.parseInt(Utils.validateRequiredParam(params, "port")); + int taskServerPort = Integer.parseInt(Utils.validateRequiredParam(params, "port")); String buildServerPipeName = Utils.validateRequiredParam(params, "pipeName"); String bundleDirectory = Utils.validateRequiredParam(params, "bundleDir"); boolean startBuildServer = Boolean.parseBoolean(Utils.validateRequiredParam(params, "startBuildServer")); - startGradleServerThread(gradleServerPort); + startTaskServerThread(taskServerPort); if (startBuildServer) { startBuildServerThread(buildServerPipeName, bundleDirectory); } } - private static void startGradleServerThread(int port) throws InterruptedException { + private static void startTaskServerThread(int port) throws InterruptedException { GradleServer server = new GradleServer(port); Thread serverThread = new Thread(() -> { try { diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleService.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/TaskService.java similarity index 97% rename from gradle-server/src/main/java/com/github/badsyntax/gradle/GradleService.java rename to gradle-server/src/main/java/com/github/badsyntax/gradle/TaskService.java index 0cb85b29e..135be3e14 100644 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleService.java +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/TaskService.java @@ -10,7 +10,7 @@ import com.github.badsyntax.gradle.handlers.StopDaemonsHandler; import io.grpc.stub.StreamObserver; -public class GradleService extends GradleGrpc.GradleImplBase { +public class TaskService extends GradleGrpc.GradleImplBase { @Override public void getBuild(GetBuildRequest req, StreamObserver responseObserver) { From d7e366811e251a2aafa5b4bb215ebb3a19fdf11b Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Mon, 15 Jul 2024 15:59:54 +0800 Subject: [PATCH 20/31] 1.Add error handle for rpc connection 2.Add telemetry --- extension/src/Extension.ts | 7 +++++-- extension/src/bs/BspProxy.ts | 29 +++++++++++++++++++++++++++- extension/src/client/GradleClient.ts | 4 ++-- extension/src/server/GradleServer.ts | 17 +++++++++++++--- 4 files changed, 49 insertions(+), 8 deletions(-) diff --git a/extension/src/Extension.ts b/extension/src/Extension.ts index dd7e911a8..e69a0bcf0 100644 --- a/extension/src/Extension.ts +++ b/extension/src/Extension.ts @@ -81,12 +81,15 @@ export class Extension { const serverLogger = new Logger("gradle-server"); serverLogger.setLoggingChannel(loggingChannel); + const bspLogger = new Logger("bspProxy"); + bspLogger.setLoggingChannel(loggingChannel); + if (getConfigIsDebugEnabled()) { Logger.setLogVerbosity(LogVerbosity.DEBUG); } const statusBarItem = vscode.window.createStatusBarItem(); - this.bspProxy = new BspProxy(this.context); + this.bspProxy = new BspProxy(this.context, bspLogger); this.server = new GradleServer({ host: "localhost" }, context, serverLogger, this.bspProxy); this.client = new GradleClient(this.server, statusBarItem, clientLogger); this.pinnedTasksStore = new PinnedTasksStore(context); @@ -335,7 +338,7 @@ export class Extension { } private async showRestartWindow(): Promise { - const msg = "Please reload to make the change take effect. Reload now?"; + const msg = "Please reload the window to make the change take effect. Reload now?"; const action = "Reload"; const selection = await window.showWarningMessage(msg, action); return selection; diff --git a/extension/src/bs/BspProxy.ts b/extension/src/bs/BspProxy.ts index c7d4088fd..a84489477 100644 --- a/extension/src/bs/BspProxy.ts +++ b/extension/src/bs/BspProxy.ts @@ -2,6 +2,8 @@ import { JdtlsImporterConnector } from "./JdtlsImporterConnector"; import { BuildServerConnector } from "./BuildServerConnector"; import * as vscode from "vscode"; import * as rpc from "vscode-jsonrpc/node"; +import { Logger } from "../logger/index"; +import { sendInfo } from "vscode-extension-telemetry-wrapper"; /** * Forwards JSON-RPC messages between the build server and the Java JDT LS importer. @@ -15,7 +17,7 @@ export class BspProxy { private buildServerConnector: BuildServerConnector; private jdtlsImporterConnector: JdtlsImporterConnector; - constructor(context: vscode.ExtensionContext) { + constructor(context: vscode.ExtensionContext, private readonly logger: Logger) { this.buildServerConnector = new BuildServerConnector(); this.jdtlsImporterConnector = new JdtlsImporterConnector(context); } @@ -64,5 +66,30 @@ export class BspProxy { } importerConnection?.sendNotification(method); }); + importerConnection?.onError(([error, message, code]) => { + this.logger.error(`Error on importerConnection: ${error.message}`); + sendInfo("", { + kind: "bspProxy-importerConnectionError", + data2: JSON.stringify({ + error, + message, + code, + }), + }); + // TODO: Implement more specific error handling logic here + }); + + buildServerConnection?.onError(([error, message, code]) => { + this.logger.error(`Error on buildServerConnection: ${error.message}`); + sendInfo("", { + kind: "bspProxy-importerConnectionError", + data2: JSON.stringify({ + error, + message, + code, + }), + }); + // TODO: Implement more specific error handling logic here + }); } } diff --git a/extension/src/client/GradleClient.ts b/extension/src/client/GradleClient.ts index 43178a93b..5f6fcb1dd 100644 --- a/extension/src/client/GradleClient.ts +++ b/extension/src/client/GradleClient.ts @@ -1,7 +1,7 @@ import * as vscode from "vscode"; import * as grpc from "@grpc/grpc-js"; import { connectivityState as ConnectivityState } from "@grpc/grpc-js"; - +import { commands } from "vscode"; import { Output, GetBuildRequest, @@ -495,7 +495,7 @@ export class GradleClient implements vscode.Disposable { OPT_RESTART ); if (input === OPT_RESTART) { - await this.handleServerStart(); + await commands.executeCommand("workbench.action.reloadWindow"); } } diff --git a/extension/src/server/GradleServer.ts b/extension/src/server/GradleServer.ts index 2a4ee9ec2..bf2bc6405 100644 --- a/extension/src/server/GradleServer.ts +++ b/extension/src/server/GradleServer.ts @@ -3,6 +3,8 @@ import * as path from "path"; import * as cp from "child_process"; import * as getPort from "get-port"; import * as kill from "tree-kill"; +import { commands } from "vscode"; +import { sendInfo } from "vscode-extension-telemetry-wrapper"; import { getGradleServerCommand, getGradleServerEnv } from "./serverUtil"; import { Logger } from "../logger/index"; import { NO_JAVA_EXECUTABLE } from "../constant"; @@ -92,13 +94,22 @@ export class GradleServer { } public async showRestartMessage(): Promise { - const OPT_RESTART = "Restart Server"; + const OPT_RESTART = "Reload"; const input = await vscode.window.showErrorMessage( - "No connection to gradle server. Try restarting the server.", + "No connection to gradle server. Try reload the window.", OPT_RESTART ); if (input === OPT_RESTART) { - await this.start(); + sendInfo("", { + kind: "taskServerRestart", + data2: "true", + }); + await commands.executeCommand("workbench.action.reloadWindow"); + } else { + sendInfo("", { + kind: "taskServerRestart", + data2: "false", + }); } } From 807f9a9e5c8456ab6bd69e9b27baacad1c21fa10 Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Tue, 16 Jul 2024 10:08:17 +0800 Subject: [PATCH 21/31] Fix: typo --- extension/src/client/GradleClient.ts | 4 ++-- extension/src/server/GradleServer.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extension/src/client/GradleClient.ts b/extension/src/client/GradleClient.ts index 5f6fcb1dd..d5eea24d6 100644 --- a/extension/src/client/GradleClient.ts +++ b/extension/src/client/GradleClient.ts @@ -489,9 +489,9 @@ export class GradleClient implements vscode.Disposable { }; public async showRestartMessage(): Promise { - const OPT_RESTART = "Re-connect Client"; + const OPT_RESTART = "Reload"; const input = await vscode.window.showErrorMessage( - "The Gradle client was unable to connect. Try re-connecting.", + "The Gradle client was unable to connect. Try reloading the window.", OPT_RESTART ); if (input === OPT_RESTART) { diff --git a/extension/src/server/GradleServer.ts b/extension/src/server/GradleServer.ts index bf2bc6405..c7952d0d2 100644 --- a/extension/src/server/GradleServer.ts +++ b/extension/src/server/GradleServer.ts @@ -96,7 +96,7 @@ export class GradleServer { public async showRestartMessage(): Promise { const OPT_RESTART = "Reload"; const input = await vscode.window.showErrorMessage( - "No connection to gradle server. Try reload the window.", + "No connection to gradle server. Try reloading the window.", OPT_RESTART ); if (input === OPT_RESTART) { From 9deaeb6e1f8e1faa9f15b4f6e50bac3250d3898e Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Wed, 17 Jul 2024 13:13:04 +0800 Subject: [PATCH 22/31] 1.Update github workflow to feat local dependency 2.Update GraleClient restart logic 3.Update gradle-server jdk version>=17 --- .github/workflows/main.yml | 14 +++++++++++--- extension/src/client/GradleClient.ts | 7 +++---- gradle-server/build.gradle | 4 ++-- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 399da1368..23c818481 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,10 +13,10 @@ jobs: - uses: actions/checkout@v2 with: fetch-depth: 0 # required by sonarqube - - name: Use Java 11 + - name: Use Java 17 uses: actions/setup-java@v1 with: - java-version: "11" + java-version: "17" architecture: x64 - name: Use Node 16.14.2 uses: actions/setup-node@v4 @@ -31,6 +31,14 @@ jobs: key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- + - name: Checkout microsoft/build-server-for-gradle + uses: actions/checkout@v2 + with: + repository: microsoft/build-server-for-gradle + path: extension/build-server-for-gradle + - name: Build Jars + run: ../gradlew buildJars + working-directory: extension/ - name: Lint uses: gradle/gradle-build-action@v2 with: @@ -61,7 +69,7 @@ jobs: fail-fast: false matrix: node-version: [16.14.2] - java-version: ["8", "11", "17", "21"] + java-version: ["17", "21"] os: [ubuntu-latest, windows-latest, macos-latest] steps: - uses: actions/checkout@v2 diff --git a/extension/src/client/GradleClient.ts b/extension/src/client/GradleClient.ts index d5eea24d6..c5af5809b 100644 --- a/extension/src/client/GradleClient.ts +++ b/extension/src/client/GradleClient.ts @@ -1,7 +1,6 @@ import * as vscode from "vscode"; import * as grpc from "@grpc/grpc-js"; import { connectivityState as ConnectivityState } from "@grpc/grpc-js"; -import { commands } from "vscode"; import { Output, GetBuildRequest, @@ -489,13 +488,13 @@ export class GradleClient implements vscode.Disposable { }; public async showRestartMessage(): Promise { - const OPT_RESTART = "Reload"; + const OPT_RESTART = "Re-connect Client"; const input = await vscode.window.showErrorMessage( - "The Gradle client was unable to connect. Try reloading the window.", + "The Gradle client was unable to connect. Try re-connecting.", OPT_RESTART ); if (input === OPT_RESTART) { - await commands.executeCommand("workbench.action.reloadWindow"); + await this.handleServerStart(); } } diff --git a/gradle-server/build.gradle b/gradle-server/build.gradle index 95b5730aa..7dd6c1fac 100644 --- a/gradle-server/build.gradle +++ b/gradle-server/build.gradle @@ -6,8 +6,8 @@ plugins { description = 'vscode-gradle :: gradle-server' java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } dependencies { From 6f1701f203409161290f7d1aae5f636e2cb39b20 Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Wed, 17 Jul 2024 14:00:26 +0800 Subject: [PATCH 23/31] Update: lint ignore --- extension/.eslintignore | 2 ++ extension/.prettierignore | 2 ++ 2 files changed, 4 insertions(+) diff --git a/extension/.eslintignore b/extension/.eslintignore index 2e8035a6e..3e65bd4bc 100644 --- a/extension/.eslintignore +++ b/extension/.eslintignore @@ -9,3 +9,5 @@ src/proto/ build/ beta/ webpack.config.js +build-server-for-gradle/ +bin/ diff --git a/extension/.prettierignore b/extension/.prettierignore index 84d8e1e9c..290df17f4 100644 --- a/extension/.prettierignore +++ b/extension/.prettierignore @@ -9,3 +9,5 @@ lib/ src/proto/ build/ src/java-test-runner.api.ts +build-server-for-gradle/ +bin/ From 3291eb66d340164e3bcae93d8d313716dbb5bb94 Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Wed, 17 Jul 2024 14:44:11 +0800 Subject: [PATCH 24/31] fix: powermock jdk17 --- gradle-server/build.gradle | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/gradle-server/build.gradle b/gradle-server/build.gradle index 7dd6c1fac..a4cdf4ece 100644 --- a/gradle-server/build.gradle +++ b/gradle-server/build.gradle @@ -135,5 +135,15 @@ project.tasks.named("processResources") { duplicatesStrategy = 'include' } +test { + jvmArgs '--add-opens=java.base/java.lang=ALL-UNNAMED', + '--add-opens=java.base/java.util=ALL-UNNAMED', + '--add-opens=java.base/java.lang.reflect=ALL-UNNAMED', + '--add-opens=java.base/java.nio.file=ALL-UNNAMED', + '--add-opens=java.base/java.io=ALL-UNNAMED', + '--add-opens=java.base/sun.nio.fs=ALL-UNNAMED' +} + + compileJava.dependsOn 'generateProto', 'spotlessCheck' assemble.dependsOn serverStartScripts From 8b5e1d9ce968c3abdddc45e55fb14754027096fe Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Fri, 19 Jul 2024 13:38:53 +0800 Subject: [PATCH 25/31] Refine jdk search logic that start gradle-server --- CONTRIBUTING.md | 8 ++- extension/src/Extension.ts | 6 +- extension/src/client/GradleClient.ts | 1 + .../src/languageServer/languageServer.ts | 7 ++- extension/src/server/GradleServer.ts | 34 +++++------ extension/src/server/serverUtil.ts | 4 +- extension/src/util/config.ts | 60 ++++++------------- .../github/badsyntax/gradle/GradleServer.java | 5 +- 8 files changed, 51 insertions(+), 74 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cea07c049..ed6a0b136 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ Start by opening an issue using one of the issue templates, or propose a change ### Build Gradle Server and Gradle Language Server. 1. Install [nvm](https://github.com/nvm-sh/nvm) -2. Install [Java version >= 8](https://adoptium.net/) +2. Install [Java version >= 17](https://adoptium.net/) 3. Change directory to the root of the project 4. Select Node version: `nvm use` 5. If using an Apple M1: @@ -45,7 +45,11 @@ The extension uses a Gradle plugin (`com.microsoft.gradle.GradlePlugin`) to get 1. Run vscode launch configuration `Debug Gradle Server & Extension`. 2. Run vscode launch configuration `Attach to Gradle Server` when you notice the `Gradle: Connecting...` message in the bottom status bar. -> Note: If `Java: Error` message appear in the bottom status bar, it indicates that the connection attempt in step 2 was too slow. [GradleBuildClient](/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java#L107) requires the Gradle Server to be active to establish a connection. If this happens, retry the attachment more swiftly. +> **Note:** If the "Java: Error" message appears in the bottom status bar and the following error is logged in the `.log` file: +> ```java +> java.lang.NullPointerException: Cannot invoke "ch.epfl.scala.bsp4j.WorkspaceBuildTargetsResult.getTargets()" +> ``` +> it indicates that the connection attempt to the Gradle Server was too slow. The [GradleBuildClient](/extension/jdtls.ext/com.microsoft.gradle.bs.importer/src/com/microsoft/gradle/bs/importer/ImporterPlugin.java#L107) requires an active Gradle Server to successfully establish a connection. If you encounter this issue, please retry the connection promptly to avoid this error. ## Debugging Gradle Language Server (editing feature related) diff --git a/extension/src/Extension.ts b/extension/src/Extension.ts index e69a0bcf0..b810ba547 100644 --- a/extension/src/Extension.ts +++ b/extension/src/Extension.ts @@ -331,10 +331,8 @@ export class Extension { } private async restartServer(): Promise { - if (this.server.isReady()) { - await this.client.cancelBuilds(); - await commands.executeCommand("workbench.action.reloadWindow"); - } + await this.client.cancelBuilds(); + await commands.executeCommand("workbench.action.reloadWindow"); } private async showRestartWindow(): Promise { diff --git a/extension/src/client/GradleClient.ts b/extension/src/client/GradleClient.ts index c5af5809b..43178a93b 100644 --- a/extension/src/client/GradleClient.ts +++ b/extension/src/client/GradleClient.ts @@ -1,6 +1,7 @@ import * as vscode from "vscode"; import * as grpc from "@grpc/grpc-js"; import { connectivityState as ConnectivityState } from "@grpc/grpc-js"; + import { Output, GetBuildRequest, diff --git a/extension/src/languageServer/languageServer.ts b/extension/src/languageServer/languageServer.ts index 6f80ede5c..d40d2cbd7 100644 --- a/extension/src/languageServer/languageServer.ts +++ b/extension/src/languageServer/languageServer.ts @@ -15,7 +15,8 @@ import { getConfigJavaImportGradleUserHome, getConfigJavaImportGradleVersion, getConfigJavaImportGradleWrapperEnabled, - getSupportedJavaHome, + findValidJavaHome, + getJavaExecutablePathFromJavaHome, } from "../util/config"; import { prepareLanguageServerParams } from "./utils"; const CHANNEL_NAME = "Gradle for Java (Language Server)"; @@ -54,10 +55,10 @@ export async function startLanguageServer( serverOptions = awaitServerConnection.bind(null, port); } else { // keep consistent with gRPC server - const javaHome = await getSupportedJavaHome(); + const javaHome = await findValidJavaHome(); let javaCommand; if (javaHome) { - javaCommand = path.join(javaHome, "bin", "java"); + javaCommand = getJavaExecutablePathFromJavaHome(javaHome); } else { if (!checkEnvJavaExecutable()) { // we have already show error message in gRPC server for no java executable found, so here we will just reject and return diff --git a/extension/src/server/GradleServer.ts b/extension/src/server/GradleServer.ts index c7952d0d2..db504c2dc 100644 --- a/extension/src/server/GradleServer.ts +++ b/extension/src/server/GradleServer.ts @@ -8,7 +8,7 @@ import { sendInfo } from "vscode-extension-telemetry-wrapper"; import { getGradleServerCommand, getGradleServerEnv } from "./serverUtil"; import { Logger } from "../logger/index"; import { NO_JAVA_EXECUTABLE } from "../constant"; -import { getRedHatJavaExecutablePath, getJavaExecutablePath, redHatJavaInstalled } from "../util/config"; +import { redHatJavaInstalled } from "../util/config"; import { BspProxy } from "../bs/BspProxy"; const SERVER_LOGLEVEL_REGEX = /^\[([A-Z]+)\](.*)$/; @@ -46,22 +46,12 @@ export class GradleServer { await vscode.window.showErrorMessage(NO_JAVA_EXECUTABLE); return; } - let startBuildServer = "false"; - if (redHatJavaInstalled()) { - const javaExecPath = getRedHatJavaExecutablePath() || (await getJavaExecutablePath()); - if (!javaExecPath) { - await vscode.window.showErrorMessage(NO_JAVA_EXECUTABLE); - } else { - startBuildServer = "true"; - } + const startBuildServer = redHatJavaInstalled() ? "true" : "false"; + const args = [`--port=${this.taskServerPort}`, `--startBuildServer=${startBuildServer}`]; + if (startBuildServer === "true") { + const buildServerPipeName = this.bspProxy.getBuildServerPipeName(); + args.push(`--pipeName=${buildServerPipeName}`, `--bundleDir=${bundleDirectory}`); } - const buildServerPipeName = this.bspProxy.getBuildServerPipeName(); - const args = [ - `--port=${this.taskServerPort}`, - `--pipeName=${buildServerPipeName}`, - `--bundleDir=${bundleDirectory}`, - `--startBuildServer=${startBuildServer}`, - ]; this.logger.debug(`Gradle Server cmd: ${cmd} ${args.join(" ")}`); this.process = cp.spawn(`"${cmd}"`, args, { @@ -82,7 +72,7 @@ export class GradleServer { this.restarting = false; await this.start(); } else if (code !== 0) { - await this.handleServerStartError(); + await this.handleServerStartError(code); } }); @@ -101,13 +91,13 @@ export class GradleServer { ); if (input === OPT_RESTART) { sendInfo("", { - kind: "taskServerRestart", + kind: "serverProcessExitRestart", data2: "true", }); await commands.executeCommand("workbench.action.reloadWindow"); } else { sendInfo("", { - kind: "taskServerRestart", + kind: "serverProcessExitRestart", data2: "false", }); } @@ -145,7 +135,11 @@ export class GradleServer { } } - private async handleServerStartError(): Promise { + private async handleServerStartError(code: number | null): Promise { + sendInfo("", { + kind: "serverProcessExit", + data2: code ? code.toString() : "", + }); await this.showRestartMessage(); } diff --git a/extension/src/server/serverUtil.ts b/extension/src/server/serverUtil.ts index 4462b5727..6034627ab 100644 --- a/extension/src/server/serverUtil.ts +++ b/extension/src/server/serverUtil.ts @@ -1,4 +1,4 @@ -import { checkEnvJavaExecutable, getSupportedJavaHome } from "../util/config"; +import { checkEnvJavaExecutable, findValidJavaHome, getRedHatJavaEmbeddedJRE } from "../util/config"; export function getGradleServerCommand(): string { const platform = process.platform; @@ -16,7 +16,7 @@ export interface ProcessEnv { } export async function getGradleServerEnv(): Promise { - const javaHome = await getSupportedJavaHome(); + const javaHome = getRedHatJavaEmbeddedJRE() || (await findValidJavaHome()); const env = { ...process.env }; if (javaHome) { Object.assign(env, { diff --git a/extension/src/util/config.ts b/extension/src/util/config.ts index db3c115ec..3ddff44ce 100644 --- a/extension/src/util/config.ts +++ b/extension/src/util/config.ts @@ -1,9 +1,8 @@ import { execSync } from "child_process"; -import { getRuntime, JAVA_FILENAME } from "jdk-utils"; +import { JAVA_FILENAME } from "jdk-utils"; import * as vscode from "vscode"; import { GradleConfig } from "../proto/gradle_pb"; import { RootProject } from "../rootProject/RootProject"; -import * as fs from "fs"; import * as fse from "fs-extra"; import * as path from "path"; import { findDefaultRuntimeFromSettings, getMajorVersion, listJdks } from "./jdkUtils"; @@ -29,11 +28,13 @@ export function getConfigJavaImportGradleJavaHome(): string | null { return vscode.workspace.getConfiguration("java").get("import.gradle.java.home", null); } -export async function getJavaExecutablePath(): Promise { - const REQUIRED_JDK_VERSION = 17; +export function getJavaExecutablePathFromJavaHome(javaHome: string): string { + return path.join(javaHome, "bin", JAVA_FILENAME); +} - // search from jdt.ls.java.home, java.home - const javaHomeGetters = [getJdtlsConfigJavaHome, getConfigJavaHome]; +export async function findValidJavaHome(): Promise { + const REQUIRED_JDK_VERSION = 17; + const javaHomeGetters = [getJdtlsConfigJavaHome, getConfigJavaHome, getConfigJavaImportGradleJavaHome]; let javaHome: string | undefined = undefined; let javaVersion = 0; @@ -42,31 +43,25 @@ export async function getJavaExecutablePath(): Promise { if (javaHome) { javaVersion = await getMajorVersion(javaHome); if (javaVersion >= REQUIRED_JDK_VERSION) { - const javaExecPath = path.join(javaHome, "bin", JAVA_FILENAME); - if (fs.existsSync(javaExecPath)) { - return javaExecPath; - } + return javaHome; } } } - // search valid JDKs from env.JAVA_HOME, env.PATH, SDKMAN, jEnv, jabba, Common directories + // Search valid JDKs from env.JAVA_HOME, env.PATH, SDKMAN, jEnv, jabba, common directories const javaRuntimes = await listJdks(); - const validJdks = javaRuntimes.filter((r) => r.version!.major >= REQUIRED_JDK_VERSION); if (validJdks.length > 0) { - javaHome = validJdks[0].homedir; - javaVersion = validJdks[0].version!.major; + return validJdks[0].homedir; } - //search java.configuration.runtimes if still not found - if (!javaHome) { - javaHome = await findDefaultRuntimeFromSettings(); - javaVersion = await getMajorVersion(javaHome); - } - if (javaHome && javaVersion >= REQUIRED_JDK_VERSION) { - return path.join(javaHome, "bin", JAVA_FILENAME); + // Search java.configuration.runtimes if still not found + javaHome = await findDefaultRuntimeFromSettings(); + javaVersion = await getMajorVersion(javaHome); + if (javaVersion >= REQUIRED_JDK_VERSION) { + return javaHome; } + return undefined; } @@ -74,7 +69,7 @@ export function redHatJavaInstalled(): boolean { return !!vscode.extensions.getExtension("redhat.java"); } -export function getRedHatJavaExecutablePath(): string | undefined { +export function getRedHatJavaEmbeddedJRE(): string | undefined { if (!redHatJavaInstalled()) { return undefined; } @@ -83,31 +78,14 @@ export function getRedHatJavaExecutablePath(): string | undefined { if (fse.existsSync(jreHome) && fse.statSync(jreHome).isDirectory()) { const candidates = fse.readdirSync(jreHome); for (const candidate of candidates) { - const javaExecutable = path.join(jreHome, candidate, "bin", JAVA_FILENAME); - if (fse.existsSync(javaExecutable)) { - return javaExecutable; + if (fse.existsSync(path.join(jreHome, candidate, "bin", JAVA_FILENAME))) { + return path.join(jreHome, candidate); } } } return undefined; } -export function getConfigGradleJavaHome(): string | null { - return getConfigJavaImportGradleJavaHome() || getJdtlsConfigJavaHome() || getConfigJavaHome(); -} - -export async function getSupportedJavaHome(): Promise { - const javaHome = getConfigGradleJavaHome() || process.env.JAVA_HOME; - if (javaHome) { - const runtime = await getRuntime(javaHome, { withVersion: true }); - if (runtime?.version) { - // check the JDK version of given java home is supported, otherwise return undefined - return runtime.version.major >= 8 && runtime.version.major <= 21 ? javaHome : undefined; - } - } - return undefined; -} - export function checkEnvJavaExecutable(): boolean { try { execSync("java -version", { stdio: "pipe" }); diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java index 69b06843b..0adb88616 100644 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java @@ -59,12 +59,13 @@ public static void main(String[] args) throws Exception { Map params = Utils.parseArgs(args); int taskServerPort = Integer.parseInt(Utils.validateRequiredParam(params, "port")); - String buildServerPipeName = Utils.validateRequiredParam(params, "pipeName"); - String bundleDirectory = Utils.validateRequiredParam(params, "bundleDir"); boolean startBuildServer = Boolean.parseBoolean(Utils.validateRequiredParam(params, "startBuildServer")); startTaskServerThread(taskServerPort); + if (startBuildServer) { + String buildServerPipeName = Utils.validateRequiredParam(params, "pipeName"); + String bundleDirectory = Utils.validateRequiredParam(params, "bundleDir"); startBuildServerThread(buildServerPipeName, bundleDirectory); } } From b3c13e398b5b892dbb3b3dfc90b7d24db7511b1a Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Fri, 19 Jul 2024 15:02:35 +0800 Subject: [PATCH 26/31] Fix: comments --- extension/src/Extension.ts | 6 +++--- extension/src/server/GradleServer.ts | 6 +++--- .../java/com/github/badsyntax/gradle/GradleServer.java | 9 ++++----- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/extension/src/Extension.ts b/extension/src/Extension.ts index b810ba547..9be11cf7e 100644 --- a/extension/src/Extension.ts +++ b/extension/src/Extension.ts @@ -332,12 +332,12 @@ export class Extension { private async restartServer(): Promise { await this.client.cancelBuilds(); - await commands.executeCommand("workbench.action.reloadWindow"); + await commands.executeCommand("workbench.action.restartExtensionHost"); } private async showRestartWindow(): Promise { - const msg = "Please reload the window to make the change take effect. Reload now?"; - const action = "Reload"; + const msg = "Please restart the extension to make the change take effect. Restart now?"; + const action = "Restart"; const selection = await window.showWarningMessage(msg, action); return selection; } diff --git a/extension/src/server/GradleServer.ts b/extension/src/server/GradleServer.ts index db504c2dc..6c984ff3a 100644 --- a/extension/src/server/GradleServer.ts +++ b/extension/src/server/GradleServer.ts @@ -84,9 +84,9 @@ export class GradleServer { } public async showRestartMessage(): Promise { - const OPT_RESTART = "Reload"; + const OPT_RESTART = "Restart"; const input = await vscode.window.showErrorMessage( - "No connection to gradle server. Try reloading the window.", + "No connection to gradle server. Try restarting the server.", OPT_RESTART ); if (input === OPT_RESTART) { @@ -94,7 +94,7 @@ export class GradleServer { kind: "serverProcessExitRestart", data2: "true", }); - await commands.executeCommand("workbench.action.reloadWindow"); + await commands.executeCommand("workbench.action.restartExtensionHost"); } else { sendInfo("", { kind: "serverProcessExitRestart", diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java index 0adb88616..bdf611ecd 100644 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleServer.java @@ -59,10 +59,9 @@ public static void main(String[] args) throws Exception { Map params = Utils.parseArgs(args); int taskServerPort = Integer.parseInt(Utils.validateRequiredParam(params, "port")); - boolean startBuildServer = Boolean.parseBoolean(Utils.validateRequiredParam(params, "startBuildServer")); - startTaskServerThread(taskServerPort); + boolean startBuildServer = Boolean.parseBoolean(Utils.validateRequiredParam(params, "startBuildServer")); if (startBuildServer) { String buildServerPipeName = Utils.validateRequiredParam(params, "pipeName"); String bundleDirectory = Utils.validateRequiredParam(params, "bundleDir"); @@ -70,20 +69,20 @@ public static void main(String[] args) throws Exception { } } - private static void startTaskServerThread(int port) throws InterruptedException { + private static void startTaskServerThread(int port) { GradleServer server = new GradleServer(port); Thread serverThread = new Thread(() -> { try { server.start(); server.blockUntilShutdown(); } catch (IOException | InterruptedException e) { - e.printStackTrace(); + throw new RuntimeException(e); } }); serverThread.start(); } - private static void startBuildServerThread(String pipeName, String directory) throws InterruptedException { + private static void startBuildServerThread(String pipeName, String directory) { BuildServerThread buildServerConnectionThread = new BuildServerThread(pipeName, directory); Thread buildServerThread = new Thread(buildServerConnectionThread); buildServerThread.start(); From d59d60587f907d393d00c30f47ad11e9e38768a5 Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Fri, 19 Jul 2024 16:08:11 +0800 Subject: [PATCH 27/31] Fix: comment --- extension/src/util/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/src/util/config.ts b/extension/src/util/config.ts index 3ddff44ce..8dc484d3b 100644 --- a/extension/src/util/config.ts +++ b/extension/src/util/config.ts @@ -7,6 +7,7 @@ import * as fse from "fs-extra"; import * as path from "path"; import { findDefaultRuntimeFromSettings, getMajorVersion, listJdks } from "./jdkUtils"; type AutoDetect = "on" | "off"; +const REQUIRED_JDK_VERSION = 17; export function getConfigIsAutoDetectionEnabled(rootProject: RootProject): boolean { return ( @@ -33,7 +34,6 @@ export function getJavaExecutablePathFromJavaHome(javaHome: string): string { } export async function findValidJavaHome(): Promise { - const REQUIRED_JDK_VERSION = 17; const javaHomeGetters = [getJdtlsConfigJavaHome, getConfigJavaHome, getConfigJavaImportGradleJavaHome]; let javaHome: string | undefined = undefined; let javaVersion = 0; From 7507a3c418d754cda3f03d52fabc16f1b6c3c6d6 Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Mon, 22 Jul 2024 10:29:23 +0800 Subject: [PATCH 28/31] fix comments --- extension/src/Extension.ts | 34 ++++++++++------------------ extension/src/bs/BspProxy.ts | 18 +++++---------- extension/src/server/GradleServer.ts | 17 +++++--------- 3 files changed, 24 insertions(+), 45 deletions(-) diff --git a/extension/src/Extension.ts b/extension/src/Extension.ts index 9be11cf7e..05fbc6925 100644 --- a/extension/src/Extension.ts +++ b/extension/src/Extension.ts @@ -38,6 +38,7 @@ import { GradleBuildContentProvider } from "./client/GradleBuildContentProvider" import { BuildServerController } from "./bs/BuildServerController"; import { GradleTestRunner } from "./bs/GradleTestRunner"; import { BspProxy } from "./bs/BspProxy"; +const OPT_RESTART = "Restart"; export class Extension { private readonly bspProxy: BspProxy; @@ -311,17 +312,12 @@ export class Extension { instrumentOperation(GRADLE_PROPERTIES_FILE_CHANGE, async (_operationId: string, uri: vscode.Uri) => { logger.info("Gradle wrapper properties changed:", uri.fsPath); const selection = await this.showRestartWindow(); - if (selection === "Reload") { - sendInfo("", { - kind: "wrapperPropertiesChangedReloadRequest", - data2: "true", - }); + sendInfo("", { + kind: "wrapperPropertiesChangedReloadRequest", + data2: selection === OPT_RESTART ? "true" : "false", + }); + if (selection === OPT_RESTART) { await this.restartServer(); - } else { - sendInfo("", { - kind: "wrapperPropertiesChangedReloadRequest", - data2: "false", - }); } if (isLanguageServerStarted) { void vscode.commands.executeCommand("gradle.distributionChanged"); @@ -337,8 +333,7 @@ export class Extension { private async showRestartWindow(): Promise { const msg = "Please restart the extension to make the change take effect. Restart now?"; - const action = "Restart"; - const selection = await window.showWarningMessage(msg, action); + const selection = await window.showWarningMessage(msg, OPT_RESTART); return selection; } @@ -355,17 +350,12 @@ export class Extension { event.affectsConfiguration("java.import.gradle.java.home") ) { const selection = await this.showRestartWindow(); - if (selection === "Reload") { - sendInfo("", { - kind: "javaHomeChangedReloadRequest", - data2: "true", - }); + sendInfo("", { + kind: "javaHomeChangedReloadRequest", + data2: selection === OPT_RESTART ? "true" : "false", + }); + if (selection === OPT_RESTART) { await this.restartServer(); - } else { - sendInfo("", { - kind: "javaHomeChangedReloadRequest", - data2: "false", - }); } } else if ( event.affectsConfiguration("gradle.javaDebug.cleanOutput") || diff --git a/extension/src/bs/BspProxy.ts b/extension/src/bs/BspProxy.ts index a84489477..4ab77080e 100644 --- a/extension/src/bs/BspProxy.ts +++ b/extension/src/bs/BspProxy.ts @@ -66,28 +66,22 @@ export class BspProxy { } importerConnection?.sendNotification(method); }); - importerConnection?.onError(([error, message, code]) => { + importerConnection?.onError(([error]) => { this.logger.error(`Error on importerConnection: ${error.message}`); sendInfo("", { kind: "bspProxy-importerConnectionError", - data2: JSON.stringify({ - error, - message, - code, - }), + message: error.message, + errorStack: error.stack ? error.stack.toString() : "", }); // TODO: Implement more specific error handling logic here }); - buildServerConnection?.onError(([error, message, code]) => { + buildServerConnection?.onError(([error]) => { this.logger.error(`Error on buildServerConnection: ${error.message}`); sendInfo("", { kind: "bspProxy-importerConnectionError", - data2: JSON.stringify({ - error, - message, - code, - }), + message: error.message, + errorStack: error.stack ? error.stack.toString() : "", }); // TODO: Implement more specific error handling logic here }); diff --git a/extension/src/server/GradleServer.ts b/extension/src/server/GradleServer.ts index 6c984ff3a..b2f3a0d53 100644 --- a/extension/src/server/GradleServer.ts +++ b/extension/src/server/GradleServer.ts @@ -85,21 +85,16 @@ export class GradleServer { public async showRestartMessage(): Promise { const OPT_RESTART = "Restart"; - const input = await vscode.window.showErrorMessage( + const selection = await vscode.window.showErrorMessage( "No connection to gradle server. Try restarting the server.", OPT_RESTART ); - if (input === OPT_RESTART) { - sendInfo("", { - kind: "serverProcessExitRestart", - data2: "true", - }); + sendInfo("", { + kind: "serverProcessExitRestart", + data2: selection === OPT_RESTART ? "true" : "false", + }); + if (selection === OPT_RESTART) { await commands.executeCommand("workbench.action.restartExtensionHost"); - } else { - sendInfo("", { - kind: "serverProcessExitRestart", - data2: "false", - }); } } From 7446361e848c59b49d2c4ff91a91ce076486402e Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Mon, 22 Jul 2024 11:28:20 +0800 Subject: [PATCH 29/31] Fix: comment --- extension/src/Extension.ts | 2 +- extension/src/constant.ts | 2 ++ extension/src/server/GradleServer.ts | 3 +-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/extension/src/Extension.ts b/extension/src/Extension.ts index 05fbc6925..5d19cab34 100644 --- a/extension/src/Extension.ts +++ b/extension/src/Extension.ts @@ -32,13 +32,13 @@ import { GRADLE_COMPLETION, GRADLE_PROPERTIES_FILE_CHANGE, VSCODE_TRIGGER_COMPLETION, + OPT_RESTART, } from "./constant"; import { instrumentOperation, sendInfo } from "vscode-extension-telemetry-wrapper"; import { GradleBuildContentProvider } from "./client/GradleBuildContentProvider"; import { BuildServerController } from "./bs/BuildServerController"; import { GradleTestRunner } from "./bs/GradleTestRunner"; import { BspProxy } from "./bs/BspProxy"; -const OPT_RESTART = "Restart"; export class Extension { private readonly bspProxy: BspProxy; diff --git a/extension/src/constant.ts b/extension/src/constant.ts index 2ff546bb7..1d01ed0d5 100644 --- a/extension/src/constant.ts +++ b/extension/src/constant.ts @@ -21,6 +21,8 @@ export const GRADLE_BUILD_FILE_NAMES = ["build.gradle", "settings.gradle", "buil export const NO_JAVA_EXECUTABLE = "No Java executable found, please consider to configure your 'java.jdt.ls.java.home' setting or set JAVA_HOME in your path or put a Java executable in your path."; +export const OPT_RESTART = "Restart"; + export enum CompletionKinds { DEPENDENCY_GROUP = "dependency_group", DEPENDENCY_ARTIFACT = "dependency_artifact", diff --git a/extension/src/server/GradleServer.ts b/extension/src/server/GradleServer.ts index b2f3a0d53..2b36d2054 100644 --- a/extension/src/server/GradleServer.ts +++ b/extension/src/server/GradleServer.ts @@ -7,7 +7,7 @@ import { commands } from "vscode"; import { sendInfo } from "vscode-extension-telemetry-wrapper"; import { getGradleServerCommand, getGradleServerEnv } from "./serverUtil"; import { Logger } from "../logger/index"; -import { NO_JAVA_EXECUTABLE } from "../constant"; +import { NO_JAVA_EXECUTABLE, OPT_RESTART } from "../constant"; import { redHatJavaInstalled } from "../util/config"; import { BspProxy } from "../bs/BspProxy"; @@ -84,7 +84,6 @@ export class GradleServer { } public async showRestartMessage(): Promise { - const OPT_RESTART = "Restart"; const selection = await vscode.window.showErrorMessage( "No connection to gradle server. Try restarting the server.", OPT_RESTART From 722406a12d28a182ba35291423fe42942126c8a1 Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Mon, 22 Jul 2024 14:32:19 +0800 Subject: [PATCH 30/31] Fix: build.gradle file --- extension/build.gradle | 11 +++++------ extension/src/util/config.ts | 6 +++--- gradle-server/build.gradle | 1 - 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/extension/build.gradle b/extension/build.gradle index e7f533005..a2ada7e16 100644 --- a/extension/build.gradle +++ b/extension/build.gradle @@ -225,11 +225,10 @@ task buildBuildServer(type: CrossPlatformExec) { } } -task copyBuildServerJars(type: Copy) { +task copyBuildServerPluginJars(type: Copy) { dependsOn ':extension:buildBuildServer' from('./build-server-for-gradle/server/build/libs/') { - include '**/*.jar' - include '**/init.gradle' + include 'plugins/' } into 'server' mustRunAfter copyDocs @@ -239,15 +238,15 @@ task copyBuildServerJars(type: Copy) { task copyBuildServerJarsToGradleServer(type: Copy) { dependsOn ':extension:buildBuildServer' from('./build-server-for-gradle/server/build/libs/') { - include '**/*.jar' - include '**/init.gradle' + include 'runtime/' } + from('./build-server-for-gradle/server/build/libs/server.jar') into '../gradle-server/build/libs/' mustRunAfter copyDocs } task buildJars() { - dependsOn copyJdtlsPluginJar, copyBuildServerJars, copyBuildServerJarsToGradleServer + dependsOn copyJdtlsPluginJar, copyBuildServerPluginJars, copyBuildServerJarsToGradleServer } build.finalizedBy buildProd, buildTest diff --git a/extension/src/util/config.ts b/extension/src/util/config.ts index 8dc484d3b..f71cd6412 100644 --- a/extension/src/util/config.ts +++ b/extension/src/util/config.ts @@ -50,9 +50,9 @@ export async function findValidJavaHome(): Promise { // Search valid JDKs from env.JAVA_HOME, env.PATH, SDKMAN, jEnv, jabba, common directories const javaRuntimes = await listJdks(); - const validJdks = javaRuntimes.filter((r) => r.version!.major >= REQUIRED_JDK_VERSION); - if (validJdks.length > 0) { - return validJdks[0].homedir; + const validJdks = javaRuntimes.find((r) => r.version!.major >= REQUIRED_JDK_VERSION); + if (validJdks !== undefined) { + return validJdks.homedir; } // Search java.configuration.runtimes if still not found diff --git a/gradle-server/build.gradle b/gradle-server/build.gradle index a4cdf4ece..62720a014 100644 --- a/gradle-server/build.gradle +++ b/gradle-server/build.gradle @@ -14,7 +14,6 @@ dependencies { implementation project(":gradle-plugin-api") implementation files('build/libs/server.jar') implementation fileTree(dir: 'build/libs/runtime', include: ['*.jar'], exclude: ['gradle-tooling-api-*.jar', 'slf4j-api-*.jar']) - implementation fileTree(dir: 'build/libs/plugins', include: ['*.jar']) implementation("org.gradle:gradle-tooling-api:${toolingAPIVersion}") implementation 'javax.annotation:javax.annotation-api:1.3.2' implementation "io.grpc:grpc-protobuf:${grpcVersion}" From be63f6d6afb0c56567f2225fe7b18bd65d2a4eea Mon Sep 17 00:00:00 2001 From: Jiaaming <2455951489@qq.com> Date: Mon, 22 Jul 2024 15:51:30 +0800 Subject: [PATCH 31/31] Fix: extension start sequence --- extension/src/Extension.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/src/Extension.ts b/extension/src/Extension.ts index 5d19cab34..69c483b84 100644 --- a/extension/src/Extension.ts +++ b/extension/src/Extension.ts @@ -250,9 +250,9 @@ export class Extension { if (!this.server.isReady()) { await this.server.start(); } - await this.bspProxy.start(); await vscode.commands.executeCommand("setContext", "gradle:activated", activated); await vscode.commands.executeCommand("setContext", "gradle:defaultView", true); + await this.bspProxy.start(); } private registerCommands(): void {