Skip to content

Gradle Extension

issuelens[bot] edited this page Oct 13, 2026 · 6 revisions

Gradle Extension

BSP importer: project JDK selection

This section describes the JDTLS importer in microsoft/vscode-gradle/extension/jdtls.ext/com.microsoft.gradle.bs.importer, not the task server or the separate Gradle Build Server. Evidence is pinned to source commit d2fad8a1d5b1096ea89ce7971e1b567482c6071f, merged by PR #1951 for issue #1949. The PR does not change the legacy task-server compatibility check, and its changelog entry is under Unreleased, not evidence of deployment.

Compatibility bounds and preserved project settings

GradleBuildServerBuildSupport.getHighestCompatibleJavaVersion supplies these new fallback upper bounds, choosing the highest matching Gradle threshold:

Gradle threshold Java upper bound
8.10 23
8.14 24
9.1.0 25
9.4.0 26
9.8.0 27

At this revision, 9.8.1 maps to 27; older mappings remain, including 8.8 to 22. These are importer selection bounds at the cited revision, not a guarantee about future Gradle releases or a cap on project source levels.

setProjectJdk reads the BSP JVM target's source/target compatibility, writes the JDT compiler source, target, and compliance options, and passes target compatibility as the expected VM version and source compatibility as the fallback lower bound. EclipseVmUtil.getCompatibleVMWithHighestVersion prefers a valid exact target-version match before applying fallback bounds, even when that match exceeds the upper bound. Otherwise it selects the highest valid VM within the bounds from the execution environment's compatible VMs. Missing installations, embedded Java-extension JREs, and unknown Java versions are excluded. If no registered VM is found, findOrRegisterStandardVM registers the supplied Gradle daemon Java home. The compatibility matrix does not rewrite project source/target levels. These behaviors are verified in the linked source at the section's full commit SHA and retained by PR #1951.

Importer regression validation

GradleBuildServerBuildSupportTest contains 11 tests covering release/prerelease/patch boundaries, older mappings, exact-version preference, fallback bounds, invalid runtimes, and source/target preservation. Modeled Java 27 cases include a Java 25 service runtime with a Java 27 target, a Java 27 daemon, and an exact Java 27 match above the Gradle 9.1.0 fallback bound. These use mocked BSP targets and VM registrations; they are not live JDK 27 end-to-end imports. This maintenance review inspected tests but did not execute them.

The importer pom.xml configures test as the test source directory and Surefire 3.5.2, with explicit test-scoped BSP4J 2.1.0-M4, JUnit 4.13.2, and Mockito 5.20.0 dependencies. The explicit BSP4J test dependency is separate from the existing dependency-plugin artifact configuration.

CONTRIBUTING.md documents this PowerShell test entry point from the repository root (its development prerequisites list Java >= 21):

$env:MAVEN_OPTS = '-Djdk.xml.totalEntitySizeLimit=0 -Djdk.xml.maxGeneralEntitySizeLimit=0'
.\extension\jdtls.ext\mvnw.cmd -B -ntp -f .\extension\jdtls.ext\pom.xml -Declipse.p2.mirrors=false verify

