Skip to content

Commit 84a429b

Browse files
committed
fix(java): guard native packaging by libc
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: de80978a-085e-476a-97a2-d2c5858446f6
1 parent 7e41e94 commit 84a429b

6 files changed

Lines changed: 261 additions & 15 deletions

File tree

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,13 @@ jobs:
4040
with:
4141
node-version: 22
4242

43+
- name: Validate Linux glibc native host
44+
run: node copilot-native/scripts/validate-native-host.mjs linux-x64
45+
4346
- name: Run Java SDK tests (InProcess)
4447
env:
4548
CI: "true"
46-
run: mvn clean verify -Pinprocess
49+
run: mvn clean verify -Pinprocess -Dcopilot.native.skip.download=false
4750

4851
- name: Generate Test Report Summary
4952
if: always()
@@ -125,7 +128,9 @@ jobs:
125128
if: matrix.test-jdk == '25'
126129
env:
127130
CI: "true"
128-
run: mvn verify -Dskip.test.harness=true
131+
run: |
132+
node copilot-native/scripts/validate-native-host.mjs linux-x64
133+
mvn verify -Dskip.test.harness=true -Dcopilot.native.libc=glibc -Dcopilot.native.skip.download=false
129134
130135
- name: Switch to JDK 17
131136
if: matrix.test-jdk == '17'

java/README.md

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -491,27 +491,37 @@ mvn jacoco:prepare-agent@wire-up-coverage-instrumentation antrun:run@print-test-
491491

492492
Run native-runtime Maven commands from the `java` directory. Native packaging requires Node.js and npm in addition to JDK 25 and Maven because `copilot-native/scripts/fetch-native.mjs` retrieves the pinned npm runtime package.
493493

494-
Validated on a native Linux x64 host: Maven activates the `native-linux-x64` profile automatically on Linux `amd64` when `copilot.native.skip.download` is not set. That profile runs the native fetch-script tests, fetches the pinned `@github/copilot-linux-x64` package during `generate-resources`, packages the `linux-x64` classifier JAR during `package`, and verifies its native contents. Ensure npm can authenticate to the package registry before running the build.
494+
Validated on a native Linux x64 glibc host: Maven activates the `native-linux-x64` profile on Linux `amd64` when `copilot.native.libc=glibc` is set. The build validates the host before downloading or packaging native files. The profile runs the native script tests, fetches the pinned `@github/copilot-linux-x64` package during `generate-resources`, packages the `linux-x64` classifier JAR during `package`, and verifies its native contents. An absent or explicitly false `copilot.native.skip.download` value preserves normal native packaging. Ensure npm can authenticate to the package registry before running the build.
495495

496-
On macOS, Windows, Linux ARM64, and other unsupported hosts, do not force the Linux profile. A normal build produces only the OS-neutral primary, sources, and Javadoc JARs; it does not run the Linux x64 fetch-script tests, download or stage Linux native files, or produce a `linux-x64` classifier JAR. Use this command to validate that behavior:
496+
Before opting in, validate that Node.js reports glibc for the build host:
497497

498498
```bash
499-
mvn -pl copilot-native clean verify
499+
node copilot-native/scripts/validate-native-host.mjs linux-x64
500+
mvn -pl copilot-native clean verify -Dcopilot.native.libc=glibc
500501
```
501502

502-
To build only the OS-neutral artifacts on any host, disable native download and packaging:
503+
The `inprocess` test profile performs the same validation and native packaging automatically, so the full in-process test command remains:
503504

504505
```bash
505-
mvn -pl copilot-native clean package -DskipTests -Dcopilot.native.skip.download=true
506+
mvn -Pinprocess clean verify
507+
```
508+
509+
On macOS, Windows, Linux ARM64, Linux x64 musl, and other unsupported hosts, do not set `copilot.native.libc=glibc`. A normal build produces only the OS-neutral primary, sources, and Javadoc JARs; it does not run the Linux x64 native script tests, download or stage Linux native files, or produce a `linux-x64` classifier JAR.
510+
511+
To build only the OS-neutral artifacts on any host, or override the glibc opt-in, disable native download and packaging:
512+
513+
```bash
514+
mvn -pl copilot-native clean package -DskipTests -Dcopilot.native.libc=glibc -Dcopilot.native.skip.download=true
506515
```
507516

