Picocli: Using picocli with GraalVM's native-image tool

Created on 12 Jul 2018  路  22Comments  路  Source: remkop/picocli

I have tried to use the AOT compilation with the GraalVM' native-image tool. I tried something similar to the example from the picocli documentation first, i.e.

``` .java
public static void main(String[] args) {
CommandLine.run(new Example(), System.out, args);
}

This was unsuccessful as the compiled native executable did not seem to parse and bind the arguments to the commands fields. What did work though was to let picocli do the annotation processing in the JVM which generated the image, i.e. in a static field initializer:

``` .java
    private static final CommandLine CMD = new CommandLine(new Example());

    public static void main(String[] args) {
        CMD.parseWithHandlers(new CommandLine.RunLast(),
                new CommandLine.DefaultExceptionHandler<List<Object>>(),
                args);
    }

I am posting this here so that others can benefit from it. It would be nice if you included instructions for using native-image with picocli into the documentation. :)

codegen enhancement

Most helpful comment

An article that describes how to use the tool is here: https://github.com/remkop/picocli/wiki/Picocli-on-GraalVM:-Blazingly-Fast-Command-Line-Apps

Feedback welcome!

All 22 comments

I haven鈥檛 tried GraalVM yet, unsure when I鈥檒l be able to get to it.

Would you be able to provide a patch or pull request for the picocli user manual? That would be great!

I am working on it. I have encountered another issue while doing so, see #417.

My plan is to add an example and a section in the user manual. Do you sync the user manual with the wiki automatically or is that done manually?

Thanks for #417!

The manual on the wiki is out of date. Please modify the index.adoc file instead.

If instructions are non-trivial or longer than a few paragraphs we can create a separate documentation page (and link from and back to the index page).

I'm highly interested!

As I develop a (hobby) command line application in Java with picocli, and actual time spent by execution of command is sometimes a very tiny fraction of whole execution time.

AOT should help a lot.

@stengerh I can update the wiki to the latest version of the user manual if that helps.

@remkop I don't think this will be necessary.

I am working on a chapter for the user manual. I took a closer at picocli and AOT compilation, so there will be more information than in my post above. However I got side-tracked by summer-time activities. ;)

I can push a preliminary version later this week. I still want to clean up some rough edges before I create a pull request. Here's a summary of my findings so far:

  • Configuration by annotations requires tricks (see first post). Reflection configuration might work but I haven't tested this yet.
  • Collection and map typed arguments are better than array type arguments. Array typed arguments require configuration by code. Configuration through files does not work. This seems to be a limitation or bug with GraalVM reflection configuration parser for array types.
  • Explicit initialization for collection and map typed arguments is better, especially if using implementation types (e.g. LinkedList or TreeMap), since this avoids the need for reflection and thus reflection configuration.
  • Conditionally-enabled picocli type converters don't work as Class.forName is not available. This also affects arguments of type java.lang.Class and java.sql.Driver and everything else which relies on the ServiceLoader mechanism.

It would be nice to AOT compile and run the picocli unit tests to get a more complete picture but this would require a testing framework which does not rely on runtime reflection (particularly Class.forName). I haven't looked into this yet.

That all sounds awesome. Take your time and enjoy the summer!

(Sounds like you have a lot of material there. Maybe interesting to also write it up in a blog post or an article for InfoQ, DZone or JavaCodeGeeks.)

@stengerh I鈥檓 still very interested in this.

I tried picocli on the latest GraalVM and it's not working out of the box. Hopefully as graal matures it'll support this use case.

@bootstraponline Thanks for reporting this! I haven't had a chance to look at GraalVM yet.

Looking at the stack trace in that ticket, the issue may be that GraalVM does not discover the default constructor via reflection. If not explicitly used in the code, this constructor may have been removed from the byte code by Graal.

You may be able to work around this by providing a custom picocli.CommandLine.IFactory implementation that instantiates your commands without using reflection (by maintaining a list of known classes and explicitly calling the constructor for them).

GraalVM has a bunch of issues right now (~140 on GitHub alone). I think even if this specific problem was resolved, it's not mature enough for real world use.

We aim for compatibility with existing language implementations. Java and JavaScript programs are expected to run fully compatible out-of-the-box.

https://www.graalvm.org/docs/faq/

I'm hoping the bug report helps the Graal team figure out the problem. If picocli starts working out of the box that'll be amazing.

FYI I solved the reflection issue and ran into another Graal bug. Here's the reflection config I used:
https://github.com/TestArmada/flank/pull/289/files

The Graal team is investigating.

Thanks for pushing this issue and thanks for keeping me in the loop!

It might be an idea to add a command line tool to picocli to generate the -H:ReflectionConfigurationFiles=<file> file from a CommandSpec or a class with @Command annotations.

https://github.com/oracle/graal/blob/master/substratevm/REFLECTION.md

Agreed. That was my next step if graal worked.

Another idea is to create an annotation processor that generates code (either java source code or byte code) for a class with picocli annotations such that the CommandSpec for the command(s) is created at compile time instead of with reflection at runtime.

A first version of ReflectionConfigGenerator is available in the new picocli-codegen module.

This generates JSON that can be specified in the -H:ReflectionConfigurationFiles=<file> option of the native-image tool.

Could you give it a try?

