Skip to content

Commit 4147bcd

Browse files
authored
clojure 1.12 and java 23 (#66)
* clojure 1.12 and java 23 * wip * wip * wip * wip * wip * dynaload * escape * rm GRAALVM_HOME * undo wording change
1 parent a03a3df commit 4147bcd

File tree

2 files changed

+115
-54
lines changed

2 files changed

+115
-54
lines changed

Diff for: README.adoc

+92-49
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ ifdef::env-github[]
1010
endif::[]
1111
:toc:
1212
:toclevels: 3
13-
:clojure-version: 1.11.1
13+
:clojure-version: 1.12.0
1414
:graal-build-time-version: 1.0.5
1515

1616
== Rationale
@@ -148,24 +148,30 @@ It will compile just fine:
148148
----
149149
$ mkdir -p classes
150150
$ clojure -M -e "(compile 'refl.main)"
151-
$ native-image -cp "$(clojure -Spath):classes" -H:Name=refl -H:+ReportExceptionStackTraces \
152-
--features=clj_easy.graal_build_time.InitClojureClasses --no-fallback refl.main
151+
$ native-image \
152+
-cp "$(clojure -Spath):classes" \
153+
-H:Name=refl \
154+
-H:+ReportExceptionStackTraces \
155+
--features=clj_easy.graal_build_time.InitClojureClasses \
156+
--no-fallback \
157+
refl.main
153158
----
154159
But when we go to run the native image, we'll see the following failure:
155160
[source,shell]
156161
----
157162
$ ./refl
158163
Exception in thread "main" java.lang.IllegalArgumentException: No matching field found: toUpperCase for class java.lang.String
159-
at clojure.lang.Reflector.getInstanceField(Reflector.java:397)
160-
at clojure.lang.Reflector.invokeNoArgInstanceMember(Reflector.java:440)
161-
at refl.main$refl_str.invokeStatic(main.clj:5)
162-
at refl.main$refl_str.invoke(main.clj:4)
163-
at refl.main$_main.invokeStatic(main.clj:8)
164-
at refl.main$_main.doInvoke(main.clj:7)
165-
at clojure.lang.RestFn.invoke(RestFn.java:397)
166-
at clojure.lang.AFn.applyToHelper(AFn.java:152)
167-
at clojure.lang.RestFn.applyTo(RestFn.java:132)
168-
at refl.main.main(Unknown Source)
164+
at clojure.lang.Reflector.getInstanceField(Reflector.java:426)
165+
at clojure.lang.Reflector.invokeNoArgInstanceMember(Reflector.java:469)
166+
at refl.main$refl_str.invokeStatic(main.clj:5)
167+
at refl.main$refl_str.invoke(main.clj:4)
168+
at refl.main$_main.invokeStatic(main.clj:8)
169+
at refl.main$_main.doInvoke(main.clj:7)
170+
at clojure.lang.RestFn.invoke(RestFn.java:400)
171+
at clojure.lang.AFn.applyToHelper(AFn.java:152)
172+
at clojure.lang.RestFn.applyTo(RestFn.java:135)
173+
at refl.main.main(Unknown Source)
174+
at [email protected]/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)
169175
----
170176

171177
==== Use Type Hints to Avoid Reflection
@@ -215,8 +221,13 @@ If we recompile our updated source:
215221
----
216222
$ mkdir -p classes
217223
$ clojure -M -e "(compile 'refl.main)"
218-
$ native-image -cp "$(clojure -Spath):classes" -H:Name=refl -H:+ReportExceptionStackTraces \
219-
--features=clj_easy.graal_build_time.InitClojureClasses --no-fallback refl.main
224+
$ native-image \
225+
-cp "$(clojure -Spath):classes" \
226+
-H:Name=refl \
227+
-H:+ReportExceptionStackTraces \
228+
--features=clj_easy.graal_build_time.InitClojureClasses \
229+
--no-fallback \
230+
refl.main
220231
----
221232
We no longer see our reflection warning and our native image now works just fine:
222233
[source,clojure]
@@ -225,10 +236,7 @@ $ ./refl
225236
ALL GOOD!
226237
----
227238

