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] 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",