diff --git a/.github/workflows/validate-pr.yml b/.github/workflows/validate-pr.yml index e2eddd119..177e8380b 100644 --- a/.github/workflows/validate-pr.yml +++ b/.github/workflows/validate-pr.yml @@ -252,11 +252,15 @@ jobs: restore-keys: | ${{ runner.os }}-ivy- - name: Setup GraalVM environment - uses: olafurpg/setup-scala@v10 + uses: graalvm/setup-graalvm@v1 with: - java-version: graalvm@22.0.0=tgz+https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.0.0.2/graalvm-ce-java11-linux-amd64-22.0.0.2.tar.gz - - name: Install native-image - run: gu install native-image + java-version: 17.0.8 + distribution: 'graalvm' + cache: 'sbt' + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Docker Setup Buildx + uses: docker/setup-buildx-action@v2.9.1 - name: Validate run: sbt "^validateGraalVMNativeImage" diff --git a/.gitignore b/.gitignore index 96a52da37..a0d11fa9a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ target/ .ensime* .bloop/* .metals/* + +.bsp/ diff --git a/build.sbt b/build.sbt index 489c4149b..8556db2cd 100644 --- a/build.sbt +++ b/build.sbt @@ -3,10 +3,10 @@ organization := "com.github.sbt" homepage := Some(url("https://github.com/sbt/sbt-native-packager")) Global / onChangedBuildSource := ReloadOnSourceChanges -Global / scalaVersion := "2.12.12" +Global / scalaVersion := "2.12.13" // crossBuildingSettings -crossSbtVersions := Vector("1.1.6") +crossSbtVersions := Vector("1.9.3") Compile / scalacOptions ++= Seq("-deprecation") javacOptions ++= Seq("-source", "1.8", "-target", "1.8") diff --git a/project/build.properties b/project/build.properties index 9edb75b77..52413ab79 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.4 +sbt.version=1.9.3 diff --git a/src/main/scala/com/typesafe/sbt/packager/graalvmnativeimage/GraalVMNativeImageKeys.scala b/src/main/scala/com/typesafe/sbt/packager/graalvmnativeimage/GraalVMNativeImageKeys.scala index 8460b265d..01264be6b 100644 --- a/src/main/scala/com/typesafe/sbt/packager/graalvmnativeimage/GraalVMNativeImageKeys.scala +++ b/src/main/scala/com/typesafe/sbt/packager/graalvmnativeimage/GraalVMNativeImageKeys.scala @@ -18,4 +18,8 @@ trait GraalVMNativeImageKeys { trait GraalVMNativeImageKeysEx extends GraalVMNativeImageKeys { val graalVMNativeImageCommand = settingKey[String]("GraalVM native-image executable command") + + val graalVMNativeImagePlatformArch = settingKey[Option[String]]( + "Platform architecture of GraalVM to build with. This only works when building the native image with a container." + ) } diff --git a/src/main/scala/com/typesafe/sbt/packager/graalvmnativeimage/GraalVMNativeImagePlugin.scala b/src/main/scala/com/typesafe/sbt/packager/graalvmnativeimage/GraalVMNativeImagePlugin.scala index c3ba42ab3..016b4e6ff 100644 --- a/src/main/scala/com/typesafe/sbt/packager/graalvmnativeimage/GraalVMNativeImagePlugin.scala +++ b/src/main/scala/com/typesafe/sbt/packager/graalvmnativeimage/GraalVMNativeImagePlugin.scala @@ -27,7 +27,7 @@ object GraalVMNativeImagePlugin extends AutoPlugin { import autoImport._ - private val GraalVMBaseImage = "ghcr.io/graalvm/graalvm-ce" + private val GraalVMBaseImagePath = "ghcr.io/graalvm/" override def requires: Plugins = JavaAppPackaging @@ -37,6 +37,7 @@ object GraalVMNativeImagePlugin extends AutoPlugin { target in GraalVMNativeImage := target.value / "graalvm-native-image", graalVMNativeImageOptions := Seq.empty, graalVMNativeImageGraalVersion := None, + graalVMNativeImagePlatformArch := None, graalVMNativeImageCommand := (if (scala.util.Properties.isWin) "native-image.cmd" else "native-image"), resourceDirectory in GraalVMNativeImage := sourceDirectory.value / "graal", mainClass in GraalVMNativeImage := (mainClass in Compile).value @@ -47,9 +48,22 @@ object GraalVMNativeImagePlugin extends AutoPlugin { includeFilter := "*", resources := resourceDirectories.value.descendantsExcept(includeFilter.value, excludeFilter.value).get, UniversalPlugin.autoImport.containerBuildImage := Def.taskDyn { + val splitPackageVersion = "(.*):(.*)".r graalVMNativeImageGraalVersion.value match { - case Some(tag) => generateContainerBuildImage(s"$GraalVMBaseImage:$tag") - case None => Def.task(None: Option[String]) + case Some(splitPackageVersion(packageName, tag)) => + packageName match { + case "native-image-community" | "native-image" => + Def.task(Some(s"$GraalVMBaseImagePath$packageName:$tag"): Option[String]) + case "graalvm-community" | "graalvm-ce" => + generateContainerBuildImage( + s"${GraalVMBaseImagePath}$packageName:$tag", + graalVMNativeImagePlatformArch.value + ) + case _ => sys.error("Other ghcr.io/graalvm images are unsupported") + } + case Some(tag) => + generateContainerBuildImage(s"${GraalVMBaseImagePath}graalvm-ce:$tag", graalVMNativeImagePlatformArch.value) + case None => Def.task(None: Option[String]) } }.value, packageBin := { @@ -59,6 +73,7 @@ object GraalVMNativeImagePlugin extends AutoPlugin { val className = mainClass.value.getOrElse(sys.error("Could not find a main class.")) val classpathJars = scriptClasspathOrdering.value val extraOptions = graalVMNativeImageOptions.value + val platformArch = graalVMNativeImagePlatformArch.value val streams = Keys.streams.value val dockerCommand = DockerPlugin.autoImport.dockerExecCommand.value val graalResourceDirectories = resourceDirectories.value @@ -85,6 +100,7 @@ object GraalVMNativeImagePlugin extends AutoPlugin { className, classpathJars, extraOptions, + platformArch, dockerCommand, resourceMappings, image, @@ -131,23 +147,29 @@ object GraalVMNativeImagePlugin extends AutoPlugin { className: String, classpathJars: Seq[(File, String)], extraOptions: Seq[String], + platformArch: Option[String], dockerCommand: Seq[String], resources: Seq[(File, String)], image: String, streams: TaskStreams ): File = { - + import sys.process._ stage(targetDirectory, classpathJars, resources, streams) val graalDestDir = "/opt/graalvm" val stageDestDir = s"$graalDestDir/stage" val resourcesDestDir = s"$stageDestDir/resources" + val hostPlatform = + (dockerCommand ++ Seq("system", "info", "--format", "{{.OSType}}/{{.Architecture}}")).!!.trim + .replace("x86_64", "amd64") val command = dockerCommand ++ Seq( "run", "--workdir", "/opt/graalvm", "--rm", + "--platform", + platformArch.getOrElse(hostPlatform), "-v", s"${targetDirectory.getAbsolutePath}:$graalDestDir", image, @@ -166,15 +188,24 @@ object GraalVMNativeImagePlugin extends AutoPlugin { * This can be used to build a custom build image starting from a custom base image. Can be used like so: * * ``` - * (containerBuildImage in GraalVMNativeImage) := generateContainerBuildImage("my-docker-hub-username/my-graalvm").value + * (containerBuildImage in GraalVMNativeImage) := generateContainerBuildImage("my-docker-hub-username/my-graalvm", Some("arm64")).value * ``` * * The passed in docker image must have GraalVM installed and on the PATH, including the gu utility. */ - def generateContainerBuildImage(baseImage: String): Def.Initialize[Task[Option[String]]] = + def generateContainerBuildImage( + baseImage: String, + platformArch: Option[String] = None + ): Def.Initialize[Task[Option[String]]] = Def.task { + import sys.process._ + val dockerCommand = (DockerPlugin.autoImport.dockerExecCommand in GraalVMNativeImage).value val streams = Keys.streams.value + val hostPlatform = + (dockerCommand ++ Seq("system", "info", "--format", "{{.OSType}}/{{.Architecture}}")).!!.trim + .replace("x86_64", "amd64") + val platformValue = platformArch.getOrElse(hostPlatform) val (baseName, tag) = baseImage.split(":", 2) match { case Array(n, t) => (n, t) @@ -182,8 +213,17 @@ object GraalVMNativeImagePlugin extends AutoPlugin { } val imageName = s"${baseName.replace('/', '-')}-native-image:$tag" + import sys.process._ - if ((dockerCommand ++ Seq("image", "ls", imageName, "--quiet")).!!.trim.isEmpty) { + val buildContainerExists = (dockerCommand ++ Seq( + "image", + "ls", + "--filter", + s"label=com.typesafe.sbt.packager.graalvmnativeimage.platform=$platformValue", + "--quiet", + imageName + )).!!.trim.nonEmpty + if (!buildContainerExists) { streams.log.info(s"Generating new GraalVM native-image image based on $baseImage: $imageName") val dockerContent = Dockerfile( @@ -194,9 +234,29 @@ object GraalVMNativeImagePlugin extends AutoPlugin { ExecCmd("ENTRYPOINT", "native-image") ).makeContent - val command = dockerCommand ++ Seq("build", "-t", imageName, "-") - - val ret = sys.process.Process(command) #< + val buildCommand = dockerCommand ++ Seq( + "build", + "--label", + s"com.typesafe.sbt.packager.graalvmnativeimage.platform=$platformValue", + "-t", + imageName, + "-" + ) + + val buildxCommand = dockerCommand ++ Seq( + "buildx", + "build", + "--platform", + platformValue, + "--load", + "--label", + s"com.typesafe.sbt.packager.graalvmnativeimage.platform=$platformValue", + "-t", + imageName, + "-" + ) + + val ret = sys.process.Process(platformArch.map(_ => buildxCommand).getOrElse(buildCommand)) #< new ByteArrayInputStream(dockerContent.getBytes()) ! DockerPlugin.publishLocalLogger(streams.log) @@ -219,7 +279,7 @@ object GraalVMNativeImagePlugin extends AutoPlugin { val mappings = classpathJars ++ resources.map { case (resource, path) => resource -> s"resources/$path" } - Stager.stage(GraalVMBaseImage)(streams, stageDir, mappings) + Stager.stage(GraalVMBaseImagePath)(streams, stageDir, mappings) } } diff --git a/src/sbt-test/graalvm-native-image/docker-native-image-arm64/build.sbt b/src/sbt-test/graalvm-native-image/docker-native-image-arm64/build.sbt new file mode 100644 index 000000000..bd23343a2 --- /dev/null +++ b/src/sbt-test/graalvm-native-image/docker-native-image-arm64/build.sbt @@ -0,0 +1,7 @@ +enablePlugins(GraalVMNativeImagePlugin) + +name := "docker-test" +version := "0.1.0" +graalVMNativeImageOptions := Seq("--no-fallback") +graalVMNativeImageGraalVersion := Some("native-image-community:17.0.8") +graalVMNativeImagePlatformArch := Some("arm64") diff --git a/src/sbt-test/graalvm-native-image/docker-native-image-arm64/project/plugins.sbt b/src/sbt-test/graalvm-native-image/docker-native-image-arm64/project/plugins.sbt new file mode 100644 index 000000000..218f1a27d --- /dev/null +++ b/src/sbt-test/graalvm-native-image/docker-native-image-arm64/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.github.sbt" % "sbt-native-packager" % sys.props("project.version")) diff --git a/src/sbt-test/graalvm-native-image/docker-native-image-arm64/src/main/scala/Main.scala b/src/sbt-test/graalvm-native-image/docker-native-image-arm64/src/main/scala/Main.scala new file mode 100644 index 000000000..43c2459f3 --- /dev/null +++ b/src/sbt-test/graalvm-native-image/docker-native-image-arm64/src/main/scala/Main.scala @@ -0,0 +1,5 @@ +object Main { + def main(args: Array[String]): Unit = { + println("Hello Graal") + } +} diff --git a/src/sbt-test/graalvm-native-image/docker-native-image-arm64/test b/src/sbt-test/graalvm-native-image/docker-native-image-arm64/test new file mode 100644 index 000000000..f4e58abe3 --- /dev/null +++ b/src/sbt-test/graalvm-native-image/docker-native-image-arm64/test @@ -0,0 +1,3 @@ +# Generate the GraalVM native image +> show graalvm-native-image:packageBin +$ exec bash -c 'docker run --rm --platform arm64 -v .:/test -w /test ubuntu ./target/graalvm-native-image/docker-test | grep -q "Hello Graal"' diff --git a/src/sbt-test/graalvm-native-image/docker-native-image-legacy-specify-package/build.sbt b/src/sbt-test/graalvm-native-image/docker-native-image-legacy-specify-package/build.sbt new file mode 100644 index 000000000..38b1dd18c --- /dev/null +++ b/src/sbt-test/graalvm-native-image/docker-native-image-legacy-specify-package/build.sbt @@ -0,0 +1,6 @@ +enablePlugins(GraalVMNativeImagePlugin) + +name := "docker-test" +version := "0.1.0" +graalVMNativeImageOptions := Seq("--no-fallback") +graalVMNativeImageGraalVersion := Some("native-image:22.3.3") diff --git a/src/sbt-test/graalvm-native-image/docker-native-image-legacy-specify-package/project/plugins.sbt b/src/sbt-test/graalvm-native-image/docker-native-image-legacy-specify-package/project/plugins.sbt new file mode 100644 index 000000000..218f1a27d --- /dev/null +++ b/src/sbt-test/graalvm-native-image/docker-native-image-legacy-specify-package/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.github.sbt" % "sbt-native-packager" % sys.props("project.version")) diff --git a/src/sbt-test/graalvm-native-image/docker-native-image-legacy-specify-package/src/main/scala/Main.scala b/src/sbt-test/graalvm-native-image/docker-native-image-legacy-specify-package/src/main/scala/Main.scala new file mode 100644 index 000000000..43c2459f3 --- /dev/null +++ b/src/sbt-test/graalvm-native-image/docker-native-image-legacy-specify-package/src/main/scala/Main.scala @@ -0,0 +1,5 @@ +object Main { + def main(args: Array[String]): Unit = { + println("Hello Graal") + } +} diff --git a/src/sbt-test/graalvm-native-image/docker-native-image-legacy-specify-package/test b/src/sbt-test/graalvm-native-image/docker-native-image-legacy-specify-package/test new file mode 100644 index 000000000..3d6230bcd --- /dev/null +++ b/src/sbt-test/graalvm-native-image/docker-native-image-legacy-specify-package/test @@ -0,0 +1,3 @@ +# Generate the GraalVM native image +> show graalvm-native-image:packageBin +$ exec bash -c 'target/graalvm-native-image/docker-test | grep -q "Hello Graal"' diff --git a/src/sbt-test/graalvm-native-image/docker-native-image-legacy/build.sbt b/src/sbt-test/graalvm-native-image/docker-native-image-legacy/build.sbt new file mode 100644 index 000000000..c82125a5b --- /dev/null +++ b/src/sbt-test/graalvm-native-image/docker-native-image-legacy/build.sbt @@ -0,0 +1,6 @@ +enablePlugins(GraalVMNativeImagePlugin) + +name := "docker-test" +version := "0.1.0" +graalVMNativeImageOptions := Seq("--no-fallback") +graalVMNativeImageGraalVersion := Some("22.3.3") diff --git a/src/sbt-test/graalvm-native-image/docker-native-image-legacy/project/plugins.sbt b/src/sbt-test/graalvm-native-image/docker-native-image-legacy/project/plugins.sbt new file mode 100644 index 000000000..218f1a27d --- /dev/null +++ b/src/sbt-test/graalvm-native-image/docker-native-image-legacy/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.github.sbt" % "sbt-native-packager" % sys.props("project.version")) diff --git a/src/sbt-test/graalvm-native-image/docker-native-image-legacy/src/main/scala/Main.scala b/src/sbt-test/graalvm-native-image/docker-native-image-legacy/src/main/scala/Main.scala new file mode 100644 index 000000000..43c2459f3 --- /dev/null +++ b/src/sbt-test/graalvm-native-image/docker-native-image-legacy/src/main/scala/Main.scala @@ -0,0 +1,5 @@ +object Main { + def main(args: Array[String]): Unit = { + println("Hello Graal") + } +} diff --git a/src/sbt-test/graalvm-native-image/docker-native-image-legacy/test b/src/sbt-test/graalvm-native-image/docker-native-image-legacy/test new file mode 100644 index 000000000..3d6230bcd --- /dev/null +++ b/src/sbt-test/graalvm-native-image/docker-native-image-legacy/test @@ -0,0 +1,3 @@ +# Generate the GraalVM native image +> show graalvm-native-image:packageBin +$ exec bash -c 'target/graalvm-native-image/docker-test | grep -q "Hello Graal"' diff --git a/src/sbt-test/graalvm-native-image/docker-native-image/build.sbt b/src/sbt-test/graalvm-native-image/docker-native-image/build.sbt index 899d15a56..9663b1fa2 100644 --- a/src/sbt-test/graalvm-native-image/docker-native-image/build.sbt +++ b/src/sbt-test/graalvm-native-image/docker-native-image/build.sbt @@ -2,4 +2,5 @@ enablePlugins(GraalVMNativeImagePlugin) name := "docker-test" version := "0.1.0" -graalVMNativeImageGraalVersion := Some("22.0.0.2") \ No newline at end of file +graalVMNativeImageOptions := Seq("--no-fallback") +graalVMNativeImageGraalVersion := Some("graalvm-community:17.0.8") diff --git a/src/sbt-test/graalvm-native-image/simple-native-image/build.sbt b/src/sbt-test/graalvm-native-image/simple-native-image/build.sbt index 27042d757..b3eb05a63 100644 --- a/src/sbt-test/graalvm-native-image/simple-native-image/build.sbt +++ b/src/sbt-test/graalvm-native-image/simple-native-image/build.sbt @@ -1,5 +1,5 @@ enablePlugins(GraalVMNativeImagePlugin) name := "simple-test" - version := "0.1.0" +graalVMNativeImageOptions := Seq("--no-fallback") diff --git a/src/sphinx/formats/graalvm-native-image.rst b/src/sphinx/formats/graalvm-native-image.rst index bd110785d..386943dd7 100644 --- a/src/sphinx/formats/graalvm-native-image.rst +++ b/src/sphinx/formats/graalvm-native-image.rst @@ -5,17 +5,20 @@ GraalVM Native Image Plugin GraalVM's ``native-image`` compiles Java programs AOT (ahead-of-time) into native binaries. - https://www.graalvm.org/22.1/reference-manual/native-image/ documents the AOT compilation of GraalVM. + https://www.graalvm.org/latest/reference-manual/native-image/ documents the AOT compilation of GraalVM. The plugin supports both using a local installation of the GraalVM ``native-image`` utility, or building inside a Docker container. If you intend to run the native image on Linux, then building inside a Docker container is recommended since GraalVM native images can only be built for the platform they are built on. By building in a Docker -container, you can build Linux native images not just on Linux but also on Windows and macOS. +container, you can build Linux native images not only on Linux but also on Windows and macOS and for different architectures +like amd64 or arm64. Requirements ------------ To build using a local installation of GraalVM, you must have the ``native-image`` utility of GraalVM in your ``PATH``. +To build using a docker container, you must have a working installation of docker. +To build for a different architecture, you must have docker with the buildx plugin and QEMU set up for the target architecture. ``native-image`` quick installation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -65,16 +68,41 @@ By default, a local build will be done, expecting the ``native-image`` command t customized using the following settings. ``graalVMNativeImageGraalVersion`` - Setting this enables generating a Docker container to build the native image, and then building it in that container. - It must correspond to a valid version of the - `Oracle GraalVM Community Edition Docker image `_. This setting has no - effect if ``containerBuildImage`` is explicitly set. + Setting this enables using a Docker container to build the native image. + It should be in the format ``:``. `Supported packages `_ include: + * ``graalvm-ce`` - Versions prior to and including 22.3.3. An intermediate image will be created. + * ``native-image`` - Versions prior to and including 22.3.3. The docker image will be used directly. + * ``graalvm-community`` - Versions after and including 17.0.7. An intermediate image will be created. + * ``native-image-community`` - Versions after and including 17.0.7. The docker image will be used directly. + + The legacy format of specifying the version number is supported up to 22.3.3 + + This setting has no effect if ``containerBuildImage`` is explicitly set. + + For example: + + .. code-block:: scala + + graalVMNativeImageGraalVersion := Some("19.1.1") // Legacy GraalVM versions supported up to 22.3.3 + graalVMNativeImageGraalVersion := Some("graalvm-ce:19.1.1") // Legacy GraalVM versions supported up to 22.3.3 + graalVMNativeImageGraalVersion := Some("native-image:19.1.1") // Uses the legacy native-image image from GraalVM directly + graalVMNativeImageGraalVersion := Some("graalvm-community:17.0.8") // New GraalVM version scheme + graalVMNativeImageGraalVersion := Some("native-image-community:17.0.8") // Uses the native-image image from GraalVM directly + + ``graalVMNativeImagePlatformArch`` + Setting this enables building the native image for a different platform architecture. Requires ``graalVMNativeImageGraalVersion`` + or ``containerBuildImage`` to be set. Multiplatform builds are currently not supported. Defaults to the platform of the host. + If ``containerBuildImage`` is specified, ensure that your specified image has the same platform that you are targeting. + + Requires Docker buildx plugin with a valid builder and QEMU set up for the target platform architecture. + `See here for more information `_. For example: .. code-block:: scala - graalVMNativeImageGraalVersion := Some("19.1.1") + graalVMNativeImagePlatformArch := Some("arm64") + graalVMNativeImagePlatformArch := Some("linux/amd64") ``containerBuildImage``