228-
NOTE: As an example, prior versions of Clojure's own `clojure.stacktrace` made use of reflection (see https://clojure.atlassian.net/browse/CLJ-2502[JIRA CLJ-2502]).
229-
But this has been addressed via type hints.
230-
231-
Enable or disable the `*warn-on-reflection*` depending on the alias, the following methods are available for each tool.
239+
To enable or disable `\*warn-on-reflection*`, the following methods are available for each tool.
232240

233241
- `leiningen`: Use `:global-vars` in project.clj
234242
[source,clojure]
@@ -287,9 +295,14 @@ Then recompile specifying our reflection config:
287295
----
288296
$ mkdir -p classes
289297
$ clojure -M -e "(compile 'refl.main)"
290-
$ native-image -cp "$(clojure -Spath):classes" -H:Name=refl -H:+ReportExceptionStackTraces \
298+
$ native-image \
299+
-cp "$(clojure -Spath):classes" \
300+
-H:Name=refl \
301+
-H:+ReportExceptionStackTraces \
291302
-H:ReflectionConfigurationFiles=reflect-config.json \
292-
--features=clj_easy.graal_build_time.InitClojureClasses --no-fallback refl.main
303+
--features=clj_easy.graal_build_time.InitClojureClasses \
304+
--no-fallback \
305+
refl.main
293306
----
294307

295308
We have success:
@@ -299,21 +312,33 @@ $ ./refl
299312
ALL GOOD!
300313
----
301314

302-
See the https://www.graalvm.org/reference-manual/native-image/Reflection/[GraalVM docs on reflection for details] on the reflection config format.
315+
See the https://www.graalvm.org/jdk23/reference-manual/native-image/metadata/#reflection[GraalVM docs on reflection for details] on the reflection config format.
303316

304317
==== Reflection Config for Arrays
305-
To configure reflection config for an array of Java objects, you need to specify `[Lfully.qualified.class`.
306-
For example a `Statement[]` would be specified as `"[Ljava.sql.Statement"`.
318+
To configure reflection config for an array of Java objects, you need to specify `[Lfully.qualified.class;`.
319+
Arrays of primitives or arrays are slightly different.
320+
For example a `Statement[]` would be specified as `"[Ljava.sql.Statement;"`.
307321

