Skip to content

Commit f86c7e0

Browse files
edburnsbrunoborgesCopilotCopilot
authored
Integrate Bruno's PR 1478 with Ed's desired CI/CD changes (#1483)
Bruno authored the core code with Copilot. * feat(java): add JDK 25 default executor Use a multi-release JAR to select virtual threads as the default internal executor on JDK 25+, while retaining the common pool on older JDKs. Keep user-provided executors caller-owned and only shut down SDK-owned defaults. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * test(java): cover owned default executor shutdown Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor(java): make default executor provider internal Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor(java): update InternalExecutorProvider to manage executor ownership and shutdown capability * feat(java): add integration tests for multi-release JAR behavior and executor management * feat(java): add JDK 25 multi-release overlay class verification and update documentation * test(java): split InternalExecutorProvider unit test into single-assertion tests Addresses PR #1478 review (Copilot AI): the existing baseProviderUsesCommonPoolWithoutOwnership method bundled three unrelated assertions (commonPool identity, user-executor ownership, package-private visibility). Split into baseProviderReturnsCommonPool, userProvidedExecutorIsNotOwned, and providerIsPackagePrivate so failures point at a single condition each. * fix(java): dispatch owned-executor shutdown off the executor in forceStop() Addresses PR #1478 review (Copilot AI, discussion r3314987809): CopilotClient#forceStop() chains shutdownOwnedExecutor() onto cleanupConnection() via whenComplete(...), but cleanupConnection() is itself built on async work scheduled on the SDK-owned executor (e.g. CompletableFuture.supplyAsync(..., executor) in connection setup). On JDK 25+ this means the whenComplete lambda can land on one of the owned executor's threads; awaitTermination(...) then blocks waiting for the very thread it is running on, forcing the full AUTOCLOSEABLE_TIMEOUT_SECONDS timeout followed by shutdownNow(). Fix: dispatch the shutdown continuation via whenCompleteAsync(...) onto a private one-shot SHUTDOWN_DISPATCHER that spawns a fresh daemon thread named "copilot-client-shutdown". This guarantees the awaitTermination call is never made from inside the executor it is draining. close() is unaffected: it calls stop().get(...) synchronously and runs shutdownOwnedExecutor() in its finally block on the caller's thread. * fix(java): short-circuit shutdownOwnedExecutor() when already shut down Addresses PR #1478 review (Copilot AI, discussion r3314987870): close() and forceStop() can each invoke shutdownOwnedExecutor() (e.g. user calls forceStop() and then close() in try-with-resources). A second call would redundantly invoke shutdown() and awaitTermination() on an already- terminated ExecutorService. While the JDK handles this gracefully (awaitTermination returns immediately after a prior shutdownNow), the redundant call obscures diagnostics. Short-circuit at the top of shutdownOwnedExecutor() when isShutdown() is already true and log at FINE so the second invocation is visible without spamming normal output. * spotless:apply * ci(java): build/publish on JDK 25 to include MR-JAR overlay; matrix SDK tests across JDK 17 and 25 The java25-multi-release Maven profile is activated only on JDK 25+ (<jdk>[25,)</jdk>). Without it, the build skips the compile-java25 execution, the packaged JAR has no META-INF/versions/25/InternalExecutorProvider.class, and the manifest lacks Multi-Release: true. The InternalExecutorProvider JDK 25 overlay (Executors.newVirtualThreadPerTaskExecutor()) is then effectively dead in CI and in published Maven Central artifacts -- consumers on JDK 25+ silently fall back to ForkJoinPool.commonPool(). Changes: - java-publish-snapshot.yml: set up JDK 25 (was 17). The pom keeps <maven.compiler.release>17</maven.compiler.release> so baseline bytecode remains JDK 17 compatible; --release 17 is supported by the JDK 25 compiler. - java-publish-maven.yml: same JDK bump for release:perform. - java-sdk-tests.yml: matrix on java-version: [17, 25]. JDK 25 entry exercises the MR-JAR overlay end-to-end via InternalExecutorProviderIT (asserts feature >= 25 => canBeShutdown=true, virtual=true) and runs the new verify-java25-overlay antrun structural guard. Side-effects (site artifact upload, JaCoCo badge generation, badge-update PR) remain gated to the JDK 17 entry so the badge source-of-truth stays a single baseline. Failure artifact name suffixed with -jdk${matrix.java-version} to avoid collisions. Branch protection note: the job's check name changes from "Java SDK Tests" to "Java SDK Tests (JDK 17)" + "Java SDK Tests (JDK 25)". Update branch protection rules accordingly after merge if required-checks reference the old name. * On branch edburns/review-brunoborges-pr-1478 Test invocation changes. modified: .github/workflows/java-sdk-tests.yml - Have `test-jdk17` be a parameter, set to true by default. - Do not use matrix. It is vital that the tests verify that the MR-JAR facility works when compiled using JDK 25 with `maven.compiler.release` set to 17. To that end, this workflow does not re-compile the jar or the tests, but uses a the JDK 17 just to run the tests. - Use a separate banner for each variant. - Use separate summary for 17 and 25 tests. modified: java/pom.xml - Print banners for inspection during tests. - Use Maven enforcer plugin to require using 25 when compiling. modified: java/README.md - Add content for running the tests with 17. * On branch edburns/review-brunoborges-pr-1478 modified: .github/workflows/docs-validation.yml - Use Java 25 per @brunoborges. modified: .github/workflows/java-sdk-tests.yml - Update labels. modified: .vscode/settings.json - Additional Java settings. modified: scripts/docs-validation/package.json The `--lang` parsing in validate.ts uses `args.find((a) => a.startsWith("--lang="))`, which won't match `--lang typescript` (space-separated). So `targetLang` is always `undefined`, and every job validates **all** languages. This is a pre-existing bug — but it was invisible before because `mvn install` succeeded with the pre-installed JDK 17 on all runners. However, the reason it matters **now** is: after fixing the `validate-java` job to use JDK 25, the other 4 jobs (TypeScript, Go, Python, C#) still don't have JDK 25. Since they also accidentally validate Java (due to the broken `--lang` filter), they'd continue to fail. So you actually have two options: **Option A:** Only fix JDK in `validate-java` AND fix `--lang` parsing so other jobs stop accidentally validating Java. **Option B:** Only fix JDK in `validate-java` AND add JDK 25 setup to all 4 other jobs too (ugly but works without touching the script). The `--lang` fix is the cleaner path, but it's a separate pre-existing bug, not something introduced by PR #1483. If you'd prefer to keep the changes minimal and just address the PR's breakage, I can revert the package.json change and instead add `setup-java` with JDK 25 to every job. What's your preference? * On branch edburns/review-brunoborges-pr-1478 modified: .github/actions/java-test-report/action.yml modified: .github/workflows/java-sdk-tests.yml - Ensure the correct Copilot CLI is used for tests. * On branch edburns/review-brunoborges-pr-1478 modified: .github/workflows/java-sdk-tests.yml Now the JDK 17 run: 1. `jacoco:prepare-agent@wire-up-coverage-instrumentation` — generates the JaCoCo agent arg compatible with JDK 17 and sets `testExecutionAgentArgs` 2. `antrun:run@print-test-jdk-banner` — prints the JDK banner 3. `surefire:test` — runs pre-compiled tests with the JaCoCo agent attached 4. `jacoco:report@build-coverage-report-from-tests` — generates the HTML/XML/CSV reports The `-DtestExecutionAgentArgs=` override is removed so JaCoCo's prepared value flows through to Surefire's `<argLine>`. The test report action already reads from the default jacoco.xml path, so the coverage section will appear in both reports automatically. * On branch edburns/review-brunoborges-pr-1478 Address copilot review comments. modified: .github/actions/java-test-report/action.yml - Add failsafe tests to the report. modified: .github/workflows/java-sdk-tests.yml - Ensure failsafe tests are invoked in the 17 case. modified: java/README.md - Fix spelling error. - Fix artifact version error. - Fix 17 invocation. * Ensure the Jar is produced --------- Co-authored-by: Bruno Borges <brborges@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent dbfda40 commit f86c7e0

16 files changed

Lines changed: 687 additions & 74 deletions

File tree

.github/actions/java-test-report/action.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ inputs:
44
report-path:
55
description: "Path to the test report XML files (glob pattern)"
66
required: false
7-
default: "java/target/surefire-reports*/TEST-*.xml"
7+
default: "java/target/{surefire-reports*,failsafe-reports}/TEST-*.xml"
88
jacoco-path:
99
description: "Path to the JaCoCo XML report"
1010
required: false
@@ -17,13 +17,17 @@ inputs:
1717
description: "Name for the check run"
1818
required: false
1919
default: "Java SDK Test Results"
20+
title:
21+
description: "Title for the test report summary"
22+
required: false
23+
default: "Copilot Java SDK :: Test Results"
2024
runs:
2125
using: "composite"
2226
steps:
2327
- name: Generate Test Summary
2428
shell: bash
2529
run: |
26-
echo "## 🧪 Copilot Java SDK :: Test Results" >> $GITHUB_STEP_SUMMARY
30+
echo "## 🧪 ${{ inputs.title }}" >> $GITHUB_STEP_SUMMARY
2731
echo "" >> $GITHUB_STEP_SUMMARY
2832
2933
if ls ${{ inputs.report-path }} 1>/dev/null 2>&1; then

.github/workflows/docs-validation.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ jobs:
143143
- uses: actions/setup-java@v4
144144
with:
145145
distribution: 'microsoft'
146-
java-version: '17'
146+
java-version: '25'
147147
cache: 'maven'
148148

149149
- name: Install SDK to local repo

.github/workflows/java-publish-maven.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,10 @@ jobs:
5454
5555
- uses: ./.github/actions/setup-copilot
5656

57-
- name: Set up JDK 17
57+
- name: Set up JDK 25
5858
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
5959
with:
60-
java-version: "17"
60+
java-version: "25"
6161
distribution: "microsoft"
6262
cache: "maven"
6363
server-id: central

.github/workflows/java-publish-snapshot.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ jobs:
3030

3131
- uses: ./.github/actions/setup-copilot
3232

33-
- name: Set up JDK 17
33+
- name: Set up JDK 25
3434
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
3535
with:
36-
java-version: "17"
36+
java-version: "25"
3737
distribution: "microsoft"
3838
cache: "maven"
3939
server-id: central

.github/workflows/java-sdk-tests.yml

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,14 @@ permissions:
3737

3838
jobs:
3939
java-sdk:
40-
name: "Java SDK Tests"
40+
name: "Java SDK Tests (JDK ${{ matrix.test-jdk }})"
4141
if: github.event.repository.fork == false
4242

4343
runs-on: ubuntu-latest
44+
strategy:
45+
fail-fast: false
46+
matrix:
47+
test-jdk: ["25", "17"]
4448
defaults:
4549
run:
4650
shell: bash
@@ -52,22 +56,26 @@ jobs:
5256

5357
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
5458
with:
55-
java-version: "17"
59+
java-version: "25"
5660
distribution: "microsoft"
5761
cache: "maven"
5862

5963
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
6064
with:
6165
node-version: 22
6266

63-
- uses: ./.github/actions/setup-copilot
64-
id: setup-copilot
67+
- name: Build SDK and set up test harness
68+
run: mvn test-compile jar:jar
69+
70+
- name: Verify Javadoc generation
71+
if: matrix.test-jdk == '25'
72+
run: mvn javadoc:javadoc -q
6573

66-
- name: Install test harness dependencies
67-
working-directory: ./test/harness
68-
run: npm ci --ignore-scripts
74+
- name: Verify CLI works
75+
run: node target/copilot-sdk/nodejs/node_modules/@github/copilot/index.js --version
6976

7077
- name: Run spotless check
78+
if: matrix.test-jdk == '25'
7179
run: |
7280
mvn spotless:check
7381
if [ $? -ne 0 ]; then
@@ -76,18 +84,30 @@ jobs:
7684
fi
7785
echo "✅ spotless:check passed"
7886
79-
- name: Verify Javadoc generation
80-
run: mvn compile javadoc:javadoc -q
87+
- name: Run Java SDK tests (JDK 25)
88+
if: matrix.test-jdk == '25'
89+
env:
90+
CI: "true"
91+
run: mvn verify -Dskip.test.harness=true
8192

82-
- name: Run Java SDK tests
93+
- name: Switch to JDK 17
94+
if: matrix.test-jdk == '17'
95+
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
96+
with:
97+
java-version: "17"
98+
distribution: "microsoft"
99+
100+
- name: Run Java SDK tests (JDK 17, no recompilation)
101+
if: matrix.test-jdk == '17'
83102
env:
84103
CI: "true"
85-
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
86-
COPILOT_CLI_PATH: ${{ steps.setup-copilot.outputs.cli-path }}
87-
run: mvn verify
104+
run: |
105+
echo "Running tests against JDK 25-built classes using JDK 17 runtime..."
106+
java -version
107+
mvn jacoco:prepare-agent@wire-up-coverage-instrumentation antrun:run@print-test-jdk-banner surefire:test failsafe:integration-test failsafe:verify jacoco:report@build-coverage-report-from-tests -Denforcer.skip=true
88108
89109
- name: Upload test results for site generation
90-
if: success() && github.ref == 'refs/heads/main'
110+
if: success() && github.ref == 'refs/heads/main' && matrix.test-jdk == '25'
91111
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
92112
with:
93113
name: test-results-for-site
@@ -98,12 +118,12 @@ jobs:
98118
retention-days: 1
99119

100120
- name: Generate JaCoCo badge
101-
if: success() && github.ref == 'refs/heads/main'
121+
if: success() && github.ref == 'refs/heads/main' && matrix.test-jdk == '25'
102122
working-directory: .
103123
run: .github/scripts/generate-java-coverage-badge.sh java/target/site/jacoco-coverage/jacoco.csv .github/badges
104124

105125
- name: Create PR for JaCoCo badge update
106-
if: success() && github.ref == 'refs/heads/main'
126+
if: success() && github.ref == 'refs/heads/main' && matrix.test-jdk == '25'
107127
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v7
108128
with:
109129
commit-message: "Update Java JaCoCo coverage badge"
@@ -116,12 +136,14 @@ jobs:
116136
- name: Generate Test Report Summary
117137
if: always()
118138
uses: ./.github/actions/java-test-report
139+
with:
140+
title: "Copilot Java SDK :: Test Results JDK ${{ matrix.test-jdk }}"
119141

120142
- name: Upload test results on failure
121143
if: failure()
122144
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
123145
with:
124-
name: java-test-results
146+
name: java-test-results-jdk-${{ matrix.test-jdk }}
125147
path: |
126148
java/target/surefire-reports/
127149
java/target/surefire-reports-isolated/

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@
2525
"[go]": {
2626
"editor.defaultFormatter": "golang.go"
2727
},
28-
"java.configuration.updateBuildConfiguration": "automatic"
28+
"java.configuration.updateBuildConfiguration": "automatic",
29+
"java.compile.nullAnalysis.mode": "automatic"
2930
}

