Skip to content

Commit 585b039

Browse files
authored
Merge pull request #17 from fehu/master
Co-authored-by: Ólafur Páll Geirsson <[email protected]>
2 parents 6d4668b + 405e506 commit 585b039

File tree

7 files changed

+194
-17
lines changed

7 files changed

+194
-17
lines changed

plugin/src/main/scala/sbtnativeimage/NativeImagePlugin.scala

+71-14
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
package sbtnativeimage
22

33
import java.io.File
4-
import java.nio.file.Files
5-
import java.nio.file.StandardCopyOption
4+
import java.nio.file.{ Files, Path, Paths, StandardCopyOption }
65
import java.util.jar.Attributes
76
import java.util.jar.JarOutputStream
87
import java.util.jar.Manifest
@@ -16,7 +15,6 @@ import sbt.Keys._
1615
import sbt._
1716
import sbt.plugins.JvmPlugin
1817
import sbt.complete.DefaultParsers._
19-
import java.nio.file.Paths
2018
import scala.util.Try
2119

2220
object NativeImagePlugin extends AutoPlugin {
@@ -44,10 +42,27 @@ object NativeImagePlugin extends AutoPlugin {
4442
taskKey[File](
4543
"Path to a coursier binary that is used to launch GraalVM native-image."
4644
)
45+
lazy val nativeImageInstalled: SettingKey[Boolean] =
46+
settingKey[Boolean]("Whether GraalVM is manually installed or should be downloaded with coursier.")
47+
lazy val nativeImageGraalHome: TaskKey[Path] =
48+
taskKey[Path]("Path to GraalVM home directory.")
4749
lazy val nativeImageCommand: TaskKey[Seq[String]] =
4850
taskKey[Seq[String]](
4951
"The command arguments to launch the GraalVM native-image binary."
5052
)
53+
lazy val nativeImageRunAgent: InputKey[Unit] =
54+
inputKey[Unit](
55+
"Run application, tracking all usages of dynamic features of an execution with `native-image-agent`."
56+
)
57+
lazy val nativeImageAgentOutputDir: SettingKey[File] =
58+
settingKey[File](
59+
"Directory where `native-image-agent` should put generated configurations."
60+
)
61+
lazy val nativeImageAgentMerge: SettingKey[Boolean] =
62+
settingKey[Boolean](
63+
"Whether `native-image-agent` should merge generated configurations." +
64+
s" (See $assistedConfigurationOfNativeImageBuildsLink for details)"
65+
)
5166
lazy val nativeImage: TaskKey[File] =
5267
taskKey[File]("Generate a native image for this project.")
5368
lazy val nativeImageRun: InputKey[Unit] =
@@ -62,6 +77,9 @@ object NativeImagePlugin extends AutoPlugin {
6277
taskKey[Seq[String]](
6378
"Extra command-line arguments that should be forwarded to the native-image optimizer."
6479
)
80+
81+
private lazy val assistedConfigurationOfNativeImageBuildsLink =
82+
"https://www.graalvm.org/reference-manual/native-image/BuildConfiguration/#assisted-configuration-of-native-image-builds"
6583
}
6684
import autoImport._
6785

@@ -110,23 +128,30 @@ object NativeImagePlugin extends AutoPlugin {
110128
out
111129
}
112130
},
113-
nativeImageCommand := Def.taskDyn {
114-
if (
131+
nativeImageInstalled := Def.settingDyn {
132+
val installed =
115133
"true".equalsIgnoreCase(System.getProperty("native-image-installed")) ||
116-
"true".equalsIgnoreCase(System.getenv("NATIVE_IMAGE_INSTALLED"))
117-
) {
118-
val binary =
119-
if (Properties.isWin) "native-image.cmd" else "native-image"
120-
val path =
121-
Paths.get(System.getenv("JAVA_HOME")).resolve("bin").resolve(binary)
122-
Def.task(List[String](path.toString()))
134+
"true".equalsIgnoreCase(System.getenv("NATIVE_IMAGE_INSTALLED")) ||
135+
"true".equalsIgnoreCase(System.getProperty("graalvm-installed")) ||
136+
"true".equalsIgnoreCase(System.getenv("GRAALVM_INSTALLED"))
137+
Def.setting(installed)
138+
}.value,
139+
nativeImageGraalHome := Def.taskDyn {
140+
if (nativeImageInstalled.value) {
141+
val path = Paths.get {
142+
List("GRAAL_HOME", "GRAALVM_HOME", "JAVA_HOME").iterator
143+
.map(key => Option(System.getenv(key)))
144+
.collectFirst { case Some(value) => value }
145+
.getOrElse("")
146+
}
147+
Def.task(path)
123148
} else {
124149
Def.task {
125150
val coursier = nativeImageCoursier.value.absolutePath
126151
val svm = nativeImageVersion.value
127152
val jvm = nativeImageJvm.value
128153
val index = nativeImageJvmIndex.value
129-
val javaHome = Paths.get(
154+
Paths.get(
130155
Process(
131156
List(
132157
coursier,
@@ -138,8 +163,21 @@ object NativeImagePlugin extends AutoPlugin {
138163
)
139164
).!!.trim
140165
)
166+
}
167+
}
168+
}.value,
169+
nativeImageCommand := Def.taskDyn {
170+
val graalHome = nativeImageGraalHome.value
171+
if (nativeImageInstalled.value) {
172+
val binary =
173+
if (Properties.isWin) "native-image.cmd" else "native-image"
174+
val path =
175+
graalHome.resolve("bin").resolve(binary)
176+
Def.task(List[String](path.toString()))
177+
} else {
178+
Def.task {
141179
val cmd = if (Properties.isWin) ".cmd" else ""
142-
val ni = javaHome.resolve("bin").resolve(s"native-image$cmd")
180+
val ni = graalHome.resolve("bin").resolve(s"native-image$cmd")
143181
if (!Files.isExecutable(ni)) {
144182
val gu = ni.resolveSibling(s"gu$cmd")
145183
Process(List(gu.toString, "install", "native-image")).!
@@ -155,6 +193,25 @@ object NativeImagePlugin extends AutoPlugin {
155193
}
156194
}
157195
}.value,
196+
nativeImageAgentOutputDir := target.value / "native-image-configs",
197+
nativeImageAgentMerge := false,
198+
nativeImageRunAgent := {
199+
val graalHome = nativeImageGraalHome.value.toFile
200+
val agentConfig = if (nativeImageAgentMerge.value) "config-merge-dir" else "config-output-dir"
201+
val agentOption = s"-agentlib:native-image-agent=$agentConfig=${nativeImageAgentOutputDir.value}"
202+
val tpr = thisProjectRef.value
203+
val settings = Seq(
204+
fork in (tpr, Compile, run) := true,
205+
javaHome in (tpr, Compile, run) := Some(graalHome),
206+
javaOptions in (tpr, Compile, run) += agentOption
207+
)
208+
val state0 = state.value
209+
val extracted = Project.extract(state0)
210+
val newState = extracted.append(settings, state0)
211+
val arguments = spaceDelimited("<arg>").parsed
212+
val input = if (arguments.isEmpty) "" else arguments.mkString(" ")
213+
Project.extract(newState).runInputTask(run in (tpr, Compile), input, newState)
214+
},
158215
nativeImageOutput :=
159216
target.in(NativeImage).value / name.in(NativeImage).value,
160217
nativeImageCopy := {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
lazy val example = project
2+
.settings(
3+
scalaVersion := "2.12.12",
4+
mainClass.in(Compile) := Some("example.Hello3"),
5+
nativeImageOptions ++= Seq(
6+
"--no-fallback",
7+
s"-H:ReflectionConfigurationFiles=${ target.value / "native-image-configs" / "reflect-config.json" }"
8+
),
9+
nativeImageCommand := List(
10+
sys.env.getOrElse(
11+
"NATIVE_IMAGE_COMMAND",
12+
"missing environment variable 'NATIVE_IMAGE_COMMAND'. " +
13+
"To fix this problem, manually install GraalVM native-image and update the environment " +
14+
"variable to point to the absolute path of this binary."
15+
)
16+
)
17+
)
18+
.enablePlugins(NativeImagePlugin)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package example
2+
3+
import java.nio.file.Files
4+
import java.nio.file.Paths
5+
import java.nio.charset.StandardCharsets
6+
7+
object Hello3 {
8+
def main(args: Array[String]): Unit = {
9+
val cl = this.getClass.getClassLoader
10+
val c = cl.loadClass("example.Hello3")
11+
val h3 = c.getConstructor().newInstance()
12+
val text = h3.toString
13+
Files.write(
14+
Paths.get("hello3.obtained"),
15+
text.getBytes(StandardCharsets.UTF_8)
16+
)
17+
}
18+
}
19+
20+
class Hello3 {
21+
override def toString: String = "Hello 3"
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hello 3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
addSbtPlugin("org.scalameta" % "sbt-native-image" % sys.props("plugin.version"))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
> example/nativeImageRunAgent
2+
$ delete hello3.obtained
3+
> example/nativeImage
4+
$ absent hello3.obtained
5+
> example/nativeImageRun
6+
$ must-mirror hello3.expected hello3.obtained

readme.md

+75-3
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,17 @@ native-image generation and to automate your native-image workflows.
8787
- [`nativeImageCoursier`](#nativeimagecoursier): the path to a `coursier` binary
8888
- [`nativeImageOutput`](#nativeimageoutput): the path to the generated
8989
native-image binary
90+
- [`nativeImageInstalled`](#nativeimageinstalled):
91+
whether GraalVM is manually installed or should be downloaded with coursier
92+
- [`nativeImageGraalHome`](#nativeimagegraalhome):
93+
path to GraalVM home directory
94+
- [`nativeImageRunAgent`](#nativeimagerunagent):
95+
run application, tracking all usages of dynamic features of an execution with
96+
[`native-image-agent`][assisted-configuration-of-native-image-builds]
97+
- [`nativeImageAgentOutputDir`](#nativeimageagentoutputdir):
98+
directory where `native-image-agent` should put generated configurations
99+
- [`nativeImageAgentMerge`](#nativeimageagentmerge):
100+
whether `native-image-agent` should merge generated configurations
90101

91102
### `nativeImage`
92103

@@ -169,9 +180,7 @@ uses the [Cousier JVM index](https://github.com/coursier/jvm-index).
169180

170181
**Description**: the base command that is used to launch native-image.
171182

172-
**Default**: automatically installs GraalVM `native-image` via
173-
[Coursier](https://get-coursier.io/). Customize this setting if you prefer to
174-
manually install native-image.
183+
**Default**: resolves the command using `nativeImageGraalHome` task.
175184

176185
**Example usage**: `nativeImageCommand := List("/path/to/native-image")`
177186

@@ -206,6 +215,67 @@ project. for available options.
206215

207216
**Example usage**: `nativeImageOutput := file("target") / "my-binary"`
208217

218+
### `nativeImageInstalled`
219+
220+
**Type**: `SettingKey[Boolean]`
221+
222+
**Description**: whether GraalVM is manually installed or should be downloaded with coursier.
223+
224+
**Default**: checks if `NATIVE_IMAGE_INSTALLED` / `GRAALVM_INSTALLED` environment variables or
225+
`native-image-installed` / `graalvm-installed` properties are set to `true`.
226+
227+
### `nativeImageGraalHome`
228+
229+
**Type**: `TaskKey[Path]`
230+
231+
**Description**: path to GraalVM home directory.
232+
233+
**Default**: if `nativeImageInstalled` is `true`, then tries to read the path from
234+
environment variables 1) `GRAAL_HOME`, 2) `GRAALVM_HOME` or 3) `JAVA_HOME` (in given order).
235+
Otherwise, automatically installs GraalVM via [Coursier](https://get-coursier.io/).
236+
Customize this setting if you prefer to not to use environment variables.
237+
238+
**Example usage**: `nativeImageGraalHome := file("/path/to/graalvm/base/directory").toPath`
239+
240+
### `nativeImageRunAgent`
241+
242+
**Type**: `InputKey[Unit]`
243+
244+
**Description**: run application, tracking all usages of dynamic features of an execution with
245+
[`native-image-agent`][assisted-configuration-of-native-image-builds].
246+
247+
**Example usage**:
248+
```
249+
# Step 0: Start sbt shell.
250+
$ sbt
251+
# Step 1: Run application on the JVM with native-image agent.
252+
> myProject/nativeImageRunAgent arg1 arg2
253+
# Step 2: Create native-image binary with assisted configuration.
254+
> myProject/nativeImage
255+
# Step 3: Run native-image that was generated with assisted configuration.
256+
> myProject/nativeImageRun arg1 arg2
257+
```
258+
259+
### `nativeImageAgentOutputDir`
260+
261+
**Type**: `SettingKey[File]`
262+
263+
**Description**: directory where `native-image-agent` should put generated configurations.
264+
265+
**Default**: `baseDirectory.value / "native-image-configs"`
266+
267+
**Example usage**: `nativeImageAgentOutputDir := baseDirectory.value / "native-image-agent" / "out"`
268+
269+
### `nativeImageAgentMerge`
270+
271+
**Type**: `SettingKey[Boolean]`
272+
273+
**Description**: whether `native-image-agent` should merge generated configurations.
274+
275+
**Default**: `false`
276+
277+
**Example usage**: `nativeImageAgentMerge := true`
278+
209279
## Generate native-image from GitHub Actions
210280

211281
The easiest way to distribute native-image binaries for Linux and macOS is to
@@ -245,3 +315,5 @@ The key differences between sbt-native-packager and sbt-native-image are:
245315
- sbt-native-packager has Docker support, which is helpful if you need more
246316
fine-grained control over the linking environment. There are no plans to add
247317
Docker support in sbt-native-image.
318+
319+
[assisted-configuration-of-native-image-builds]: https://www.graalvm.org/reference-manual/native-image/BuildConfiguration/#assisted-configuration-of-native-image-builds

0 commit comments

Comments
 (0)