308-
You can discover this name by calling `(.getClass instance)` in a REPL.
322+
You can discover this name by calling `(.getName (class instance))` in a REPL.
309323
A contrived example:
310324
[source,clojure]
311325
----
312326
❯ clj
313-
Clojure 1.11.1
327+
Clojure 1.12.0
314328
user=> (def foo (java.util.Locale/getAvailableLocales))
315-
user=> (.getClass foo)
316-
[Ljava.util.Locale;
329+
#'user/foo
330+
user=> (.getName (class foo))
331+
"[Ljava.util.Locale;"
332+
user=> (.getName java.util.Locale/1)
333+
"[Ljava.util.Locale;"
334+
user=> (.getName java.util.Locale/2)
335+
"[[Ljava.util.Locale;"
336+
user=> (.getName java.util.Locale/3)
337+
"[[[Ljava.util.Locale;"
338+
user=> (.getName byte/1)
339+
"[B"
340+
user=> (.getName byte/2)
341+
"[[B"
317342
----
318343

319344
==== Automatically Discovering Reflection Config [[reflection-discovery]]
@@ -344,39 +369,56 @@ Let's recompile our original reflection example app and then run it from GraalVM
344369
----
345370
$ mkdir -p classes
346371
$ clojure -M -e "(compile 'refl.main)"
372+
Reflection warning, refl/main.clj:7:3 - reference to field toUpperCase can't be resolved.
347373
refl.main
348374
$ java -agentlib:native-image-agent=caller-filter-file=filter.json,config-output-dir=. \
349-
-cp $(clojure -Spath):classes refl.main
375+
-cp $(clojure -Spath):classes \
376+
refl.main
350377
ALL GOOD!
351378
----
352379

353-
This will output `reflect-config.json`:
380+
This will output `reachability-metadata.json`:
354381
[source,json]
355382
----
356383
[
357-
{
358-
"name":"java.lang.String",
359-
"queryAllPublicMethods":true,
360-
"methods":[{"name":"toUpperCase","parameterTypes":[] }]
361-
},
362-
{
363-
"name":"java.lang.reflect.Method",
364-
"methods":[{"name":"canAccess","parameterTypes":["java.lang.Object"] }]
365-
},
366-
{
367-
"name":"java.util.concurrent.atomic.AtomicBoolean",
368-
"fields":[{"name":"value"}]
369-
},
370-
{
371-
"name":"java.util.concurrent.atomic.AtomicReference",
372-
"fields":[{"name":"value"}]
373-
}
384+
"reflection": [
385+
...
386+
{
387+
"type": "java.lang.String",
388+
"methods": [
389+
{
390+
"name": "toUpperCase",
391+
"parameterTypes": []
392+
}
393+
]
394+
},
395+
{
396+
"type": "java.lang.reflect.Method",
397+
"methods": [
398+
{
399+
"name": "canAccess",
400+
"parameterTypes": [
401+
"java.lang.Object"
402+
]
403+
}
404+
]
405+
},
406+
{
407+
"type": "java.util.concurrent.atomic.AtomicBoolean",
408+
"fields": [
409+
{
410+
"name": "value"
411+
}
412+
]
413+
},
414+
...
415+
]
374416
]
375417
----
376418

377419
The entry for `java.lang.reflect.Method` is expected, see link:#clojure.lang.reflector[clojure.lang.Reflector].
378420

379-
// TODO: Why AtomicBoolean and AtomicReference?
421+
// TODO: Why AtomicBoolean?
380422

381423
You then feed this generated reflection config to native-image just like you would for a link:#hand-coded-reflection-config[hand-coded one].
382424

@@ -442,7 +484,8 @@ The most convenient place for you to set that system property will vary dependin
442484

443485
=== Optional Transitive Dependencies
444486

445-
A Clojure app that optionally requires transitive dependencies can be made to work under GraalVM with https://github.com/borkdude/dynaload[dynaload].
487+
A Clojure app that optionally requires transitive dependencies can often be made to work under GraalVM
488+
by replacing dynamic calls to `requiring-resolve` and `require` with https://github.com/borkdude/dynaload[dynaload].
446489
You'll want to follow https://github.com/borkdude/dynaload#graalvm[its advice for GraalVM].
447490

448491
=== Static Linking

Diff for: doc/hello-world.adoc

+23-5
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,15 @@ ifdef::env-github[]
99
:warning-caption: :warning:
1010
endif::[]
1111
:toc: preamble
12-
:clojure-version: 1.11.1
12+
:clojure-version: 1.12.0
1313
:graal-build-time-version: 1.0.5
1414

1515
This tutorial covers creating a native binary from a Clojure hello world program using GraalVM.
1616

1717
== Common Setup
1818

1919
Download and install the most current https://github.com/graalvm/graalvm-ce-builds/releases[GraalVM Community Edition] for your operating system.
20-
There are Java 17 and Java 20 versions.
21-
It doesn’t matter which one you pick for this tutorial.
20+
We use Java 23 in this tutorial.
2221

2322
[IMPORTANT]
2423
====
@@ -30,7 +29,26 @@ $ native-image --version
3029
----
3130
====
3231

33-
TIP: You can optionally use a tool like https://sdkman.io/[SDKMAN!] to download and install GraalVM.
32+
TIP: You can optionally use a tool like https://sdkman.io/[SDKMAN!] to download and install GraalVM:
33+
34+
```bash
35+
# browse available versions
36+
$ sdk list java
37+
38+
# install relevant versions
39+
$ sdk use java 21.0.2-graalce
40+
$ sdk use java 23.0.1-graal
41+
42+
# example aliases to switch jvm and setup env variables
43+
$ alias graal21='sdk use java 21.0.2-graalce'
44+
$ alias graal23='sdk use java 23.0.1-graal'
45+
46+
# enable Java 23 in current terminal
47+
$ graal23
48+
Using java version 23.0.1-graal in this shell.
49+
$ type native-image
50+
native-image is ~/.sdkman/candidates/java/23.0.1-graal/bin/native-image
51+
```
3452

3553
== Clojure Deps CLI
3654

@@ -208,7 +226,7 @@ native-image \
208226
--no-fallback
209227
----
210228

211-
NOTE: we reference our built `-jar`
229+
NOTE: we reference our built jar via `-jar`.
212230

213231
This creates `hello-world`, a native image for your program.
214232

0 commit comments

Comments
 (0)