508517
The verified Linux x64 checks are:
509518

510519
```bash
511-
mvn -pl copilot-native help:active-profiles
512-
mvn -pl copilot-native test
513-
mvn clean verify
514-
mvn clean package -pl copilot-native -DskipTests -Dcopilot.native.skip.download=true
520+
node --test copilot-native/scripts/fetch-native.test.mjs copilot-native/scripts/validate-native-host.test.mjs
521+
mvn -pl copilot-native help:active-profiles -Dcopilot.native.libc=glibc -Dcopilot.native.skip.download=false
522+
mvn -pl copilot-native test -Dcopilot.native.libc=glibc
523+
mvn clean verify -Dcopilot.native.libc=glibc
524+
mvn clean package -pl copilot-native -DskipTests -Dcopilot.native.libc=glibc -Dcopilot.native.skip.download=true
515525
```
516526

517527
On a supported Linux x64 host, the classifier JAR contains `native/linux-x64/runtime.node`, `native/linux-x64/platform.properties`, and `native/linux-x64/copilot`. The placeholder JAR remains OS-neutral and contains no native binaries. Unsupported hosts retain the placeholder-only behavior without producing a `-linux-x64.jar`.

java/copilot-native/pom.xml

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,20 @@
7070
<groupId>org.codehaus.mojo</groupId>
7171
<artifactId>exec-maven-plugin</artifactId>
7272
<executions>
73+
<execution>
74+
<id>validate-native-host</id>
75+
<phase>none</phase>
76+
<goals>
77+
<goal>exec</goal>
78+
</goals>
79+
<configuration>
80+
<executable>node</executable>
81+
<arguments>
82+
<argument>${project.basedir}/scripts/validate-native-host.mjs</argument>
83+
<argument>${copilot.native.classifier}</argument>
84+
</arguments>
85+
</configuration>
86+
</execution>
7387
<execution>
7488
<id>fetch-native</id>
7589
<phase>none</phase>
@@ -98,6 +112,7 @@
98112
<arguments>
99113
<argument>--test</argument>
100114
<argument>${project.basedir}/scripts/fetch-native.test.mjs</argument>
115+
<argument>${project.basedir}/scripts/validate-native-host.test.mjs</argument>
101116
</arguments>
102117
</configuration>
103118
</execution>
@@ -229,7 +244,8 @@
229244
<arch>amd64</arch>
230245
</os>
231246
<property>
232-
<name>!copilot.native.skip.download</name>
247+
<name>copilot.native.libc</name>
248+
<value>glibc</value>
233249
</property>
234250
</activation>
235251
<properties>
@@ -242,6 +258,65 @@
242258
<groupId>org.codehaus.mojo</groupId>
243259
<artifactId>exec-maven-plugin</artifactId>
244260
<executions>
261+
<execution>
262+
<id>validate-native-host</id>
263+
<phase>validate</phase>
264+
</execution>
265+
<execution>
266+
<id>fetch-native</id>
267+
<phase>generate-resources</phase>
268+
</execution>
269+
<execution>
270+
<id>test-fetch-native</id>
271+
<phase>test</phase>
272+
</execution>
273+
</executions>
274+
</plugin>
275+
<plugin>
276+
<groupId>org.apache.maven.plugins</groupId>
277+
<artifactId>maven-jar-plugin</artifactId>
278+
<executions>
279+
<execution>
280+
<id>jar-native</id>
281+
<phase>package</phase>
282+
</execution>
283+
</executions>
284+
</plugin>
285+
<plugin>
286+
<groupId>org.apache.maven.plugins</groupId>
287+
<artifactId>maven-antrun-plugin</artifactId>
288+
<executions>
289+
<execution>
290+
<id>verify-native-jars</id>
291+
<phase>package</phase>
292+
</execution>
293+
</executions>
294+
</plugin>
295+
</plugins>
296+
</build>
297+
</profile>
298+
<!--
299+
The SDK's inprocess test profile requires this module's classifier
300+
artifact. Bind the same guarded native lifecycle so `-Pinprocess`
301+
works without a separate libc property. Host validation fails
302+
before download or packaging on non-glibc and unsupported hosts.
303+
-->
304+
<profile>
305+
<id>inprocess</id>
306+
<properties>
307+
<copilot.native.classifier>linux-x64</copilot.native.classifier>
308+
<copilot.native.cli.filename>copilot</copilot.native.cli.filename>
309+
</properties>
310+
<build>
311+
<plugins>
312+
<plugin>
313+
<groupId>org.codehaus.mojo</groupId>
314+
<artifactId>exec-maven-plugin</artifactId>
315+
<executions>
316+
<execution>
317+
<id>validate-native-host</id>
318+
<phase>validate</phase>
319+
</execution>
245320
<execution>
246321
<id>fetch-native</id>
247322
<phase>generate-resources</phase>
@@ -295,6 +370,40 @@
295370
<configuration>
296371
<skip>true</skip>
297372
</configuration>
373+
<executions>
374+
<execution>
375+
<id>validate-native-host</id>
376+
<phase>none</phase>
377+
</execution>
378+
<execution>
379+
<id>fetch-native</id>
380+
<phase>none</phase>
381+
</execution>
382+
<execution>
383+
<id>test-fetch-native</id>
384+
<phase>none</phase>
385+
</execution>
386+
</executions>
387+
</plugin>
388+
<plugin>
389+
<groupId>org.apache.maven.plugins</groupId>
390+
<artifactId>maven-jar-plugin</artifactId>
391+
<executions>
392+
<execution>
393+
<id>jar-native</id>
394+
<phase>none</phase>
395+
</execution>
396+
</executions>
397+
</plugin>
398+
<plugin>
399+
<groupId>org.apache.maven.plugins</groupId>
400+
<artifactId>maven-antrun-plugin</artifactId>
401+
<executions>
402+
<execution>
403+
<id>verify-native-jars</id>
404+
<phase>none</phase>
405+
</execution>
406+
</executions>
298407
</plugin>
299408
</plugins>
300409
</build>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------------------------------------------*/
4+
5+
import { pathToFileURL } from "node:url";
6+
7+
export function validateNativeHost(classifier, host) {
8+
if (classifier !== "linux-x64") {
9+
throw new Error(`Unsupported native build classifier: ${classifier}`);
10+
}
11+
if (host.platform !== "linux" || host.arch !== "x64") {
12+
throw new Error(
13+
`Native ${classifier} packaging requires Linux x64; detected ${host.platform}-${host.arch}`,
14+
);
15+
}
16+
if (!host.glibcVersionRuntime) {
17+
throw new Error(
18+
`Native ${classifier} packaging requires glibc; musl and unknown libc hosts are unsupported`,
19+
);
20+
}
21+
22+
return `Validated native build host: ${classifier} (glibc ${host.glibcVersionRuntime})`;
23+
}
24+
25+
export function detectNativeHost() {
26+
const report = process.report?.getReport();
27+
return {
28+
platform: process.platform,
29+
arch: process.arch,
30+
glibcVersionRuntime: report?.header?.glibcVersionRuntime,
31+
};
32+
}
33+
34+
function main() {
35+
const [classifier] = process.argv.slice(2);
36+
if (!classifier) {
37+
console.error("Usage: node validate-native-host.mjs <classifier>");
38+
process.exitCode = 1;
39+
return;
40+
}
41+
42+
try {
43+
console.log(validateNativeHost(classifier, detectNativeHost()));
44+
} catch (error) {
45+
console.error(error.message);
46+
process.exitCode = 1;
47+
}
48+
}
49+
50+
if (
51+
process.argv[1] &&
52+
import.meta.url === pathToFileURL(process.argv[1]).href
53+
) {
54+
main();
55+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------------------------------------------*/
4+
5+
import assert from "node:assert/strict";
6+
import test from "node:test";
7+
8+
import { validateNativeHost } from "./validate-native-host.mjs";
9+
10+
test("accepts Linux x64 with glibc", () => {
11+
assert.equal(
12+
validateNativeHost("linux-x64", {
13+
platform: "linux",
14+
arch: "x64",
15+
glibcVersionRuntime: "2.39",
16+
}),
17+
"Validated native build host: linux-x64 (glibc 2.39)",
18+
);
19+
});
20+
21+
test("rejects Linux x64 with musl or unknown libc", () => {
22+
assert.throws(
23+
() =>
24+
validateNativeHost("linux-x64", {
25+
platform: "linux",
26+
arch: "x64",
27+
glibcVersionRuntime: undefined,
28+
}),
29+
/requires glibc/,
30+
);
31+
});
32+
33+
test("rejects a non-Linux host", () => {
34+
assert.throws(
35+
() =>
36+
validateNativeHost("linux-x64", {
37+
platform: "darwin",
38+
arch: "x64",
39+
glibcVersionRuntime: undefined,
40+
}),
41+
/requires Linux x64/,
42+
);
43+
});
44+
45+
test("rejects a non-x64 host", () => {
46+
assert.throws(
47+
() =>
48+
validateNativeHost("linux-x64", {
49+
platform: "linux",
50+
arch: "arm64",
51+
glibcVersionRuntime: "2.39",
52+
}),
53+
/requires Linux x64/,
54+
);
55+
});
56+
57+
test("rejects an unimplemented classifier", () => {
58+
assert.throws(
59+
() =>
60+
validateNativeHost("linuxmusl-x64", {
61+
platform: "linux",
62+
arch: "x64",
63+
glibcVersionRuntime: undefined,
64+
}),
65+
/Unsupported native build classifier/,
66+
);
67+
});

java/docs/adr/adr-007-native-bundling-strategy.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ If none succeeds, startup fails. The PATH fallback does not claim to support eve
208208

209209
### Current platform scope
210210

211-
The platform detector recognizes the 8 classifiers listed in this ADR. The Maven build binds native packaging only when the build host matches an implemented classifier. Currently, only Linux x64 hosts fetch and package the `linux-x64` classifier. Unsupported hosts build only the OS-neutral placeholder, sources, and Javadoc artifacts. Additional classifier artifacts remain follow-up work.
211+
The platform detector recognizes the 8 classifiers listed in this ADR. The Maven build binds native packaging only for an explicitly selected implemented classifier. Currently, Linux x64 glibc hosts can opt in with `copilot.native.libc=glibc`, while the `inprocess` test profile selects the required `linux-x64` classifier automatically. Both paths validate the host before downloading or packaging native files. Linux x64 musl and other unsupported hosts build only the OS-neutral placeholder, sources, and Javadoc artifacts unless they explicitly request in-process tests, which fail during host validation. Additional classifier artifacts remain follow-up work.
212212

213213
## Binding technology: JNA over Panama FFM
214214

@@ -359,9 +359,9 @@ The pattern follows DJL's `LibUtils.loadLibrary()` approach: detect the platform
359359
2. Locates the matching `runtime.node` binary on the classpath (via `getResourceAsStream` from the classifier JAR).
360360
3. Extracts `runtime.node` and the transitional CLI entrypoint into `~/.copilot/runtime-cache/` if valid cached files are not already present.
361361
4. Loads it via [JNA](#references) using the C ABI entry points, per the [binding technology decision](#binding-technology-jna-over-panama-ffm) above. The JNA-specific code is confined behind an internal binding interface to preserve a future FFM migration path.
362-
* A supported host profile fetches the pinned matching `@github/copilot-<classifier>` npm package, verifies its SHA-512 integrity from `nodejs/package-lock.json`, and packages the version-matched runtime and CLI files.
362+
* A validated supported-host profile fetches the pinned matching `@github/copilot-<classifier>` npm package, verifies its SHA-512 integrity from `nodejs/package-lock.json`, and packages the version-matched runtime and CLI files.
363363
* The current release work publishes the `linux-x64` classifier. The planned classifier set expands to the other detected platforms.
364-
* Adding an implemented platform requires a host profile that supplies the classifier and platform CLI filename and binds the shared fetch, fetch-script test, package, and verification executions to the Maven lifecycle.
364+
* Adding an implemented platform requires validated host activation, a profile that supplies the classifier and platform CLI filename, and lifecycle bindings for the shared host validation, fetch, script test, package, and verification executions.
365365
* `cli-native.node` is not bundled. It provides terminal UI features that are irrelevant to the Java SDK's programmatic API surface.
366366

367367
## Related work items

0 commit comments

Comments
 (0)