All validation sources above are from microsoft/vscode-gradle at d2fad8a1d5b1096ea89ce7971e1b567482c6071f (PR #1951); the development runtime prerequisite is distinct from imported project source/target compatibility.

Task distribution and Java runtime configuration

The following configuration and live-test evidence is from microsoft/vscode-gradle at c0a51f482f9e5beefd8b6187581c4c7d0683c231, merged by PR #1953 for issue #1950. This adds live compatibility coverage alongside the importer unit tests above; it does not change the importer selection matrix. The changelog records the change under Unreleased.

  • Task Gradle distribution: GradleProjectConnector.setConnectorConfig retains wrapper precedence. With the wrapper disabled, selection is explicit java.import.gradle.version, configured java.import.gradle.home, system GRADLE_HOME (environment, then Java system property), then fallback 9.8.1. build.gradle's toolingAPIVersion also changes from 9.2.0 to 9.8.1. This is a client/fallback update, not a forced upgrade of project wrappers.
  • Keep JVM roles separate: the README settings guidance distinguishes Java language-server runtime (java.jdt.ls.java.home), explicit Gradle daemon selection (java.import.gradle.java.home), and project toolchain (java.toolchain.languageVersion plus matching java.configuration.runtimes). Changing a toolchain alone does not change the daemon JVM. The task service is another process: getGradleServerEnv prefers the Java extension's embedded JRE, otherwise findValidJavaHome checks the Gradle-import Java home before JDTLS/legacy settings and environment discovery. Thus the task service need not use the language-server JVM. These selection functions are supporting context, not runtime-selection changes introduced by PR #1953.

Compatibility validation and diagnosis

GradleProjectConnectorTest adds five precedence/fallback tests. GradleToolingApiVersionTest.fallbackVersionMatchesResolvedToolingApiDependency checks the fallback constant against the resolved Tooling API package's implementation version; keep it synchronized with build.gradle.

The separate runCompatibilityTests.ts runner requires both JDK25_HOME and JDK27_HOME. It sets the Java language server to JDK 25 and the project runtime to JDK 27 in both scenarios:

Scenario Distribution Task-service / daemon JVM Project toolchain
GRADLE_COMPATIBILITY_DAEMON=27 Wrapper disabled; 9.8.1 fallback; inherited GRADLE_HOME removed 27 27
GRADLE_COMPATIBILITY_DAEMON=25 9.8.1 wrapper 25 27

The runner uses isolated VS Code user/extension directories, Java extension 1.56.0's universal VSIX without an embedded JRE, and Java debugger 0.59.0. Its gradle-java27 fixture specifies a Java 27 toolchain and JUnit 4.13.2, without replacing the older fixtures. The six live integration tests assert task discovery and daemon version, BSP import with Java 27 source/target/runtime, dependency-model retrieval, JavaExec/JUnit execution, and real JavaExec/Test breakpoint stops. Debug checks inspect the source frame, evaluate feature as 27, continue, and require successful task completion. They explicitly pass an empty argument string to gradle.debugTask; they do not validate the command's omitted-argument path.

CONTRIBUTING.md directs developers to build importer, BSP server, task server, and extension using JDK 21, then run node out/test/runCompatibilityTests.js from extension (prefix with xvfb-run -a on headless Linux). Without GRADLE_COMPATIBILITY_DAEMON, both scenarios run. The runner stops daemons only in its scenario-specific Gradle user home and retains failed workspaces under extension/.vscode-test for diagnosis. .github/workflows/main.yml's test-java27-compatibility adds Ubuntu daemon-25/27 jobs alongside existing jobs and includes extension/server in the shared build artifact.

All sources in these two sections are pinned to c0a51f482f9e5beefd8b6187581c4c7d0683c231 (PR #1953). Maintenance inspected source and assertions, not executed tests or remote CI results. These are configured validation scenarios, not a blanket compatibility guarantee or proof of release/deployment.

BSP importer: dependency outputs and hot code replace

Evidence: microsoft/vscode-gradle at merged source commit 26d8cb0519fc2ee3e085e0af0246d2e2c630b419, PR #1960, addressing issue #1948. Source identity: microsoft/vscode-gradle#1960:26d8cb0519fc2ee3e085e0af0246d2e2c630b419. This concerns the JDTLS Gradle/BSP importer adapter, not the task service or a change to debugger/JDTLS core implementations.

Synchronization boundary and refresh scope

BuildServerBuilder.build and refreshOutputs wait for buildTargetCompile, reject BSP StatusCode.ERROR, then synchronously refresh output resources before the builder returns. Gradle can compile dependent projects without invoking their Eclipse builders; refreshing only the application outputs can therefore omit dependency class-file changes from the workspace build delta used for hot code replace. The PR expands workspace synchronization, not the compile request's target list or the debugger protocol.

  • Traverse raw-classpath CPE_PROJECT entries recursively, resolving workspace projects by the dependency path's last segment. Only accessible Gradle build-server dependencies are followed; inaccessible projects and projects without a Java model are skipped.
  • A per-invocation visited-project set deduplicates shared dependencies and terminates cycles. This is a conservative dependency refresh closure, not evidence that every visited project was recompiled.
  • Preserve explicit per-source output refreshes at infinite depth. When a source entry uses the default output, refresh that default folder if it exists. Refresh CoreExceptions propagate instead of reporting successful synchronization.

Diagnosis and regression coverage

For the issue #1948 symptom—dependency bytecode changes on disk but the running JVM retains old behavior after HCR—inspect the importer's dependency-output synchronization boundary, rather than assuming Gradle did not compile or that a debugger change is required. PR #1960 supplies the importer-side remedy at the cited source revision; merge alone does not establish an installed extension version or deployment.

BuildServerBuilderTest adds four mocked regression tests for transitive/separate-source outputs, shared dependencies and cycles, closed/non-Gradle/non-Java dependencies, and propagation of dependency refresh failures. They invoke refreshOutputs directly; they do not exercise a real JVM HCR session or assert Eclipse POST_BUILD events. The existing importer Maven verification entry point above applies. Maintenance inspected both complete PR file patches and both final files at the full merged SHA, and did not execute tests. The PR reports a separate local app/lib live HCR comparison; its capture harness is not part of the product diff and is not evidence of automated CI HCR coverage.

BSP test debugging: init-script lifetime

Evidence: microsoft/vscode-gradle at merged source/source-tip commit 601eeb54467b44d4a263ecfb6af3ddfa41de7c85, PR #1961, addressing issue #1940. Source identity: microsoft/vscode-gradle#1961:601eeb54467b44d4a263ecfb6af3ddfa41de7c85. This changes the extension runner and its JDTLS importer delegate, not the separate BSP server implementation or debugger protocol. Merge does not establish release or deployment.

Completion contract and ownership

  • GradleTestRunner.launchWithBsp creates a unique temporary init script for BSP debugging, registers its path in bspInitScripts before submission, and supplies the path as the optional sixth argument of java.gradle.delegateTest (after project, tests, Gradle args, VM args, and environment). Command return means asynchronous submission, not test completion; debugger attachment still starts without waiting for the BSP response. Test-report/finishTestRun events do not release the script.
  • GradleDelegateCommandHandler.executeCommand / notifyTestRequestComplete observes the buildTargetTest future and sends java.gradle.buildServer.onDidCompleteTestRequest with that exact path. Normal responses (including error/cancelled status), remote ResponseErrorException, and exceptions before a request is submitted permit notification. Callers omitting the path receive no script notification.
  • BuildServerController's completion command forwards to GradleTestRunner.completeBspTestRequest. The runner removes only a registered path from its ownership set before deleting the file, making duplicate/unowned notifications harmless and allowing overlapping launches and completion before command return. Deletion failure logs Failed to delete Gradle test init script:; it does not reject completion or automatically retry.

Failure diagnosis and validation limits

The missing generated initialization-script symptom in BSP debugging can arise from deleting a script on submission return before Gradle consumes it; PR #1961 replaces that cleanup boundary with correlated request completion. The importer logs BSP test completion was not confirmed; retaining init script on connection loss, local future cancellation, or other non-remote-response failures, because those do not prove Gradle stopped using the file. The extension likewise retains the script on ambiguous command failure. Recognized unavailable-delegate errors release the BSP script before the existing XML fallback; launchXmlFallback retains its own per-launch cleanup in finally after runBuild. These behaviors are verified in the runner and handler sources linked above; this is not a guarantee that every ordinary Run/Test Explorer error shares the debugging cause.

GradleDelegateCommandHandlerTest adds nine mocked tests for asynchronous submission, remote failure, confirmed cancellation, transport/local-cancellation retention, pre-submission failure, already-completed requests, omitted paths, and overlapping requests. GradleTestRunner.test.ts adds six stubbed regression cases for lifetime through reporting, ownership/duplicates, early completion, ambiguous failure, unavailable-delegate fallback, and cleanup warnings, plus XML cleanup assertions. Maintenance inspected all six PR patches, rechecked stable PR metadata, and verified the three implementation files and both tests at the full merged/source-tip SHA; it did not execute tests. The PR reports separate live Windows BSP debugging validation, but not reproduction of the reporter's full macOS/Test Explorer scenario; its live harness is not part of these automated regression files.

Repository maintenance: team memory

Coordinator dispatch authentication

At source commit 1ac4b9733133abc1ef9c31ea09c7b562c3e8800e, PR #1958 restores target-scoped App-token wiring in microsoft/vscode-gradle/.github/workflows/team-memory-post-merge.yml, jobs.team-memory.steps. This is repository automation, not Gradle runtime behavior. Source identity: microsoft/vscode-gradle#1958:1ac4b9733133abc1ef9c31ea09c7b562c3e8800e.

  • Authentication change: Get coordinator dispatch token uses actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 (annotated v3.2.0), with vars.ISSUELENS_DISPATCH_APP_CLIENT_ID and secrets.ISSUELENS_DISPATCH_APP_PRIVATE_KEY. Its inputs restrict the target to owner microsoft, repository vscode-java-pack, requesting Contents read and Actions write. The queue step receives steps.dispatch-token.outputs.token, replacing github.token. PR #1958 also removes source-job contents: read / actions: write; top-level permissions: {} remains. These dispatch permissions are not wiki Contents-write permissions.
  • Retained dispatch contract: the queue action remains pinned to microsoft/IssueLens/.github/actions/queue-team-memory@a81d2d96167fc0e69ac631c2edc85f858e693289, targeting microsoft/vscode-java-pack / team-memory-coordinator.yml / main. Its five string inputs are source_repository, source_run_id, source_run_attempt, push_before (event before), and push_after (github.sha).
  • Retained gates and lifecycle: Queue IssueLens Team Memory triggers on pushes to develop. jobs.team-memory.if requires opt-in ISSUELENS_TEAM_MEMORY_ENABLED == 'true', canonical repository microsoft/vscode-gradle, the current default-branch ref, a push event, matching workflow/event SHA, and false created, deleted, and forced flags. It runs on ubuntu-latest with a 10-minute timeout. This source snapshot has no manual-dispatch trigger, checkout, or concurrency declaration; the historical direct-agent workflow below is not the current entry point. See Java Pack for separately sourced coordinator knowledge.

All current caller details above were verified in the linked workflow at the full source SHA, which is also the supplied push-after/source-tip revision. The complete one-file PR patch changes authentication only; retained gates and dispatch wiring are context, not features introduced by #1958. Maintenance inspected source and rechecked PR identities after paging; it did not execute tests, mint a dispatch token, or validate a live Gradle dispatch. Referenced configuration names do not establish that values or App installation permissions are configured, and this merge does not establish deployment or end-to-end delivery. External action internals and coordinator implementation were not revalidated.

Historical direct-agent caller (superseded)

The following retained account describes the older PR #1943 snapshot only; its manual recovery, permissions, action, concurrency, and timeout must not be applied to the current dispatcher above.

The following describes the caller workflow in microsoft/vscode-gradle, not Gradle task execution, language services, or the separate Gradle Build Server. Evidence: .github/workflows/team-memory-post-merge.yml, especially on, concurrency, and jobs.team-memory, at source commit 7dc995fc0fc1d705244e3db738ad423862134159 (PR #1943).

  • Automatic entry point: pushes to develop; this replaces the closed-PR pull_request_target trigger. The job additionally requires ISSUELENS_TEAM_MEMORY_ENABLED == 'true', the current default-branch ref, and either a push or manual-dispatch event. The push branch filter is literal develop, while the job's ref check uses repository default-branch metadata; a default-branch rename therefore requires reviewing the filter.
  • Manual recovery: dispatch Update IssueLens Team Memory on the default branch with the required string input pull_request_number, described as a merged PR number. The workflow passes that input to the action as pull-request-number; manual dispatch does not bypass the opt-in or branch gate.
  • Concurrency and timeout: the group is issuelens-${{ github.repository }}-team-memory-${{ github.event.after || inputs.pull_request_number || github.run_id }}. Pushes are keyed by their after-SHA, manual requests by PR number, with run ID as fallback. In-progress cancellation is disabled. The job runs on ubuntu-latest with a 30-minute timeout.
  • Action boundary and permissions: the sole step invokes microsoft/IssueLens/.github/actions/issuelens@2b5317815e15179899014a02c41678620ce2d390 with request-type: team-memory, output-mode: activity, and summary-mode: status. There is no checkout step. Workflow-level permissions are empty; job permissions are contents: read, pull-requests: read, and id-token: write.

Historical verification limits

The caller source establishes these triggers, gates, inputs, and permissions, not the external action's internal PR-selection or batching implementation. Source inspection does not establish that opt-in variables, Azure federation, credentials, destination App permissions, or a live workflow run are correctly configured. PR #1943 changes only this workflow; it does not establish a Gradle feature release or deployment.

Clone this wiki locally