java/README.md

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,27 @@ Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build A
1919

2020
## Installation
2121

22-
### Requirements
22+
### Runtime requirements
2323

24-
- Java 17 or later. **JDK 25 recommended**. Selecting JDK 25 enables the use of virtual threads, as shown in the [Quick Start](#quick-start).
25-
- GitHub Copilot CLI 1.0.17 or later installed and in `PATH` (or provide custom `cliPath`)
24+
- Java 17 or later. **JDK 25 recommended**. The distributed jar is a multi-release jar (MR-JAR) and is compiled on JDK 25 with `maven.compiler.release` set to 17. This means, when run on JDK 25 and later, the SDK automatically uses virtual threads for its default internal executor.
25+
- GitHub Copilot CLI 1.0.55-5. or later installed and in `PATH` (or provide custom `cliPath`)
2626

2727
### Maven
2828

2929
```xml
3030
<dependency>
3131
<groupId>com.github</groupId>
3232
<artifactId>copilot-sdk-java</artifactId>
33-
<version>1.0.0-beta-java.4</version>
33+
<version>1.0.0-beta-10-java.0</version>
3434
</dependency>
3535
```
3636

37+
### Gradle
38+
39+
```groovy
40+
implementation 'com.github:copilot-sdk-java:1.0.0-beta-10-java.0'
41+
42+
3743
#### Snapshot Builds
3844
3945
Snapshot builds of the next development version are published to Maven Central Snapshots. To use them, add the repository and update the dependency version in your `pom.xml`:
@@ -50,14 +56,14 @@ Snapshot builds of the next development version are published to Maven Central S
5056
<dependency>
5157
<groupId>com.github</groupId>
5258
<artifactId>copilot-sdk-java</artifactId>
53-
<version>1.0.0-beta-java.5-SNAPSHOT</version>
59+
<version>1.0.0-beta-10-java.0-SNAPSHOT</version>
5460
</dependency>
5561
```
5662

5763
### Gradle
5864

5965
```groovy
60-
implementation 'com.github:copilot-sdk-java:1.0.0-beta-java.4'
66+
implementation 'com.github:copilot-sdk-java:1.0.0-beta-10-java.0-SNAPSHOT'
6167
```
6268

6369
## Quick Start
@@ -66,23 +72,16 @@ implementation 'com.github:copilot-sdk-java:1.0.0-beta-java.4'
6672
import com.github.copilot.CopilotClient;
6773
import com.github.copilot.generated.AssistantMessageEvent;
6874
import com.github.copilot.generated.SessionUsageInfoEvent;
69-
import com.github.copilot.rpc.CopilotClientOptions;
7075
import com.github.copilot.rpc.MessageOptions;
7176
import com.github.copilot.rpc.PermissionHandler;
7277
import com.github.copilot.rpc.SessionConfig;
7378

74-
import java.util.concurrent.Executors;
75-
7679
public class CopilotSDK {
7780
public static void main(String[] args) throws Exception {
7881
var lastMessage = new String[]{null};
7982

8083
// Create and start client
81-
try (var client = new CopilotClient()) { // JDK 25+: comment out this line
82-
// JDK 25+: uncomment the following 3 lines for virtual thread support
83-
// var options = new CopilotClientOptions()
84-
// .setExecutor(Executors.newVirtualThreadPerTaskExecutor());
85-
// try (var client = new CopilotClient(options)) {
84+
try (var client = new CopilotClient()) {
8685
client.start().get();
8786

8887
// Create a session
@@ -160,6 +159,8 @@ This SDK tracks the official [Copilot SDK](https://github.com/github/copilot-sdk
160159

161160
### Development Setup
162161

162+
Requires JDK 25 or later for development.
163+
163164
```bash
164165
# Clone the repository
165166
git clone https://github.com/github/copilot-sdk.git
@@ -168,8 +169,12 @@ cd copilot-sdk/java
168169
# Enable git hooks for code formatting
169170
git config core.hooksPath .githooks
170171

171-
# Build and test
172+
# Build and test with JDK 25
172173
mvn clean verify
174+
175+
# Set your paths for JDK 17
176+
# Run the JDK 25 built jar with JDK 17 JVM for tests. Do not re-compile the jar.
177+
mvn jacoco:prepare-agent@wire-up-coverage-instrumentation antrun:run@print-test-jdk-banner surefire:test failsafe:integration-test failsafe:verify jacoco:report@build-coverage-report-from-tests -Denforcer.skip=true
173178
```
174179

175180
The tests require the official [copilot-sdk](https://github.com/github/copilot-sdk) test harness, which is automatically cloned during build.
@@ -190,6 +195,3 @@ See [SECURITY.md](SECURITY.md) for reporting security vulnerabilities.
190195

191196
MIT — see [LICENSE](LICENSE) for details.
192197

193-
## Acknowledgement
194-
195-
- Initially developed with Copilot and [Bruno Borges](https://www.linkedin.com/in/brunocborges/).

0 commit comments

Comments
 (0)