alexarchambault / mill-native-image

Apache License 2.0
18 stars 6 forks source link

Native image binary doesn't run using plugin. Works with scala-cli. #19

Closed carlosedp closed 1 year ago

carlosedp commented 1 year ago

I have a simple ZIO-http app and found-out that using scala-cli it can package the app with native image and it works fine.

Using the mill-native-image plugin, the binary fails with some LazyVal error... Dunno exactly what is different between both as the native-image options are the same.

The project sources are at: https://github.com/carlosedp/ziohttp

The mill project used scala-cli export function and I just added the plugin native image parameters.

To test it out, just clone the repo and run nativepackage.sh to use scala-cli (which worksfine). Using the plugin, run cd ziohttpproj && ./mill show ziohttp.nativeImage, then run ./out/ziohttp/nativeImage.dest/ziohttp.bin. It fails with:

❯ ./out/ziohttp/nativeImage.dest/ziohttp.bin
Exception in thread "main" scala.runtime.LazyVals$$anon$1
    at scala.runtime.LazyVals$.$init$$$anonfun$3(LazyVals.scala:21)
    at scala.Option.getOrElse(Option.scala:201)
    at scala.runtime.LazyVals$.<clinit>(LazyVals.scala:22)
    at zio.ZLayer.<clinit>(ZLayer.scala:42)
    at java.base@17.0.5/java.lang.Class.ensureInitialized(DynamicHub.java:528)
    at zio.ZLayer$Suspend$.apply(ZLayer.scala:439)
    at zio.ZLayer$.suspend(ZLayer.scala:675)
    at zio.ZLayer$.fromZIOEnvironment(ZLayer.scala:621)
    at zio.ZLayer$.<clinit>(ZLayer.scala:566)
    at zio.ZIOAppDefault.$init$(ZIOAppDefault.scala:43)
    at ZioHttpApp$.<clinit>(ziohttp.scala:7)
    at ZioHttpApp.main(ziohttp.scala)

Similar error Ref. https://github.com/lampepfl/dotty/issues/13985

carlosedp commented 1 year ago

I believe it has something to do with creating the native-config file like https://github.com/VirtusLab/scala-cli/pull/830.

Tried doing something like:

diff --git a/plugin/src/io/github/alexarchambault/millnativeimage/NativeImage.scala b/plugin/src/io/github/alexarchambault/millnativeimage/NativeImage.scala
index 0ff85f3..41f06cb 100644
--- a/plugin/src/io/github/alexarchambault/millnativeimage/NativeImage.scala
+++ b/plugin/src/io/github/alexarchambault/millnativeimage/NativeImage.scala
@@ -356,12 +356,27 @@ object NativeImage {

     def command(nativeImage: String, extraNativeImageArgs: Seq[String], destDir: Option[String], destName: String, classPath: String) = {
       val destDirOptions = destDir.toList.map(d => s"-H:Path=$d")
+      val nativeConfigFile = os.temp(suffix = ".json")
+      os.write.over(
+        nativeConfigFile,
+        """[
+          |  {
+          |    "name": "sun.misc.Unsafe",
+          |    "allDeclaredConstructors": true,
+          |    "allPublicConstructors": true,
+          |    "allDeclaredMethods": true,
+          |    "allDeclaredFields": true
+          |  }
+          |]
+          |""".stripMargin
+      )
       Seq(nativeImage) ++
       extraNativeImageArgs ++
       nativeImageOptions ++
       destDirOptions ++
       Seq(
         s"-H:Name=$destName",
+        s"-H:ReflectionConfigurationFiles=$nativeConfigFile",
         "-cp",
         classPath,
         mainClass

But something is still missing cause I'm getting:

❯ /Users/cdepaula/repos/scala-playground/ziohttp/ziohttpproj/out/ziohttp/nativeImage.dest/ziohttp.bin
Exception in thread "main" java.lang.ExceptionInInitializerError
    at java.base@17.0.5/java.lang.Class.ensureInitialized(DynamicHub.java:528)
    at izumi.reflect.macrortti.LightTypeTag$.parse(LightTypeTag.scala:271)
    at zio.ZIOAppDefault.$init$(ZIOAppDefault.scala:45)
    at ZioHttpApp$.<clinit>(ziohttp.scala:7)
    at ZioHttpApp.main(ziohttp.scala)
Caused by: java.lang.NoSuchFieldException: 0bitmap$1
    at java.base@17.0.5/java.lang.Class.getDeclaredField(DynamicHub.java:961)
    at scala.runtime.LazyVals$.getOffset(LazyVals.scala:107)
    at izumi.reflect.macrortti.LightTypeTag.<clinit>(LightTypeTag.scala:49)
    ... 5 more

Maybe @romanowski can give some tips :)

carlosedp commented 1 year ago

Apparently it's not as trivial as I thought... from what I understood, scala-cli manipulates the bytecode to allow the unsafe declarations thru BytecodeProcessor and all...

Maybe the plugin could import scala-cli and it would make things easier... dunno :)

romanowski commented 1 year ago

Yes, Scala CLI changes a bit implementation of lazy vals in Scala 3 so people does not need to apply config for each lazy val in their code (and all its dependencies). Lazy vals were reworked so it should be better in the future.

As for the rewrites, we have an application in Scala CLI that changes the classpath that goes into graal native image generation. We publish it so sonatype so it should be possible to use it your build.

carlosedp commented 1 year ago

Thanks a lot @romanowski ... ~I think the bigger issue here is that the mill-native-image plugin is Scala 2.13 code and the scala3-graal lib is Scala 3.~

~I tried adding the lib as ivy"org.virtuslab.scala-cli:scala3-graal_3:0.1.18" and using -Ytasty-reader but in the sources it complained due to conflicts but I don't know much about using cross-version Scala libs:~

Maybe @alexarchambault have some pointer.

carlosedp commented 1 year ago

Implemented in #20.

carlosedp commented 1 year ago

This is solved by using Scala 3.3 which implements LazyVals correctly.