An article that describes how to use the tool is here: https://github.com/remkop/picocli/wiki/Picocli-on-GraalVM:-Blazingly-Fast-Command-Line-Apps

Feedback welcome!

Closing this ticket. The ReflectionConfigGenerator tool will be in the picocli-codegen artefact which will be released with picocli 3.7.

I tried the code gen using graalvm-ce-1.0.0-rc9

java -cp picocli-3.8.0.jar:picocli-codegen-3.8.0.jar:flank-SNAPSHOT.jar \
picocli.codegen.aot.graalvm.ReflectionConfigGenerator ftl.Main > main.json

I deleted all the system classes in main.json which aren't from my app.

native-image -H:ReflectionConfigurationFiles=main.json -jar flank-SNAPSHOT.jar

I think, unfortunately, graal isn't Kotlin compatible. Hopefully future versions of Graal expand compatibility.

Stacktrace

$ native-image -H:ReflectionConfigurationFiles=main.json -jar flank-SNAPSHOT.jar --report-unsupported-elements-at-runtime
Build on Server(pid: 46750, port: 50862)
[flank-SNAPSHOT:46750]    classlist:   2,799.63 ms
[flank-SNAPSHOT:46750]        (cap):     823.23 ms
[flank-SNAPSHOT:46750]        setup:   1,066.87 ms
warning: unknown locality of class Lkotlin/coroutines/CoroutineContext$plus$1;, assuming class is not local. To remove the warning report an issue to the library or language author. The issue is caused by Lkotlin/coroutines/CoroutineContext$plus$1; which is not following the naming convention.
[flank-SNAPSHOT:46750]     analysis:  15,986.16 ms
error: Non-reducible loop
Detailed message:
Error: Non-reducible loop
Call path from entry point to kotlin.collections.SlidingWindowKt$windowedIterator$1.invokeSuspend(Object):
    at kotlin.collections.SlidingWindowKt$windowedIterator$1.invokeSuspend(SlidingWindow.kt)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
    at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:234)
    at com.oracle.svm.core.jdk.RuntimeSupport.executeHooks(RuntimeSupport.java:142)
    at com.oracle.svm.core.jdk.RuntimeSupport.executeStartupHooks(RuntimeSupport.java:87)
    at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:156)
    at com.oracle.svm.core.code.CEntryPointCallStubs.com_002eoracle_002esvm_002ecore_002eJavaMainWrapper_002erun_0028int_002corg_002egraalvm_002enativeimage_002ec_002etype_002eCCharPointerPointer_0029(generated:0)
Original exception that caused the problem: org.graalvm.compiler.core.common.PermanentBailoutException: Non-reducible loop
    at org.graalvm.compiler.java.BciBlockMapping.computeBlockOrder(BciBlockMapping.java:876)
    at org.graalvm.compiler.java.BciBlockMapping.build(BciBlockMapping.java:524)
    at org.graalvm.compiler.java.BciBlockMapping.create(BciBlockMapping.java:1103)
    at org.graalvm.compiler.java.BytecodeParser.build(BytecodeParser.java:804)
    at org.graalvm.compiler.java.BytecodeParser.buildRootMethod(BytecodeParser.java:782)
    at org.graalvm.compiler.java.GraphBuilderPhase$Instance.run(GraphBuilderPhase.java:95)
    at org.graalvm.compiler.phases.Phase.run(Phase.java:49)
    at org.graalvm.compiler.phases.BasePhase.apply(BasePhase.java:197)
    at org.graalvm.compiler.phases.Phase.apply(Phase.java:42)
    at org.graalvm.compiler.phases.Phase.apply(Phase.java:38)
    at com.oracle.graal.pointsto.flow.MethodTypeFlowBuilder.parse(MethodTypeFlowBuilder.java:204)
    at com.oracle.graal.pointsto.flow.MethodTypeFlowBuilder.apply(MethodTypeFlowBuilder.java:323)
    at com.oracle.graal.pointsto.flow.MethodTypeFlow.doParse(MethodTypeFlow.java:310)
    at com.oracle.graal.pointsto.flow.MethodTypeFlow.ensureParsed(MethodTypeFlow.java:300)
    at com.oracle.graal.pointsto.flow.MethodTypeFlow.addContext(MethodTypeFlow.java:107)
    at com.oracle.graal.pointsto.DefaultAnalysisPolicy$DefaultVirtualInvokeTypeFlow.onObservedUpdate(DefaultAnalysisPolicy.java:186)
    at com.oracle.graal.pointsto.flow.TypeFlow.notifyObservers(TypeFlow.java:347)
    at com.oracle.graal.pointsto.flow.TypeFlow.update(TypeFlow.java:389)
    at com.oracle.graal.pointsto.BigBang$2.run(BigBang.java:508)
    at com.oracle.graal.pointsto.util.CompletionExecutor.lambda$execute$0(CompletionExecutor.java:174)
    at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1402)
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)

Error: Processing image build request failed


Thanks for the info! Do you think there is anything picocli can do to help with this?

Thanks for the info! Do you think there is anything picocli can do to help with this?

Unless you want to redesign picocli to work based on a Kotlin compiler plugin to fully avoid reflection, I don't think so.

Reflection doesn't play nice with the current version of graal (error: Non-reducible loop). I'm hoping they fix that over time.

The picocli library and ReflectionConfigGenerator are easy to use.

Was this page helpful?
0 / 5 - 0 ratings