Skip to content

Commit 0149dd6

Browse files
committed
feat: experimental downstream native-image target
Signed-off-by: Sam Gammon <sam@elide.dev>
1 parent 86e8e2e commit 0149dd6

File tree

10 files changed

+335
-0
lines changed

10 files changed

+335
-0
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
public final class org/pkl/svm/PklFeature : org/graalvm/nativeimage/hosted/Feature {
2+
public fun <init> ()V
3+
public fun beforeAnalysis (Lorg/graalvm/nativeimage/hosted/Feature$BeforeAnalysisAccess;)V
4+
public fun getDescription ()Ljava/lang/String;
5+
public fun getURL ()Ljava/lang/String;
6+
}
7+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"initializeAtRunTime": [
3+
// needed for messagepack-java (see https://github.com/msgpack/msgpack-java/issues/600)
4+
"org.msgpack.core.buffer.DirectBufferAccess"
5+
],
6+
"initializeAtBuildTime": [
7+
"org.antlr.v4",
8+
"org.organicdesign",
9+
"org.pkl",
10+
"org.snakeyaml",
11+
"org.msgpack",
12+
"org.w3c.dom"
13+
]
14+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import java.nio.file.Path
17+
18+
plugins {
19+
application
20+
pklAllProjects
21+
pklKotlinLibrary
22+
pklPublishLibrary
23+
`maven-publish`
24+
alias(libs.plugins.kotlinxSerialization)
25+
}
26+
27+
val downstreamImageClasspath: Configuration by configurations.creating
28+
val downstreamImageModulepath: Configuration by configurations.creating
29+
30+
dependencies {
31+
api(libs.svm)
32+
implementation(libs.kotlinxSerializationJson)
33+
compileOnly(libs.truffleSvm)
34+
testImplementation(projects.pklTools)
35+
36+
downstreamImageClasspath(libs.kotlinStdLib)
37+
downstreamImageClasspath(libs.kotlinxSerializationJson)
38+
downstreamImageClasspath(projects.pklExecutor)
39+
downstreamImageClasspath(projects.pklConfigJava)
40+
downstreamImageModulepath(libs.svm)
41+
downstreamImageModulepath(libs.truffleSvm)
42+
}
43+
44+
val classinitConfig = layout.projectDirectory.file(Path.of("config", "classinit.jsonc").toString())
45+
46+
val nativeImageConfig = buildInfo.graalvmConfig(classinitConfig)
47+
48+
tasks.processResources.configure {
49+
from(classinitConfig) { into("META-INF/native-image/org.pkl-lang/pkl-nativeimage") }
50+
}
51+
52+
tasks.javadoc.configure { enabled = false }
53+
54+
val buildDownstreamNativeImage by
55+
tasks.registering(Exec::class) {
56+
executable = "native-image"
57+
dependsOn(tasks.testClasses, tasks.processResources, tasks.jar)
58+
59+
val outBin =
60+
layout.buildDirectory.file("executable-test").get().asFile.resolve("pkl-embedded").path
61+
62+
outputs.files(outBin)
63+
argumentProviders.add(
64+
CommandLineArgumentProvider {
65+
buildList {
66+
mapOf<String, Any>(
67+
// @TODO: this should be removed once pkl supports JPMS as a true Java Module.
68+
"polyglotimpl.DisableClassPathIsolation" to true
69+
)
70+
.forEach { add("-D${it.key}=${it.value}") }
71+
72+
add("-o")
73+
add(outBin)
74+
75+
add("--module-path")
76+
add(
77+
listOf(downstreamImageModulepath.asPath, tasks.jar.get().outputs.files.asPath)
78+
.joinToString(":")
79+
)
80+
81+
add("--class-path")
82+
add(
83+
listOf(
84+
downstreamImageClasspath.asPath,
85+
layout.buildDirectory.dir("classes/kotlin/test").get().asFile.path,
86+
layout.buildDirectory.dir("resources/main").get().asFile.path,
87+
layout.buildDirectory.dir("resources/test").get().asFile.path,
88+
)
89+
.joinToString(":")
90+
)
91+
92+
add("com.sample.test.ImageSampleMainKt")
93+
}
94+
}
95+
)
96+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
module org.pkl.nativeimage {
17+
requires kotlin.stdlib;
18+
requires kotlinx.serialization.core;
19+
requires kotlinx.serialization.json;
20+
requires org.graalvm.nativeimage;
21+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.pkl.svm
17+
18+
import java.nio.charset.StandardCharsets
19+
import kotlinx.serialization.Serializable
20+
import kotlinx.serialization.json.Json
21+
import org.graalvm.nativeimage.hosted.Feature
22+
import org.graalvm.nativeimage.hosted.RuntimeClassInitialization
23+
24+
/**
25+
* # Pkl for Native Image
26+
*
27+
* This [Feature] implements support for the Pkl language implementation within a Native Image
28+
* project. This class informs the compiler with necessary configurations and settings in order to
29+
* embed Pkl within the final binary.
30+
*
31+
* This feature's presence on the classpath is sufficient for its activation, since it is specified
32+
* in the `META-INF/native-image/.../native-image.properties` file.
33+
*/
34+
@Suppress("unused")
35+
class PklFeature : Feature {
36+
private companion object {
37+
private const val PKL_DEFAULT_CLASS_INIT_CONFIG =
38+
"/META-INF/native-image/org.pkl-lang/pkl-nativeimage/classinit.jsonc"
39+
}
40+
41+
override fun getURL(): String = "https://github.com/apple/pkl"
42+
43+
override fun getDescription(): String = "Enables Pkl support with native image"
44+
45+
@Serializable
46+
private data class ClassInitConfig(
47+
val initializeAtBuildTime: List<String>,
48+
val initializeAtRunTime: List<String>,
49+
)
50+
51+
private fun loadClassInitConfig(): ClassInitConfig {
52+
val configPath =
53+
System.getProperty("pkl.native.image.class-init-config", PKL_DEFAULT_CLASS_INIT_CONFIG)
54+
55+
val classInitConfig =
56+
PklFeature::class.java.getResourceAsStream(configPath)
57+
?: PklFeature::class.java.module.getResourceAsStream(configPath)
58+
return requireNotNull(classInitConfig) {
59+
"Class initialization configuration file not found: $configPath"
60+
}
61+
.reader(StandardCharsets.UTF_8)
62+
.use {
63+
Json.decodeFromString(
64+
it
65+
.readText()
66+
.lines()
67+
.filter { line ->
68+
!line.trim().startsWith("//") // filter out comments
69+
}
70+
.joinToString("\n")
71+
)
72+
}
73+
}
74+
75+
override fun beforeAnalysis(access: Feature.BeforeAnalysisAccess) {
76+
loadClassInitConfig().let {
77+
it.initializeAtBuildTime.forEach(RuntimeClassInitialization::initializeAtBuildTime)
78+
it.initializeAtRunTime.forEach(RuntimeClassInitialization::initializeAtRunTime)
79+
}
80+
}
81+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
Args = --features=org.pkl.svm.PklFeature
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.sample.test
17+
18+
import org.pkl.config.java.ConfigEvaluator
19+
import org.pkl.core.ModuleSource
20+
21+
fun main() {
22+
val evaluator = ConfigEvaluator.preconfigured()
23+
val config = evaluator.evaluate(ModuleSource.modulePath("config.pkl"))
24+
println(config.toString())
25+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//===----------------------------------------------------------------------===//
2+
// Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// https://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//===----------------------------------------------------------------------===//
16+
module example.Birds
17+
18+
/// The birds that we know about.
19+
birds: Mapping<String, Bird>
20+
21+
class Bird {
22+
/// The name of the bird
23+
name: String
24+
25+
/// The bird's features
26+
features: Features
27+
}
28+
29+
class Features {
30+
/// Can this bird mimick other sounds?
31+
voiceMimickry: Boolean
32+
33+
/// Can this bird fly?
34+
flies: Boolean
35+
36+
/// Can this bird swim?
37+
swims: Boolean
38+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
2+
//===----------------------------------------------------------------------===//
3+
// Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// https://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
//===----------------------------------------------------------------------===//
17+
amends "Birds.pkl"
18+
19+
birds {
20+
["Penguin"] {
21+
name = "Penguin"
22+
features {
23+
flies = false
24+
voiceMimickry = false
25+
swims = true
26+
}
27+
}
28+
["Parrot"] {
29+
name = "Parrot"
30+
features {
31+
flies = true
32+
voiceMimickry = true
33+
swims = false
34+
}
35+
}
36+
}

settings.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ include("pkl-tools")
4949

5050
include("pkl-server")
5151

52+
include("pkl-nativeimage")
53+
5254
pluginManagement {
5355
repositories {
5456
mavenCentral()

0 commit comments

Comments
 (0)