diff --git a/.azure-pipelines/publish-to-maven.yml b/.azure-pipelines/publish-to-maven.yml new file mode 100644 index 000000000..a1ef0a204 --- /dev/null +++ b/.azure-pipelines/publish-to-maven.yml @@ -0,0 +1,104 @@ +name: $(Date:yyyyMMdd).$(Rev:r) +resources: + repositories: + - repository: MicroBuildTemplate + type: git + name: 1ESPipelineTemplates/MicroBuildTemplate + ref: refs/tags/release +trigger: none +extends: + template: azure-pipelines/1ES.Official.Publish.yml@MicroBuildTemplate + parameters: + pool: + os: linux + name: 1ES_JavaTooling_Pool + image: 1ES_JavaTooling_Ubuntu-2004 + sdl: + sourceAnalysisPool: + name: 1ES_JavaTooling_Pool + image: 1ES_JavaTooling_Windows_2022 + os: windows + stages: + - stage: PublishToMaven + jobs: + - job: PublishToMaven + displayName: Maven Release job + templateContext: + type: releaseJob + isProduction: true + steps: + - task: DownloadBuildArtifacts@1 + displayName: 'Download Jar Artifacts' + inputs: + buildType: specific + project: 'a4d27ce2-a42d-4b71-8eef-78cee9a9728e' + pipeline: 16486 + downloadType: specific + extractTars: false + itemPattern: 'm2/**' + - script: | + echo "import public key" + echo $GPG_PUBLIC_B64 | base64 -d | gpg --import + + echo "import secret key" + echo $GPG_SECRET_B64 | base64 -d | gpg --batch --passphrase $GPGPASS --import + displayName: 'import GPG keys' + env: + GPG_PUBLIC_B64: $(GPG_PUBLIC_B64) + GPG_SECRET_B64: $(GPG_SECRET_B64) + GPGPASS: $(GPGPASS) + - task: NodeTool@0 + displayName: 'Use Node 20.x' + inputs: + versionSpec: 20.x + - script: | + cd $(System.ArtifactsDirectory)/m2 + pluginJarFile=$(basename -- java-debug-parent/*.pom) + + # remove .* from end + noExt=${pluginJarFile%.*} + + # remove *- from start + export releaseVersion=${noExt##*-} + echo $releaseVersion + + export artifactFolder=$(pwd .) + wget https://raw.githubusercontent.com/microsoft/java-debug/master/scripts/publishMaven.js + + export GPG_TTY=$(tty) + node publishMaven.js -task gpg + displayName: 'sign artifacts' + env: + GPG_PUBLIC_B64: $(GPG_PUBLIC_B64) + GPG_SECRET_B64: $(GPG_SECRET_B64) + GPGPASS: $(GPGPASS) + NEXUS_OSSRHPASS: $(NEXUS_OSSRHPASS) + NEXUS_OSSRHUSER: $(NEXUS_OSSRHUSER) + NEXUS_STAGINGPROFILEID: $(NEXUS_STAGINGPROFILEID) + - template: MicroBuild.Publish.yml@MicroBuildTemplate + parameters: + intent: 'PackageDistribution' + contentType: 'Maven' + contentSource: 'Folder' + folderLocation: '$(System.ArtifactsDirectory)/m2/java-debug-parent' + waitForReleaseCompletion: true + owners: 'jinbwan@microsoft.com' + approvers: 'roml@microsoft.com' + - template: MicroBuild.Publish.yml@MicroBuildTemplate + parameters: + intent: 'PackageDistribution' + contentType: 'Maven' + contentSource: 'Folder' + folderLocation: '$(System.ArtifactsDirectory)/m2/com.microsoft.java.debug.core' + waitForReleaseCompletion: true + owners: 'jinbwan@microsoft.com' + approvers: 'roml@microsoft.com' + - template: MicroBuild.Publish.yml@MicroBuildTemplate + parameters: + intent: 'PackageDistribution' + contentType: 'Maven' + contentSource: 'Folder' + folderLocation: '$(System.ArtifactsDirectory)/m2/com.microsoft.java.debug.plugin' + waitForReleaseCompletion: true + owners: 'jinbwan@microsoft.com' + approvers: 'roml@microsoft.com' \ No newline at end of file diff --git a/.azure-pipelines/signjars-nightly.yml b/.azure-pipelines/signjars-nightly.yml new file mode 100644 index 000000000..8b1e8d12a --- /dev/null +++ b/.azure-pipelines/signjars-nightly.yml @@ -0,0 +1,138 @@ +name: $(Date:yyyyMMdd).$(Rev:r) +variables: + - name: Codeql.Enabled + value: true +schedules: + - cron: 0 5 * * 1,2,3,4,5 + branches: + include: + - refs/heads/main +resources: + repositories: + - repository: self + type: git + ref: refs/heads/main + - repository: 1esPipelines + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release +trigger: none +extends: + template: v1/1ES.Official.PipelineTemplate.yml@1esPipelines + parameters: + pool: + os: linux + name: 1ES_JavaTooling_Pool + image: 1ES_JavaTooling_Ubuntu-2004 + sdl: + sourceAnalysisPool: + name: 1ES_JavaTooling_Pool + image: 1ES_JavaTooling_Windows_2022 + os: windows + customBuildTags: + - MigrationTooling-mseng-VSJava-13474-Tool + stages: + - stage: Build + jobs: + - job: Job_1 + displayName: Sign-Jars-Nightly + templateContext: + outputs: + - output: pipelineArtifact + artifactName: plugin + targetPath: $(Build.ArtifactStagingDirectory) + displayName: "Publish Artifact: plugin" + steps: + - checkout: self + fetchTags: true + - task: UseDotNet@2 + displayName: 'Use .NET Core 3.1.x' + inputs: + packageType: 'sdk' + version: '3.1.x' + - task: UseDotNet@2 + displayName: 'Use .NET Core 8.0.x' + inputs: + packageType: 'sdk' + version: '8.0.x' + - task: MicroBuildSigningPlugin@4 + displayName: 'Install Signing Plugin' + inputs: + signType: real + azureSubscription: 'MicroBuild Signing Task (MSEng)' + useEsrpCli: true + ConnectedPMEServiceName: 0e38ce24-f885-4c86-b997-5887b97a1899 + feedSource: 'https://mseng.pkgs.visualstudio.com/DefaultCollection/_packaging/MicroBuildToolset/nuget/v3/index.json' + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + MicroBuildOutputFolderOverride: '$(Agent.TempDirectory)' + - task: JavaToolInstaller@0 + displayName: Use Java 21 + inputs: + versionSpec: "21" + jdkArchitectureOption: x64 + jdkSourceOption: PreInstalled + - task: CmdLine@2 + displayName: Parse the release version from pom.xml + inputs: + script: |- + #!/bin/bash + + sudo apt-get install xmlstarlet + xmlstarlet --version + RELEASE_VERSION=$(xmlstarlet sel -t -v "/_:project/_:version" pom.xml) + echo $RELEASE_VERSION + echo "##vso[task.setvariable variable=RELEASE_VERSION]$RELEASE_VERSION" + - task: CmdLine@2 + displayName: Build core.jar + inputs: + script: | + ./mvnw clean install -f com.microsoft.java.debug.core/pom.xml -Dmaven.repo.local=./.repository + + mkdir -p jars + mv .repository/com/microsoft/java/com.microsoft.java.debug.core/$RELEASE_VERSION/com.microsoft.java.debug.core*.jar jars/ + - task: CmdLine@2 + displayName: Sign core jars + inputs: + script: | + files=$(find . -type f -name "com.microsoft.java.debug.core*.jar") + for file in $files; do + fileName=$(basename "$file") + dotnet "$MBSIGN_APPFOLDER/DDSignFiles.dll" -- /file:"$fileName" /certs:100010171 + done + workingDirectory: 'jars' + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + - task: CmdLine@2 + displayName: install signed core.jar + inputs: + script: cp jars/com.microsoft.java.debug.core*.jar .repository/com/microsoft/java/com.microsoft.java.debug.core/$RELEASE_VERSION/ + - task: CmdLine@2 + displayName: Build plugin.jar + inputs: + script: |- + ./mvnw clean install -N -f pom.xml -Dmaven.repo.local=./.repository + ./mvnw clean install -f com.microsoft.java.debug.target/pom.xml -Dmaven.repo.local=./.repository + ./mvnw clean install -f com.microsoft.java.debug.plugin/pom.xml -Dmaven.repo.local=./.repository + + mkdir -p jars + mv .repository/com/microsoft/java/com.microsoft.java.debug.plugin/$RELEASE_VERSION/com.microsoft.java.debug.plugin*.jar jars/ + - task: CmdLine@2 + displayName: Sign plugin jars + inputs: + script: | + files=$(find . -type f -name "com.microsoft.java.debug.plugin*.jar") + for file in $files; do + fileName=$(basename "$file") + dotnet "$MBSIGN_APPFOLDER/DDSignFiles.dll" -- /file:"$fileName" /certs:100010171 + done + workingDirectory: 'jars' + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + - task: CopyFiles@2 + displayName: "Copy plugin.jar to: $(Build.ArtifactStagingDirectory)" + inputs: + Contents: |+ + jars/com.microsoft.java.debug.plugin*.jar + + TargetFolder: $(Build.ArtifactStagingDirectory) diff --git a/.azure-pipelines/signjars-rc.yml b/.azure-pipelines/signjars-rc.yml new file mode 100644 index 000000000..e87444603 --- /dev/null +++ b/.azure-pipelines/signjars-rc.yml @@ -0,0 +1,169 @@ +name: $(Date:yyyyMMdd).$(Rev:r) +variables: + - name: Codeql.Enabled + value: true +resources: + repositories: + - repository: self + type: git + ref: refs/heads/main + - repository: 1esPipelines + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release +trigger: none +extends: + template: v1/1ES.Official.PipelineTemplate.yml@1esPipelines + parameters: + pool: + os: linux + name: 1ES_JavaTooling_Pool + image: 1ES_JavaTooling_Ubuntu-2004 + sdl: + sourceAnalysisPool: + name: 1ES_JavaTooling_Pool + image: 1ES_JavaTooling_Windows_2022 + os: windows + customBuildTags: + - MigrationTooling-mseng-VSJava-9151-Tool + stages: + - stage: Build + jobs: + - job: Job_1 + displayName: Sign-Jars-RC + templateContext: + outputs: + - output: pipelineArtifact + artifactName: m2 + targetPath: $(Build.ArtifactStagingDirectory)/m2 + displayName: "Publish Artifact: m2" + steps: + - checkout: self + fetchTags: true + - task: UseDotNet@2 + displayName: 'Use .NET Core 3.1.x' + inputs: + packageType: 'sdk' + version: '3.1.x' + - task: UseDotNet@2 + displayName: 'Use .NET Core 8.0.x' + inputs: + packageType: 'sdk' + version: '8.0.x' + - task: MicroBuildSigningPlugin@4 + displayName: 'Install Signing Plugin' + inputs: + signType: real + azureSubscription: 'MicroBuild Signing Task (MSEng)' + useEsrpCli: true + ConnectedPMEServiceName: 0e38ce24-f885-4c86-b997-5887b97a1899 + feedSource: 'https://mseng.pkgs.visualstudio.com/DefaultCollection/_packaging/MicroBuildToolset/nuget/v3/index.json' + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + MicroBuildOutputFolderOverride: '$(Agent.TempDirectory)' + - task: JavaToolInstaller@0 + displayName: Use Java 21 + inputs: + versionSpec: "21" + jdkArchitectureOption: x64 + jdkSourceOption: PreInstalled + - task: CmdLine@2 + displayName: Parse the release version from pom.xml + inputs: + script: |- + #!/bin/bash + + sudo apt-get install xmlstarlet + xmlstarlet --version + RELEASE_VERSION=$(xmlstarlet sel -t -v "/_:project/_:version" pom.xml) + echo $RELEASE_VERSION + echo "##vso[task.setvariable variable=RELEASE_VERSION]$RELEASE_VERSION" + - task: CmdLine@2 + displayName: Build core.jar + inputs: + script: | + ./mvnw -N clean install -Dmaven.repo.local=./.repository + + ./mvnw clean install -f com.microsoft.java.debug.core/pom.xml -Dmaven.repo.local=./.repository + + mkdir -p jars + mv .repository/com/microsoft/java/com.microsoft.java.debug.core/$RELEASE_VERSION/com.microsoft.java.debug.core*.jar jars/ + - task: CmdLine@2 + displayName: Sign core jars + inputs: + script: | + files=$(find . -type f -name "com.microsoft.java.debug.core*.jar") + for file in $files; do + fileName=$(basename "$file") + dotnet "$MBSIGN_APPFOLDER/DDSignFiles.dll" -- /file:"$fileName" /certs:100010171 + done + workingDirectory: 'jars' + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + - task: CmdLine@2 + displayName: install signed core.jar + inputs: + script: cp jars/com.microsoft.java.debug.core*.jar .repository/com/microsoft/java/com.microsoft.java.debug.core/$RELEASE_VERSION/ + - task: CmdLine@2 + displayName: Build plugin.jar + inputs: + script: |- + ./mvnw clean install -f com.microsoft.java.debug.target/pom.xml -Dmaven.repo.local=./.repository + ./mvnw clean install -f com.microsoft.java.debug.plugin/pom.xml -Dmaven.repo.local=./.repository + + mkdir -p jars + mv .repository/com/microsoft/java/com.microsoft.java.debug.plugin/$RELEASE_VERSION/com.microsoft.java.debug.plugin*.jar jars/ + - task: CmdLine@2 + displayName: Sign plugin jars + inputs: + script: | + files=$(find . -type f -name "com.microsoft.java.debug.plugin*.jar") + for file in $files; do + fileName=$(basename "$file") + dotnet "$MBSIGN_APPFOLDER/DDSignFiles.dll" -- /file:"$fileName" /certs:100010171 + done + workingDirectory: 'jars' + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + - task: CmdLine@2 + displayName: install signed plugin.jar + inputs: + script: cp jars/com.microsoft.java.debug.plugin*.jar .repository/com/microsoft/java/com.microsoft.java.debug.plugin/$RELEASE_VERSION/ + - task: CmdLine@2 + displayName: build m2 artifacts + inputs: + script: | + ./mvnw source:jar -f com.microsoft.java.debug.core/pom.xml -Dmaven.repo.local=./.repository + ./mvnw javadoc:jar -f com.microsoft.java.debug.core/pom.xml -Ddoclint=none -Dmaven.repo.local=./.repository + + ./mvnw source:jar -f com.microsoft.java.debug.plugin/pom.xml -Dmaven.repo.local=./.repository + ./mvnw javadoc:jar -f com.microsoft.java.debug.plugin/pom.xml -Ddoclint=none -Dmaven.repo.local=./.repository + + mkdir -p m2/java-debug-parent + cp pom.xml m2/java-debug-parent/java-debug-parent-$RELEASE_VERSION.pom + + mkdir -p m2/com.microsoft.java.debug.core + cp com.microsoft.java.debug.core/target/com.microsoft.java.debug.core*.jar m2/com.microsoft.java.debug.core + cp com.microsoft.java.debug.core/pom.xml m2/com.microsoft.java.debug.core/com.microsoft.java.debug.core-$RELEASE_VERSION.pom + + mkdir -p m2/com.microsoft.java.debug.plugin + cp com.microsoft.java.debug.plugin/target/com.microsoft.java.debug.plugin*.jar m2/com.microsoft.java.debug.plugin + cp com.microsoft.java.debug.plugin/pom.xml m2/com.microsoft.java.debug.plugin/com.microsoft.java.debug.plugin-$RELEASE_VERSION.pom + - task: CmdLine@2 + displayName: Sign m2 jars + inputs: + script: | + files=$(find . -type f -name "*.jar") + for file in $files; do + # fileName=$(basename "$file") + dotnet "$MBSIGN_APPFOLDER/DDSignFiles.dll" -- /file:"$file" /certs:100010171 + done + workingDirectory: 'm2' + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + - task: CopyFiles@2 + displayName: "Copy m2 to: $(Build.ArtifactStagingDirectory)" + inputs: + Contents: |+ + m2/** + TargetFolder: $(Build.ArtifactStagingDirectory) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b11684f52..9d7579633 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @testforstephen @jdneo \ No newline at end of file +* @testforstephen @jdneo @chagong @wenytang-ms diff --git a/.github/llms.md b/.github/llms.md new file mode 100644 index 000000000..55d69b238 --- /dev/null +++ b/.github/llms.md @@ -0,0 +1,38 @@ +# Extension Pack for Java +Extension Pack for Java is a collection of popular extensions that can help write, test and debug Java applications in Visual Studio Code. By installing Extension Pack for Java, the following extensions are installed: + +- [📦 Language Support for Java™ by Red Hat ](https://marketplace.visualstudio.com/items?itemName=redhat.java) + - Code Navigation + - Auto Completion + - Refactoring + - Code Snippets +- [📦 Debugger for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-debug) + - Debugging +- [📦 Test Runner for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-test) + - Run & Debug JUnit/TestNG Test Cases +- [📦 Maven for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-maven) + - Project Scaffolding + - Custom Goals +- [📦 Gradle for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-gradle) + - View Gradle tasks and project dependencies + - Gradle file authoring + - Import Gradle projects via [Gradle Build Server](https://github.com/microsoft/build-server-for-gradle) +- [📦 Project Manager for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-dependency) + - Manage Java projects, referenced libraries, resource files, packages, classes, and class members +- [📦 Visual Studio IntelliCode](https://marketplace.visualstudio.com/items?itemName=VisualStudioExptTeam.vscodeintellicode) + - AI-assisted development + - Completion list ranked by AI + +## Label +When labeling an issue, follow the rules below per label category: +### General Rules +- Analyze if the issue is related with the scope of using extensions for Java development. If not, STOP labelling IMMEDIATELY. +- Assign label per category. +- If a category is not applicable or you're unsure, you may skip it. +- Do not assign multiple labels within the same category, unless explicitly allowed as an exception. + +### Issue Type Labels +- [bug]: Primary label for real bug issues +- [enhancement]: Primary label for enhancement issues +- [documentation]: Primary label for documentation issues +- [question]: Primary label for question issues \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 41c85cb62..553d95a9a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,80 +12,83 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 - - name: Set up JDK 17 - uses: actions/setup-java@v1 - with: - java-version: '17' + - name: Set up JDK 21 + uses: actions/setup-java@v5 + with: + java-version: '21' + distribution: 'temurin' - - name: Cache local Maven repository - uses: actions/cache@v2 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- + - name: Cache local Maven repository + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- - - name: Verify - run: ./mvnw clean verify + - name: Verify + run: ./mvnw clean verify -U - - name: Checkstyle - run: ./mvnw checkstyle:check + - name: Checkstyle + run: ./mvnw checkstyle:check windows: name: Windows runs-on: windows-latest timeout-minutes: 30 steps: - - name: Set git to use LF - run: | - git config --global core.autocrlf false - git config --global core.eol lf + - name: Set git to use LF + run: | + git config --global core.autocrlf false + git config --global core.eol lf - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 - - name: Set up JDK 17 - uses: actions/setup-java@v1 - with: - java-version: '17' + - name: Set up JDK 21 + uses: actions/setup-java@v5 + with: + java-version: '21' + distribution: 'temurin' - - name: Cache local Maven repository - uses: actions/cache@v2 - with: - path: $HOME/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- + - name: Cache local Maven repository + uses: actions/cache@v4 + with: + path: $HOME/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- - - name: Verify - run: ./mvnw.cmd clean verify + - name: Verify + run: ./mvnw.cmd clean verify - - name: Checkstyle - run: ./mvnw.cmd checkstyle:check + - name: Checkstyle + run: ./mvnw.cmd checkstyle:check darwin: name: macOS runs-on: macos-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v2 - - - name: Set up JDK 17 - uses: actions/setup-java@v1 - with: - java-version: '17' - - - name: Cache local Maven repository - uses: actions/cache@v2 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- - - - name: Verify - run: ./mvnw clean verify - - - name: Checkstyle - run: ./mvnw checkstyle:check + - uses: actions/checkout@v5 + + - name: Set up JDK 21 + uses: actions/setup-java@v5 + with: + java-version: '21' + distribution: 'temurin' + + - name: Cache local Maven repository + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Verify + run: ./mvnw clean verify -U + + - name: Checkstyle + run: ./mvnw checkstyle:check diff --git a/.github/workflows/triage-agent.yml b/.github/workflows/triage-agent.yml new file mode 100644 index 000000000..f1df6e117 --- /dev/null +++ b/.github/workflows/triage-agent.yml @@ -0,0 +1,125 @@ +name: AI Triage +on: + issues: + types: [opened] + workflow_dispatch: + inputs: + issue_number: + description: 'Issue number to triage (manual run). e.g. 123' + required: true + +run-name: >- + AI Triage for Issue #${{ github.event.issue.number || github.event.inputs.issue_number }} + +permissions: + issues: write + contents: read + +jobs: + label_and_comment: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Get issue data + id: get_issue + uses: actions/github-script@v6 + with: + script: | + const eventName = context.eventName; + let issue; + if (eventName === 'workflow_dispatch') { + const inputs = context.payload.inputs || {}; + const issueNumber = inputs.issue_number || inputs.issueNumber; + if (!issueNumber) core.setFailed('Input issue_number is required for manual run.'); + const { data } = await github.rest.issues.get({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: parseInt(issueNumber, 10), + }); + issue = data; + } else if (context.payload.issue) { + issue = context.payload.issue; + } else { + core.setFailed('No issue information found in the event payload.'); + } + core.setOutput('id', String(issue.number)); + core.setOutput('user', String((issue.user && issue.user.login) || '')); + core.setOutput('title', String(issue.title || '')); + core.setOutput('body', String(issue.body || '')); + const labelNames = (issue.labels || []).map(label => label.name); + core.setOutput('labels', JSON.stringify(labelNames)); + + - name: Call Azure Function + id: call_azure_function + env: + PAYLOAD: >- + { + "authToken": "${{ secrets.GITHUB_TOKEN }}", + "repoId": "microsoft/java-debug", + "issueData": { + "id": ${{ steps.get_issue.outputs.id }}, + "user": ${{ toJson(steps.get_issue.outputs.user) }}, + "title": ${{ toJson(steps.get_issue.outputs.title) }}, + "body": ${{ toJson(steps.get_issue.outputs.body) }}, + "labels": ${{ steps.get_issue.outputs.labels }} + }, + "mode": "DirectUpdate" + } + + run: | + # Make the HTTP request with improved error handling and timeouts + echo "Making request to triage agent..." + + # Add timeout handling and better error detection + set +e # Don't exit on curl failure + response=$(timeout ${{ vars.TRIAGE_AGENT_TIMEOUT }} curl \ + --max-time 0 \ + --connect-timeout 30 \ + --fail-with-body \ + --silent \ + --show-error \ + --write-out "HTTPSTATUS:%{http_code}" \ + --header "Content-Type: application/json" \ + --request POST \ + --data "$PAYLOAD" \ + ${{ secrets.TRIAGE_FUNCTION_LINK }} 2>&1) + + curl_exit_code=$? + set -e # Re-enable exit on error + + echo "Curl exit code: $curl_exit_code" + + # Check if curl command timed out or failed + if [ $curl_exit_code -eq 124 ]; then + echo "❌ Request timed out after 650 seconds" + exit 1 + elif [ $curl_exit_code -ne 0 ]; then + echo "❌ Curl command failed with exit code: $curl_exit_code" + echo "Response: $response" + exit 1 + fi + + # Extract HTTP status code and response body + http_code=$(echo "$response" | grep -o "HTTPSTATUS:[0-9]*" | cut -d: -f2) + response_body=$(echo "$response" | sed 's/HTTPSTATUS:[0-9]*$//') + + echo "HTTP Status Code: $http_code" + + # Validate HTTP status code + if [ -z "$http_code" ]; then + echo "❌ Failed to extract HTTP status code from response" + echo "Raw response: $response" + exit 1 + fi + + # Check if the request was successful + if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then + echo "✅ Azure Function call succeeded" + else + echo "❌ Azure Function call failed with status code: $http_code" + echo "Response: $response_body" + exit 1 + fi \ No newline at end of file diff --git a/.github/workflows/triage-all-open-issues.yml b/.github/workflows/triage-all-open-issues.yml new file mode 100644 index 000000000..092f4ef70 --- /dev/null +++ b/.github/workflows/triage-all-open-issues.yml @@ -0,0 +1,145 @@ +name: AI Triage - Process All Open Issues +on: + workflow_dispatch: + inputs: + dry_run: + description: 'Dry run mode - only list issues without processing' + required: false + default: false + type: boolean + max_issues: + description: 'Maximum number of issues to process (0 = all)' + required: false + default: '0' + type: string + +permissions: + issues: write + contents: read + actions: write + +jobs: + get_open_issues: + runs-on: ubuntu-latest + outputs: + issue_numbers: ${{ steps.get_issues.outputs.issue_numbers }} + total_count: ${{ steps.get_issues.outputs.total_count }} + + steps: + - name: Get all open issues + id: get_issues + uses: actions/github-script@v6 + with: + script: | + // Use Search API to filter issues at API level + const { data } = await github.rest.search.issuesAndPullRequests({ + q: `repo:${context.repo.owner}/${context.repo.repo} is:issue is:open -label:ai-triaged -label:invalid`, + sort: 'created', + order: 'asc', + per_page: 100 + }); + + const actualIssues = data.items; + + let issuesToProcess = actualIssues; + const maxIssues = parseInt('${{ inputs.max_issues }}' || '0'); + + if (maxIssues > 0 && actualIssues.length > maxIssues) { + issuesToProcess = actualIssues.slice(0, maxIssues); + console.log(`Limiting to first ${maxIssues} issues out of ${actualIssues.length} total`); + } + + const issueNumbers = issuesToProcess.map(issue => issue.number); + const totalCount = issuesToProcess.length; + + console.log(`Found ${actualIssues.length} open issues, processing ${totalCount}:`); + issuesToProcess.forEach(issue => { + console.log(` #${issue.number}: ${issue.title}`); + }); + + core.setOutput('issue_numbers', JSON.stringify(issueNumbers)); + core.setOutput('total_count', totalCount); + + process_issues: + runs-on: ubuntu-latest + needs: get_open_issues + if: needs.get_open_issues.outputs.total_count > 0 + + strategy: + # Process issues one by one (max-parallel: 1) + max-parallel: 1 + matrix: + issue_number: ${{ fromJSON(needs.get_open_issues.outputs.issue_numbers) }} + + steps: + - name: Log current issue being processed + run: | + echo "🔄 Processing issue #${{ matrix.issue_number }}" + echo "Total issues to process: ${{ needs.get_open_issues.outputs.total_count }}" + + - name: Check if dry run mode + if: inputs.dry_run == true + run: | + echo "🔍 DRY RUN MODE: Would process issue #${{ matrix.issue_number }}" + echo "Skipping actual triage processing" + + - name: Trigger triage workflow for issue + if: inputs.dry_run != true + uses: actions/github-script@v6 + with: + script: | + const issueNumber = '${{ matrix.issue_number }}'; + + try { + console.log(`Triggering triage workflow for issue #${issueNumber}`); + + const response = await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'triage-agent.yml', + ref: 'main', + inputs: { + issue_number: issueNumber + } + }); + + console.log(`✅ Successfully triggered triage workflow for issue #${issueNumber}`); + + } catch (error) { + console.error(`❌ Failed to trigger triage workflow for issue #${issueNumber}:`, error); + core.setFailed(`Failed to process issue #${issueNumber}: ${error.message}`); + } + + - name: Wait for workflow completion + if: inputs.dry_run != true + run: | + echo "⏳ Waiting for triage workflow to complete for issue #${{ matrix.issue_number }}..." + echo "Timeout: ${{ vars.TRIAGE_AGENT_TIMEOUT }} seconds" + sleep ${{ vars.TRIAGE_AGENT_TIMEOUT }} # Wait for triage workflow completion + + summary: + runs-on: ubuntu-latest + needs: [get_open_issues, process_issues] + if: always() + + steps: + - name: Print summary + run: | + echo "## Triage Processing Summary" + echo "Total open issues found: ${{ needs.get_open_issues.outputs.total_count }}" + + if [ "${{ inputs.dry_run }}" == "true" ]; then + echo "Mode: DRY RUN (no actual processing performed)" + else + echo "Mode: FULL PROCESSING" + fi + + if [ "${{ needs.process_issues.result }}" == "success" ]; then + echo "✅ All issues processed successfully" + elif [ "${{ needs.process_issues.result }}" == "failure" ]; then + echo "❌ Some issues failed to process" + elif [ "${{ needs.process_issues.result }}" == "skipped" ]; then + echo "⏭️ Processing was skipped (no open issues found)" + else + echo "⚠️ Processing completed with status: ${{ needs.process_issues.result }}" + fi diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar deleted file mode 100644 index 41c70a7e0..000000000 Binary files a/.mvn/wrapper/maven-wrapper.jar and /dev/null differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 9e0264d04..44f3cf2c1 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1 +1,2 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.3/apache-maven-3.9.3-bin.zip \ No newline at end of file +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip diff --git a/com.microsoft.java.debug.core/.classpath b/com.microsoft.java.debug.core/.classpath index 9ba41a249..b6fe6b96c 100644 --- a/com.microsoft.java.debug.core/.classpath +++ b/com.microsoft.java.debug.core/.classpath @@ -13,7 +13,7 @@ - + diff --git a/com.microsoft.java.debug.core/pom.xml b/com.microsoft.java.debug.core/pom.xml index 635993d56..8bfdb7292 100644 --- a/com.microsoft.java.debug.core/pom.xml +++ b/com.microsoft.java.debug.core/pom.xml @@ -5,7 +5,7 @@ com.microsoft.java java-debug-parent - 0.50.0 + 0.53.2 com.microsoft.java.debug.core jar @@ -42,7 +42,7 @@ org.apache.commons commons-lang3 - 3.6 + 3.18.0 com.google.code.gson @@ -62,7 +62,7 @@ commons-io commons-io - 2.11.0 + 2.14.0 diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Breakpoint.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Breakpoint.java index dfdbad4bb..82859b268 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Breakpoint.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Breakpoint.java @@ -325,7 +325,8 @@ private Location findMethodLocaiton(ReferenceType refType, String methodName, St for (Method method : methods) { if (!method.isAbstract() && !method.isNative() && methodName.equals(method.name()) - && (methodSiguature.equals(method.genericSignature()) || methodSiguature.equals(method.signature()))) { + && (methodSiguature.equals(method.genericSignature()) || methodSiguature.equals(method.signature()) + || toNoneGeneric(methodSiguature).equals(method.signature()))) { location = method.location(); break; } @@ -334,6 +335,28 @@ private Location findMethodLocaiton(ReferenceType refType, String methodName, St return location; } + static String toNoneGeneric(String genericSig) { + StringBuilder builder = new StringBuilder(); + boolean append = true; + int depth = 0; + char[] chars = genericSig.toCharArray(); + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + if (c == '<') { + depth++; + append = (depth == 0); + } + if (append) { + builder.append(c); + } + if (c == '>') { + depth--; + append = (depth == 0); + } + } + return builder.toString(); + } + private List findLocaitonsOfLine(Method method, int lineNumber) { try { return method.locationsOfLine(lineNumber); diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java index fbad52fe2..38a234fa9 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java @@ -119,7 +119,9 @@ public void detach() { @Override public void terminate() { - if (vm.process() == null || vm.process().isAlive()) { + if (vm.process() != null && vm.process().isAlive()) { + vm.process().destroy(); + } else if (vm.process() == null || vm.process().isAlive()) { vm.exit(0); } } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java index f8ac01c3e..bbf215c67 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java @@ -32,7 +32,7 @@ public class DebugAdapterContext implements IDebugAdapterContext { private static final int MAX_CACHE_ITEMS = 10000; private final StepFilters defaultFilters = new StepFilters(); - private Map sourceMappingCache = Collections.synchronizedMap(new LRUCache<>(MAX_CACHE_ITEMS)); + private Map sourceMappingCache = Collections.synchronizedMap(new LRUCache<>(MAX_CACHE_ITEMS)); private IProviderContext providerContext; private IProtocolServer server; @@ -212,7 +212,7 @@ public void setVariableFormatter(IVariableFormatter variableFormatter) { } @Override - public Map getSourceLookupCache() { + public Map getSourceLookupCache() { return sourceMappingCache; } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java index 9a38e8598..e65270eef 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java @@ -85,7 +85,7 @@ public interface IDebugAdapterContext { void setVariableFormatter(IVariableFormatter variableFormatter); - Map getSourceLookupCache(); + Map getSourceLookupCache(); void setDebuggeeEncoding(Charset encoding); diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ISourceLookUpProvider.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ISourceLookUpProvider.java index 5ffed6e0f..f33742852 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ISourceLookUpProvider.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ISourceLookUpProvider.java @@ -8,7 +8,6 @@ * Contributors: * Microsoft Corporation - initial API and implementation *******************************************************************************/ - package com.microsoft.java.debug.core.adapter; import java.util.List; @@ -42,18 +41,31 @@ public interface ISourceLookUpProvider extends IProvider { JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceBreakpoint[] sourceBreakpoints) throws DebugException; /** - * Given a fully qualified class name and source file path, search the associated disk source file. - * - * @param fullyQualifiedName - * the fully qualified class name (e.g. com.microsoft.java.debug.core.adapter.ISourceLookUpProvider). - * @param sourcePath - * the qualified source file path (e.g. com\microsoft\java\debug\core\adapter\ISourceLookupProvider.java). - * @return the associated source file uri. + * Deprecated, please use {@link #getSource(String, String)} instead. */ + @Deprecated String getSourceFileURI(String fullyQualifiedName, String sourcePath); String getSourceContents(String uri); + /** + * Retrieves a {@link Source} object representing the source code associated with the given fully qualified class name and source file path. + * The implementation of this interface can determine a source is "local" or "remote". + * In case of "remote" a follow up "source" request will be issued by the client + * + * @param fullyQualifiedName + * the fully qualified class name, + * e.g., "com.microsoft.java.debug.core.adapter.ISourceLookUpProvider". + * @param sourcePath + * the qualified source file path, + * e.g., "com/microsoft/java/debug/core/adapter/ISourceLookupProvider.java". + * @return A {@link Source} object encapsulating the source file URI obtained from + * {@link #getSourceFileURI(String, String)} and the source type as {@link SourceType#LOCAL}. + */ + default Source getSource(String fullyQualifiedName, String sourcePath) { + return new Source(getSourceFileURI(fullyQualifiedName, sourcePath), SourceType.LOCAL); + } + /** * Returns the Java runtime that the specified project's build path used. * @param projectName diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/Source.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/Source.java new file mode 100644 index 000000000..d00b4cb4c --- /dev/null +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/Source.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +public class Source { + public final String uri; + public final SourceType type; + + public Source(String uri, SourceType type) { + this.uri = uri; + this.type = type; + } + + public String getUri() { + return this.uri; + } + + public SourceType getType() { + return this.type; + } +} diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/SourceType.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/SourceType.java new file mode 100644 index 000000000..724bf3bda --- /dev/null +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/SourceType.java @@ -0,0 +1,16 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ +package com.microsoft.java.debug.core.adapter; + +public enum SourceType { + REMOTE, + LOCAL +} diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchUtils.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchUtils.java index 27fdb1813..7370328b2 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchUtils.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchUtils.java @@ -95,7 +95,7 @@ public static synchronized Path generateClasspathJar(String[] classPaths) throws // In jar manifest, the absolute path C:\a.jar should be converted to the url style file:///C:/a.jar String classpathValue = String.join(" ", classpathUrls); attributes.put(Attributes.Name.CLASS_PATH, classpathValue); - String baseName = "cp_" + getMd5(classpathValue); + String baseName = "cp_" + getSha256(classpathValue); cleanupTempFiles(baseName, ".jar"); Path tempfile = createTempFile(baseName, ".jar"); JarOutputStream jar = new JarOutputStream(new FileOutputStream(tempfile.toFile()), manifest); @@ -127,7 +127,7 @@ public static synchronized Path generateArgfile(String vmArgs, String[] classPat } argfile = argfile.replace("\\", "\\\\"); - String baseName = "cp_" + getMd5(argfile); + String baseName = "cp_" + getSha256(argfile); cleanupTempFiles(baseName, ".argfile"); Path tempfile = createTempFile(baseName, ".argfile"); Files.writeString(tempfile, argfile, encoding); @@ -364,12 +364,15 @@ private static Path createTempFile(String baseName, String suffix) throws IOExce } } - private static String getMd5(String input) { + private static String getSha256(String input) { try { - MessageDigest md = MessageDigest.getInstance("MD5"); + MessageDigest md = MessageDigest.getInstance("SHA-256"); byte[] messageDigest = md.digest(input.getBytes()); - BigInteger md5 = new BigInteger(1, messageDigest); - return md5.toString(Character.MAX_RADIX); + // Use only first 16 bytes to keep filename shorter + byte[] truncated = new byte[16]; + System.arraycopy(messageDigest, 0, truncated, 0, 16); + BigInteger hash = new BigInteger(1, truncated); + return hash.toString(Character.MAX_RADIX); } catch (NoSuchAlgorithmException e) { return Integer.toString(input.hashCode(), Character.MAX_RADIX); } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java index 0ba3b2004..6f54bcedb 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java @@ -29,22 +29,24 @@ import com.google.gson.JsonObject; import com.microsoft.java.debug.core.AsyncJdwpUtils; import com.microsoft.java.debug.core.DebugSettings; +import com.microsoft.java.debug.core.DebugSettings.Switch; import com.microsoft.java.debug.core.DebugUtility; import com.microsoft.java.debug.core.IBreakpoint; -import com.microsoft.java.debug.core.DebugSettings.Switch; import com.microsoft.java.debug.core.adapter.AdapterUtils; import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider; +import com.microsoft.java.debug.core.adapter.Source; +import com.microsoft.java.debug.core.adapter.SourceType; import com.microsoft.java.debug.core.adapter.formatter.SimpleTypeFormatter; import com.microsoft.java.debug.core.adapter.variables.StackFrameReference; +import com.microsoft.java.debug.core.protocol.Events.TelemetryEvent; import com.microsoft.java.debug.core.protocol.Messages.Response; import com.microsoft.java.debug.core.protocol.Requests.Arguments; import com.microsoft.java.debug.core.protocol.Requests.Command; import com.microsoft.java.debug.core.protocol.Requests.StackTraceArguments; import com.microsoft.java.debug.core.protocol.Responses; import com.microsoft.java.debug.core.protocol.Types; -import com.microsoft.java.debug.core.protocol.Events.TelemetryEvent; import com.sun.jdi.AbsentInformationException; import com.sun.jdi.IncompatibleThreadStateException; import com.sun.jdi.LocalVariable; @@ -112,7 +114,7 @@ public CompletableFuture handle(Command command, Arguments arguments, result.add(lspFrame); frameReference.setSource(lspFrame.source); int jdiLineNumber = AdapterUtils.convertLineNumber(jdiFrame.lineNumber, context.isDebuggerLinesStartAt1(), context.isClientLinesStartAt1()); - if (jdiLineNumber != lspFrame.line) { + if (jdiLineNumber != lspFrame.line && lspFrame.source != null && lspFrame.source.path != null) { decompiledClasses.add(lspFrame.source.path); } } @@ -141,7 +143,7 @@ public CompletableFuture handle(Command command, Arguments arguments, } private static List resolveStackFrameInfos(StackFrame[] frames, boolean async) - throws AbsentInformationException, IncompatibleThreadStateException { + throws AbsentInformationException, IncompatibleThreadStateException { List jdiFrames = new ArrayList<>(); List> futures = new ArrayList<>(); for (StackFrame frame : frames) { @@ -220,8 +222,10 @@ private Types.StackFrame convertDebuggerStackFrameToClient(StackFrameInfo jdiFra // display "Unknown Source" in the Call Stack View. clientSource = null; } + // DAP specifies lineNumber to be set to 0 when unavailable + clientLineNumber = 0; } else if (DebugSettings.getCurrent().debugSupportOnDecompiledSource == Switch.ON - && clientSource != null && clientSource.path != null) { + && clientSource != null && clientSource.path != null) { // Align the original line with the decompiled line. int[] lineMappings = context.getProvider(ISourceLookUpProvider.class).getOriginalLineMappings(clientSource.path); int[] renderLines = AdapterUtils.binarySearchMappedLines(lineMappings, clientLineNumber); @@ -244,7 +248,7 @@ private Types.StackFrame convertDebuggerStackFrameToClient(StackFrameInfo jdiFra }); if (match) { clientColumnNumber = AdapterUtils.convertColumnNumber(breakpoint.getColumnNumber(), - context.isDebuggerColumnsStartAt1(), context.isClientColumnsStartAt1()); + context.isDebuggerColumnsStartAt1(), context.isClientColumnsStartAt1()); } } } @@ -256,33 +260,44 @@ private Types.StackFrame convertDebuggerStackFrameToClient(StackFrameInfo jdiFra /** * Find the source mapping for the specified source file name. */ - public static Types.Source convertDebuggerSourceToClient(String fullyQualifiedName, String sourceName, String relativeSourcePath, + public static Types.Source convertDebuggerSourceToClient(String fullyQualifiedName, String sourceName, + String relativeSourcePath, IDebugAdapterContext context) throws URISyntaxException { + // use a lru cache for better performance - String uri = context.getSourceLookupCache().computeIfAbsent(fullyQualifiedName, key -> { - String fromProvider = context.getProvider(ISourceLookUpProvider.class).getSourceFileURI(key, relativeSourcePath); - // avoid return null which will cause the compute function executed again - return StringUtils.isBlank(fromProvider) ? "" : fromProvider; + Source source = context.getSourceLookupCache().computeIfAbsent(fullyQualifiedName, key -> { + Source result = context.getProvider(ISourceLookUpProvider.class).getSource(key, relativeSourcePath); + if (result == null) { + return new Source("", SourceType.LOCAL); + } + return result; }); + Integer sourceReference = 0; + String uri = source.getUri(); + + if (source.getType().equals(SourceType.REMOTE)) { + sourceReference = context.createSourceReference(source.getUri()); + } + if (!StringUtils.isBlank(uri)) { // The Source.path could be a file system path or uri string. if (uri.startsWith("file:")) { String clientPath = AdapterUtils.convertPath(uri, context.isDebuggerPathsAreUri(), context.isClientPathsAreUri()); - return new Types.Source(sourceName, clientPath, 0); + return new Types.Source(sourceName, clientPath, sourceReference); } else { // If the debugger returns uri in the Source.path for the StackTrace response, VSCode client will try to find a TextDocumentContentProvider // to render the contents. // Language Support for Java by Red Hat extension has already registered a jdt TextDocumentContentProvider to parse the jdt-based uri. // The jdt uri looks like 'jdt://contents/rt.jar/java.io/PrintStream.class?=1.helloworld/%5C/usr%5C/lib%5C/jvm%5C/java-8-oracle%5C/jre%5C/ // lib%5C/rt.jar%3Cjava.io(PrintStream.class'. - return new Types.Source(sourceName, uri, 0); + return new Types.Source(sourceName, uri, sourceReference); } } else { // If the source lookup engine cannot find the source file, then lookup it in the source directories specified by user. String absoluteSourcepath = AdapterUtils.sourceLookup(context.getSourcePaths(), relativeSourcePath); if (absoluteSourcepath != null) { - return new Types.Source(sourceName, absoluteSourcepath, 0); + return new Types.Source(sourceName, absoluteSourcepath, sourceReference); } else { return null; } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java index 71f28355a..e8f782668 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java @@ -268,7 +268,8 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession, } else if (currentStackDepth == threadState.stackDepth) { // If the ending step location is same as the original location where the step into operation is originated, // do another step of the same kind. - if (isSameLocation(currentStepLocation, threadState.stepLocation)) { + if (isSameLocation(currentStepLocation, threadState.stepLocation, + threadState.targetStepIn)) { context.getStepResultManager().removeMethodResult(threadId); threadState.pendingStepRequest = DebugUtility.createStepIntoRequest(thread, context.getStepFilters().allowClasses, @@ -438,15 +439,19 @@ private boolean shouldDoExtraStepInto(int originalStackDepth, Location originalL return true; } - private boolean isSameLocation(Location original, Location current) { + private boolean isSameLocation(Location current, Location original, MethodInvocation targetStepIn) { if (original == null || current == null) { return false; } Method originalMethod = original.method(); Method currentMethod = current.method(); + // if the lines doesn't match, check if the current line is still behind the + // target if a target exist. This handles where the target is part of a + // expression which is wrapped. return originalMethod.equals(currentMethod) - && original.lineNumber() == current.lineNumber(); + && (original.lineNumber() == current.lineNumber() + || (targetStepIn != null && targetStepIn.lineEnd >= current.lineNumber())); } /** diff --git a/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/BreakpointTest.java b/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/BreakpointTest.java new file mode 100644 index 000000000..5cc6fa3d7 --- /dev/null +++ b/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/BreakpointTest.java @@ -0,0 +1,17 @@ +package com.microsoft.java.debug.core; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class BreakpointTest { + @Test + public void testToNoneGeneric() { + assertEquals("Ljava.util.List;", Breakpoint.toNoneGeneric("Ljava.util.List;")); + assertEquals("(Ljava/util/Map;)Ljava/util/Map;", Breakpoint.toNoneGeneric( + "(Ljava/util/Map;>;)Ljava/util/Map;>;")); + assertEquals("(Ljava/util/Map;)Ljava/util/Map;", + Breakpoint.toNoneGeneric( + "(Ljava/util/Map;Ljava/util/List;>;)Ljava/util/Map;Ljava/util/List;>;")); + } +} diff --git a/com.microsoft.java.debug.plugin/.classpath b/com.microsoft.java.debug.plugin/.classpath index ca5f41e6f..b2be945c8 100644 --- a/com.microsoft.java.debug.plugin/.classpath +++ b/com.microsoft.java.debug.plugin/.classpath @@ -1,16 +1,16 @@ - + - + - + diff --git a/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF b/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF index 84a0ab36f..f2ceffb53 100644 --- a/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF +++ b/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF @@ -2,8 +2,8 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Java Debug Server Plugin Bundle-SymbolicName: com.microsoft.java.debug.plugin;singleton:=true -Bundle-Version: 0.50.0 -Bundle-RequiredExecutionEnvironment: JavaSE-11 +Bundle-Version: 0.53.2 +Bundle-RequiredExecutionEnvironment: JavaSE-21 Bundle-ActivationPolicy: lazy Bundle-Activator: com.microsoft.java.debug.plugin.internal.JavaDebuggerServerPlugin Bundle-Vendor: Microsoft @@ -21,8 +21,8 @@ Require-Bundle: org.eclipse.core.runtime, org.apache.commons.lang3, org.eclipse.lsp4j, com.google.guava -Bundle-ClassPath: lib/commons-io-2.11.0.jar, +Bundle-ClassPath: lib/commons-io-2.19.0.jar, ., lib/rxjava-2.2.21.jar, lib/reactive-streams-1.0.4.jar, - lib/com.microsoft.java.debug.core-0.50.0.jar + lib/com.microsoft.java.debug.core-0.53.2.jar diff --git a/com.microsoft.java.debug.plugin/pom.xml b/com.microsoft.java.debug.plugin/pom.xml index b895ffd28..2e2c0e6a8 100644 --- a/com.microsoft.java.debug.plugin/pom.xml +++ b/com.microsoft.java.debug.plugin/pom.xml @@ -5,7 +5,7 @@ com.microsoft.java java-debug-parent - 0.50.0 + 0.53.2 com.microsoft.java.debug.plugin eclipse-plugin @@ -51,12 +51,12 @@ commons-io commons-io - 2.11.0 + 2.19.0 com.microsoft.java com.microsoft.java.debug.core - 0.50.0 + 0.53.2 diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/BreakpointLocationLocator.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/BreakpointLocationLocator.java index b14afde83..97581242e 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/BreakpointLocationLocator.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/BreakpointLocationLocator.java @@ -22,8 +22,8 @@ public class BreakpointLocationLocator public BreakpointLocationLocator(CompilationUnit compilationUnit, int lineNumber, boolean bindingsResolved, - boolean bestMatch) { - super(compilationUnit, lineNumber, bindingsResolved, bestMatch); + boolean bestMatch, int offset, int end) { + super(compilationUnit, lineNumber, bindingsResolved, bestMatch, offset, end); } @Override diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedLaunchingConnector.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedLaunchingConnector.java index 3551ab2e6..24c65785d 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedLaunchingConnector.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedLaunchingConnector.java @@ -195,7 +195,6 @@ private static String[] constructLaunchCommand(Map l StringBuilder execString = new StringBuilder(); execString.append("\"" + javaHome + slash + "bin" + slash + javaExec + "\""); - execString.append(" -Xdebug -Xnoagent -Djava.compiler=NONE"); execString.append(" -Xrunjdwp:transport=dt_socket,address=" + address + ",server=n,suspend=" + (suspend ? "y" : "n")); if (javaOptions != null) { execString.append(" " + javaOptions); diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/Compile.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/Compile.java index 9cb6fbbfa..245281816 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/Compile.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/Compile.java @@ -14,6 +14,7 @@ package com.microsoft.java.debug.plugin.internal; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; import java.util.logging.Logger; @@ -28,14 +29,16 @@ import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.ls.core.internal.BuildWorkspaceStatus; import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; import org.eclipse.jdt.ls.core.internal.ProjectUtils; import org.eclipse.jdt.ls.core.internal.ResourceUtils; +import org.eclipse.jdt.ls.core.internal.handlers.BuildWorkspaceHandler; import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager; +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.eclipse.lsp4j.extended.ProjectBuildParams; import com.microsoft.java.debug.core.Configuration; @@ -45,20 +48,12 @@ public class Compile { private static final int GRADLE_BS_COMPILATION_ERROR = 100; public static Object compile(CompileParams params, IProgressMonitor monitor) { - IProject mainProject = params == null ? null : ProjectUtils.getProject(params.getProjectName()); - if (mainProject == null) { - try { - // Q: is infer project by main class name necessary? perf impact? - List javaProjects = ResolveClasspathsHandler.getJavaProjectFromType(params.getMainClass()); - if (javaProjects.size() == 1) { - mainProject = javaProjects.get(0).getProject(); - } - } catch (CoreException e) { - JavaLanguageServerPlugin.logException("Failed to resolve project from main class name.", e); - } + if (params == null) { + throw new IllegalArgumentException("The compile parameters should not be null."); } - if (isBspProject(mainProject) && !ProjectUtils.isGradleProject(mainProject)) { + IProject mainProject = JdtUtils.getMainProject(params.getProjectName(), params.getMainClass()); + if (JdtUtils.isBspProject(mainProject) && !ProjectUtils.isGradleProject(mainProject)) { // Just need to trigger a build for the target project, the Gradle build server will // handle the build dependencies for us. try { @@ -78,20 +73,37 @@ public static Object compile(CompileParams params, IProgressMonitor monitor) { return BuildWorkspaceStatus.SUCCEED; } - try { - if (monitor.isCanceled()) { - return BuildWorkspaceStatus.CANCELLED; - } + if (monitor.isCanceled()) { + return BuildWorkspaceStatus.CANCELLED; + } - long compileAt = System.currentTimeMillis(); - if (params != null && params.isFullBuild()) { - ResourcesPlugin.getWorkspace().build(IncrementalProjectBuilder.CLEAN_BUILD, monitor); - ResourcesPlugin.getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, monitor); - } else { - ResourcesPlugin.getWorkspace().build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor); + ProjectBuildParams buildParams = new ProjectBuildParams(); + List identifiers = new LinkedList<>(); + buildParams.setFullBuild(params.isFullBuild); + for (IJavaProject javaProject : ProjectUtils.getJavaProjects()) { + if (ProjectsManager.getDefaultProject().equals(javaProject.getProject())) { + continue; } - logger.info("Time cost for ECJ: " + (System.currentTimeMillis() - compileAt) + "ms"); + // we only build project which is not a BSP project, in case that the compile request is triggered by + // HCR with auto-build disabled, the build for BSP projects will be triggered by JavaHotCodeReplaceProvider. + if (!JdtUtils.isBspProject(javaProject.getProject())) { + identifiers.add(new TextDocumentIdentifier(javaProject.getProject().getLocationURI().toString())); + } + } + if (identifiers.size() == 0) { + return BuildWorkspaceStatus.SUCCEED; + } + buildParams.setIdentifiers(identifiers); + long compileAt = System.currentTimeMillis(); + BuildWorkspaceHandler buildWorkspaceHandler = new BuildWorkspaceHandler(JavaLanguageServerPlugin.getProjectsManager()); + BuildWorkspaceStatus status = buildWorkspaceHandler.buildProjects(buildParams, monitor); + logger.info("Time cost for ECJ: " + (System.currentTimeMillis() - compileAt) + "ms"); + if (status == BuildWorkspaceStatus.FAILED || status == BuildWorkspaceStatus.CANCELLED) { + return status; + } + + try { IResource currentResource = mainProject; if (isUnmanagedFolder(mainProject) && StringUtils.isNotBlank(params.getMainClass())) { IType mainType = ProjectUtils.getJavaProject(mainProject).findType(params.getMainClass()); @@ -135,17 +147,14 @@ public static Object compile(CompileParams params, IProgressMonitor monitor) { } } - if (problemMarkers.isEmpty()) { - return BuildWorkspaceStatus.SUCCEED; + if (!problemMarkers.isEmpty()) { + return BuildWorkspaceStatus.WITH_ERROR; } - - return BuildWorkspaceStatus.WITH_ERROR; } catch (CoreException e) { - JavaLanguageServerPlugin.logException("Failed to build workspace.", e); - return BuildWorkspaceStatus.FAILED; - } catch (OperationCanceledException e) { - return BuildWorkspaceStatus.CANCELLED; + JavaLanguageServerPlugin.log(e); } + + return BuildWorkspaceStatus.SUCCEED; } private static boolean isUnmanagedFolder(IProject project) { @@ -153,11 +162,6 @@ private static boolean isUnmanagedFolder(IProject project) { && ProjectUtils.isJavaProject(project); } - private static boolean isBspProject(IProject project) { - return project != null && ProjectUtils.isJavaProject(project) - && ProjectUtils.hasNature(project, "com.microsoft.gradle.bs.importer.GradleBuildServerProjectNature"); - } - private static IProject getDefaultProject() { return getWorkspaceRoot().getProject(ProjectsManager.DEFAULT_PROJECT_NAME); } diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/CompletionProposalRequestor.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/CompletionProposalRequestor.java index 1df752f24..431c283f5 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/CompletionProposalRequestor.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/CompletionProposalRequestor.java @@ -72,6 +72,8 @@ public final class CompletionProposalRequestor extends CompletionRequestor { CompletionItemKind.Text); // @formatter:on + private static boolean isFilterFailed = false; + /** * Constructor. * @param typeRoot ITypeRoot @@ -321,7 +323,7 @@ private boolean isFiltered(CompletionProposal proposal) { case CompletionProposal.JAVADOC_TYPE_REF: case CompletionProposal.TYPE_REF: { char[] declaringType = getDeclaringType(proposal); - return declaringType != null && org.eclipse.jdt.ls.core.internal.contentassist.TypeFilter.isFiltered(declaringType); + return declaringType != null && isFiltered(declaringType); } default: // do nothing } @@ -332,6 +334,22 @@ private boolean isFiltered(CompletionProposal proposal) { return false; } + // Temp workaround for the completion error https://github.com/microsoft/java-debug/issues/534 + private static boolean isFiltered(char[] fullTypeName) { + if (isFilterFailed) { + return false; + } + + try { + return JavaLanguageServerPlugin.getInstance().getTypeFilter().filter(new String(fullTypeName)); + } catch (NoSuchMethodError ex) { + isFilterFailed = true; + JavaLanguageServerPlugin.logException("isFiltered for the completion failed.", ex); + } + + return false; + } + /** * copied from * org.eclipse.jdt.ui.text.java.CompletionProposalCollector.getDeclaringType(CompletionProposal) diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaHotCodeReplaceProvider.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaHotCodeReplaceProvider.java index 2bf905c89..3a2b2f3df 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaHotCodeReplaceProvider.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaHotCodeReplaceProvider.java @@ -34,16 +34,20 @@ import java.util.logging.Level; import java.util.logging.Logger; +import org.eclipse.core.resources.IBuildConfiguration; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IResourceDeltaVisitor; +import org.eclipse.core.resources.IncrementalProjectBuilder; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; @@ -55,6 +59,7 @@ import org.eclipse.jdt.core.util.ISourceAttribute; import org.eclipse.jdt.internal.core.util.Util; import org.eclipse.jdt.ls.core.internal.JobHelpers; +import org.eclipse.jdt.ls.core.internal.ProjectUtils; import com.microsoft.java.debug.core.Configuration; import com.microsoft.java.debug.core.DebugException; @@ -63,6 +68,7 @@ import com.microsoft.java.debug.core.IDebugSession; import com.microsoft.java.debug.core.StackFrameUtility; import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.Constants; import com.microsoft.java.debug.core.adapter.ErrorCode; import com.microsoft.java.debug.core.adapter.HotCodeReplaceEvent; import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; @@ -104,6 +110,8 @@ public class JavaHotCodeReplaceProvider implements IHotCodeReplaceProvider, IRes private List deltaClassNames = new ArrayList<>(); + private String mainProjectName = ""; + /** * Visitor for resource deltas. */ @@ -269,6 +277,7 @@ public void initialize(IDebugAdapterContext context, Map options } this.context = context; currentDebugSession = context.getDebugSession(); + this.mainProjectName = ((String) options.get(Constants.PROJECT_NAME)); } @Override @@ -319,6 +328,7 @@ public void onClassRedefined(Consumer> consumer) { @Override public CompletableFuture> redefineClasses() { + triggerBuildForBspProject(); JobHelpers.waitForBuildJobs(10 * 1000); return CompletableFuture.supplyAsync(() -> { List classNames = new ArrayList<>(); @@ -737,4 +747,39 @@ private List getStackFrames(ThreadReference thread, boolean refresh) } }); } + + /** + * Trigger build separately if the main project is a BSP project. + * This is because auto build for BSP project will not update the class files to disk. + */ + private void triggerBuildForBspProject() { + // check if the workspace contains BSP project first. This is for performance consideration. + // Due to that getJavaProjectFromType() is a heavy operation. + if (!containsBspProjects()) { + return; + } + + IProject mainProject = JdtUtils.getMainProject(this.mainProjectName, context.getMainClass()); + if (mainProject != null && JdtUtils.isBspProject(mainProject)) { + try { + ResourcesPlugin.getWorkspace().build( + new IBuildConfiguration[]{mainProject.getActiveBuildConfig()}, + IncrementalProjectBuilder.INCREMENTAL_BUILD, + false /*buildReference*/, + new NullProgressMonitor() + ); + } catch (CoreException e) { + // ignore compilation errors + } + } + } + + private boolean containsBspProjects() { + for (IJavaProject javaProject : ProjectUtils.getJavaProjects()) { + if (JdtUtils.isBspProject(javaProject.getProject())) { + return true; + } + } + return false; + } } diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtSourceLookUpProvider.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtSourceLookUpProvider.java index f0c8795cc..5652b922e 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtSourceLookUpProvider.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtSourceLookUpProvider.java @@ -20,11 +20,14 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.jar.Attributes; import java.util.jar.Manifest; import java.util.logging.Level; @@ -32,6 +35,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import org.apache.commons.lang3.StringUtils; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; @@ -72,8 +76,8 @@ import com.microsoft.java.debug.core.Configuration; import com.microsoft.java.debug.core.DebugException; import com.microsoft.java.debug.core.DebugSettings; -import com.microsoft.java.debug.core.JavaBreakpointLocation; import com.microsoft.java.debug.core.DebugSettings.Switch; +import com.microsoft.java.debug.core.JavaBreakpointLocation; import com.microsoft.java.debug.core.adapter.AdapterUtils; import com.microsoft.java.debug.core.adapter.Constants; import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; @@ -85,6 +89,9 @@ public class JdtSourceLookUpProvider implements ISourceLookUpProvider { private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); private static final String JDT_SCHEME = "jdt"; private static final String PATH_SEPARATOR = "/"; + private static final Set IMPLICITLY_DECLARED_CLASSES = new HashSet<>( + Arrays.asList("org.eclipse.jdt.core.dom.UnnamedClass", + "org.eclipse.jdt.core.dom.ImplicitTypeDeclaration")); private ISourceContainer[] sourceContainers = null; private HashMap options = new HashMap(); @@ -172,6 +179,14 @@ public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceB .map(sourceBreakpoint -> new JavaBreakpointLocation(sourceBreakpoint.line, sourceBreakpoint.column)) .toArray(JavaBreakpointLocation[]::new); if (astUnit != null) { + List types = astUnit.types(); + String unnamedClass = null; + // See https://github.com/eclipse-jdt/eclipse.jdt.core/pull/2220 + // Given that the JDT plans to rename UnamedClass to ImplicitTypeDeclaration, we will check + // the class name of the ASTNode to prevent the potential breaking in the future. + if (types.size() == 1 && IMPLICITLY_DECLARED_CLASSES.contains(types.get(0).getClass().getName())) { + unnamedClass = inferPrimaryTypeName(sourceUri, astUnit); + } Map resolvedLocations = new HashMap<>(); for (JavaBreakpointLocation sourceLocation : sourceLocations) { int sourceLine = sourceLocation.lineNumber(); @@ -210,8 +225,11 @@ public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceB // mark it as "unverified". // In future, we could consider supporting to update the breakpoint to a valid // location. + + // passing the offset to the constructor, it can recognize the multiline lambda + // expression well BreakpointLocationLocator locator = new BreakpointLocationLocator(astUnit, - sourceLine, true, true); + sourceLine, true, true, astUnit.getPosition(sourceLine, 0), 0); astUnit.accept(locator); // When the final valid line location is same as the original line, that // represents it's a valid breakpoint. @@ -219,7 +237,7 @@ public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceB // be hit in current implementation. if (sourceLine == locator.getLineLocation() && locator.getLocationType() == BreakpointLocationLocator.LOCATION_LINE) { - sourceLocation.setClassName(locator.getFullyQualifiedTypeName()); + sourceLocation.setClassName(StringUtils.isBlank(unnamedClass) ? locator.getFullyQualifiedTypeName() : unnamedClass); if (resolvedLocations.containsKey(sourceLine)) { sourceLocation.setAvailableBreakpointLocations(resolvedLocations.get(sourceLine)); } else { @@ -228,7 +246,7 @@ public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceB resolvedLocations.put(sourceLine, inlineLocations); } } else if (locator.getLocationType() == BreakpointLocationLocator.LOCATION_METHOD) { - sourceLocation.setClassName(locator.getFullyQualifiedTypeName()); + sourceLocation.setClassName(StringUtils.isBlank(unnamedClass) ? locator.getFullyQualifiedTypeName() : unnamedClass); sourceLocation.setMethodName(locator.getMethodName()); sourceLocation.setMethodSignature(locator.getMethodSignature()); } @@ -238,6 +256,27 @@ public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceB return sourceLocations; } + private String inferPrimaryTypeName(String uri, CompilationUnit astUnit) { + String fileName = ""; + String filePath = AdapterUtils.toPath(uri); + if (filePath != null && Files.isRegularFile(Paths.get(filePath))) { + fileName = Paths.get(filePath).getFileName().toString(); + } else if (astUnit.getTypeRoot() != null) { + fileName = astUnit.getTypeRoot().getElementName(); + } + + if (StringUtils.isNotBlank(fileName)) { + String[] extensions = JavaCore.getJavaLikeExtensions(); + for (String extension : extensions) { + if (fileName.endsWith("." + extension)) { + return fileName.substring(0, fileName.length() - 1 - extension.length()); + } + } + } + + return fileName; + } + private BreakpointLocation[] getInlineBreakpointLocations(final CompilationUnit astUnit, int sourceLine) { List locations = new ArrayList<>(); // The starting position of each line is the default breakpoint location for diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtUtils.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtUtils.java index a4c06c6d4..66562ae74 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtUtils.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtUtils.java @@ -40,6 +40,8 @@ import org.eclipse.jdt.launching.JavaRuntime; import org.eclipse.jdt.launching.sourcelookup.containers.JavaProjectSourceContainer; import org.eclipse.jdt.launching.sourcelookup.containers.PackageFragmentRootSourceContainer; +import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; +import org.eclipse.jdt.ls.core.internal.ProjectUtils; import com.microsoft.java.debug.core.DebugException; import com.microsoft.java.debug.core.StackFrameUtility; @@ -415,4 +417,36 @@ public static boolean isSameFile(IResource resource1, IResource resource2) { return Objects.equals(resource1.getLocation(), resource2.getLocation()); } + + /** + * Check if the project is managed by Gradle Build Server. + */ + public static boolean isBspProject(IProject project) { + return project != null && ProjectUtils.isJavaProject(project) + && ProjectUtils.hasNature(project, "com.microsoft.gradle.bs.importer.GradleBuildServerProjectNature"); + } + + /** + * Get main project according to the main project name or main class name, + * or return null if the main project cannot be resolved. + */ + public static IProject getMainProject(String mainProjectName, String mainClassName) { + IProject mainProject = null; + if (StringUtils.isNotBlank(mainProjectName)) { + mainProject = ProjectUtils.getProject(mainProjectName); + } + + if (mainProject == null && StringUtils.isNotBlank(mainClassName)) { + try { + List javaProjects = ResolveClasspathsHandler.getJavaProjectFromType(mainClassName); + if (javaProjects.size() == 1) { + mainProject = javaProjects.get(0).getProject(); + } + } catch (CoreException e) { + JavaLanguageServerPlugin.logException("Failed to resolve project from main class name.", e); + } + } + + return mainProject; + } } diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/MethodInvocationLocator.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/MethodInvocationLocator.java index a25fbc3f6..654cbb00b 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/MethodInvocationLocator.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/MethodInvocationLocator.java @@ -217,7 +217,7 @@ public boolean visit(ClassInstanceCreation node) { private boolean shouldVisitNode(ASTNode node) { int start = unit.getLineNumber(node.getStartPosition()); - int end = unit.getLineNumber(node.getStartPosition() + node.getLength()); + int end = unit.getLineNumber(node.getStartPosition() + node.getLength() - 1); if (line >= start && line <= end) { return true; diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java index f70d0c044..fc8189445 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java @@ -35,6 +35,7 @@ import org.eclipse.core.runtime.IPath; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.search.IJavaSearchConstants; import org.eclipse.jdt.core.search.IJavaSearchScope; @@ -43,6 +44,7 @@ import org.eclipse.jdt.core.search.SearchParticipant; import org.eclipse.jdt.core.search.SearchPattern; import org.eclipse.jdt.core.search.SearchRequestor; +import org.eclipse.jdt.internal.core.SourceMethod; import org.eclipse.jdt.ls.core.internal.ProjectUtils; import org.eclipse.jdt.ls.core.internal.ResourceUtils; import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager; @@ -99,10 +101,19 @@ private List resolveMainClassCore(List arguments) { private List resolveMainClassUnderPaths(List parentPaths) { // Limit to search main method from source code only. - IJavaSearchScope searchScope = SearchEngine.createJavaSearchScope(ProjectUtils.getJavaProjects(), - IJavaSearchScope.REFERENCED_PROJECTS | IJavaSearchScope.SOURCES); - SearchPattern pattern = SearchPattern.createPattern("main(String[]) void", IJavaSearchConstants.METHOD, - IJavaSearchConstants.DECLARATIONS, SearchPattern.R_CASE_SENSITIVE | SearchPattern.R_EXACT_MATCH); + IJavaProject[] projects; + if (parentPaths == null || parentPaths.isEmpty()) { + projects = ProjectUtils.getJavaProjects(); + } else { + projects = Stream.of(ProjectUtils.getAllProjects()) + .filter(p -> ProjectUtils.isJavaProject(p) && p.getLocation() != null && ResourceUtils.isContainedIn(p.getLocation(), parentPaths)) + .map(p -> JavaCore.create(p)) + .filter(p -> p.exists()) + .toArray(IJavaProject[]::new); + } + IJavaSearchScope searchScope = SearchEngine.createJavaSearchScope(projects, + IJavaSearchScope.SOURCES); + SearchPattern pattern = createMainMethodSearchPattern(); final List res = new ArrayList<>(); SearchRequestor requestor = new SearchRequestor() { @Override @@ -110,40 +121,36 @@ public void acceptSearchMatch(SearchMatch match) { Object element = match.getElement(); if (element instanceof IMethod) { IMethod method = (IMethod) element; - try { - if (method.isMainMethod()) { - IResource resource = method.getResource(); - if (resource != null) { - IProject project = resource.getProject(); - if (project != null) { - String mainClass = method.getDeclaringType().getFullyQualifiedName(); - IJavaProject javaProject = JdtUtils.getJavaProject(project); - if (javaProject != null) { - String moduleName = JdtUtils.getModuleName(javaProject); - if (moduleName != null) { - mainClass = moduleName + "/" + mainClass; - } + if (isMainMethod(method)) { + IResource resource = method.getResource(); + if (resource != null) { + IProject project = resource.getProject(); + if (project != null) { + String mainClass = method.getDeclaringType().getFullyQualifiedName(); + IJavaProject javaProject = JdtUtils.getJavaProject(project); + if (javaProject != null) { + String moduleName = JdtUtils.getModuleName(javaProject); + if (moduleName != null) { + mainClass = moduleName + "/" + mainClass; } - String projectName = ProjectsManager.DEFAULT_PROJECT_NAME.equals(project.getName()) ? null : project.getName(); - if (parentPaths.isEmpty() - || ResourceUtils.isContainedIn(project.getLocation(), parentPaths) - || isContainedInInvisibleProject(project, parentPaths)) { - String filePath = null; - - if (match.getResource() instanceof IFile) { - try { - filePath = match.getResource().getLocation().toOSString(); - } catch (Exception ex) { - // ignore - } + } + String projectName = ProjectsManager.DEFAULT_PROJECT_NAME.equals(project.getName()) ? null : project.getName(); + if (parentPaths.isEmpty() + || ResourceUtils.isContainedIn(project.getLocation(), parentPaths) + || isContainedInInvisibleProject(project, parentPaths)) { + String filePath = null; + + if (match.getResource() instanceof IFile) { + try { + filePath = match.getResource().getLocation().toOSString(); + } catch (Exception ex) { + // ignore } - res.add(new ResolutionItem(mainClass, projectName, filePath)); } + res.add(new ResolutionItem(mainClass, projectName, filePath)); } } } - } catch (JavaModelException e) { - // ignore } } } @@ -166,8 +173,7 @@ private List resolveMainClassUnderProject(final String projectNa IJavaProject javaProject = ProjectUtils.getJavaProject(projectName); IJavaSearchScope searchScope = SearchEngine.createJavaSearchScope(javaProject == null ? new IJavaProject[0] : new IJavaProject[] {javaProject}, IJavaSearchScope.REFERENCED_PROJECTS | IJavaSearchScope.SOURCES); - SearchPattern pattern = SearchPattern.createPattern("main(String[]) void", IJavaSearchConstants.METHOD, - IJavaSearchConstants.DECLARATIONS, SearchPattern.R_CASE_SENSITIVE | SearchPattern.R_EXACT_MATCH); + SearchPattern pattern = createMainMethodSearchPattern(); final List res = new ArrayList<>(); SearchRequestor requestor = new SearchRequestor() { @Override @@ -175,35 +181,31 @@ public void acceptSearchMatch(SearchMatch match) { Object element = match.getElement(); if (element instanceof IMethod) { IMethod method = (IMethod) element; - try { - if (method.isMainMethod()) { - IResource resource = method.getResource(); - if (resource != null) { - IProject project = resource.getProject(); - if (project != null) { - String mainClass = method.getDeclaringType().getFullyQualifiedName(); - IJavaProject javaProject = JdtUtils.getJavaProject(project); - if (javaProject != null) { - String moduleName = JdtUtils.getModuleName(javaProject); - if (moduleName != null) { - mainClass = moduleName + "/" + mainClass; - } + if (isMainMethod(method)) { + IResource resource = method.getResource(); + if (resource != null) { + IProject project = resource.getProject(); + if (project != null) { + String mainClass = method.getDeclaringType().getFullyQualifiedName(); + IJavaProject javaProject = JdtUtils.getJavaProject(project); + if (javaProject != null) { + String moduleName = JdtUtils.getModuleName(javaProject); + if (moduleName != null) { + mainClass = moduleName + "/" + mainClass; } + } - String filePath = null; - if (match.getResource() instanceof IFile) { - try { - filePath = match.getResource().getLocation().toOSString(); - } catch (Exception ex) { - // ignore - } + String filePath = null; + if (match.getResource() instanceof IFile) { + try { + filePath = match.getResource().getLocation().toOSString(); + } catch (Exception ex) { + // ignore } - res.add(new ResolutionItem(mainClass, projectName, filePath)); } + res.add(new ResolutionItem(mainClass, projectName, filePath)); } } - } catch (JavaModelException e) { - // ignore } } } @@ -221,6 +223,29 @@ public void acceptSearchMatch(SearchMatch match) { return resolutions; } + private SearchPattern createMainMethodSearchPattern() { + SearchPattern pattern1 = SearchPattern.createPattern("main(String[]) void", IJavaSearchConstants.METHOD, + IJavaSearchConstants.DECLARATIONS, SearchPattern.R_CASE_SENSITIVE | SearchPattern.R_EXACT_MATCH); + SearchPattern pattern2 = SearchPattern.createPattern("main() void", IJavaSearchConstants.METHOD, + IJavaSearchConstants.DECLARATIONS, SearchPattern.R_CASE_SENSITIVE | SearchPattern.R_EXACT_MATCH); + return SearchPattern.createOrPattern(pattern1, pattern2); + } + + private boolean isMainMethod(IMethod method) { + try { + if (method instanceof SourceMethod + && ((SourceMethod) method).isMainMethodCandidate()) { + return true; + } + + return method.isMainMethod(); + } catch (JavaModelException e) { + // do nothing + } + + return false; + } + private boolean isContainedInInvisibleProject(IProject project, Collection rootPaths) { if (project == null) { return false; diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainMethodHandler.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainMethodHandler.java index a3c61e814..be1092bb3 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainMethodHandler.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainMethodHandler.java @@ -15,6 +15,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; @@ -30,7 +31,11 @@ import org.eclipse.jdt.core.ISourceRange; import org.eclipse.jdt.core.ISourceReference; import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; +import org.eclipse.jdt.internal.core.SourceMethod; import org.eclipse.jdt.ls.core.internal.JDTUtils; import org.eclipse.jdt.ls.core.internal.handlers.DocumentLifeCycleHandler; import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager; @@ -104,16 +109,50 @@ private static List searchMainMethods(ICompilationUnit compilationUnit) * Returns the main method defined in the type. */ public static IMethod getMainMethod(IType type) throws JavaModelException { + boolean allowInstanceMethod = isInstanceMainMethodSupported(type); + List methods = new ArrayList<>(); for (IMethod method : type.getMethods()) { - // Have at most one main method in the member methods of the type. + if (method instanceof SourceMethod + && ((SourceMethod) method).isMainMethodCandidate()) { + methods.add(method); + } + if (method.isMainMethod()) { - return method; + methods.add(method); + } + + if (!allowInstanceMethod && !methods.isEmpty()) { + return methods.get(0); } } + if (!methods.isEmpty()) { + methods.sort((method1, method2) -> { + return getMainMethodPriority(method1) - getMainMethodPriority(method2); + }); + + return methods.get(0); + } + return null; } + private static boolean isInstanceMainMethodSupported(IType type) { + Map options = type.getJavaProject().getOptions(true); + return CompilerOptions.versionToJdkLevel(options.get(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM)) >= ClassFileConstants.JDK21; + } + + /** + * See Java 22 JEP 463 https://openjdk.org/jeps/463. + * It searches the main method in the launched class by following a specific order: + * - If the launched class contains a main method with a String[] parameter then choose that method. + * - Otherwise, if the class contains a main method with no parameters then choose that method. + */ + private static int getMainMethodPriority(IMethod method) { + String[] params = method.getParameterTypes(); + return params.length == 1 ? 1 : 2; + } + private static List getPotentialMainClassTypes(ICompilationUnit compilationUnit) throws JavaModelException { List result = new ArrayList<>(); IType[] topLevelTypes = compilationUnit.getTypes(); diff --git a/com.microsoft.java.debug.repository/category.xml b/com.microsoft.java.debug.repository/category.xml index 639d50082..f30a2f4c4 100644 --- a/com.microsoft.java.debug.repository/category.xml +++ b/com.microsoft.java.debug.repository/category.xml @@ -1,6 +1,6 @@ - + diff --git a/com.microsoft.java.debug.repository/pom.xml b/com.microsoft.java.debug.repository/pom.xml index 4f05eb331..573aff2ac 100644 --- a/com.microsoft.java.debug.repository/pom.xml +++ b/com.microsoft.java.debug.repository/pom.xml @@ -4,7 +4,7 @@ com.microsoft.java java-debug-parent - 0.50.0 + 0.53.2 com.microsoft.java.debug.repository eclipse-repository diff --git a/com.microsoft.java.debug.target/com.microsoft.java.debug.tp.target b/com.microsoft.java.debug.target/com.microsoft.java.debug.tp.target index ef0a5284e..31462a770 100644 --- a/com.microsoft.java.debug.target/com.microsoft.java.debug.tp.target +++ b/com.microsoft.java.debug.target/com.microsoft.java.debug.tp.target @@ -1,35 +1,20 @@ - + + + - - - - - - - - - - - - - - - - - - - - - + + - - - - - + + + + + + - \ No newline at end of file + + diff --git a/com.microsoft.java.debug.target/pom.xml b/com.microsoft.java.debug.target/pom.xml index 80395f6dd..8e49b638b 100644 --- a/com.microsoft.java.debug.target/pom.xml +++ b/com.microsoft.java.debug.target/pom.xml @@ -4,7 +4,7 @@ com.microsoft.java java-debug-parent - 0.50.0 + 0.53.2 com.microsoft.java.debug.tp ${base.name} :: Target Platform diff --git a/mvnw b/mvnw index e96ccd5fb..e9cf8d330 100755 --- a/mvnw +++ b/mvnw @@ -19,209 +19,277 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Maven2 Start Up Batch script -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir +# Apache Maven Wrapper startup batch script, version 3.3.3 # # Optional ENV vars # ----------------- -# M2_HOME - location of maven2's installed home dir -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output # ---------------------------------------------------------------------------- -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac -fi +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" - else - export JAVA_HOME="/Library/Java/Home" + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 fi fi - ;; -esac + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi fi -fi - -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" +} - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } - saveddir=`pwd` +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac - M2_HOME=`dirname "$PRG"`/.. +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} - cd "$saveddir" - # echo Using m2 at $M2_HOME +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" fi -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" fi -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" - # TODO classpath? +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" fi -if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" - else - javaExecutable="`readlink -f \"$javaExecutable\"`" - fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" fi -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true fi else - JAVACMD="`which java`" + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 fi fi -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" fi -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" fi +fi - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi fi - # end of workaround done - echo "${basedir}" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" - fi -} - -BASE_DIR=`find_maven_basedir "$(pwd)"` -if [ -z "$BASE_DIR" ]; then - exit 1; + set -f fi -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR -fi -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" fi -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" -exec "$JAVACMD" \ - $MAVEN_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd index 019bd74d7..3fd2be860 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -1,143 +1,189 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Maven2 Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" - -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause - -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% - -exit /B %ERROR_CODE% +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.3 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' + +$MAVEN_M2_PATH = "$HOME/.m2" +if ($env:MAVEN_USER_HOME) { + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" +} + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/pom.xml b/pom.xml index d562f82b5..b8320ef8f 100644 --- a/pom.xml +++ b/pom.xml @@ -6,12 +6,12 @@ ${base.name} :: Parent The Java Debug Server is an implementation of Visual Studio Code (VSCode) Debug Protocol. It can be used in Visual Studio Code to debug Java programs. https://github.com/Microsoft/java-debug - 0.50.0 + 0.53.2 pom Java Debug Server for Visual Studio Code UTF-8 - 4.0.3 + 5.0.0 ${basedir}