Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
975de92001 | ||
|
|
2d7a368b48 | ||
|
|
155fc5a867 | ||
|
|
5b53816463 | ||
|
|
095f064148 | ||
|
|
1c785a75af | ||
|
|
72b237e204 | ||
|
|
c43feddbac | ||
|
|
c39b1e7a59 | ||
|
|
3dccfe3d8b | ||
|
|
fc5d658aa3 | ||
|
|
85b4a4669a | ||
|
|
1f5c62ecef | ||
|
|
7e8e5f648d | ||
|
|
875fac857c | ||
|
|
7fa1ce7852 | ||
|
|
70947166a3 | ||
|
|
56e178934a | ||
|
|
ffd8a5d484 | ||
|
|
a9b7fe3a33 | ||
|
|
6ad50eb589 | ||
|
|
d54dda0d83 | ||
|
|
89cb6c98a1 | ||
|
|
5cb5d41a44 | ||
|
|
b1b36b09ac | ||
|
|
0b582b50b4 | ||
|
|
99c2d84c43 | ||
|
|
ec2d12a654 | ||
|
|
e0d727be60 | ||
|
|
ea68f7b258 | ||
|
|
434f699887 | ||
|
|
3e703995ca | ||
|
|
21d278eba5 | ||
|
|
939e844c03 | ||
|
|
0cb9843af7 | ||
|
|
05d8e88592 | ||
|
|
dd026c7023 | ||
|
|
dd83b9ab4f | ||
|
|
3f5797de6f | ||
|
|
ac0d9f1d31 | ||
|
|
95d7a4bcc9 | ||
|
|
23897fd8c3 | ||
|
|
f7adfceaeb | ||
|
|
a06684b2c0 | ||
|
|
ce977f1ffe | ||
|
|
08f0c3781b | ||
|
|
97c73e7c28 | ||
|
|
78c0ebce9d | ||
|
|
ef5e34c210 | ||
|
|
e18d962033 | ||
|
|
9f79fe47da | ||
|
|
b97abe13bc | ||
|
|
fe96e0b1b0 | ||
|
|
eff9a9e164 | ||
|
|
8399f11186 | ||
|
|
6811abf11c | ||
|
|
f96fba00e1 | ||
|
|
d062312df9 | ||
|
|
f4ad0da658 | ||
|
|
88a50d167e | ||
|
|
f973fd6717 | ||
|
|
66fb5c133b | ||
|
|
1a3d7a1a76 | ||
|
|
862c61f2e2 | ||
|
|
dbcecd9677 | ||
|
|
ba25f4820c | ||
|
|
adaafc4045 | ||
|
|
82fe176c69 | ||
|
|
6423b2add5 | ||
|
|
b9dd3c4de1 | ||
|
|
d4dac43e30 | ||
|
|
15c070621e | ||
|
|
74c5ded7db | ||
|
|
a5bcacf91d | ||
|
|
d839bff713 | ||
|
|
5747506f3a | ||
|
|
b8c7a42318 | ||
|
|
8331570aaf | ||
|
|
1271da2741 | ||
|
|
e83d8fa14b | ||
|
|
d1e043be4e | ||
|
|
43e132a383 | ||
|
|
119b1571bb | ||
|
|
d5a7dc021e | ||
|
|
7ed88e250b | ||
|
|
cbffd6c114 | ||
|
|
e0b3b87e19 | ||
|
|
cec08c651e | ||
|
|
397183a620 | ||
|
|
c71b107e32 | ||
|
|
d0de24b85b | ||
|
|
e3881f6149 | ||
|
|
9b52a4fd82 | ||
|
|
8fba228790 | ||
|
|
574ad844f3 | ||
|
|
90245589b6 | ||
|
|
c95b3d11f4 | ||
|
|
713adac824 | ||
|
|
f3efcbfc9b | ||
|
|
f284a0560a | ||
|
|
249e563291 | ||
|
|
7f5ae2a712 | ||
|
|
92b26aca17 | ||
|
|
0035199176 | ||
|
|
2c2f50113d | ||
|
|
9f28c1cb11 | ||
|
|
adee565a8c | ||
|
|
2b697015c7 | ||
|
|
1208fa9438 | ||
|
|
1cfdd3d30d | ||
|
|
cb01d6275b | ||
|
|
a90eb74c2d | ||
|
|
ae9160df51 | ||
|
|
79af6b1676 | ||
|
|
7404f7d160 | ||
|
|
6551a59f9e | ||
|
|
4dd37219c0 | ||
|
|
ac43bdbb17 | ||
|
|
762db144ce | ||
|
|
99c9f53063 | ||
|
|
c5896d76d0 | ||
|
|
b3698683ab | ||
|
|
417c08ae11 | ||
|
|
5333d55269 | ||
|
|
043f0e129b | ||
|
|
a55a3d7c33 | ||
|
|
2ca2aaf9ec | ||
|
|
8ad5759630 | ||
|
|
871041e655 | ||
|
|
50a09f59dc | ||
|
|
f6107b6e86 | ||
|
|
79dd95c4e6 | ||
|
|
be03023499 | ||
|
|
1a3dc52a96 | ||
|
|
67df676359 | ||
|
|
175e2b6273 | ||
|
|
2c9e3161d8 | ||
|
|
2242cc9c82 | ||
|
|
03792be984 | ||
|
|
3b5e9df0da | ||
|
|
50cfc0bcde | ||
|
|
3d03a47933 | ||
|
|
a1421ff667 | ||
|
|
773e964ea2 | ||
|
|
c44cbc546b | ||
|
|
71a0b6d113 | ||
|
|
4f2d2873ae | ||
|
|
42f107881f | ||
|
|
71948d69c4 | ||
|
|
60b0d86b5f | ||
|
|
b4ca82bf36 | ||
|
|
1cc47faac3 | ||
|
|
d6107c071b | ||
|
|
f21175df9a | ||
|
|
a59fd9d2ad | ||
|
|
ed1d600779 | ||
|
|
7a7f3abc50 | ||
|
|
25d86db416 | ||
|
|
997cfc9ae2 | ||
|
|
9916c23cfc |
No files matched your search
+1
-1
@@ -2,7 +2,7 @@ amends "pkl:Project"
|
||||
|
||||
dependencies {
|
||||
["pkl.impl.ghactions"] {
|
||||
uri = "package://pkg.pkl-lang.org/pkl-project-commons/pkl.impl.ghactions@1.8.1"
|
||||
uri = "package://pkg.pkl-lang.org/pkl-project-commons/pkl.impl.ghactions@1.8.2"
|
||||
}
|
||||
["gha"] {
|
||||
uri = "package://pkg.pkl-lang.org/pkl-pantry/com.github.actions@1.4.0"
|
||||
|
||||
@@ -3,30 +3,30 @@
|
||||
"resolvedDependencies": {
|
||||
"package://pkg.pkl-lang.org/pkl-pantry/com.github.actions@1": {
|
||||
"type": "remote",
|
||||
"uri": "projectpackage://pkg.pkl-lang.org/pkl-pantry/com.github.actions@1.7.0",
|
||||
"uri": "projectpackage://pkg.pkl-lang.org/pkl-pantry/com.github.actions@1.9.0",
|
||||
"checksums": {
|
||||
"sha256": "e3461c3f8d00a6a657f9fa4f391afdab4a53a2e528862a4d8624e545cd3cb2b1"
|
||||
"sha256": "5947924cf94fae9d8cdca42ade3f86db2675842cab7254214e7c0e281ddb6908"
|
||||
}
|
||||
},
|
||||
"package://pkg.pkl-lang.org/pkl-project-commons/pkl.impl.ghactions@1": {
|
||||
"type": "remote",
|
||||
"uri": "projectpackage://pkg.pkl-lang.org/pkl-project-commons/pkl.impl.ghactions@1.8.1",
|
||||
"uri": "projectpackage://pkg.pkl-lang.org/pkl-project-commons/pkl.impl.ghactions@1.8.2",
|
||||
"checksums": {
|
||||
"sha256": "476cd931de6868b3f5e088ac741261d7beb9f4de3305564675510fd74ffb83f3"
|
||||
"sha256": "12cca6326806ec37abc790a6707b6d907b4f8ba97628f2bc7fb327b5a6f68b49"
|
||||
}
|
||||
},
|
||||
"package://pkg.pkl-lang.org/pkl-pantry/pkl.experimental.deepToTyped@1": {
|
||||
"type": "remote",
|
||||
"uri": "projectpackage://pkg.pkl-lang.org/pkl-pantry/pkl.experimental.deepToTyped@1.2.0",
|
||||
"uri": "projectpackage://pkg.pkl-lang.org/pkl-pantry/pkl.experimental.deepToTyped@1.2.1",
|
||||
"checksums": {
|
||||
"sha256": "84c7feb391f4ac273a99dc89b8fd51dbcd21dbda4ce640f6908527f83acdd4d6"
|
||||
"sha256": "6e752d1e84aff24c3a1801658f27f12af70840c39b3fe7bac509c8178d566d25"
|
||||
}
|
||||
},
|
||||
"package://pkg.pkl-lang.org/pkl-pantry/pkl.github.dependabotManagedActions@1": {
|
||||
"type": "remote",
|
||||
"uri": "projectpackage://pkg.pkl-lang.org/pkl-pantry/pkl.github.dependabotManagedActions@1.1.6",
|
||||
"uri": "projectpackage://pkg.pkl-lang.org/pkl-pantry/pkl.github.dependabotManagedActions@1.1.9",
|
||||
"checksums": {
|
||||
"sha256": "85ad51d257bad4729f0b9edecade48fcad6f55bacd0fa36106782475e912919a"
|
||||
"sha256": "467b91559f31ad7401c78d3df9569b5ec7aa4e1b49209f9f22d02bd72971e0bd"
|
||||
}
|
||||
},
|
||||
"package://pkg.pkl-lang.org/pkl-pantry/com.github.dependabot@1": {
|
||||
|
||||
@@ -5,14 +5,21 @@ updates:
|
||||
default-days: 7
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
interval: quarterly
|
||||
- package-ecosystem: github-actions
|
||||
cooldown:
|
||||
default-days: 7
|
||||
directory: /
|
||||
groups:
|
||||
first-party:
|
||||
patterns:
|
||||
- actions/*
|
||||
codeql:
|
||||
patterns:
|
||||
- github/codeql-action/*
|
||||
ignore:
|
||||
- dependency-name: '*'
|
||||
update-types:
|
||||
- version-update:semver-major
|
||||
schedule:
|
||||
interval: weekly
|
||||
interval: quarterly
|
||||
+17
-10
@@ -61,29 +61,36 @@ local gradleCheckJobs: PklJobs = new {
|
||||
|
||||
local buildNativeJobs: Mapping<String, BuildNativeJob> = new {
|
||||
for (_dist in List("release", "snapshot")) {
|
||||
for (_project in List("pkl-cli", "pkl-doc")) {
|
||||
for (_project in List("pkl-cli", "pkl-doc", "libpkl")) {
|
||||
for (_arch in List("amd64", "aarch64")) {
|
||||
for (_os in List("macOS", "linux")) {
|
||||
["\(_project)-\(_os)-\(_arch)-\(_dist)"] {
|
||||
arch = _arch
|
||||
os = _os
|
||||
isRelease = _dist == "release"
|
||||
project = _project
|
||||
}
|
||||
["\(_project)-linux-\(_arch)-\(_dist)"] {
|
||||
arch = _arch
|
||||
os = "linux"
|
||||
isRelease = _dist == "release"
|
||||
project = _project
|
||||
buildDir = if (_project == "libpkl") "libpkl/build/native-libs/**/*" else "\(_project)*/build/executable/**/*"
|
||||
}
|
||||
}
|
||||
["\(_project)-macOS-aarch64-\(_dist)"] {
|
||||
arch = "aarch64"
|
||||
os = "macOS"
|
||||
isRelease = _dist == "release"
|
||||
project = _project
|
||||
}
|
||||
["\(_project)-alpine-linux-amd64-\(_dist)"] {
|
||||
arch = "amd64"
|
||||
os = "linux"
|
||||
musl = true
|
||||
isRelease = _dist == "release"
|
||||
project = _project
|
||||
buildDir = if (_project == "libpkl") "libpkl*/build/distributions/**/*" else "\(_project)*/build/executable/**/*"
|
||||
}
|
||||
["\(_project)-windows-amd64-\(_dist)"] {
|
||||
arch = "amd64"
|
||||
os = "windows"
|
||||
isRelease = _dist == "release"
|
||||
project = _project
|
||||
buildDir = if (_project == "libpkl") "libpkl*/build/distributions/**/*" else "\(_project)*/build/executable/**/*"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,7 +227,7 @@ release {
|
||||
needs = releaseJobs.keys.toListing()
|
||||
}
|
||||
["github-release"] = new GithubRelease {
|
||||
needs = "deploy-release"
|
||||
needs = releaseJobs.keys.toListing()
|
||||
}
|
||||
} |> toWorkflowJobs
|
||||
}
|
||||
@@ -230,7 +237,7 @@ dependabot {
|
||||
new {
|
||||
`package-ecosystem` = "gradle"
|
||||
schedule {
|
||||
interval = "weekly"
|
||||
interval = "quarterly"
|
||||
}
|
||||
cooldown {
|
||||
`default-days` = 7
|
||||
|
||||
@@ -16,7 +16,6 @@ local command =
|
||||
}.join(" ")
|
||||
|
||||
steps {
|
||||
catalog.`actions/checkout@v6`
|
||||
new {
|
||||
name = "gradle build java executables"
|
||||
shell = "bash"
|
||||
@@ -25,7 +24,7 @@ steps {
|
||||
(catalog.`actions/upload-artifact@v5`) {
|
||||
name = "Upload executable artifacts"
|
||||
with {
|
||||
name = "executable-java"
|
||||
name = "deploy-artifacts-java-executables"
|
||||
path = "*/build/executable/**/*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
extends "GradleJob.pkl"
|
||||
|
||||
import "@gha/catalog.pkl"
|
||||
import "@gha/context.pkl"
|
||||
|
||||
/// Whether to link to musl. Otherwise, links to glibc.
|
||||
musl: Boolean(implies(module.os == "linux")) = false
|
||||
@@ -9,11 +8,12 @@ musl: Boolean(implies(module.os == "linux")) = false
|
||||
/// The Gradle project under which to generate the executable
|
||||
project: String
|
||||
|
||||
/// Path pattern for artifact uploads (glob passed to actions/upload-artifact)
|
||||
buildDir: String = "\(project)*/build/executable/**/*"
|
||||
|
||||
local needsCCompiler = project == "libpkl"
|
||||
|
||||
extraGradleArgs {
|
||||
when (os == "macOS" && arch == "amd64") {
|
||||
"-Dpkl.targetArch=\(module.arch)"
|
||||
#""-Dpkl.native--native-compiler-path=${GITHUB_WORKSPACE}/.github/scripts/cc_macos_amd64.sh""#
|
||||
}
|
||||
when (musl) {
|
||||
"-Dpkl.musl=true"
|
||||
}
|
||||
@@ -44,22 +44,41 @@ steps {
|
||||
run = #"git status || git config --system --add safe.directory "$GITHUB_WORKSPACE""#
|
||||
}
|
||||
}
|
||||
when (needsCCompiler && os == "windows") {
|
||||
new {
|
||||
name = "Set up MSVC"
|
||||
uses = "ilammy/msvc-dev-cmd@v1"
|
||||
}
|
||||
}
|
||||
new {
|
||||
name = "gradle buildNative"
|
||||
shell = "bash"
|
||||
run = "./gradlew \(module.gradleArgs) \(project):buildNative"
|
||||
}
|
||||
(catalog.`actions/upload-artifact@v5`) {
|
||||
name = "Upload executable artifacts"
|
||||
name = "Upload built artifacts"
|
||||
with {
|
||||
name =
|
||||
if (musl)
|
||||
"executable-\(project)-alpine-\(module.os)-\(module.arch)"
|
||||
"deploy-artifacts-\(project)-alpine-\(module.os)-\(module.arch)"
|
||||
else
|
||||
"executable-\(project)-\(module.os)-\(module.arch)"
|
||||
"deploy-artifacts-\(project)-\(module.os)-\(module.arch)"
|
||||
// Need to insert a wildcard to make actions/upload-artifact preserve the folder hierarchy.
|
||||
// See https://github.com/actions/upload-artifact/issues/206
|
||||
path = "\(project)*/build/executable/**/*"
|
||||
path = module.buildDir
|
||||
}
|
||||
}
|
||||
(catalog.`actions/upload-artifact@v5`) {
|
||||
name = "Upload compiled classes list"
|
||||
with {
|
||||
name =
|
||||
if (musl)
|
||||
"compiled-classes-\(project)-alpine-\(module.os)-\(module.arch)"
|
||||
else
|
||||
"compiled-classes-\(project)-\(module.os)-\(module.arch)"
|
||||
// Need to insert a wildcard to make actions/upload-artifact preserve the folder hierarchy.
|
||||
// See https://github.com/actions/upload-artifact/issues/206
|
||||
path = "\(project)*/build/compiled-classes/**/*"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ steps {
|
||||
catalog.`actions/checkout@v6`
|
||||
(catalog.`actions/download-artifact@v6`) {
|
||||
with {
|
||||
pattern = "executable-**"
|
||||
pattern = "deploy-artifacts-**"
|
||||
`merge-multiple` = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,11 @@ fixed job {
|
||||
permissions {
|
||||
contents = "write"
|
||||
}
|
||||
needs = "deploy-release"
|
||||
needs = module.needs
|
||||
steps {
|
||||
(catalog.`actions/download-artifact@v6`) {
|
||||
with {
|
||||
pattern = "executable-**"
|
||||
pattern = "deploy-artifacts-**"
|
||||
`merge-multiple` = true
|
||||
}
|
||||
}
|
||||
@@ -29,10 +29,8 @@ fixed job {
|
||||
// language=bash
|
||||
run =
|
||||
#"""
|
||||
# exclude build_artifacts.txt from publish
|
||||
rm -f */build/executable/*.build_artifacts.txt
|
||||
find */build/executable/* -type d | xargs rm -rf
|
||||
gh release create ${TAG_NAME} \
|
||||
--draft \
|
||||
--title "${TAG_NAME}" \
|
||||
--target "${GIT_SHA}" \
|
||||
--verify-tag \
|
||||
|
||||
@@ -45,6 +45,9 @@ fixed job {
|
||||
when (os == "windows") {
|
||||
["JAVA_HOME"] = "/jdk"
|
||||
}
|
||||
when (os == "macOS") {
|
||||
["DEVELOPER_DIR"] = "/Applications/Xcode_swift_6.4.app"
|
||||
}
|
||||
}
|
||||
when (os == "macOS") {
|
||||
`if` =
|
||||
@@ -65,6 +68,7 @@ fixed job {
|
||||
new Listing {
|
||||
"self-hosted"
|
||||
"macos"
|
||||
"goldengate"
|
||||
when (nightlyMacOS) {
|
||||
"nightly"
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
clang -arch x86_64 "$@"
|
||||
@@ -2,7 +2,7 @@ set -e
|
||||
mkdir -p ~/staticdeps/
|
||||
|
||||
ZLIB_VERSION="1.2.13"
|
||||
MUSL_VERSION="1.2.5"
|
||||
MUSL_VERSION="9fa28ece75d8a2191de7c5bb53bed224c5947417" # v1.2.6
|
||||
|
||||
# install zlib
|
||||
if [[ ! -f ~/staticdeps/include/zlib.h ]]; then
|
||||
@@ -34,25 +34,29 @@ if [[ ! -f ~/staticdeps/include/zlib.h ]]; then
|
||||
rm -rf /tmp/dep_zlib-${ZLIB_VERSION}
|
||||
fi
|
||||
|
||||
declare -a MUSL_REPOS=(
|
||||
https://git.musl-libc.org/git/musl
|
||||
https://gogs.librecmc.org/OWEALS/musl.git
|
||||
)
|
||||
|
||||
# install musl
|
||||
if [[ ! -f ~/staticdeps/bin/x86_64-linux-musl-gcc ]]; then
|
||||
# Download musl tarball and signature
|
||||
curl -Lf "https://musl.libc.org/releases/musl-${MUSL_VERSION}.tar.gz" -o /tmp/musl.tar.gz
|
||||
curl -Lf "https://musl.libc.org/releases/musl-${MUSL_VERSION}.tar.gz.asc" -o /tmp/musl.tar.gz.asc
|
||||
|
||||
# Import musl GPG key
|
||||
gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 836489290BB6B70F99FFDA0556BCDB593020450F
|
||||
|
||||
# Verify GPG signature
|
||||
echo "Verifying musl GPG signature..."
|
||||
gpg --verify /tmp/musl.tar.gz.asc /tmp/musl.tar.gz
|
||||
|
||||
mkdir -p "/tmp/dep_musl-${MUSL_VERSION}"
|
||||
cd "/tmp/dep_musl-${MUSL_VERSION}"
|
||||
|
||||
# shellcheck disable=SC2002
|
||||
cat /tmp/musl.tar.gz | tar --strip-components=1 -xzC .
|
||||
|
||||
# Download musl git repo
|
||||
cd /tmp
|
||||
for repo in "${MUSL_REPOS[@]}"; do
|
||||
if git clone --depth 1 --revision "$MUSL_VERSION" "$repo" /tmp/musl; then
|
||||
break
|
||||
else
|
||||
rm -rf /tmp/musl
|
||||
fi
|
||||
done
|
||||
cd /tmp/musl
|
||||
|
||||
for patch in $GITHUB_WORKSPACE/.github/scripts/musl-*.patch; do
|
||||
echo "musl-${MUSL_VERSION}: applying $patch..."
|
||||
cat "$patch" | git apply
|
||||
done
|
||||
|
||||
echo "musl-${MUSL_VERSION}: configure..."
|
||||
./configure --disable-shared --prefix="$HOME"/staticdeps > /dev/null
|
||||
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
>From 7813e9119eb5fe681b6750b8a033a0490bca0941 Mon Sep 17 00:00:00 2001
|
||||
From: Szabolcs Nagy <nsz@port70.net>
|
||||
Date: Thu, 23 Apr 2026 21:22:18 +0000
|
||||
Subject: [PATCH] regex: avoid int overflow in regcomp
|
||||
|
||||
regcomp has many int arith ops that may overflow. before tre_expand_ast
|
||||
int values are limited by 2x the input length n, e.g.:
|
||||
|
||||
input ERE int field max value
|
||||
aaaaaaaaaaaa... position n
|
||||
()()()()()()... submatch_id n/2+1
|
||||
()||||||||||... num_tags 2n-3
|
||||
|
||||
so overflow before expand requires large n (and a lot of memory). later
|
||||
some values can become big due to repetitions:
|
||||
|
||||
input ERE int field that overflows
|
||||
(((a{255}){255}){255}){255} position
|
||||
((a|b*){255,}){255,} num_transitions
|
||||
|
||||
num_transitions and memory use can be big without {k,} repetitions too:
|
||||
|
||||
input ERE what max value
|
||||
(a|a|a|a...|a)* num_transitions n^2/4
|
||||
(()|a|a|...|a)* sum tnfa tags > n^3/16 - n^2
|
||||
|
||||
so memory can run out quickly as n grows (with 31bit va space 1700 long
|
||||
input is enough even without {k,}) about half is tnfa and half is ast
|
||||
firstpos/lastpos data. but overflow can happen before allocations fail.
|
||||
|
||||
fix early overflow by limiting n. the stack usage is in [n,11n] without
|
||||
repetitions, usually 7n, so the stack limit got adjusted too. the limit
|
||||
is such that almost all inputs that worked before still work.
|
||||
|
||||
overflows during and after expand are fixed by explicit checks via a
|
||||
check_add helper and xcalloc instead of xmalloc.
|
||||
---
|
||||
src/regex/regcomp.c | 41 +++++++++++++++++++++++++++++++++++------
|
||||
1 file changed, 35 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/src/regex/regcomp.c b/src/regex/regcomp.c
|
||||
index b4b81968..a7115b12 100644
|
||||
--- a/src/regex/regcomp.c
|
||||
+++ b/src/regex/regcomp.c
|
||||
@@ -1680,6 +1680,12 @@ tre_add_tags(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *tree,
|
||||
AST to TNFA compilation routines.
|
||||
*/
|
||||
|
||||
+/* check if a+b overflows for a,b in [0,INT_MAX] */
|
||||
+static int check_add(unsigned a, unsigned b)
|
||||
+{
|
||||
+ return a + b > INT_MAX;
|
||||
+}
|
||||
+
|
||||
typedef enum {
|
||||
COPY_RECURSE,
|
||||
COPY_SET_RESULT_PTR
|
||||
@@ -1731,6 +1737,8 @@ tre_copy_ast(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *ast,
|
||||
/* XXX - e.g. [ab] has only one position but two
|
||||
nodes, so we are creating holes in the state space
|
||||
here. Not fatal, just wastes memory. */
|
||||
+ if (check_add(pos, *pos_add))
|
||||
+ return REG_ESPACE;
|
||||
pos += *pos_add;
|
||||
num_copied++;
|
||||
}
|
||||
@@ -1826,6 +1834,8 @@ tre_copy_ast(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *ast,
|
||||
break;
|
||||
}
|
||||
}
|
||||
+ if (check_add(*pos_add, num_copied))
|
||||
+ return REG_ESPACE;
|
||||
*pos_add += num_copied;
|
||||
return status;
|
||||
}
|
||||
@@ -1870,6 +1880,8 @@ tre_expand_ast(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *ast,
|
||||
tre_literal_t *lit= node->obj;
|
||||
if (!IS_SPECIAL(lit) || IS_BACKREF(lit))
|
||||
{
|
||||
+ if (check_add(lit->position, pos_add))
|
||||
+ return REG_ESPACE;
|
||||
lit->position += pos_add;
|
||||
if (lit->position > max_pos)
|
||||
max_pos = lit->position;
|
||||
@@ -1998,6 +2010,8 @@ tre_expand_ast(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *ast,
|
||||
}
|
||||
|
||||
iter_depth--;
|
||||
+ if (check_add(pos_add_total, pos_add - pos_add_last))
|
||||
+ return REG_ESPACE;
|
||||
pos_add_total += pos_add - pos_add_last;
|
||||
if (iter_depth == 0)
|
||||
pos_add = pos_add_total;
|
||||
@@ -2010,6 +2024,8 @@ tre_expand_ast(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *ast,
|
||||
}
|
||||
}
|
||||
|
||||
+ if (check_add(*position, pos_add_total))
|
||||
+ return REG_ESPACE;
|
||||
*position += pos_add_total;
|
||||
|
||||
/* `max_pos' should never be larger than `*position' if the above
|
||||
@@ -2711,9 +2727,19 @@ regcomp(regex_t *restrict preg, const char *restrict regex, int cflags)
|
||||
/* Parse context. */
|
||||
tre_parse_ctx_t parse_ctx;
|
||||
|
||||
+ /* Limits: before tre_expand_ast, int values like position, subid or
|
||||
+ * num_tags are bounded by 2x input length, used stack entries are
|
||||
+ * bounded by 16x input length. input length is checked upfront to
|
||||
+ * avoid overflows before expand. later, values that can grow big are
|
||||
+ * bounds checked via check_add and xcalloc. */
|
||||
+ const int input_limit = 1024000;
|
||||
+ const int stack_limit = 8*input_limit;
|
||||
+ if (strnlen(regex, input_limit) >= input_limit)
|
||||
+ return REG_ESPACE;
|
||||
+
|
||||
/* Allocate a stack used throughout the compilation process for various
|
||||
purposes. */
|
||||
- stack = tre_stack_new(512, 1024000, 128);
|
||||
+ stack = tre_stack_new(512, stack_limit, 128);
|
||||
if (!stack)
|
||||
return REG_ESPACE;
|
||||
/* Allocate a fast memory allocator. */
|
||||
@@ -2801,9 +2827,12 @@ regcomp(regex_t *restrict preg, const char *restrict regex, int cflags)
|
||||
for example "a*" or "ab*". Figure out a simple way to detect
|
||||
this possibility. */
|
||||
tmp_ast_l = tree;
|
||||
- tmp_ast_r = tre_ast_new_literal(mem, 0, 0, parse_ctx.position++);
|
||||
+ tmp_ast_r = tre_ast_new_literal(mem, 0, 0, parse_ctx.position);
|
||||
if (tmp_ast_r == NULL)
|
||||
ERROR_EXIT(REG_ESPACE);
|
||||
+ if (check_add(parse_ctx.position, 1))
|
||||
+ ERROR_EXIT(REG_ESPACE);
|
||||
+ parse_ctx.position++;
|
||||
|
||||
tree = tre_ast_new_catenation(mem, tmp_ast_l, tmp_ast_r);
|
||||
if (tree == NULL)
|
||||
@@ -2813,22 +2842,22 @@ regcomp(regex_t *restrict preg, const char *restrict regex, int cflags)
|
||||
if (errcode != REG_OK)
|
||||
ERROR_EXIT(errcode);
|
||||
|
||||
- counts = xmalloc(sizeof(int) * parse_ctx.position);
|
||||
+ counts = xcalloc(sizeof(int), parse_ctx.position);
|
||||
if (counts == NULL)
|
||||
ERROR_EXIT(REG_ESPACE);
|
||||
|
||||
- offs = xmalloc(sizeof(int) * parse_ctx.position);
|
||||
+ offs = xcalloc(sizeof(int), parse_ctx.position);
|
||||
if (offs == NULL)
|
||||
ERROR_EXIT(REG_ESPACE);
|
||||
|
||||
- for (i = 0; i < parse_ctx.position; i++)
|
||||
- counts[i] = 0;
|
||||
tre_ast_to_tnfa(tree, NULL, counts, NULL);
|
||||
|
||||
add = 0;
|
||||
for (i = 0; i < parse_ctx.position; i++)
|
||||
{
|
||||
offs[i] = add;
|
||||
+ if (check_add(add, counts[i] + 1))
|
||||
+ ERROR_EXIT(REG_ESPACE);
|
||||
add += counts[i] + 1;
|
||||
counts[i] = 0;
|
||||
}
|
||||
--
|
||||
2.21.0
|
||||
@@ -0,0 +1,177 @@
|
||||
>From 228da39e38c1cae13cbe637e771412c1984dba5d Mon Sep 17 00:00:00 2001
|
||||
From: Rich Felker <dalias@aerifal.cx>
|
||||
Date: Thu, 9 Apr 2026 22:51:30 -0400
|
||||
Subject: [PATCH 1/3] qsort: fix leonardo heap corruption from bug in
|
||||
doubleword ctz primitive
|
||||
|
||||
the pntz function, implementing a "count trailing zeros" variant for a
|
||||
bit vector consisting of two size_t words, erroneously returned zero
|
||||
rather than the number of bits in the low word when the first bit set
|
||||
was the low bit of the high word.
|
||||
|
||||
as a result, a loop in the trinkle function which should have a
|
||||
guaranteed small bound on the number of iterations, could run
|
||||
unboundedly, thereby overflowing a stack-based working-space array
|
||||
which was sized for the bound.
|
||||
|
||||
CVE-2026-40200 has been assigned for this issue.
|
||||
---
|
||||
src/stdlib/qsort.c | 8 ++++----
|
||||
1 file changed, 4 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/src/stdlib/qsort.c b/src/stdlib/qsort.c
|
||||
index ab79dc6f..13219ab3 100644
|
||||
--- a/src/stdlib/qsort.c
|
||||
+++ b/src/stdlib/qsort.c
|
||||
@@ -34,11 +34,11 @@
|
||||
|
||||
typedef int (*cmpfun)(const void *, const void *, void *);
|
||||
|
||||
+/* returns index of first bit set, excluding the low bit assumed to always
|
||||
+ * be set, starting from low bit of p[0] up through high bit of p[1] */
|
||||
static inline int pntz(size_t p[2]) {
|
||||
- int r = ntz(p[0] - 1);
|
||||
- if(r != 0 || (r = 8*sizeof(size_t) + ntz(p[1])) != 8*sizeof(size_t)) {
|
||||
- return r;
|
||||
- }
|
||||
+ if (p[0] != 1) return ntz(p[0] - 1);
|
||||
+ if (p[1]) return 8*sizeof(size_t) + ntz(p[1]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
--
|
||||
2.21.0
|
||||
|
||||
|
||||
>From b3291b9a9f77f1f993d2b4f8c68a26cf09221ae7 Mon Sep 17 00:00:00 2001
|
||||
From: Rich Felker <dalias@aerifal.cx>
|
||||
Date: Thu, 9 Apr 2026 23:40:53 -0400
|
||||
Subject: [PATCH 2/3] qsort: hard-preclude oob array writes independent of any
|
||||
invariants
|
||||
|
||||
while the root cause of CVE-2026-40200 was a faulty ctz primitive, the
|
||||
fallout of the bug would have been limited to erroneous sorting or
|
||||
infinite loop if not for the stores to a stack-based array that
|
||||
depended on trusting invariants in order not to go out of bounds.
|
||||
|
||||
increase the size of the array to a power of two so that we can mask
|
||||
indices into it to force them into range. in the absence of any
|
||||
further bug, the masking is a no-op, but it does not have any
|
||||
measurable performance cost, and it makes spatial memory safety
|
||||
trivial to prove (and for readers not familiar with the algorithms to
|
||||
trust).
|
||||
---
|
||||
src/stdlib/qsort.c | 20 +++++++++++++-------
|
||||
1 file changed, 13 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git a/src/stdlib/qsort.c b/src/stdlib/qsort.c
|
||||
index 13219ab3..e4bce9f7 100644
|
||||
--- a/src/stdlib/qsort.c
|
||||
+++ b/src/stdlib/qsort.c
|
||||
@@ -89,10 +89,16 @@ static inline void shr(size_t p[2], int n)
|
||||
p[1] >>= n;
|
||||
}
|
||||
|
||||
+/* power-of-two length for working array so that we can mask indices and
|
||||
+ * not depend on any invariant of the algorithm for spatial memory safety.
|
||||
+ * the original size was just 14*sizeof(size_t)+1 */
|
||||
+#define AR_LEN (16 * sizeof(size_t))
|
||||
+#define AR_MASK (AR_LEN - 1)
|
||||
+
|
||||
static void sift(unsigned char *head, size_t width, cmpfun cmp, void *arg, int pshift, size_t lp[])
|
||||
{
|
||||
unsigned char *rt, *lf;
|
||||
- unsigned char *ar[14 * sizeof(size_t) + 1];
|
||||
+ unsigned char *ar[AR_LEN];
|
||||
int i = 1;
|
||||
|
||||
ar[0] = head;
|
||||
@@ -104,16 +110,16 @@ static void sift(unsigned char *head, size_t width, cmpfun cmp, void *arg, int p
|
||||
break;
|
||||
}
|
||||
if(cmp(lf, rt, arg) >= 0) {
|
||||
- ar[i++] = lf;
|
||||
+ ar[i++ & AR_MASK] = lf;
|
||||
head = lf;
|
||||
pshift -= 1;
|
||||
} else {
|
||||
- ar[i++] = rt;
|
||||
+ ar[i++ & AR_MASK] = rt;
|
||||
head = rt;
|
||||
pshift -= 2;
|
||||
}
|
||||
}
|
||||
- cycle(width, ar, i);
|
||||
+ cycle(width, ar, i & AR_MASK);
|
||||
}
|
||||
|
||||
static void trinkle(unsigned char *head, size_t width, cmpfun cmp, void *arg, size_t pp[2], int pshift, int trusty, size_t lp[])
|
||||
@@ -121,7 +127,7 @@ static void trinkle(unsigned char *head, size_t width, cmpfun cmp, void *arg, si
|
||||
unsigned char *stepson,
|
||||
*rt, *lf;
|
||||
size_t p[2];
|
||||
- unsigned char *ar[14 * sizeof(size_t) + 1];
|
||||
+ unsigned char *ar[AR_LEN];
|
||||
int i = 1;
|
||||
int trail;
|
||||
|
||||
@@ -142,7 +148,7 @@ static void trinkle(unsigned char *head, size_t width, cmpfun cmp, void *arg, si
|
||||
}
|
||||
}
|
||||
|
||||
- ar[i++] = stepson;
|
||||
+ ar[i++ & AR_MASK] = stepson;
|
||||
head = stepson;
|
||||
trail = pntz(p);
|
||||
shr(p, trail);
|
||||
@@ -150,7 +156,7 @@ static void trinkle(unsigned char *head, size_t width, cmpfun cmp, void *arg, si
|
||||
trusty = 0;
|
||||
}
|
||||
if(!trusty) {
|
||||
- cycle(width, ar, i);
|
||||
+ cycle(width, ar, i & AR_MASK);
|
||||
sift(head, width, cmp, arg, pshift, lp);
|
||||
}
|
||||
}
|
||||
--
|
||||
2.21.0
|
||||
|
||||
|
||||
>From 5122f9f3c99fee366167c5de98b31546312921ab Mon Sep 17 00:00:00 2001
|
||||
From: Luca Kellermann <mailto.luca.kellermann@gmail.com>
|
||||
Date: Fri, 10 Apr 2026 03:03:22 +0200
|
||||
Subject: [PATCH 3/3] qsort: fix shift UB in shl and shr
|
||||
|
||||
if shl() or shr() are called with n==8*sizeof(size_t), n is adjusted
|
||||
to 0. the shift by (sizeof(size_t) * 8 - n) that then follows will
|
||||
consequently shift by the width of size_t, which is UB and in practice
|
||||
produces an incorrect result.
|
||||
|
||||
return early in this case. the bitvector p was already shifted by the
|
||||
required amount.
|
||||
---
|
||||
src/stdlib/qsort.c | 2 ++
|
||||
1 file changed, 2 insertions(+)
|
||||
|
||||
diff --git a/src/stdlib/qsort.c b/src/stdlib/qsort.c
|
||||
index e4bce9f7..28607450 100644
|
||||
--- a/src/stdlib/qsort.c
|
||||
+++ b/src/stdlib/qsort.c
|
||||
@@ -71,6 +71,7 @@ static inline void shl(size_t p[2], int n)
|
||||
n -= 8 * sizeof(size_t);
|
||||
p[1] = p[0];
|
||||
p[0] = 0;
|
||||
+ if (!n) return;
|
||||
}
|
||||
p[1] <<= n;
|
||||
p[1] |= p[0] >> (sizeof(size_t) * 8 - n);
|
||||
@@ -83,6 +84,7 @@ static inline void shr(size_t p[2], int n)
|
||||
n -= 8 * sizeof(size_t);
|
||||
p[0] = p[1];
|
||||
p[1] = 0;
|
||||
+ if (!n) return;
|
||||
}
|
||||
p[0] >>= n;
|
||||
p[0] |= p[1] << (sizeof(size_t) * 8 - n);
|
||||
--
|
||||
2.21.0
|
||||
@@ -0,0 +1,319 @@
|
||||
>From 67219f0130ec7c876ac0b299046460fad31caabf Mon Sep 17 00:00:00 2001
|
||||
From: Rich Felker <dalias@aerifal.cx>
|
||||
Date: Mon, 30 Mar 2026 16:00:50 -0400
|
||||
Subject: [PATCH] fix pathological slowness & incorrect mappings in iconv
|
||||
gb18030 decoder
|
||||
|
||||
in order to implement the "UTF" aspect of gb18030 (ability to
|
||||
represent arbitrary unicode characters not present in the 2-byte
|
||||
mapping), we have to apply the index obtained from the encoded 4-byte
|
||||
sequence into the set of unmapped characters. this was done by
|
||||
scanning repeatedly over the table of mapped characters and counting
|
||||
off mapped characters below a running index by which to adjust the
|
||||
running index by on each iteration. this iterative process eventually
|
||||
leaves us with the value of the Nth unmapped character replacing the
|
||||
index, but depending on which particular character that is, the number
|
||||
of iterations needed to find it can be in the tens of thousands, and
|
||||
each iteration traverses the whole 126x190 table in the inner loop.
|
||||
this can lead to run times exceeding an entire second per character on
|
||||
moderate-speed machines.
|
||||
|
||||
on top of that, the transformation logic produced wrong results for
|
||||
BMP characters above the the surrogate range, as a result of not
|
||||
correctly accounting for it being excluded, and for characters outside
|
||||
the BMP, as a result of a misunderstanding of how gb18030 encodes
|
||||
them.
|
||||
|
||||
this patch replaces the unmapped character lookup with a single linear
|
||||
search of a list of unmapped ranges. there are only 206 such ranges,
|
||||
and these are permanently assigned and unchangeable as a consequence
|
||||
of the character encoding having to be stable, so a simple array of
|
||||
16-bit start/length values for each range consumes only 824 bytes, a
|
||||
very reasonable size cost here.
|
||||
|
||||
this new table accounts for the previously-incorrect surrogate
|
||||
handling, and non-BMP characters are handled correctly by a single
|
||||
offset, without the need for any unmapped-range search.
|
||||
|
||||
there are still a small number of mappings that are incorrect due to
|
||||
late changes made in the definition of gb18030, swapping PUA
|
||||
codepoints with proper Unicode characters. correcting these requires a
|
||||
postprocessing step that will be added later.
|
||||
---
|
||||
src/locale/gb18030utf.h | 206 ++++++++++++++++++++++++++++++++++++++++
|
||||
src/locale/iconv.c | 33 +++++--
|
||||
2 files changed, 230 insertions(+), 9 deletions(-)
|
||||
create mode 100644 src/locale/gb18030utf.h
|
||||
|
||||
diff --git a/src/locale/gb18030utf.h b/src/locale/gb18030utf.h
|
||||
new file mode 100644
|
||||
index 00000000..322a2440
|
||||
--- /dev/null
|
||||
+++ b/src/locale/gb18030utf.h
|
||||
@@ -0,0 +1,206 @@
|
||||
+{ 0x80, 36 },
|
||||
+{ 0xa5, 2 },
|
||||
+{ 0xa9, 7 },
|
||||
+{ 0xb2, 5 },
|
||||
+{ 0xb8, 31 },
|
||||
+{ 0xd8, 8 },
|
||||
+{ 0xe2, 6 },
|
||||
+{ 0xeb, 1 },
|
||||
+{ 0xee, 4 },
|
||||
+{ 0xf4, 3 },
|
||||
+{ 0xf8, 1 },
|
||||
+{ 0xfb, 1 },
|
||||
+{ 0xfd, 4 },
|
||||
+{ 0x102, 17 },
|
||||
+{ 0x114, 7 },
|
||||
+{ 0x11c, 15 },
|
||||
+{ 0x12c, 24 },
|
||||
+{ 0x145, 3 },
|
||||
+{ 0x149, 4 },
|
||||
+{ 0x14e, 29 },
|
||||
+{ 0x16c, 98 },
|
||||
+{ 0x1cf, 1 },
|
||||
+{ 0x1d1, 1 },
|
||||
+{ 0x1d3, 1 },
|
||||
+{ 0x1d5, 1 },
|
||||
+{ 0x1d7, 1 },
|
||||
+{ 0x1d9, 1 },
|
||||
+{ 0x1db, 1 },
|
||||
+{ 0x1dd, 28 },
|
||||
+{ 0x1fa, 87 },
|
||||
+{ 0x252, 15 },
|
||||
+{ 0x262, 101 },
|
||||
+{ 0x2c8, 1 },
|
||||
+{ 0x2cc, 13 },
|
||||
+{ 0x2da, 183 },
|
||||
+{ 0x3a2, 1 },
|
||||
+{ 0x3aa, 7 },
|
||||
+{ 0x3c2, 1 },
|
||||
+{ 0x3ca, 55 },
|
||||
+{ 0x402, 14 },
|
||||
+{ 0x450, 1 },
|
||||
+{ 0x452, 7102 },
|
||||
+{ 0x2011, 2 },
|
||||
+{ 0x2017, 1 },
|
||||
+{ 0x201a, 2 },
|
||||
+{ 0x201e, 7 },
|
||||
+{ 0x2027, 9 },
|
||||
+{ 0x2031, 1 },
|
||||
+{ 0x2034, 1 },
|
||||
+{ 0x2036, 5 },
|
||||
+{ 0x203c, 112 },
|
||||
+{ 0x20ad, 86 },
|
||||
+{ 0x2104, 1 },
|
||||
+{ 0x2106, 3 },
|
||||
+{ 0x210a, 12 },
|
||||
+{ 0x2117, 10 },
|
||||
+{ 0x2122, 62 },
|
||||
+{ 0x216c, 4 },
|
||||
+{ 0x217a, 22 },
|
||||
+{ 0x2194, 2 },
|
||||
+{ 0x219a, 110 },
|
||||
+{ 0x2209, 6 },
|
||||
+{ 0x2210, 1 },
|
||||
+{ 0x2212, 3 },
|
||||
+{ 0x2216, 4 },
|
||||
+{ 0x221b, 2 },
|
||||
+{ 0x2221, 2 },
|
||||
+{ 0x2224, 1 },
|
||||
+{ 0x2226, 1 },
|
||||
+{ 0x222c, 2 },
|
||||
+{ 0x222f, 5 },
|
||||
+{ 0x2238, 5 },
|
||||
+{ 0x223e, 10 },
|
||||
+{ 0x2249, 3 },
|
||||
+{ 0x224d, 5 },
|
||||
+{ 0x2253, 13 },
|
||||
+{ 0x2262, 2 },
|
||||
+{ 0x2268, 6 },
|
||||
+{ 0x2270, 37 },
|
||||
+{ 0x2296, 3 },
|
||||
+{ 0x229a, 11 },
|
||||
+{ 0x22a6, 25 },
|
||||
+{ 0x22c0, 82 },
|
||||
+{ 0x2313, 333 },
|
||||
+{ 0x246a, 10 },
|
||||
+{ 0x249c, 100 },
|
||||
+{ 0x254c, 4 },
|
||||
+{ 0x2574, 13 },
|
||||
+{ 0x2590, 3 },
|
||||
+{ 0x2596, 10 },
|
||||
+{ 0x25a2, 16 },
|
||||
+{ 0x25b4, 8 },
|
||||
+{ 0x25be, 8 },
|
||||
+{ 0x25c8, 3 },
|
||||
+{ 0x25cc, 2 },
|
||||
+{ 0x25d0, 18 },
|
||||
+{ 0x25e6, 31 },
|
||||
+{ 0x2607, 2 },
|
||||
+{ 0x260a, 54 },
|
||||
+{ 0x2641, 1 },
|
||||
+{ 0x2643, 2110 },
|
||||
+{ 0x2e82, 2 },
|
||||
+{ 0x2e85, 3 },
|
||||
+{ 0x2e89, 2 },
|
||||
+{ 0x2e8d, 10 },
|
||||
+{ 0x2e98, 15 },
|
||||
+{ 0x2ea8, 2 },
|
||||
+{ 0x2eab, 3 },
|
||||
+{ 0x2eaf, 4 },
|
||||
+{ 0x2eb4, 2 },
|
||||
+{ 0x2eb8, 3 },
|
||||
+{ 0x2ebc, 14 },
|
||||
+{ 0x2ecb, 293 },
|
||||
+{ 0x2ffc, 4 },
|
||||
+{ 0x3004, 1 },
|
||||
+{ 0x3018, 5 },
|
||||
+{ 0x301f, 2 },
|
||||
+{ 0x302a, 20 },
|
||||
+{ 0x303f, 2 },
|
||||
+{ 0x3094, 7 },
|
||||
+{ 0x309f, 2 },
|
||||
+{ 0x30f7, 5 },
|
||||
+{ 0x30ff, 6 },
|
||||
+{ 0x312a, 246 },
|
||||
+{ 0x322a, 7 },
|
||||
+{ 0x3232, 113 },
|
||||
+{ 0x32a4, 234 },
|
||||
+{ 0x3390, 12 },
|
||||
+{ 0x339f, 2 },
|
||||
+{ 0x33a2, 34 },
|
||||
+{ 0x33c5, 9 },
|
||||
+{ 0x33cf, 2 },
|
||||
+{ 0x33d3, 2 },
|
||||
+{ 0x33d6, 113 },
|
||||
+{ 0x3448, 43 },
|
||||
+{ 0x3474, 298 },
|
||||
+{ 0x359f, 111 },
|
||||
+{ 0x360f, 11 },
|
||||
+{ 0x361b, 765 },
|
||||
+{ 0x3919, 85 },
|
||||
+{ 0x396f, 96 },
|
||||
+{ 0x39d1, 14 },
|
||||
+{ 0x39e0, 147 },
|
||||
+{ 0x3a74, 218 },
|
||||
+{ 0x3b4f, 287 },
|
||||
+{ 0x3c6f, 113 },
|
||||
+{ 0x3ce1, 885 },
|
||||
+{ 0x4057, 264 },
|
||||
+{ 0x4160, 471 },
|
||||
+{ 0x4338, 116 },
|
||||
+{ 0x43ad, 4 },
|
||||
+{ 0x43b2, 43 },
|
||||
+{ 0x43de, 248 },
|
||||
+{ 0x44d7, 373 },
|
||||
+{ 0x464d, 20 },
|
||||
+{ 0x4662, 193 },
|
||||
+{ 0x4724, 5 },
|
||||
+{ 0x472a, 82 },
|
||||
+{ 0x477d, 16 },
|
||||
+{ 0x478e, 441 },
|
||||
+{ 0x4948, 50 },
|
||||
+{ 0x497b, 2 },
|
||||
+{ 0x497e, 4 },
|
||||
+{ 0x4984, 1 },
|
||||
+{ 0x4987, 20 },
|
||||
+{ 0x499c, 3 },
|
||||
+{ 0x49a0, 22 },
|
||||
+{ 0x49b8, 703 },
|
||||
+{ 0x4c78, 39 },
|
||||
+{ 0x4ca4, 111 },
|
||||
+{ 0x4d1a, 148 },
|
||||
+{ 0x4daf, 81 },
|
||||
+{ 0x9fa6, 14426 },
|
||||
+{ 0xe76c, 1 },
|
||||
+{ 0xe7c8, 1 },
|
||||
+{ 0xe7e7, 13 },
|
||||
+{ 0xe815, 1 },
|
||||
+{ 0xe819, 5 },
|
||||
+{ 0xe81f, 7 },
|
||||
+{ 0xe827, 4 },
|
||||
+{ 0xe82d, 4 },
|
||||
+{ 0xe833, 8 },
|
||||
+{ 0xe83c, 7 },
|
||||
+{ 0xe844, 16 },
|
||||
+{ 0xe856, 14 },
|
||||
+{ 0xe865, 4295 },
|
||||
+{ 0xf92d, 76 },
|
||||
+{ 0xf97a, 27 },
|
||||
+{ 0xf996, 81 },
|
||||
+{ 0xf9e8, 9 },
|
||||
+{ 0xf9f2, 26 },
|
||||
+{ 0xfa10, 1 },
|
||||
+{ 0xfa12, 1 },
|
||||
+{ 0xfa15, 3 },
|
||||
+{ 0xfa19, 6 },
|
||||
+{ 0xfa22, 1 },
|
||||
+{ 0xfa25, 2 },
|
||||
+{ 0xfa2a, 1030 },
|
||||
+{ 0xfe32, 1 },
|
||||
+{ 0xfe45, 4 },
|
||||
+{ 0xfe53, 1 },
|
||||
+{ 0xfe58, 1 },
|
||||
+{ 0xfe67, 1 },
|
||||
+{ 0xfe6c, 149 },
|
||||
+{ 0xff5f, 129 },
|
||||
+{ 0xffe6, 26 },
|
||||
diff --git a/src/locale/iconv.c b/src/locale/iconv.c
|
||||
index 52178950..4151411d 100644
|
||||
--- a/src/locale/iconv.c
|
||||
+++ b/src/locale/iconv.c
|
||||
@@ -74,6 +74,10 @@ static const unsigned short gb18030[126][190] = {
|
||||
#include "gb18030.h"
|
||||
};
|
||||
|
||||
+static const unsigned short gb18030utf[][2] = {
|
||||
+#include "gb18030utf.h"
|
||||
+};
|
||||
+
|
||||
static const unsigned short big5[89][157] = {
|
||||
#include "big5.h"
|
||||
};
|
||||
@@ -224,6 +228,8 @@ static unsigned uni_to_jis(unsigned c)
|
||||
}
|
||||
}
|
||||
|
||||
+#define countof(a) (sizeof (a) / sizeof *(a))
|
||||
+
|
||||
size_t iconv(iconv_t cd, char **restrict in, size_t *restrict inb, char **restrict out, size_t *restrict outb)
|
||||
{
|
||||
size_t x=0;
|
||||
@@ -430,15 +436,24 @@ size_t iconv(iconv_t cd, char **restrict in, size_t *restrict inb, char **restri
|
||||
d = *((unsigned char *)*in + 3);
|
||||
if (d-'0'>9) goto ilseq;
|
||||
c += d-'0';
|
||||
- c += 128;
|
||||
- for (d=0; d<=c; ) {
|
||||
- k = 0;
|
||||
- for (int i=0; i<126; i++)
|
||||
- for (int j=0; j<190; j++)
|
||||
- if (gb18030[i][j]-d <= c-d)
|
||||
- k++;
|
||||
- d = c+1;
|
||||
- c += k;
|
||||
+ /* Starting at 90 30 81 30 (189000), mapping is
|
||||
+ * linear without gaps, to U+10000 and up. */
|
||||
+ if (c >= 189000) {
|
||||
+ c -= 189000;
|
||||
+ c += 0x10000;
|
||||
+ if (c >= 0x110000) goto ilseq;
|
||||
+ break;
|
||||
+ }
|
||||
+ /* Otherwise we must process an index into set
|
||||
+ * of characters unmapped by 2-byte table. */
|
||||
+ for (int i=0; ; i++) {
|
||||
+ if (i==countof(gb18030utf))
|
||||
+ goto ilseq;
|
||||
+ if (c<gb18030utf[i][1]) {
|
||||
+ c += gb18030utf[i][0];
|
||||
+ break;
|
||||
+ }
|
||||
+ c -= gb18030utf[i][1];
|
||||
}
|
||||
break;
|
||||
}
|
||||
--
|
||||
2.21.0
|
||||
Generated
+7
-5
@@ -19,22 +19,24 @@ jobs:
|
||||
- name: EnricoMi/publish-unit-test-result-action@v2
|
||||
uses: EnricoMi/publish-unit-test-result-action@d0a4676d0e0b938bc201470d88276b7c74c712b3 # v2
|
||||
- name: actions/checkout@v6
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
- name: actions/create-github-app-token@v2
|
||||
uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2
|
||||
- name: actions/download-artifact@v6
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
||||
- name: actions/setup-java@v5
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5
|
||||
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
|
||||
- name: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
||||
- name: dawidd6/action-download-artifact@v11
|
||||
uses: dawidd6/action-download-artifact@ac66b43f0e6a346234dd65d4d0c8fbb31cb316e5 # v11
|
||||
- name: github/codeql-action/analyze@v4
|
||||
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
|
||||
uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4
|
||||
- name: github/codeql-action/init@v4
|
||||
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
|
||||
uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4
|
||||
- name: gradle/actions/dependency-submission@v6
|
||||
uses: gradle/actions/dependency-submission@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6
|
||||
uses: gradle/actions/dependency-submission@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6
|
||||
- name: gradle/actions/setup-gradle@v5
|
||||
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5
|
||||
- name: ilammy/msvc-dev-cmd@v1
|
||||
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1
|
||||
Generated
+584
-274
File diff suppressed because it is too large.
Load diff
Generated
+9
-9
@@ -15,15 +15,15 @@ jobs:
|
||||
security-events: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
|
||||
- uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4
|
||||
with:
|
||||
languages: actions
|
||||
build-mode: none
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
|
||||
uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4
|
||||
with:
|
||||
category: /language:actions
|
||||
analyze-java-kotlin:
|
||||
@@ -32,15 +32,15 @@ jobs:
|
||||
security-events: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
|
||||
- uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4
|
||||
with:
|
||||
languages: java-kotlin
|
||||
build-mode: autobuild
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
|
||||
uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4
|
||||
with:
|
||||
category: /language:java-kotlin
|
||||
analyze-javascript-typescript:
|
||||
@@ -49,14 +49,14 @@ jobs:
|
||||
security-events: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
|
||||
- uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4
|
||||
with:
|
||||
languages: javascript-typescript
|
||||
build-mode: none
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
|
||||
uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4
|
||||
with:
|
||||
category: /language:javascript-typescript
|
||||
Generated
+621
-305
File diff suppressed because it is too large.
Load diff
Generated
+589
-269
File diff suppressed because it is too large.
Load diff
Generated
+591
-278
File diff suppressed because it is too large.
Load diff
Generated
+648
-311
File diff suppressed because it is too large.
Load diff
+5
-1
@@ -12,6 +12,7 @@
|
||||
<option name="REPORT_FIELDS" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ClassCanBeRecord" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="ClassEscapesItsScope" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CustomRegExpInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="myConfigurations">
|
||||
<list>
|
||||
@@ -96,6 +97,9 @@
|
||||
<option name="REPORT_NULLS_PASSED_TO_NON_ANNOTATED_METHOD" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SameParameterValue" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="ShellCheck" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<shellcheck_settings value="SC2129" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||
<option name="processCode" value="true" />
|
||||
<option name="processLiterals" value="true" />
|
||||
@@ -104,4 +108,4 @@
|
||||
<inspection_tool class="StringToUpperWithoutLocale" enabled="true" level="WARNING" enabled_by_default="true" editorAttributes="WARNING_ATTRIBUTES" />
|
||||
<inspection_tool class="dd497f47-d38f-3fab-9ed7-eabe699620c8" enabled="true" level="ERROR" enabled_by_default="true" editorAttributes="ERRORS_ATTRIBUTES" />
|
||||
</profile>
|
||||
</component>
|
||||
</component>
|
||||
@@ -0,0 +1,24 @@
|
||||
# This file is only used to configure CLion with proper include paths.
|
||||
# Pkl builds everything through Gradle, not CMake.
|
||||
cmake_minimum_required(VERSION 4.2)
|
||||
project(pkl C)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
||||
file(GLOB NATIVE_LIB_INCLUDE_DIRS CONFIGURE_DEPENDS
|
||||
libpkl/build/native-libs/*/include)
|
||||
file(GLOB NATIVE_IMAGE_BUILD_DIRS CONFIGURE_DEPENDS
|
||||
libpkl/build/tmp/native-image-build/*)
|
||||
|
||||
include_directories(${NATIVE_LIB_INCLUDE_DIRS})
|
||||
include_directories(${NATIVE_IMAGE_BUILD_DIRS})
|
||||
include_directories(libpkl/src/main/c/include)
|
||||
|
||||
file(GLOB_RECURSE GENERATED_HEADER_FILES CONFIGURE_DEPENDS
|
||||
libpkl/build/tmp/native-libs/*/*.h)
|
||||
|
||||
add_executable(pkl
|
||||
${GENERATED_HEADER_FILES}
|
||||
libpkl/src/main/c/include/pkl.h
|
||||
libpkl/src/main/c/pkl.c
|
||||
libpkl/src/nativeTest/c/test_pkl.c)
|
||||
@@ -1,77 +0,0 @@
|
||||
## Code of Conduct
|
||||
|
||||
### Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our
|
||||
project and our community a harassment-free experience for everyone,
|
||||
regardless of age, body size, disability, ethnicity, sex
|
||||
characteristics, gender identity and expression, level of experience,
|
||||
education, socio-economic status, nationality, personal appearance,
|
||||
race, religion, or sexual identity and orientation.
|
||||
|
||||
### Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual
|
||||
attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political
|
||||
attacks
|
||||
* Public or private harassment
|
||||
* Publishing others’ private information, such as a physical or
|
||||
electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
### Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of
|
||||
acceptable behavior and are expected to take appropriate and fair
|
||||
corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit,
|
||||
or reject comments, commits, code, wiki edits, issues, and other
|
||||
contributions that are not aligned to this Code of Conduct, or to ban
|
||||
temporarily or permanently any contributor for other behaviors that they
|
||||
deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
### Scope
|
||||
|
||||
This Code of Conduct applies within all project spaces, and it also
|
||||
applies when an individual is representing the project or its community
|
||||
in public spaces. Examples of representing a project or community
|
||||
include using an official project e-mail address, posting via an
|
||||
official social media account, or acting as an appointed representative
|
||||
at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
### Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may
|
||||
be reported by contacting the open source team at
|
||||
opensource-conduct@group.apple.com. All complaints will be reviewed and
|
||||
investigated and will result in a response that is deemed necessary and
|
||||
appropriate to the circumstances. The project team is obligated to
|
||||
maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted
|
||||
separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in
|
||||
good faith may face temporary or permanent repercussions as determined
|
||||
by other members of the project’s leadership.
|
||||
|
||||
### Attribution
|
||||
|
||||
This Code of Conduct is adapted from the
|
||||
[Contributor Covenant](https://www.contributor-covenant.org), version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
+8
-1
@@ -53,7 +53,6 @@ gw pkl-cli:buildNative # build native executable for current platform
|
||||
|
||||
pkl-cli/build/executable/jpkl # run Java executable
|
||||
pkl-cli/build/executable/pkl-macos-aarch64 # run Mac executable
|
||||
pkl-cli/build/executable/pkl-macos-amd64 # run Intel Mac executable
|
||||
pkl-cli/build/executable/pkl-linux-amd64 # run Linux executable
|
||||
pkl-cli/build/executable/pkl-alpine-linux-amd64 # run Alpine Linux executable
|
||||
pkl-cli/build/executable/pkl-windows-amd64.exe # run Windows executable
|
||||
@@ -94,6 +93,14 @@ There is an IntelliJ plugin meant for development on the Pkl project itself loca
|
||||
|
||||
See https://github.com/apple/pkl-project-commons?tab=readme-ov-file#internal-intellij-plugin[its readme] for instructions on how to set it up.
|
||||
|
||||
== Working on libpkl
|
||||
|
||||
libpkl is a native library written in C, and is best written in CLion, or VSCode.
|
||||
|
||||
IntelliJ has limited support for editing C sources.
|
||||
|
||||
There is a CMakeLists.txt file whose only job is to configure CLion with proper include paths.
|
||||
|
||||
== Resources
|
||||
|
||||
For automated build setup examples see our https://github.com/apple/pkl/blob/main/.github/[GitHub Actions] jobs like our https://github.com/apple/pkl/blob/main/.github/jobs/BuildNativeJob.pkl[BuildNativeJob.pkl], where we build Pkl automatically.
|
||||
|
||||
@@ -363,3 +363,101 @@ Copyright © 2008-2010 Andrey Somov
|
||||
Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License.You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
25) JSpecify (https://jspecify.dev)
|
||||
|
||||
POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Copyright 2022 The JSpecify Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
26) mordant
|
||||
POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Copyright 2018 AJ Alt
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
27) colormath
|
||||
License URL: https://github.com/ajalt/colormath/blob/master/LICENSE
|
||||
|
||||
Copyright 2021 AJ Alt
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
28) protobuf-java
|
||||
License URL: https://github.com/protocolbuffers/protobuf/blob/main/LICENSE
|
||||
|
||||
Copyright 2008 Google Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Code generated by the Protocol Buffer compiler is owned by the owner
|
||||
of the input file used when generating it. This code is not
|
||||
standalone and requires a support library to be linked with it. This
|
||||
support library is itself covered by the above license.
|
||||
|
||||
29) pprof
|
||||
License URL: https://github.com/google/pprof/blob/main/LICENSE
|
||||
|
||||
Copyright 2016 Google Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+16
-6
@@ -19,8 +19,8 @@ plugins {
|
||||
id("me.champeau.jmh")
|
||||
}
|
||||
|
||||
val truffle: Configuration by configurations.creating
|
||||
val graal: Configuration by configurations.creating
|
||||
val truffle: Configuration = configurations.create("truffle")
|
||||
val graal: Configuration = configurations.create("graal")
|
||||
|
||||
dependencies {
|
||||
jmh(projects.pklCore)
|
||||
@@ -31,13 +31,17 @@ dependencies {
|
||||
}
|
||||
|
||||
jmh {
|
||||
// include = ["fib_class_java"]
|
||||
// include = ["fib_class_constrained1", "fib_class_constrained2"]
|
||||
// includes = listOf("ParserBenchmark")
|
||||
jmhVersion.set(libs.versions.jmh)
|
||||
// jvmArgsAppend = "-Dgraal.TruffleCompilationExceptionsAreFatal=true " +
|
||||
// "-Dgraal.Dump=Truffle,TruffleTree -Dgraal.TraceTruffleCompilation=true " +
|
||||
// "-Dgraal.TruffleFunctionInlining=false"
|
||||
jvm.set("${buildInfo.graalVmAmd64.baseDir}/bin/java")
|
||||
val graalVm =
|
||||
when (buildInfo.arch) {
|
||||
Target.Arch.AARCH64 -> buildInfo.graalVmAarch64
|
||||
Target.Arch.AMD64 -> buildInfo.graalVmAmd64
|
||||
}
|
||||
jvm.set("${graalVm.baseDir}/bin/java")
|
||||
// see:
|
||||
// https://docs.oracle.com/en/graalvm/enterprise/20/docs/graalvm-as-a-platform/implement-language/#disable-class-path-separation
|
||||
jvmArgs.set(
|
||||
@@ -51,7 +55,13 @@ jmh {
|
||||
// synchronizeIterations = false
|
||||
}
|
||||
|
||||
tasks.named("jmh") { dependsOn(":installGraalVmAmd64") }
|
||||
tasks.named("jmh") {
|
||||
if (buildInfo.arch == Target.Arch.AARCH64) {
|
||||
dependsOn(":installGraalVmAarch64")
|
||||
} else if (buildInfo.arch == Target.Arch.AMD64) {
|
||||
dependsOn(":installGraalVmAmd64")
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent this error which occurs when building in IntelliJ:
|
||||
// "Entry org/pkl/core/fib_class_typed.pkl is a duplicate but no duplicate handling strategy has
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
*/
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
import Target.Arch
|
||||
import Target.OS
|
||||
import java.io.File
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.artifacts.VersionCatalog
|
||||
import org.gradle.api.artifacts.VersionCatalogsExtension
|
||||
@@ -24,6 +27,7 @@ import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.tasks.TaskProvider
|
||||
import org.gradle.api.tasks.testing.Test
|
||||
import org.gradle.internal.extensions.stdlib.capitalized
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
import org.gradle.jvm.toolchain.*
|
||||
import org.gradle.kotlin.dsl.*
|
||||
import org.gradle.kotlin.dsl.support.serviceOf
|
||||
@@ -70,31 +74,28 @@ const val PKL_TEST_ALL_JDKS = false
|
||||
// `buildInfo` in main build scripts
|
||||
// `project.extensions.getByType<BuildInfo>()` in precompiled script plugins
|
||||
open class BuildInfo(private val project: Project) {
|
||||
inner class GraalVm(val arch: String) {
|
||||
inner class GraalVm(
|
||||
val osName: String,
|
||||
val arch: String,
|
||||
val version: String,
|
||||
val graalJdkVersion: String,
|
||||
) {
|
||||
val homeDir: String by lazy {
|
||||
System.getenv("GRAALVM_HOME") ?: "${System.getProperty("user.home")}/.graalvm"
|
||||
}
|
||||
|
||||
val version: String by lazy { libs.findVersion("graalVm").get().toString() }
|
||||
|
||||
val graalVmJdkVersion: String by lazy { libs.findVersion("graalVmJdkVersion").get().toString() }
|
||||
|
||||
val osName: String by lazy {
|
||||
when {
|
||||
os.isMacOsX -> "macos"
|
||||
os.isLinux -> "linux"
|
||||
os.isWindows -> "windows"
|
||||
else -> throw RuntimeException("${os.familyName} is not supported.")
|
||||
}
|
||||
}
|
||||
val graalVmInnovation: String by lazy { libs.findVersion("graalVmInnovation").get().toString() }
|
||||
|
||||
val baseName: String by lazy {
|
||||
"graalvm-community-jdk-${graalVmJdkVersion}_${osName}-${arch}_bin"
|
||||
}
|
||||
|
||||
val graalMajorVersion: String = graalJdkVersion.split(".").first()
|
||||
|
||||
val downloadUrl: String by lazy {
|
||||
val extension = if (os.isWindows) "zip" else "tar.gz"
|
||||
"https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-${graalVmJdkVersion}/$baseName.$extension"
|
||||
val ext = if (os.isWindows) "zip" else "tar.gz"
|
||||
val platformArch = if (arch == "amd64") "x64" else arch
|
||||
"https://github.com/graalvm/graalvm-ce-builds/releases/download/graal-$version/graalvm-community-jdk-${graalMajorVersion}i${graalVmInnovation}-${graalJdkVersion}_${osName}-${platformArch}_bin.$ext"
|
||||
}
|
||||
|
||||
val downloadFile: File by lazy {
|
||||
@@ -105,35 +106,50 @@ open class BuildInfo(private val project: Project) {
|
||||
val installDir: File by lazy { File(homeDir, baseName) }
|
||||
|
||||
val baseDir: String by lazy {
|
||||
if (os.isMacOsX) "$installDir/Contents/Home" else installDir.toString()
|
||||
if (os.isMacOS) "$installDir/Contents/Home" else installDir.toString()
|
||||
}
|
||||
}
|
||||
|
||||
/** The target machine to build, defaulting to the host system machine. */
|
||||
val targetMachine: Target by lazy { Target.from(os = os, arch = targetArch, musl = musl) }
|
||||
|
||||
/** The target architecture to build, defaulting to the system architecture. */
|
||||
val targetArch by lazy { System.getProperty("pkl.targetArch") ?: arch }
|
||||
val targetArch: Arch by lazy { System.getProperty("pkl.targetArch")?.let(Arch::fromName) ?: arch }
|
||||
|
||||
/** Tells if this is a cross-arch build (e.g. targeting amd64 when on an aarch64 machine). */
|
||||
val isCrossArch by lazy { arch != targetArch }
|
||||
|
||||
/** Tells if cross-arch builds are supported on this machine. */
|
||||
val isCrossArchSupported by lazy { os.isMacOsX }
|
||||
val isCrossArchSupported by lazy { os.isMacOS }
|
||||
|
||||
/** Whether to build native executables using the musl toolchain or not. */
|
||||
val musl: Boolean by lazy { java.lang.Boolean.getBoolean("pkl.musl") }
|
||||
|
||||
/** Same logic as [org.gradle.internal.os.OperatingSystem#arch], which is protected. */
|
||||
val arch: String by lazy {
|
||||
val arch: Arch by lazy {
|
||||
when (val arch = System.getProperty("os.arch")) {
|
||||
"x86" -> "i386"
|
||||
"x86_64" -> "amd64"
|
||||
"powerpc" -> "ppc"
|
||||
else -> arch
|
||||
"x86_64",
|
||||
"amd64" -> Arch.AMD64
|
||||
"aarch64" -> Arch.AARCH64
|
||||
else -> throw GradleException("Cannot build Pkl on arch: $arch")
|
||||
}
|
||||
}
|
||||
|
||||
val graalVmAarch64: GraalVm = GraalVm("aarch64")
|
||||
val graalVmAarch64: GraalVm by lazy { createGraalVm("aarch64") }
|
||||
|
||||
val graalVmAmd64: GraalVm = GraalVm("x64")
|
||||
val graalVmAmd64: GraalVm by lazy { createGraalVm("x64") }
|
||||
|
||||
private fun createGraalVm(arch: String): GraalVm {
|
||||
val osName =
|
||||
when {
|
||||
os.isMacOS -> "macos"
|
||||
os.isLinux -> "linux"
|
||||
os.isWindows -> "windows"
|
||||
else -> throw RuntimeException("Unsupported OS for GraalVM: ${os.name}")
|
||||
}
|
||||
val version = libs.findVersion("graalVm").get().toString()
|
||||
val graalJdkVersion = libs.findVersion("graalVmJdkVersion").get().toString()
|
||||
return GraalVm(osName, arch, version, graalJdkVersion)
|
||||
}
|
||||
|
||||
val isCiBuild: Boolean by lazy { System.getenv("CI") != null }
|
||||
|
||||
@@ -288,7 +304,15 @@ open class BuildInfo(private val project: Project) {
|
||||
classpath = template.classpath
|
||||
testClassesDirs = template.testClassesDirs
|
||||
jvmArgs.addAll(template.jvmArgs)
|
||||
jvmArgumentProviders.addAll(template.jvmArgumentProviders)
|
||||
jvmArgumentProviders.addAll(
|
||||
template.jvmArgumentProviders.map { provider ->
|
||||
// Gradle injects this arg, which is not compatible across JDK versions
|
||||
// other JDK versions (see https://github.com/gradle/gradle/issues/39442)
|
||||
CommandLineArgumentProvider {
|
||||
provider.asArguments().filter { !it.startsWith("--sun-misc-unsafe-memory-access") }
|
||||
}
|
||||
}
|
||||
)
|
||||
forkEvery = template.forkEvery
|
||||
maxParallelForks = template.maxParallelForks
|
||||
minHeapSize = template.minHeapSize
|
||||
@@ -368,8 +392,14 @@ open class BuildInfo(private val project: Project) {
|
||||
File(System.getProperty("user.home"), "staticdeps/bin/x86_64-linux-musl-gcc").exists()
|
||||
}
|
||||
|
||||
val os: org.gradle.internal.os.OperatingSystem by lazy {
|
||||
org.gradle.internal.os.OperatingSystem.current()
|
||||
val os: OS by lazy {
|
||||
val currentOs = OperatingSystem.current()
|
||||
when {
|
||||
currentOs.isMacOsX -> OS.MacOS
|
||||
currentOs.isLinux -> OS.Linux
|
||||
currentOs.isWindows -> OS.Windows
|
||||
else -> throw GradleException("Cannot build on ${currentOs.name}")
|
||||
}
|
||||
}
|
||||
|
||||
// could be `commitId: Provider<String> = project.provider { ... }`
|
||||
@@ -425,3 +455,12 @@ open class BuildInfo(private val project: Project) {
|
||||
// Shape of a function which is applied to configure multi-JDK testing.
|
||||
private typealias MultiJdkTestConfigurator =
|
||||
Test.(Pair<JavaLanguageVersion, Provider<JavaLauncher>>) -> Unit
|
||||
|
||||
private val OperatingSystem.canonicalName
|
||||
get() =
|
||||
when {
|
||||
isMacOsX -> "macos"
|
||||
isWindows -> "windows"
|
||||
isLinux -> "linux"
|
||||
else -> throw RuntimeException("Unsupported OS: $name")
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import java.io.File
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
open class BuildSourceBundle : DefaultTask() {
|
||||
/** Sources jars that are included in full. */
|
||||
@get:InputFiles val inputJars: ConfigurableFileCollection = project.objects.fileCollection()
|
||||
|
||||
/**
|
||||
* Sources jars that are only included in part, if [compiledClasses] is set.
|
||||
*
|
||||
* Only `.java` files are dropped, if their class is not in [compiledClasses]. Everything else is
|
||||
* kept: other languages (e.g. Kotlin) can't be matched by name, because the classes that they
|
||||
* compile to don't necessarily relate to the file name or package (e.g. with `@file:JvmName` or
|
||||
* `@file:JvmPackageName`), and licenses and other metadata must be retained.
|
||||
*
|
||||
* If [compiledClasses] is not set, these jars are included in full.
|
||||
*/
|
||||
@get:InputFiles
|
||||
val filteredInputJars: ConfigurableFileCollection = project.objects.fileCollection()
|
||||
|
||||
/** Fully qualified names of top-level classes, one per line. */
|
||||
@get:[InputFile Optional]
|
||||
val compiledClasses: RegularFileProperty = project.objects.fileProperty()
|
||||
|
||||
@get:OutputFile val outputZip: RegularFileProperty = project.objects.fileProperty()
|
||||
|
||||
@TaskAction
|
||||
@Suppress("unused")
|
||||
fun merge() {
|
||||
val zipFile = outputZip.asFile.get()
|
||||
if (!zipFile.getParentFile().exists()) zipFile.getParentFile().mkdirs()
|
||||
val classes =
|
||||
if (compiledClasses.isPresent) {
|
||||
compiledClasses
|
||||
.get()
|
||||
.asFile
|
||||
.readLines()
|
||||
.map { it.trim() }
|
||||
.filterTo(hashSetOf()) { it.isNotEmpty() }
|
||||
} else null
|
||||
val packages = classes?.mapTo(hashSetOf()) { it.substringBeforeLast('.', "").replace('.', '/') }
|
||||
var dropped = 0
|
||||
ZipOutputStream(zipFile.outputStream()).use { zip ->
|
||||
fun copyJar(jar: File, keep: (String) -> Boolean) {
|
||||
val jarName = jar.toPath().fileName.toString().removeSuffix(".jar")
|
||||
project.zipTree(jar).visit {
|
||||
if (isDirectory) return@visit
|
||||
val path = relativePath.pathString
|
||||
if (!keep(path)) {
|
||||
dropped++
|
||||
return@visit
|
||||
}
|
||||
zip.putNextEntry(ZipEntry("$jarName/$path"))
|
||||
copyTo(zip)
|
||||
zip.closeEntry()
|
||||
}
|
||||
}
|
||||
for (jar in inputJars) copyJar(jar) { true }
|
||||
for (jar in filteredInputJars) {
|
||||
copyJar(jar) { path ->
|
||||
classes == null || packages == null || isEvident(path, classes, packages)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (classes != null) {
|
||||
logger.lifecycle("Dropped $dropped source files that are not in ${compiledClasses.get()}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun isEvident(path: String, classes: Set<String>, packages: Set<String>): Boolean {
|
||||
// sources of multi-release jars have the same classes as the unversioned ones
|
||||
val sourcePath = path.replace(multiReleasePrefix, "")
|
||||
val packagePath = sourcePath.substringBeforeLast('/', "")
|
||||
val fileName = sourcePath.substringAfterLast('/')
|
||||
return when (sourcePath.substringAfterLast('.', "")) {
|
||||
"java" ->
|
||||
when (fileName) {
|
||||
"package-info.java" -> packagePath in packages
|
||||
"module-info.java" -> true
|
||||
else -> sourcePath.removeSuffix(".java").replace('/', '.') in classes
|
||||
}
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
private val multiReleasePrefix = Regex("^META-INF/versions/\\d+/")
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import Target.OS
|
||||
import javax.inject.Inject
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.process.ExecOperations
|
||||
|
||||
/**
|
||||
* Task for creating static libraries from object files.
|
||||
*
|
||||
* Supports both Unix `ar` and Windows `lib.exe`.
|
||||
*/
|
||||
abstract class CArchive : DefaultTask() {
|
||||
/** The object files to archive. */
|
||||
@get:InputFiles abstract val objectFiles: ConfigurableFileCollection
|
||||
|
||||
/** The output static library file. */
|
||||
@get:OutputFile abstract val outputFile: RegularFileProperty
|
||||
|
||||
@get:Inject protected abstract val execOperations: ExecOperations
|
||||
|
||||
private val buildInfo: BuildInfo = project.extensions.getByType(BuildInfo::class.java)
|
||||
|
||||
init {
|
||||
group = "build"
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
fun archive() {
|
||||
outputFile.get().asFile.delete()
|
||||
when (buildInfo.os) {
|
||||
OS.Linux,
|
||||
OS.MacOS -> archiveWithAr()
|
||||
OS.Windows -> archiveWithLib()
|
||||
}
|
||||
logger.info("Created static library -> ${outputFile.get().asFile.name}")
|
||||
}
|
||||
|
||||
private fun archiveWithAr() {
|
||||
val output = outputFile.get().asFile
|
||||
output.parentFile.mkdirs()
|
||||
|
||||
val args = buildList {
|
||||
add("ar")
|
||||
add("-rcs")
|
||||
add(output.absolutePath)
|
||||
objectFiles.files.forEach { file -> add(file.absolutePath) }
|
||||
}
|
||||
|
||||
execOperations.exec {
|
||||
workingDir = project.projectDir
|
||||
commandLine(args)
|
||||
}
|
||||
}
|
||||
|
||||
private fun archiveWithLib() {
|
||||
val output = outputFile.get().asFile
|
||||
output.parentFile.mkdirs()
|
||||
|
||||
val args = buildList {
|
||||
add("lib.exe")
|
||||
add("/OUT:${output.absolutePath}")
|
||||
objectFiles.files.forEach { file -> add(file.absolutePath) }
|
||||
}
|
||||
|
||||
execOperations.exec {
|
||||
workingDir = project.projectDir
|
||||
commandLine(args)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,410 @@
|
||||
/*
|
||||
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import Target.OS
|
||||
import javax.inject.Inject
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.provider.MapProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.gradle.api.tasks.OutputDirectory
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.process.ExecOperations
|
||||
|
||||
/**
|
||||
* Task for compiling C source files to object files or executables. Supports both GCC/Clang and
|
||||
* MSVC compilers.
|
||||
*/
|
||||
abstract class CCompile : DefaultTask() {
|
||||
/** The C source files to compile. */
|
||||
@get:InputFiles abstract val sourceFiles: ConfigurableFileCollection
|
||||
|
||||
/** The include directories for compilation. */
|
||||
@get:InputFiles abstract val includeDirs: ConfigurableFileCollection
|
||||
|
||||
/** Preprocessor definitions. */
|
||||
@get:Input @get:Optional abstract val defines: MapProperty<String, String>
|
||||
|
||||
/** C standard version (e.g., "c11", "c17", "gnu11"). */
|
||||
@get:Input @get:Optional abstract val cStandard: Property<String>
|
||||
|
||||
/** Optimization level (e.g., "0", "1", "2", "3", "s", "fast"). */
|
||||
@get:Input @get:Optional abstract val optimizationLevel: Property<String>
|
||||
|
||||
/** Whether to include debug symbols. */
|
||||
@get:Input abstract val debugSymbols: Property<Boolean>
|
||||
|
||||
/** Whether to generate position-independent code (GCC/Clang only). */
|
||||
@get:Input abstract val positionIndependentCode: Property<Boolean>
|
||||
|
||||
/** Warning flags (GCC/Clang: prefixed with -W, e.g., "all", "extra"). */
|
||||
@get:Input abstract val warningFlags: ListProperty<String>
|
||||
|
||||
/** Warning level for MSVC (0-4). */
|
||||
@get:Input @get:Optional abstract val warningLevel: Property<Int>
|
||||
|
||||
/** Feature flags (GCC/Clang only, prefixed with -f). */
|
||||
@get:Input abstract val featureFlags: ListProperty<String>
|
||||
|
||||
/** Machine-specific flags (GCC/Clang only, prefixed with -m). */
|
||||
@get:Input abstract val machineFlags: ListProperty<String>
|
||||
|
||||
/** Runtime library for MSVC (e.g., "MT", "MD", "MTd", "MDd"). */
|
||||
@get:Input @get:Optional abstract val runtimeLibrary: Property<String>
|
||||
|
||||
/** Additional compiler arguments. */
|
||||
@get:Input abstract val compilerArgs: ListProperty<String>
|
||||
|
||||
/** The architecture to compile for (macOS only). */
|
||||
@get:Input @get:Optional abstract val arch: Property<Target.Arch>
|
||||
|
||||
// ===== Linking properties =====
|
||||
|
||||
/** Whether to link into an executable (true) or just compile to object files (false). */
|
||||
@get:Input abstract val link: Property<Boolean>
|
||||
|
||||
/** Library search paths. */
|
||||
@get:InputFiles @get:Optional abstract val libraryPaths: ConfigurableFileCollection
|
||||
|
||||
/** Libraries to link by name. */
|
||||
@get:Input @get:Optional abstract val libraries: ListProperty<String>
|
||||
|
||||
/** Direct library files to link. */
|
||||
@get:InputFiles @get:Optional abstract val libraryFiles: ConfigurableFileCollection
|
||||
|
||||
/** Linker flags. */
|
||||
@get:Input @get:Optional abstract val linkerFlags: ListProperty<String>
|
||||
|
||||
/** macOS frameworks to link (GCC/Clang only). */
|
||||
@get:Input @get:Optional abstract val frameworks: ListProperty<String>
|
||||
|
||||
/** Output file when linking (executable name). */
|
||||
@get:OutputFile @get:Optional abstract val outputFile: RegularFileProperty
|
||||
|
||||
/** Create a shared library (dll, dylib, so). */
|
||||
@get:Input abstract val sharedLibrary: Property<Boolean>
|
||||
|
||||
/** The output directory for object files (when not linking). */
|
||||
@get:OutputDirectory @get:Optional abstract val outputDir: DirectoryProperty
|
||||
|
||||
@get:Inject protected abstract val execOperations: ExecOperations
|
||||
|
||||
private val buildInfo: BuildInfo = project.extensions.getByType(BuildInfo::class.java)
|
||||
|
||||
init {
|
||||
group = "build"
|
||||
// Defaults
|
||||
link.convention(false)
|
||||
debugSymbols.convention(false)
|
||||
positionIndependentCode.convention(false)
|
||||
sharedLibrary.convention(false)
|
||||
warningFlags.convention(listOf("all", "extra"))
|
||||
warningLevel.convention(3)
|
||||
featureFlags.convention(emptyList())
|
||||
machineFlags.convention(emptyList())
|
||||
compilerArgs.convention(emptyList())
|
||||
defines.convention(emptyMap())
|
||||
libraries.convention(emptyList())
|
||||
linkerFlags.convention(emptyList())
|
||||
frameworks.convention(emptyList())
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
fun compile() {
|
||||
if (link.get()) {
|
||||
compileAndLink()
|
||||
} else {
|
||||
compileOnly()
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileOnly() {
|
||||
val outputDirectory = outputDir.get().asFile
|
||||
outputDirectory.mkdirs()
|
||||
|
||||
val cFiles = sourceFiles.files.filter { it.extension == "c" }
|
||||
|
||||
if (cFiles.isEmpty()) {
|
||||
logger.info("No C files to compile")
|
||||
return
|
||||
}
|
||||
|
||||
cFiles.forEach { cFile ->
|
||||
when (buildInfo.targetMachine.os) {
|
||||
OS.Linux,
|
||||
OS.MacOS -> compileFileGCC(cFile, outputDirectory)
|
||||
OS.Windows -> compileFileMSVC(cFile, outputDirectory)
|
||||
}
|
||||
}
|
||||
cFiles.forEach { cFile ->
|
||||
val objectFile = outputDirectory.resolve("${cFile.nameWithoutExtension}.o")
|
||||
logger.info("Compiled ${cFile.name} -> ${objectFile.name}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileFileGCC(cFile: java.io.File, outputDirectory: java.io.File) {
|
||||
val objectFile = outputDirectory.resolve("${cFile.nameWithoutExtension}.o")
|
||||
|
||||
val args = buildList {
|
||||
add("cc")
|
||||
add("-c")
|
||||
addAll(buildCompilerFlagsGCC())
|
||||
add(cFile.absolutePath)
|
||||
add("-o")
|
||||
add(objectFile.absolutePath)
|
||||
}
|
||||
|
||||
execOperations.exec {
|
||||
workingDir = project.projectDir
|
||||
commandLine(args)
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileFileMSVC(cFile: java.io.File, outputDirectory: java.io.File) {
|
||||
val objectFile = outputDirectory.resolve("${cFile.nameWithoutExtension}.obj")
|
||||
|
||||
val args = buildList {
|
||||
add("cl.exe")
|
||||
add("/c")
|
||||
addAll(buildCompilerFlagsMSVC())
|
||||
add("/Fo${objectFile.absolutePath}")
|
||||
add(cFile.absolutePath)
|
||||
}
|
||||
|
||||
execOperations.exec {
|
||||
workingDir = project.projectDir
|
||||
commandLine(args)
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileAndLink() {
|
||||
when (buildInfo.targetMachine.os) {
|
||||
OS.Linux,
|
||||
OS.MacOS -> compileAndLinkGCC()
|
||||
OS.Windows -> compileAndLinkMSVC()
|
||||
}
|
||||
|
||||
logger.info("Compiled and linked -> ${outputFile.get().asFile.name}")
|
||||
}
|
||||
|
||||
private fun compileAndLinkGCC() {
|
||||
val output = outputFile.get().asFile
|
||||
|
||||
val args = buildList {
|
||||
add("cc")
|
||||
addAll(buildCompilerFlagsGCC())
|
||||
|
||||
// Add source/object files
|
||||
sourceFiles.files.forEach { file -> add(file.absolutePath) }
|
||||
|
||||
// Library search paths
|
||||
libraryPaths.files.forEach { dir -> add("-L${dir.absolutePath}") }
|
||||
|
||||
// Direct library files. These must come before `-l` libraries below: GNU ld resolves
|
||||
// symbols in a single left-to-right pass, so a static `-lz` etc. must appear after the
|
||||
// archives (e.g. libpkl.a) whose undefined symbols it resolves, or those symbols are
|
||||
// never pulled in.
|
||||
libraryFiles.files.forEach { file -> add(file.absolutePath) }
|
||||
|
||||
// Libraries by name
|
||||
libraries.get().forEach { lib -> add("-l${lib}") }
|
||||
|
||||
// Linker flags
|
||||
if (linkerFlags.get().isNotEmpty()) {
|
||||
add("-Wl,${linkerFlags.get().joinToString(",")}")
|
||||
}
|
||||
|
||||
// macOS frameworks
|
||||
if (buildInfo.os.isMacOS) {
|
||||
frameworks.get().forEach { framework ->
|
||||
add("-framework")
|
||||
add(framework)
|
||||
}
|
||||
}
|
||||
|
||||
if (sharedLibrary.get()) {
|
||||
add("-shared")
|
||||
}
|
||||
|
||||
// Output file
|
||||
add("-o")
|
||||
add(output.absolutePath)
|
||||
}
|
||||
|
||||
output.parentFile.mkdirs()
|
||||
|
||||
execOperations.exec {
|
||||
workingDir = project.projectDir
|
||||
commandLine(args)
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileAndLinkMSVC() {
|
||||
val output = outputFile.get().asFile
|
||||
output.parentFile.mkdirs()
|
||||
|
||||
val args = buildList {
|
||||
add("cl.exe")
|
||||
addAll(buildCompilerFlagsMSVC())
|
||||
|
||||
// Output executable
|
||||
add("/Fe${output.absolutePath}")
|
||||
|
||||
// Add source/object files
|
||||
sourceFiles.files.forEach { file -> add(file.absolutePath) }
|
||||
|
||||
// Linker section
|
||||
if (
|
||||
libraryPaths.files.isNotEmpty() ||
|
||||
libraries.get().isNotEmpty() ||
|
||||
libraryFiles.files.isNotEmpty() ||
|
||||
linkerFlags.get().isNotEmpty()
|
||||
) {
|
||||
add("/link")
|
||||
|
||||
if (sharedLibrary.get()) {
|
||||
add("/DLL")
|
||||
}
|
||||
|
||||
// Library search paths
|
||||
libraryPaths.files.forEach { dir -> add("/LIBPATH:${dir.absolutePath}") }
|
||||
|
||||
// Library files by name
|
||||
libraries.get().forEach { lib -> add("lib${lib}.lib") }
|
||||
|
||||
// Direct library files
|
||||
libraryFiles.files.forEach { file -> add(file.absolutePath) }
|
||||
|
||||
// Additional linker flags
|
||||
addAll(linkerFlags.get())
|
||||
}
|
||||
}
|
||||
|
||||
execOperations.exec {
|
||||
workingDir = project.projectDir
|
||||
commandLine(args)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildCompilerFlagsGCC(): List<String> = buildList {
|
||||
// Warning flags
|
||||
warningFlags.get().forEach { flag -> add("-W${flag}") }
|
||||
|
||||
// Include directories
|
||||
includeDirs.files.forEach { dir -> add("-I${dir.absolutePath}") }
|
||||
|
||||
// Preprocessor definitions
|
||||
defines.get().forEach { (key, value) ->
|
||||
if (value.isEmpty()) {
|
||||
add("-D${key}")
|
||||
} else {
|
||||
add("-D${key}=${value}")
|
||||
}
|
||||
}
|
||||
|
||||
// C standard
|
||||
if (cStandard.isPresent) {
|
||||
add("-std=${cStandard.get()}")
|
||||
}
|
||||
|
||||
// Optimization level
|
||||
if (optimizationLevel.isPresent) {
|
||||
add("-O${optimizationLevel.get()}")
|
||||
}
|
||||
|
||||
// Debug symbols
|
||||
if (debugSymbols.get()) {
|
||||
add("-g")
|
||||
}
|
||||
|
||||
// Position-independent code
|
||||
if (positionIndependentCode.get()) {
|
||||
add("-fPIC")
|
||||
}
|
||||
|
||||
// Feature flags
|
||||
featureFlags.get().forEach { flag -> add("-f${flag}") }
|
||||
|
||||
// Machine flags
|
||||
machineFlags.get().forEach { flag -> add("-m${flag}") }
|
||||
|
||||
// Platform-specific flags
|
||||
if (buildInfo.os.isMacOS && arch.isPresent) {
|
||||
add("-arch")
|
||||
add(arch.get().cCompilerName)
|
||||
}
|
||||
|
||||
// Additional compiler arguments
|
||||
addAll(compilerArgs.get())
|
||||
}
|
||||
|
||||
private fun buildCompilerFlagsMSVC(): List<String> = buildList {
|
||||
// Warning level
|
||||
if (warningLevel.isPresent) {
|
||||
add("/W${warningLevel.get()}")
|
||||
}
|
||||
|
||||
// Include directories
|
||||
includeDirs.files.forEach { dir -> add("/I${dir.absolutePath}") }
|
||||
|
||||
// Preprocessor definitions
|
||||
defines.get().forEach { (key, value) ->
|
||||
if (value.isEmpty()) {
|
||||
add("/D${key}")
|
||||
} else {
|
||||
add("/D${key}=${value}")
|
||||
}
|
||||
}
|
||||
|
||||
// C standard
|
||||
if (cStandard.isPresent) {
|
||||
add("/std:${cStandard.get()}")
|
||||
}
|
||||
|
||||
// Optimization level
|
||||
if (optimizationLevel.isPresent) {
|
||||
when (optimizationLevel.get().lowercase()) {
|
||||
"0",
|
||||
"d" -> add("/Od")
|
||||
"1" -> add("/O1")
|
||||
"2" -> add("/O2")
|
||||
"x",
|
||||
"fast" -> add("/Ox")
|
||||
"s" -> add("/Os")
|
||||
"t" -> add("/Ot")
|
||||
}
|
||||
}
|
||||
|
||||
// Debug symbols
|
||||
if (debugSymbols.get()) {
|
||||
add("/Zi")
|
||||
}
|
||||
|
||||
// Runtime library
|
||||
if (runtimeLibrary.isPresent) {
|
||||
add("/${runtimeLibrary.get()}")
|
||||
}
|
||||
|
||||
// Additional compiler arguments
|
||||
addAll(compilerArgs.get())
|
||||
}
|
||||
}
|
||||
@@ -13,52 +13,82 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
import javax.inject.Inject
|
||||
import kotlin.io.path.createDirectories
|
||||
import kotlin.io.path.exists
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.model.ObjectFactory
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.provider.MapProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.provider.SetProperty
|
||||
import org.gradle.api.services.BuildService
|
||||
import org.gradle.api.services.BuildServiceParameters
|
||||
import org.gradle.api.tasks.ClasspathNormalizer
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.Internal
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.gradle.api.tasks.OutputDirectories
|
||||
import org.gradle.api.tasks.OutputFiles
|
||||
import org.gradle.api.tasks.PathSensitivity
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.kotlin.dsl.registerIfAbsent
|
||||
import org.gradle.kotlin.dsl.withNormalizer
|
||||
import org.gradle.process.ExecOperations
|
||||
|
||||
enum class Architecture {
|
||||
AMD64,
|
||||
AARCH64,
|
||||
}
|
||||
|
||||
abstract class NativeImageBuildService : BuildService<BuildServiceParameters.None>
|
||||
|
||||
abstract class NativeImageBuild : DefaultTask() {
|
||||
@get:Input abstract val imageName: Property<String>
|
||||
@get:Input abstract val outputName: Property<String>
|
||||
|
||||
@get:Input abstract val extraNativeImageArgs: ListProperty<String>
|
||||
|
||||
@get:Input abstract val arch: Property<Architecture>
|
||||
@get:Input abstract val arch: Property<Target.Arch>
|
||||
|
||||
@get:Input abstract val mainClass: Property<String>
|
||||
/**
|
||||
* Names of extra directories, relative to the native-image working directory, that should be
|
||||
* copied into [outputDir] after a successful build.
|
||||
*/
|
||||
@get:Input abstract val additionalOutputDirectoryNames: SetProperty<String>
|
||||
|
||||
/**
|
||||
* The main class entrypoint for the executable.
|
||||
*
|
||||
* This option is not necessary if [sharedLibrary] is true.
|
||||
*/
|
||||
@get:Optional @get:Input abstract val mainClass: Property<String>
|
||||
|
||||
/** Create a shared library, instead of an executable. */
|
||||
@get:Input abstract val sharedLibrary: Property<Boolean>
|
||||
|
||||
@get:InputFiles abstract val classpath: ConfigurableFileCollection
|
||||
|
||||
private val outputDir = project.layout.buildDirectory.dir("executable")
|
||||
@get:Input abstract val envVars: MapProperty<String, String>
|
||||
|
||||
@get:OutputFile val outputFile = outputDir.flatMap { it.file(imageName) }
|
||||
@get:InputFile @get:Optional abstract val nativeCompilerPath: RegularFileProperty
|
||||
|
||||
@get:Internal abstract val compiledClassesFile: RegularFileProperty
|
||||
|
||||
@get:Internal abstract val outputDir: DirectoryProperty
|
||||
|
||||
@get:Inject protected abstract val execOperations: ExecOperations
|
||||
|
||||
@get:Inject protected abstract val objectFactory: ObjectFactory
|
||||
|
||||
private val graalVm: Provider<BuildInfo.GraalVm> = arch.map { a ->
|
||||
when (a) {
|
||||
Architecture.AMD64 -> buildInfo.graalVmAmd64
|
||||
Architecture.AARCH64 -> buildInfo.graalVmAarch64
|
||||
Target.Arch.AMD64 -> buildInfo.graalVmAmd64
|
||||
Target.Arch.AARCH64 -> buildInfo.graalVmAarch64
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +118,10 @@ abstract class NativeImageBuild : DefaultTask() {
|
||||
// CPU resources).
|
||||
usesService(buildService)
|
||||
|
||||
sharedLibrary.convention(false)
|
||||
|
||||
outputDir.convention(project.layout.buildDirectory.dir("executable"))
|
||||
|
||||
group = "build"
|
||||
|
||||
inputs
|
||||
@@ -98,37 +132,117 @@ abstract class NativeImageBuild : DefaultTask() {
|
||||
.files(nativeImageExecutable)
|
||||
.withPropertyName("graalVmNativeImage")
|
||||
.withPathSensitivity(PathSensitivity.ABSOLUTE)
|
||||
if (nativeCompilerPath.isPresent) {
|
||||
inputs.file(nativeCompilerPath)
|
||||
}
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
@Suppress("unused")
|
||||
@OutputFiles
|
||||
fun getEffectiveOutputFiles(): FileCollection {
|
||||
return objectFactory
|
||||
.fileCollection()
|
||||
.from(
|
||||
sharedLibrary.map { isLibrary ->
|
||||
val dir = outputDir.get()
|
||||
val libraryName = outputName.get()
|
||||
// if building a library, native-image outputs the shared library plus four headers
|
||||
// (and, on Windows, an import library); otherwise, it outputs just the one file
|
||||
if (isLibrary) {
|
||||
val sharedLibraryExtension = buildInfo.os.sharedLibraryExtension
|
||||
val staticLibraryExtension = buildInfo.os.staticLibraryExtension
|
||||
dir.files(
|
||||
buildList {
|
||||
add("$libraryName.$sharedLibraryExtension")
|
||||
if (buildInfo.os.isWindows) {
|
||||
// the DLL's import library, which consumers must link against instead of
|
||||
// the .dll itself
|
||||
add("$libraryName.$staticLibraryExtension")
|
||||
// a genuine, self-contained static archive with no runtime DLL dependency,
|
||||
// produced by build_windows.bat
|
||||
add("${libraryName}_s.$staticLibraryExtension")
|
||||
} else {
|
||||
// a genuine, self-contained static archive with no runtime dependency,
|
||||
// produced by build_unix.sh
|
||||
add("$libraryName.$staticLibraryExtension")
|
||||
}
|
||||
add("$libraryName.h")
|
||||
add("${libraryName}_dynamic.h")
|
||||
add("graal_isolate.h")
|
||||
add("graal_isolate_dynamic.h")
|
||||
}
|
||||
)
|
||||
} else {
|
||||
val executableName = if (buildInfo.os.isWindows) "${libraryName}.exe" else libraryName
|
||||
dir.file(executableName)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
@OutputDirectories
|
||||
fun getEffectiveOutputDirectories(): FileCollection =
|
||||
objectFactory
|
||||
.fileCollection()
|
||||
.from(additionalOutputDirectoryNames.map { names -> names.map { outputDir.get().dir(it) } })
|
||||
|
||||
@TaskAction
|
||||
protected fun run() {
|
||||
execOperations.exec {
|
||||
val workingDir =
|
||||
project.layout.buildDirectory
|
||||
.dir("tmp/native-image-build/${name}")
|
||||
.get()
|
||||
.asFile
|
||||
.toPath()
|
||||
.also { it.createDirectories() }
|
||||
val reportsDir = workingDir.resolve("reports")
|
||||
// reports are timestamped and never cleaned up by native-image; make sure we read a fresh one
|
||||
reportsDir
|
||||
.toFile()
|
||||
.listFiles { f -> f.name.startsWith(COMPILATION_REPORT_PREFIX) }
|
||||
?.forEach { it.delete() }
|
||||
val execResult = execOperations.exec {
|
||||
val exclusions =
|
||||
listOf(buildInfo.libs.findLibrary("graalSdk").get()).map { it.get().module.name }
|
||||
|
||||
executable = nativeImageExecutable.get()
|
||||
workingDir(outputDir)
|
||||
workingDir(workingDir)
|
||||
|
||||
args = buildList {
|
||||
add("--color=always")
|
||||
// must be emitted before any experimental options are used
|
||||
add("-H:+UnlockExperimentalVMOptions")
|
||||
// emit `reports/universe_compilation_*.txt`, listing all compiled methods
|
||||
add("-H:+PrintUniverse")
|
||||
// currently gives a deprecation warning, but we've been told
|
||||
// that the "initialize everything at build time" *CLI* option is likely here to stay
|
||||
add("--initialize-at-build-time=")
|
||||
// needed for messagepack-java (see https://github.com/msgpack/msgpack-java/issues/600)
|
||||
add("--initialize-at-run-time=org.msgpack.core.buffer.DirectBufferAccess")
|
||||
// prevent storing `homeDir` in native image
|
||||
add("--initialize-at-run-time=org.pkl.core.util.BaseDirectory")
|
||||
add("--initialize-at-run-time=org.pkl.core.util.BaseDirectories")
|
||||
// prevent storing `isEnabled`
|
||||
add("--initialize-at-run-time=org.pkl.core.util.DebugLogger")
|
||||
// needed for jline-terminal-jni
|
||||
add("--initialize-at-run-time=org.jline.nativ,org.jline.terminal.impl.jni")
|
||||
// needed for protobuf-java
|
||||
add("--initialize-at-run-time=com.google.protobuf.UnsafeUtil")
|
||||
add("--no-fallback")
|
||||
add("-H:IncludeResources=org/pkl/core/stdlib/.*\\.pkl")
|
||||
add("-H:IncludeResources=org/jline/utils/.*")
|
||||
add("-H:IncludeResourceBundles=org.pkl.core.errorMessages")
|
||||
add("-H:IncludeResourceBundles=org.pkl.parser.errorMessages")
|
||||
add("-H:IncludeResources=org/pkl/commons/cli/PklCARoots.pem")
|
||||
add("-H:Class=${mainClass.get()}")
|
||||
if (mainClass.isPresent) {
|
||||
add("-H:Class=${mainClass.get()}")
|
||||
}
|
||||
if (sharedLibrary.get()) {
|
||||
add("--shared")
|
||||
}
|
||||
add("-o")
|
||||
add(imageName.get())
|
||||
add(outputName.get())
|
||||
// the actual limit (currently) used by native-image is this number + 1400 (idea is to
|
||||
// compensate for Truffle's own nodes)
|
||||
add("-H:MaxRuntimeCompileMethods=1800")
|
||||
@@ -138,10 +252,11 @@ abstract class NativeImageBuild : DefaultTask() {
|
||||
// disable automatic support for JVM CLI options (puts our main class in full control of
|
||||
// argument parsing)
|
||||
add("-H:-ParseRuntimeOptions")
|
||||
// quick build mode: 40% faster compilation, 20% smaller (but presumably also slower)
|
||||
// executable
|
||||
if (!buildInfo.isReleaseBuild) {
|
||||
add("-Ob")
|
||||
// disable all optimizations
|
||||
add("-O0")
|
||||
// generate debugging information
|
||||
add("-g")
|
||||
}
|
||||
if (buildInfo.isNativeArch) {
|
||||
add("-march=native")
|
||||
@@ -157,14 +272,73 @@ abstract class NativeImageBuild : DefaultTask() {
|
||||
// make sure dev machine stays responsive (15% slowdown on my laptop)
|
||||
val processors =
|
||||
Runtime.getRuntime().availableProcessors() /
|
||||
if (buildInfo.os.isMacOsX && !buildInfo.isCiBuild) 4 else 1
|
||||
if (buildInfo.os.isMacOS && !buildInfo.isCiBuild) 4 else 1
|
||||
add("-J-XX:ActiveProcessorCount=${processors}")
|
||||
addAll(envVars.get().entries.map { "-E${it.key}=${it.value}" })
|
||||
if (nativeCompilerPath.isPresent) {
|
||||
add("--native-compiler-path=${nativeCompilerPath.get().asFile}")
|
||||
}
|
||||
// Pass through all `HOMEBREW_` prefixed environment variables to allow build with shimmed
|
||||
// tools.
|
||||
addAll(environment.keys.filter { it.startsWith("HOMEBREW_") }.map { "-E$it" })
|
||||
// tools, and `DEVELOPER_DIR` so that `cc` on macOS uses the selected Xcode toolchain.
|
||||
addAll(
|
||||
environment.keys
|
||||
.filter { it.startsWith("HOMEBREW_") || it == "DEVELOPER_DIR" }
|
||||
.map { "-E$it" }
|
||||
)
|
||||
addAll(extraNativeImageArgs.get())
|
||||
addAll(extraArgsFromProperties)
|
||||
}
|
||||
}
|
||||
|
||||
if (execResult.exitValue == 0) {
|
||||
val outputFileNames = getEffectiveOutputFiles().files.map { it.name }
|
||||
for (fileName in outputFileNames) {
|
||||
val sourceFile = workingDir.resolve(fileName)
|
||||
val targetFile = outputDir.get().asFile.toPath().resolve(fileName)
|
||||
if (!sourceFile.exists()) {
|
||||
throw GradleException("Expected to find $fileName in the working dir but didn't")
|
||||
}
|
||||
Files.copy(sourceFile, targetFile, StandardCopyOption.REPLACE_EXISTING)
|
||||
}
|
||||
for (dirName in additionalOutputDirectoryNames.get()) {
|
||||
val sourceDir = workingDir.resolve(dirName).toFile()
|
||||
if (!sourceDir.exists()) {
|
||||
throw GradleException("Expected to find directory $dirName in the working dir but didn't")
|
||||
}
|
||||
val targetDir = outputDir.get().asFile.resolve(dirName)
|
||||
targetDir.deleteRecursively()
|
||||
sourceDir.copyRecursively(targetDir, overwrite = true)
|
||||
}
|
||||
writeCompiledClasses(reportsDir.toFile())
|
||||
}
|
||||
}
|
||||
|
||||
private fun writeCompiledClasses(reportsDir: java.io.File) {
|
||||
val report =
|
||||
reportsDir
|
||||
.listFiles { f -> f.name.startsWith(COMPILATION_REPORT_PREFIX) }
|
||||
?.maxByOrNull { it.lastModified() }
|
||||
?: throw GradleException("Expected to find $COMPILATION_REPORT_PREFIX*.txt in $reportsDir")
|
||||
// Method lines look like `336 2232 apple.security.AppleProvider$Service.newInstance(...): ...`
|
||||
// (offset, size, qualified method name, signature).
|
||||
val methodLine = Regex("""^\s*\d+\s+\d+\s+([^\s(]+)\(""")
|
||||
val classNames = sortedSetOf<String>()
|
||||
report.useLines { lines ->
|
||||
for (line in lines) {
|
||||
val method = methodLine.find(line)?.groupValues?.get(1) ?: continue
|
||||
// Nested, hidden, and lambda classes all live in their top-level class' source file.
|
||||
classNames.add(method.substringBeforeLast('.').substringBefore('$').substringBefore('/'))
|
||||
}
|
||||
}
|
||||
if (classNames.isEmpty()) {
|
||||
throw GradleException("Found no compiled methods in $report")
|
||||
}
|
||||
val file = compiledClassesFile.get().asFile
|
||||
file.parentFile.mkdirs()
|
||||
file.writeText(classNames.joinToString("\n", postfix = "\n"))
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val COMPILATION_REPORT_PREFIX = "universe_compilation_"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.provider.SetProperty
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
/**
|
||||
* Collects the JDK, SubstrateVM, and Graal Java sources for every class that has methods compiled
|
||||
* into a native image.
|
||||
*
|
||||
* The classes are read from [compiledClasses], and their sources are looked up in the OpenJDK and
|
||||
* Graal repositories' tagged source archives. These archives contain the sources of every platform
|
||||
* and architecture, so this task does not need to run on the platform that it collects sources for.
|
||||
*/
|
||||
abstract class NativeImageSourcesJar : DefaultTask() {
|
||||
/**
|
||||
* Fully qualified top-level names of the classes compiled into the image, one per line.
|
||||
*
|
||||
* This is the list that native-image reports (via `-H:+PrintUniverse`) when building the image.
|
||||
*/
|
||||
@get:InputFile abstract val compiledClasses: RegularFileProperty
|
||||
|
||||
/** The gzipped tarball of the OpenJDK repository (e.g. `jdk25u`) at the JDK's release tag. */
|
||||
@get:InputFiles abstract val openJdkTarball: ConfigurableFileCollection
|
||||
|
||||
/** The gzipped tarball of the `oracle/graal` repository at the GraalVM's release tag. */
|
||||
@get:InputFiles abstract val graalTarball: ConfigurableFileCollection
|
||||
|
||||
/**
|
||||
* The platform-specific source roots to include from OpenJDK; any of `share`, `unix`, `linux`,
|
||||
* `macosx`, and `windows`.
|
||||
*/
|
||||
@get:Input abstract val openJdkPlatforms: SetProperty<String>
|
||||
|
||||
@get:OutputFile abstract val outputJar: RegularFileProperty
|
||||
|
||||
@TaskAction
|
||||
@Suppress("unused")
|
||||
fun collect() {
|
||||
val wanted =
|
||||
compiledClasses
|
||||
.get()
|
||||
.asFile
|
||||
.readLines()
|
||||
.filter { it.isNotBlank() }
|
||||
.mapTo(hashSetOf()) { it.trim().replace('.', '/') + ".java" }
|
||||
if (wanted.isEmpty()) {
|
||||
throw GradleException("No compiled classes found in ${compiledClasses.get()}")
|
||||
}
|
||||
|
||||
val platforms = openJdkPlatforms.get()
|
||||
// <repo>-<tag>/src/<module>/<platform>/classes/<package path>.java
|
||||
val openJdkSource = Regex("""^[^/]+/src/[^/]+/([^/]+)/classes/(.+\.java)$""")
|
||||
// <repo>-<tag>/<suite>/src/<project>/src/<package path>.java
|
||||
//
|
||||
// Truffle sources are intentionally omitted; they are already published as sources jars.
|
||||
val graalSource = Regex("""^[^/]+/(?:substratevm|compiler|sdk)/src/[^/]+/src/(.+\.java)$""")
|
||||
|
||||
val out = outputJar.get().asFile
|
||||
out.parentFile.mkdirs()
|
||||
val written = hashSetOf<String>()
|
||||
ZipOutputStream(out.outputStream().buffered()).use { zip ->
|
||||
fun collectFrom(tarball: java.io.File, sourcePathOf: (String) -> String?) {
|
||||
project.tarTree(project.resources.gzip(tarball)).visit {
|
||||
if (isDirectory) return@visit
|
||||
val sourcePath = sourcePathOf(relativePath.pathString) ?: return@visit
|
||||
if (sourcePath !in wanted || !written.add(sourcePath)) return@visit
|
||||
zip.putNextEntry(ZipEntry(sourcePath))
|
||||
copyTo(zip)
|
||||
zip.closeEntry()
|
||||
}
|
||||
}
|
||||
collectFrom(openJdkTarball.singleFile) { path ->
|
||||
openJdkSource
|
||||
.matchEntire(path)
|
||||
?.takeIf { it.groupValues[1] in platforms }
|
||||
?.let { it.groupValues[2] }
|
||||
}
|
||||
collectFrom(graalTarball.singleFile) { path ->
|
||||
graalSource.matchEntire(path)?.groupValues?.get(1)
|
||||
}
|
||||
}
|
||||
logger.lifecycle(
|
||||
"Collected sources of ${written.size} of ${wanted.size} compiled classes into $out"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -61,60 +61,67 @@ fun Project.configurePklPomMetadata() {
|
||||
}
|
||||
}
|
||||
|
||||
/** Configures POM validation task to check for unresolved versions and snapshots in releases. */
|
||||
fun Project.configurePomValidation() {
|
||||
val validatePom by tasks.registering {
|
||||
if (tasks.findByName("generatePomFileForLibraryPublication") == null) {
|
||||
return@registering
|
||||
}
|
||||
val generatePomFileForLibraryPublication by tasks.existing(GenerateMavenPom::class)
|
||||
val outputFile =
|
||||
layout.buildDirectory.file("validatePom") // dummy output to satisfy up-to-date check
|
||||
/**
|
||||
* Configures POM validation for [publicationName] to check for unresolved versions and snapshots in
|
||||
* releases.
|
||||
*/
|
||||
fun Project.configurePomValidation(publicationName: String = "library") {
|
||||
val generatePomTaskName =
|
||||
"generatePomFileFor${publicationName.replaceFirstChar { it.uppercase() }}Publication"
|
||||
val generatePomTasks =
|
||||
tasks.withType<GenerateMavenPom>().matching { it.name == generatePomTaskName }
|
||||
val pomFiles = providers.provider { generatePomTasks.map { it.destination } }
|
||||
|
||||
dependsOn(generatePomFileForLibraryPublication)
|
||||
inputs.file(generatePomFileForLibraryPublication.get().destination)
|
||||
outputs.file(outputFile)
|
||||
val validatePom =
|
||||
tasks.register("validatePom") {
|
||||
val outputFile =
|
||||
layout.buildDirectory.file("validatePom") // dummy output to satisfy up-to-date check
|
||||
|
||||
doLast {
|
||||
outputFile.get().asFile.delete()
|
||||
dependsOn(generatePomTasks)
|
||||
inputs.files(pomFiles)
|
||||
outputs.file(outputFile)
|
||||
onlyIf { pomFiles.get().isNotEmpty() }
|
||||
|
||||
val pomFile = generatePomFileForLibraryPublication.get().destination
|
||||
assert(pomFile.exists())
|
||||
doLast {
|
||||
outputFile.get().asFile.delete()
|
||||
|
||||
val text = pomFile.readText()
|
||||
val pomFile = pomFiles.get().single()
|
||||
assert(pomFile.exists())
|
||||
|
||||
run {
|
||||
val unresolvedVersion = Regex("<version>.*[+,()\\[\\]].*</version>")
|
||||
val matches = unresolvedVersion.findAll(text).toList()
|
||||
if (matches.isNotEmpty()) {
|
||||
throw org.gradle.api.GradleException(
|
||||
"""
|
||||
val text = pomFile.readText()
|
||||
|
||||
run {
|
||||
val unresolvedVersion = Regex("<version>.*[+,()\\[\\]].*</version>")
|
||||
val matches = unresolvedVersion.findAll(text).toList()
|
||||
if (matches.isNotEmpty()) {
|
||||
throw org.gradle.api.GradleException(
|
||||
"""
|
||||
Found unresolved version selector(s) in generated POM:
|
||||
${matches.joinToString("\n") { it.groupValues[0] }}
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val buildInfo = project.extensions.getByType<BuildInfo>()
|
||||
if (buildInfo.isReleaseBuild) {
|
||||
val snapshotVersion = Regex("<version>.*-SNAPSHOT</version>")
|
||||
val matches = snapshotVersion.findAll(text).toList()
|
||||
if (matches.isNotEmpty()) {
|
||||
throw org.gradle.api.GradleException(
|
||||
"""
|
||||
val buildInfo = project.extensions.getByType<BuildInfo>()
|
||||
if (buildInfo.isReleaseBuild) {
|
||||
val snapshotVersion = Regex("<version>.*-SNAPSHOT</version>")
|
||||
val matches = snapshotVersion.findAll(text).toList()
|
||||
if (matches.isNotEmpty()) {
|
||||
throw org.gradle.api.GradleException(
|
||||
"""
|
||||
Found snapshot version(s) in generated POM of Pkl release version:
|
||||
${matches.joinToString("\n") { it.groupValues[0] }}
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outputFile.get().asFile.writeText("OK")
|
||||
outputFile.get().asFile.writeText("OK")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named("publish") { dependsOn(validatePom) }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.provider.Property
|
||||
|
||||
/**
|
||||
* The inputs that source bundles of a project are made of.
|
||||
*
|
||||
* This is configured by the `pklSourceBundle` plugin; the plugins that register the actual source
|
||||
* bundles (`pklJvmSourceBundle` and `pklNativeSourceBundle`) consume it.
|
||||
*/
|
||||
abstract class SourceBundleSpec {
|
||||
/** Sources jars of the project itself, and of the projects that it depends on. */
|
||||
abstract val firstPartyJars: ConfigurableFileCollection
|
||||
|
||||
/** Sources jars of third-party dependencies, as resolved from the runtime classpath. */
|
||||
abstract val dependencyJars: ConfigurableFileCollection
|
||||
|
||||
/**
|
||||
* Additional sources to include in source bundles of native targets (e.g. C sources of the
|
||||
* project itself).
|
||||
*/
|
||||
abstract val extraNativeSources: ConfigurableFileCollection
|
||||
|
||||
/**
|
||||
* Whether musl and zlib are statically linked into the project's native executable for musl
|
||||
* targets, in which case their sources are included in the source bundle of those targets.
|
||||
*/
|
||||
abstract val staticMuslLibc: Property<Boolean>
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
/**
|
||||
* A build target when building native libraries or executables.
|
||||
*
|
||||
* Pkl only builds for the following listed targets.
|
||||
*/
|
||||
enum class Target(val os: OS, val arch: Arch, val musl: Boolean) {
|
||||
MacosAarch64(os = OS.MacOS, arch = Arch.AARCH64, musl = false),
|
||||
LinuxAarch64(os = OS.Linux, arch = Arch.AARCH64, musl = false),
|
||||
LinuxAmd64(os = OS.Linux, arch = Arch.AMD64, musl = false),
|
||||
AlpineLinuxAmd64(os = OS.Linux, arch = Arch.AMD64, musl = true),
|
||||
WindowsAmd64(os = OS.Windows, arch = Arch.AMD64, musl = false);
|
||||
|
||||
companion object {
|
||||
fun from(os: OS, arch: Arch, musl: Boolean): Target {
|
||||
for (target in entries) {
|
||||
if (target.os == os && target.arch == arch && target.musl == musl) {
|
||||
return target
|
||||
}
|
||||
}
|
||||
throw IllegalArgumentException("Cannot build for $os-$arch with musl: $musl")
|
||||
}
|
||||
}
|
||||
|
||||
val targetName: String
|
||||
get() {
|
||||
return if (musl) {
|
||||
assert(os == OS.Linux)
|
||||
"alpine-linux-$arch"
|
||||
} else "$os-$arch"
|
||||
}
|
||||
|
||||
enum class Arch(
|
||||
/** What we call this arch */
|
||||
val simpleName: String,
|
||||
/** What the C compiler calls this arch */
|
||||
val cCompilerName: String,
|
||||
) {
|
||||
AARCH64("aarch64", "arm64"),
|
||||
AMD64("amd64", "x86_64");
|
||||
|
||||
override fun toString() = simpleName
|
||||
|
||||
companion object {
|
||||
fun fromName(name: String): Arch =
|
||||
when (name) {
|
||||
"aarch64" -> AARCH64
|
||||
"amd64" -> AMD64
|
||||
else -> throw IllegalArgumentException("Unknown arch: $name")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class OS(
|
||||
val simpleName: String,
|
||||
val sharedLibraryExtension: String,
|
||||
val staticLibraryExtension: String,
|
||||
val objectFileExtension: String,
|
||||
val displayName: String,
|
||||
) {
|
||||
MacOS(
|
||||
simpleName = "macos",
|
||||
sharedLibraryExtension = "dylib",
|
||||
staticLibraryExtension = "a",
|
||||
objectFileExtension = "o",
|
||||
displayName = "macOS",
|
||||
),
|
||||
Linux(
|
||||
simpleName = "linux",
|
||||
sharedLibraryExtension = "so",
|
||||
staticLibraryExtension = "a",
|
||||
objectFileExtension = "o",
|
||||
displayName = "Linux",
|
||||
),
|
||||
Windows(
|
||||
simpleName = "windows",
|
||||
sharedLibraryExtension = "dll",
|
||||
staticLibraryExtension = "lib",
|
||||
objectFileExtension = "obj",
|
||||
displayName = "Windows",
|
||||
);
|
||||
|
||||
override fun toString(): String = simpleName
|
||||
|
||||
val isWindows: Boolean
|
||||
get() = this == Windows
|
||||
|
||||
val isMacOS: Boolean
|
||||
get() = this == MacOS
|
||||
|
||||
val isLinux: Boolean
|
||||
get() = this == Linux
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ configurations {
|
||||
tasks.withType<JavaCompile>().configureEach {
|
||||
javaCompiler = buildInfo.javaCompiler
|
||||
options.release = buildInfo.jvmTarget
|
||||
options.compilerArgs.add("-Xlint:unchecked")
|
||||
}
|
||||
|
||||
plugins.withType(IdeaPlugin::class).configureEach {
|
||||
@@ -79,7 +80,7 @@ plugins.withType(MavenPublishPlugin::class).configureEach {
|
||||
}
|
||||
}
|
||||
|
||||
val allDependencies by tasks.registering(DependencyReportTask::class)
|
||||
val allDependencies = tasks.register<DependencyReportTask>("allDependencies")
|
||||
|
||||
tasks.withType(Test::class).configureEach {
|
||||
System.getProperty("testReportsDir")?.let { reportsDir ->
|
||||
|
||||
@@ -24,6 +24,7 @@ plugins {
|
||||
`java-library`
|
||||
`maven-publish`
|
||||
id("com.gradleup.shadow")
|
||||
id("pklJvmSourceBundle")
|
||||
}
|
||||
|
||||
// make fat Jar available to other subprojects
|
||||
@@ -31,10 +32,6 @@ val fatJarConfiguration: Configuration = configurations.create("fatJar")
|
||||
|
||||
val fatJarPublication: MavenPublication = publishing.publications.create<MavenPublication>("fatJar")
|
||||
|
||||
// ideally we'd configure this automatically based on project dependencies
|
||||
val firstPartySourcesJarsConfiguration: Configuration =
|
||||
configurations.create("firstPartySourcesJars")
|
||||
|
||||
val relocations =
|
||||
mapOf(
|
||||
// pkl-core dependencies
|
||||
@@ -69,6 +66,11 @@ val relocations =
|
||||
|
||||
// pkl-codegen-kotlin dependencies
|
||||
"com.squareup.kotlinpoet." to "org.pkl.thirdparty.kotlinpoet.",
|
||||
|
||||
// pkl-profiler dependencies
|
||||
"com.google.protobuf." to "org.pkl.thirdparty.com.google.protobuf.",
|
||||
"com.google.perftools." to "org.pkl.thirdparty.com.google.perftools.",
|
||||
"google.protobuf." to "org.pkl.thirdparty.google.protobuf.",
|
||||
)
|
||||
|
||||
for ((key, value) in relocations) {
|
||||
@@ -84,7 +86,7 @@ for ((key, value) in relocations) {
|
||||
}
|
||||
}
|
||||
|
||||
val nonRelocations = listOf("com/oracle/truffle/", "org/graalvm/", "org/jspecify")
|
||||
val nonRelocations = listOf("com/oracle/truffle/", "org/graalvm/", "org/jspecify/")
|
||||
|
||||
tasks.shadowJar {
|
||||
inputs.property("relocations", relocations)
|
||||
@@ -132,8 +134,8 @@ tasks.shadowJar {
|
||||
|
||||
shadow { addShadowVariantIntoJavaComponent = false }
|
||||
|
||||
val testFatJar by
|
||||
tasks.registering(Test::class) {
|
||||
val testFatJar =
|
||||
tasks.register<Test>("testFatJar") {
|
||||
testClassesDirs = files(tasks.test.get().testClassesDirs)
|
||||
classpath =
|
||||
// compiled test classes
|
||||
@@ -148,50 +150,47 @@ val testFatJar by
|
||||
|
||||
tasks.check { dependsOn(testFatJar) }
|
||||
|
||||
val validateFatJar by tasks.registering {
|
||||
val outputFile = layout.buildDirectory.file("validateFatJar/result.txt")
|
||||
inputs.files(tasks.shadowJar)
|
||||
inputs.property("nonRelocations", nonRelocations)
|
||||
outputs.file(outputFile)
|
||||
val validateFatJar =
|
||||
tasks.register("validateFatJar") {
|
||||
val outputFile = layout.buildDirectory.file("validateFatJar/result.txt")
|
||||
inputs.files(tasks.shadowJar)
|
||||
inputs.property("nonRelocations", nonRelocations)
|
||||
outputs.file(outputFile)
|
||||
|
||||
doLast {
|
||||
val unshadowedFiles = mutableListOf<String>()
|
||||
zipTree(tasks.shadowJar.get().outputs.files.singleFile).visit {
|
||||
val fileDetails = this
|
||||
val path = fileDetails.relativePath.pathString
|
||||
if (
|
||||
!(fileDetails.isDirectory ||
|
||||
path.startsWith("org/pkl/") ||
|
||||
path.startsWith("META-INF/") ||
|
||||
nonRelocations.any { path.startsWith(it) })
|
||||
) {
|
||||
// don't throw exception inside `visit`
|
||||
// as this gives a misleading "Could not expand ZIP" error message
|
||||
unshadowedFiles.add(path)
|
||||
doLast {
|
||||
val unshadowedFiles = mutableListOf<String>()
|
||||
zipTree(tasks.shadowJar.get().outputs.files.singleFile).visit {
|
||||
val fileDetails = this
|
||||
val path = fileDetails.relativePath.pathString
|
||||
if (
|
||||
!(fileDetails.isDirectory ||
|
||||
path.startsWith("org/pkl/") ||
|
||||
path.startsWith("META-INF/") ||
|
||||
nonRelocations.any { path.startsWith(it) })
|
||||
) {
|
||||
// don't throw exception inside `visit`
|
||||
// as this gives a misleading "Could not expand ZIP" error message
|
||||
unshadowedFiles.add(path)
|
||||
}
|
||||
}
|
||||
if (unshadowedFiles.isEmpty()) {
|
||||
outputFile.get().asFile.writeText("SUCCESS")
|
||||
} else {
|
||||
outputFile.get().asFile.writeText("FAILURE")
|
||||
throw GradleException("Found unshadowed files:\n" + unshadowedFiles.joinToString("\n"))
|
||||
}
|
||||
}
|
||||
if (unshadowedFiles.isEmpty()) {
|
||||
outputFile.get().asFile.writeText("SUCCESS")
|
||||
} else {
|
||||
outputFile.get().asFile.writeText("FAILURE")
|
||||
throw GradleException("Found unshadowed files:\n" + unshadowedFiles.joinToString("\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.check { dependsOn(validateFatJar) }
|
||||
|
||||
val resolveSourcesJars by
|
||||
tasks.registering(ResolveSourcesJars::class) {
|
||||
configuration.set(configurations.runtimeClasspath)
|
||||
outputDir.set(layout.buildDirectory.dir("resolveSourcesJars"))
|
||||
}
|
||||
|
||||
val fatSourcesJar by
|
||||
tasks.registering(MergeSourcesJars::class) {
|
||||
val fatSourcesJar =
|
||||
tasks.register<MergeSourcesJars>("fatSourcesJar") {
|
||||
plugins.withId("pklJavaLibrary") { inputJars.from(tasks.named("sourcesJar")) }
|
||||
inputJars.from(firstPartySourcesJarsConfiguration)
|
||||
inputJars.from(resolveSourcesJars.map { fileTree(it.outputDir) })
|
||||
inputJars.from(configurations.named("firstPartySourcesJars"))
|
||||
inputJars.from(
|
||||
tasks.named<ResolveSourcesJars>("resolveSourcesJars").map { fileTree(it.outputDir) }
|
||||
)
|
||||
|
||||
mergedBinaryJars.from(tasks.shadowJar)
|
||||
relocatedPackages.set(relocations)
|
||||
@@ -213,7 +212,7 @@ publishing {
|
||||
artifact(fatSourcesJar.flatMap { it.outputJar.asFile }) { classifier = "sources" }
|
||||
|
||||
plugins.withId("pklJavaLibrary") {
|
||||
val javadocJar by tasks.existing(Jar::class)
|
||||
val javadocJar = tasks.named<Jar>("javadocJar")
|
||||
// Javadoc Jar is not fat (didn't invest effort)
|
||||
artifact(javadocJar.flatMap { it.archiveFile }) { classifier = "javadoc" }
|
||||
}
|
||||
|
||||
@@ -21,11 +21,15 @@ plugins { id("de.undercouch.download") }
|
||||
val buildInfo = project.extensions.getByType<BuildInfo>()
|
||||
|
||||
// tries to minimize chance of corruption by download-to-temp-file-and-move
|
||||
val downloadGraalVmAarch64 by
|
||||
tasks.registering(Download::class) { configureDownloadGraalVm(buildInfo.graalVmAarch64) }
|
||||
val downloadGraalVmAarch64 =
|
||||
tasks.register<Download>("downloadGraalVmAarch64") {
|
||||
configureDownloadGraalVm(buildInfo.graalVmAarch64)
|
||||
}
|
||||
|
||||
val downloadGraalVmAmd64 by
|
||||
tasks.registering(Download::class) { configureDownloadGraalVm(buildInfo.graalVmAmd64) }
|
||||
val downloadGraalVmAmd64 =
|
||||
tasks.register<Download>("downloadGraalVmAmd64") {
|
||||
configureDownloadGraalVm(buildInfo.graalVmAmd64)
|
||||
}
|
||||
|
||||
fun Download.configureDownloadGraalVm(graalvm: BuildInfo.GraalVm) {
|
||||
onlyIf { !graalvm.installDir.exists() }
|
||||
@@ -37,14 +41,14 @@ fun Download.configureDownloadGraalVm(graalvm: BuildInfo.GraalVm) {
|
||||
tempAndMove(true)
|
||||
}
|
||||
|
||||
val verifyGraalVmAarch64 by
|
||||
tasks.registering(Verify::class) {
|
||||
val verifyGraalVmAarch64 =
|
||||
tasks.register<Verify>("verifyGraalVmAarch64") {
|
||||
configureVerifyGraalVm(buildInfo.graalVmAarch64)
|
||||
dependsOn(downloadGraalVmAarch64)
|
||||
}
|
||||
|
||||
val verifyGraalVmAmd64 by
|
||||
tasks.registering(Verify::class) {
|
||||
val verifyGraalVmAmd64 =
|
||||
tasks.register<Verify>("verifyGraalVmAmd64") {
|
||||
configureVerifyGraalVm(buildInfo.graalVmAmd64)
|
||||
dependsOn(downloadGraalVmAmd64)
|
||||
}
|
||||
@@ -60,15 +64,23 @@ fun Verify.configureVerifyGraalVm(graalvm: BuildInfo.GraalVm) {
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
val installGraalVmAarch64 by
|
||||
tasks.registering(InstallGraalVm::class) {
|
||||
val installGraalVmAarch64 =
|
||||
tasks.register<InstallGraalVm>("installGraalVmAarch64") {
|
||||
dependsOn(verifyGraalVmAarch64)
|
||||
graalVm = buildInfo.graalVmAarch64
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
val installGraalVmAmd64 by
|
||||
tasks.registering(InstallGraalVm::class) {
|
||||
val installGraalVmAmd64 =
|
||||
tasks.register<InstallGraalVm>("installGraalVmAmd64") {
|
||||
dependsOn(verifyGraalVmAmd64)
|
||||
graalVm = buildInfo.graalVmAmd64
|
||||
}
|
||||
|
||||
// Picked up by GitHub Actions and uploaded as a build artifact
|
||||
tasks.withType<NativeImageBuild>().configureEach {
|
||||
compiledClassesFile =
|
||||
layout.buildDirectory.file(
|
||||
"compiled-classes/${project.name}-${project.version}-${buildInfo.targetMachine.targetName}.txt"
|
||||
)
|
||||
}
|
||||
@@ -47,8 +47,8 @@ dependencies {
|
||||
}
|
||||
}
|
||||
|
||||
val validateHtml by
|
||||
tasks.registering(JavaExec::class) {
|
||||
val validateHtml =
|
||||
tasks.register<JavaExec>("validateHtml") {
|
||||
val resultFile = layout.buildDirectory.file("validateHtml/result.txt")
|
||||
inputs.files(htmlValidator.sources)
|
||||
outputs.file(resultFile)
|
||||
|
||||
@@ -21,13 +21,14 @@ plugins {
|
||||
id("pklJavaLibrary")
|
||||
// id("pklPublishLibrary")
|
||||
id("com.gradleup.shadow")
|
||||
id("pklJvmSourceBundle")
|
||||
}
|
||||
|
||||
val executableSpec = project.extensions.create("executable", ExecutableSpec::class.java)
|
||||
val buildInfo = project.extensions.getByType<BuildInfo>()
|
||||
|
||||
val javaExecutable by
|
||||
tasks.registering(ExecutableJar::class) {
|
||||
val javaExecutable =
|
||||
tasks.register<ExecutableJar>("javaExecutable") {
|
||||
group = "build"
|
||||
dependsOn(tasks.jar)
|
||||
inJar = tasks.shadowJar.flatMap { it.archiveFile }
|
||||
@@ -77,7 +78,8 @@ fun Task.setupTestStartJavaExecutable(launcher: Provider<JavaLauncher>? = null)
|
||||
}
|
||||
}
|
||||
|
||||
val testStartJavaExecutable by tasks.registering { setupTestStartJavaExecutable() }
|
||||
val testStartJavaExecutable =
|
||||
tasks.register("testStartJavaExecutable") { setupTestStartJavaExecutable() }
|
||||
|
||||
// Setup `testStartJavaExecutable` tasks for multi-JDK testing.
|
||||
val testStartJavaExecutableOnOtherJdks =
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
// Source bundle for the JVM variant of a project: its fat JAR, or its executable JAR.
|
||||
//
|
||||
// These contain the complete sources of all dependencies, because fat JARs contain the complete
|
||||
// classes of all dependencies.
|
||||
plugins { id("pklSourceBundle") }
|
||||
|
||||
val sourceBundleSpec = extensions.getByType<SourceBundleSpec>()
|
||||
|
||||
val sourceBundleJava =
|
||||
tasks.register<BuildSourceBundle>("sourceBundleJava") {
|
||||
group = "build"
|
||||
description = "Builds the source bundle of ${project.name}'s JVM variant."
|
||||
inputJars.from(sourceBundleSpec.firstPartyJars, sourceBundleSpec.dependencyJars)
|
||||
outputZip =
|
||||
layout.buildDirectory.file(
|
||||
"sourceBundle/${project.name}-${project.version}-java-sourcebundle.zip"
|
||||
)
|
||||
}
|
||||
|
||||
tasks.named("sourceBundle") { dependsOn(sourceBundleJava) }
|
||||
@@ -22,6 +22,7 @@ plugins {
|
||||
id("pklGraalVm")
|
||||
id("pklJavaLibrary")
|
||||
id("pklNativeLifecycle")
|
||||
id("pklNativeSourceBundle")
|
||||
id("pklPublishLibrary")
|
||||
id("com.gradleup.shadow")
|
||||
}
|
||||
@@ -30,54 +31,66 @@ plugins {
|
||||
val executableSpec = project.extensions.getByType<ExecutableSpec>()
|
||||
val buildInfo = project.extensions.getByType<BuildInfo>()
|
||||
|
||||
val stagedMacAmd64Executable: Configuration by configurations.creating
|
||||
val stagedMacAarch64Executable: Configuration by configurations.creating
|
||||
val stagedLinuxAmd64Executable: Configuration by configurations.creating
|
||||
val stagedLinuxAarch64Executable: Configuration by configurations.creating
|
||||
val stagedAlpineLinuxAmd64Executable: Configuration by configurations.creating
|
||||
val stagedWindowsAmd64Executable: Configuration by configurations.creating
|
||||
// musl executables are linked with `--static`, so musl and zlib become part of the executable
|
||||
extensions.getByType<SourceBundleSpec>().staticMuslLibc = true
|
||||
|
||||
val nativeImageClasspath by configurations.creating {
|
||||
extendsFrom(configurations.runtimeClasspath.get())
|
||||
// Ensure native-image version uses GraalVM C SDKs instead of Java FFI or JNA
|
||||
// (comes from artifact `mordant-jvm-graal-ffi`).
|
||||
exclude("com.github.ajalt.mordant", "mordant-jvm-ffm")
|
||||
exclude("com.github.ajalt.mordant", "mordant-jvm-ffm-jvm")
|
||||
exclude("com.github.ajalt.mordant", "mordant-jvm-jna")
|
||||
exclude("com.github.ajalt.mordant", "mordant-jvm-jna-jvm")
|
||||
}
|
||||
val stagedMacAarch64Executable: Configuration = configurations.create("stagedMacAarch64Executable")
|
||||
val stagedLinuxAmd64Executable: Configuration = configurations.create("stagedLinuxAmd64Executable")
|
||||
val stagedLinuxAarch64Executable: Configuration =
|
||||
configurations.create("stagedLinuxAarch64Executable")
|
||||
val stagedAlpineLinuxAmd64Executable: Configuration =
|
||||
configurations.create("stagedAlpineLinuxAmd64Executable")
|
||||
val stagedWindowsAmd64Executable: Configuration =
|
||||
configurations.create("stagedWindowsAmd64Executable")
|
||||
|
||||
val nativeImageClasspath =
|
||||
configurations.create("nativeImageClasspath") {
|
||||
extendsFrom(configurations.runtimeClasspath.get())
|
||||
// Ensure native-image version uses GraalVM C SDKs instead of Java FFI or JNA
|
||||
// (comes from artifact `mordant-jvm-graal-ffi`).
|
||||
exclude("com.github.ajalt.mordant", "mordant-jvm-ffm")
|
||||
exclude("com.github.ajalt.mordant", "mordant-jvm-ffm-jvm")
|
||||
exclude("com.github.ajalt.mordant", "mordant-jvm-jna")
|
||||
exclude("com.github.ajalt.mordant", "mordant-jvm-jna-jvm")
|
||||
}
|
||||
|
||||
val libs = the<LibrariesForLibs>()
|
||||
|
||||
dependencies {
|
||||
fun executableFile(suffix: String) =
|
||||
fun executableFile(target: Target) =
|
||||
files(
|
||||
layout.buildDirectory.dir("executable").map { dir ->
|
||||
dir.file(executableSpec.name.map { "$it-$suffix" })
|
||||
dir.file(
|
||||
executableSpec.name.map { name ->
|
||||
if (target.os.isWindows) "$name-${target.targetName}.exe"
|
||||
else "$name-${target.targetName}"
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
nativeImageClasspath(libs.truffleRuntime)
|
||||
nativeImageClasspath(libs.graalSdk)
|
||||
|
||||
stagedMacAarch64Executable(executableFile("macos-aarch64"))
|
||||
stagedMacAmd64Executable(executableFile("macos-amd64"))
|
||||
stagedLinuxAmd64Executable(executableFile("linux-amd64"))
|
||||
stagedLinuxAarch64Executable(executableFile("linux-aarch64"))
|
||||
stagedAlpineLinuxAmd64Executable(executableFile("alpine-linux-amd64"))
|
||||
stagedWindowsAmd64Executable(executableFile("windows-amd64.exe"))
|
||||
stagedMacAarch64Executable(executableFile(Target.MacosAarch64))
|
||||
stagedLinuxAarch64Executable(executableFile(Target.LinuxAarch64))
|
||||
stagedLinuxAmd64Executable(executableFile(Target.LinuxAmd64))
|
||||
stagedAlpineLinuxAmd64Executable(executableFile(Target.AlpineLinuxAmd64))
|
||||
stagedWindowsAmd64Executable(executableFile(Target.WindowsAmd64))
|
||||
}
|
||||
|
||||
private fun NativeImageBuild.amd64() {
|
||||
arch = Architecture.AMD64
|
||||
dependsOn(":installGraalVmAmd64")
|
||||
}
|
||||
private fun NativeImageBuild.configure(target: Target) {
|
||||
arch = target.arch
|
||||
|
||||
private fun NativeImageBuild.aarch64() {
|
||||
arch = Architecture.AARCH64
|
||||
dependsOn(":installGraalVmAarch64")
|
||||
}
|
||||
outputName = executableSpec.name.map { "$it-${target.targetName}" }
|
||||
mainClass = executableSpec.mainClass
|
||||
|
||||
if (target.arch == Target.Arch.AARCH64) {
|
||||
dependsOn(":installGraalVmAarch64")
|
||||
} else {
|
||||
dependsOn(":installGraalVmAmd64")
|
||||
}
|
||||
|
||||
private fun NativeImageBuild.setClasspath() {
|
||||
classpath.from(sourceSets.main.map { it.output })
|
||||
classpath.from(
|
||||
project(":pkl-commons-cli").extensions.getByType(SourceSetContainer::class)["svm"].output
|
||||
@@ -85,117 +98,93 @@ private fun NativeImageBuild.setClasspath() {
|
||||
classpath.from(nativeImageClasspath)
|
||||
}
|
||||
|
||||
val macExecutableAmd64 by
|
||||
tasks.registering(NativeImageBuild::class) {
|
||||
imageName = executableSpec.name.map { "$it-macos-amd64" }
|
||||
mainClass = executableSpec.mainClass
|
||||
amd64()
|
||||
setClasspath()
|
||||
}
|
||||
val macExecutableAarch64 =
|
||||
tasks.register<NativeImageBuild>("macExecutableAarch64") { configure(Target.MacosAarch64) }
|
||||
|
||||
val macExecutableAarch64 by
|
||||
tasks.registering(NativeImageBuild::class) {
|
||||
imageName = executableSpec.name.map { "$it-macos-aarch64" }
|
||||
mainClass = executableSpec.mainClass
|
||||
aarch64()
|
||||
setClasspath()
|
||||
}
|
||||
val linuxExecutableAmd64 =
|
||||
tasks.register<NativeImageBuild>("linuxExecutableAmd64") { configure(Target.LinuxAmd64) }
|
||||
|
||||
val linuxExecutableAmd64 by
|
||||
tasks.registering(NativeImageBuild::class) {
|
||||
imageName = executableSpec.name.map { "$it-linux-amd64" }
|
||||
mainClass = executableSpec.mainClass
|
||||
amd64()
|
||||
setClasspath()
|
||||
}
|
||||
|
||||
val linuxExecutableAarch64 by
|
||||
tasks.registering(NativeImageBuild::class) {
|
||||
imageName = executableSpec.name.map { "$it-linux-aarch64" }
|
||||
mainClass = executableSpec.mainClass
|
||||
aarch64()
|
||||
setClasspath()
|
||||
val linuxExecutableAarch64 =
|
||||
tasks.register<NativeImageBuild>("linuxExecutableAarch64") {
|
||||
configure(Target.LinuxAarch64)
|
||||
// Ensure compatibility for kernels with page size set to 4k, 16k and 64k
|
||||
// (e.g. Raspberry Pi 5, Asahi Linux)
|
||||
extraNativeImageArgs.add("-H:PageSize=65536")
|
||||
}
|
||||
|
||||
val alpineExecutableAmd64 by
|
||||
tasks.registering(NativeImageBuild::class) {
|
||||
imageName = executableSpec.name.map { "$it-alpine-linux-amd64" }
|
||||
mainClass = executableSpec.mainClass
|
||||
amd64()
|
||||
setClasspath()
|
||||
extraNativeImageArgs.addAll(listOf("--static", "--libc=musl"))
|
||||
val alpineExecutableAmd64 =
|
||||
tasks.register<NativeImageBuild>("alpineExecutableAmd64") {
|
||||
configure(Target.AlpineLinuxAmd64)
|
||||
extraNativeImageArgs.addAll("--static", "--libc=musl")
|
||||
}
|
||||
|
||||
val windowsExecutableAmd64 by
|
||||
tasks.registering(NativeImageBuild::class) {
|
||||
imageName = executableSpec.name.map { "$it-windows-amd64" }
|
||||
mainClass = executableSpec.mainClass
|
||||
amd64()
|
||||
setClasspath()
|
||||
val windowsExecutableAmd64 =
|
||||
tasks.register<NativeImageBuild>("windowsExecutableAmd64") {
|
||||
configure(Target.WindowsAmd64)
|
||||
extraNativeImageArgs.add("-Dfile.encoding=UTF-8")
|
||||
}
|
||||
|
||||
val assembleNative by tasks.existing
|
||||
val assembleNative = tasks.named("assembleNative")
|
||||
|
||||
val testStartNativeExecutable by tasks.registering {
|
||||
dependsOn(assembleNative)
|
||||
val testStartNativeExecutable =
|
||||
tasks.register("testStartNativeExecutable") {
|
||||
dependsOn(assembleNative)
|
||||
|
||||
// dummy file for up-to-date checking
|
||||
val outputFile = project.layout.buildDirectory.file("testStartNativeExecutable/output.txt")
|
||||
outputs.file(outputFile)
|
||||
// dummy file for up-to-date checking
|
||||
val outputFile = project.layout.buildDirectory.file("testStartNativeExecutable/output.txt")
|
||||
outputs.file(outputFile)
|
||||
|
||||
val execOutput = providers.exec {
|
||||
commandLine(assembleNative.get().outputs.files.singleFile, "--version")
|
||||
}
|
||||
|
||||
doLast {
|
||||
val outputText = execOutput.standardOutput.asText.get()
|
||||
if (!outputText.contains(buildInfo.pklVersionNonUnique)) {
|
||||
throw GradleException(
|
||||
"Expected version output to contain current version (${buildInfo.pklVersionNonUnique}), but got '$outputText'"
|
||||
)
|
||||
val execOutput = providers.exec {
|
||||
commandLine(assembleNative.get().outputs.files.singleFile, "--version")
|
||||
}
|
||||
outputFile.get().asFile.toPath().apply {
|
||||
try {
|
||||
parent.createDirectories()
|
||||
} catch (_: java.nio.file.FileAlreadyExistsException) {}
|
||||
writeText("OK")
|
||||
|
||||
doLast {
|
||||
val outputText = execOutput.standardOutput.asText.get()
|
||||
if (!outputText.contains(buildInfo.pklVersionNonUnique)) {
|
||||
throw GradleException(
|
||||
"Expected version output to contain current version (${buildInfo.pklVersionNonUnique}), but got '$outputText'"
|
||||
)
|
||||
}
|
||||
outputFile.get().asFile.toPath().apply {
|
||||
try {
|
||||
parent.createDirectories()
|
||||
} catch (_: java.nio.file.FileAlreadyExistsException) {}
|
||||
writeText("OK")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val requiredGlibcVersion: Version = Version.parse("2.17")
|
||||
|
||||
val checkGlibc by tasks.registering {
|
||||
enabled = buildInfo.os.isLinux && !buildInfo.musl
|
||||
dependsOn(assembleNative)
|
||||
doLast {
|
||||
val exec = providers.exec {
|
||||
commandLine("objdump", "-T", assembleNative.get().outputs.files.singleFile)
|
||||
}
|
||||
val output = exec.standardOutput.asText.get()
|
||||
val minimumGlibcVersion =
|
||||
output
|
||||
.split("\n")
|
||||
.mapNotNull { line ->
|
||||
val match = Regex("GLIBC_([.0-9]*)").find(line)
|
||||
match?.groups[1]?.let { Version.parse(it.value) }
|
||||
}
|
||||
.maxOrNull()
|
||||
if (minimumGlibcVersion == null) {
|
||||
throw GradleException(
|
||||
"Could not determine glibc version from executable. objdump output: $output"
|
||||
)
|
||||
}
|
||||
if (minimumGlibcVersion > requiredGlibcVersion) {
|
||||
throw GradleException(
|
||||
"Incorrect glibc version. Found: $minimumGlibcVersion, required: $requiredGlibcVersion"
|
||||
)
|
||||
val checkGlibc =
|
||||
tasks.register("checkGlibc") {
|
||||
enabled = buildInfo.os.isLinux && !buildInfo.musl
|
||||
dependsOn(assembleNative)
|
||||
doLast {
|
||||
val exec = providers.exec {
|
||||
commandLine("objdump", "-T", assembleNative.get().outputs.files.singleFile)
|
||||
}
|
||||
val output = exec.standardOutput.asText.get()
|
||||
val minimumGlibcVersion =
|
||||
output
|
||||
.split("\n")
|
||||
.mapNotNull { line ->
|
||||
val match = Regex("GLIBC_([.0-9]*)").find(line)
|
||||
match?.groups[1]?.let { Version.parse(it.value) }
|
||||
}
|
||||
.maxOrNull()
|
||||
if (minimumGlibcVersion == null) {
|
||||
throw GradleException(
|
||||
"Could not determine glibc version from executable. objdump output: $output"
|
||||
)
|
||||
}
|
||||
if (minimumGlibcVersion > requiredGlibcVersion) {
|
||||
throw GradleException(
|
||||
"Incorrect glibc version. Found: $minimumGlibcVersion, required: $requiredGlibcVersion"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Expose underlying task's outputs
|
||||
private fun <T : Task> Task.wraps(other: TaskProvider<T>) {
|
||||
@@ -203,124 +192,68 @@ private fun <T : Task> Task.wraps(other: TaskProvider<T>) {
|
||||
outputs.files(other)
|
||||
}
|
||||
|
||||
val testNative by tasks.existing { dependsOn(testStartNativeExecutable, checkGlibc) }
|
||||
val testNative = tasks.named("testNative") { dependsOn(testStartNativeExecutable, checkGlibc) }
|
||||
|
||||
val assembleNativeMacOsAarch64 by tasks.existing { wraps(macExecutableAarch64) }
|
||||
val assembleNativeMacOsAarch64 =
|
||||
tasks.named("assembleNativeMacOsAarch64") { wraps(macExecutableAarch64) }
|
||||
|
||||
val assembleNativeMacOsAmd64 by tasks.existing { wraps(macExecutableAmd64) }
|
||||
val assembleNativeLinuxAarch64 =
|
||||
tasks.named("assembleNativeLinuxAarch64") { wraps(linuxExecutableAarch64) }
|
||||
|
||||
val assembleNativeLinuxAarch64 by tasks.existing { wraps(linuxExecutableAarch64) }
|
||||
val assembleNativeLinuxAmd64 =
|
||||
tasks.named("assembleNativeLinuxAmd64") { wraps(linuxExecutableAmd64) }
|
||||
|
||||
val assembleNativeLinuxAmd64 by tasks.existing { wraps(linuxExecutableAmd64) }
|
||||
val assembleNativeAlpineLinuxAmd64 =
|
||||
tasks.named("assembleNativeAlpineLinuxAmd64") { wraps(alpineExecutableAmd64) }
|
||||
|
||||
val assembleNativeAlpineLinuxAmd64 by tasks.existing { wraps(alpineExecutableAmd64) }
|
||||
val assembleNativeWindowsAmd64 =
|
||||
tasks.named("assembleNativeWindowsAmd64") { wraps(windowsExecutableAmd64) }
|
||||
|
||||
val assembleNativeWindowsAmd64 by tasks.existing { wraps(windowsExecutableAmd64) }
|
||||
private fun MavenPublication.configurePublication(target: Target, configuration: Configuration) {
|
||||
artifactId = "${executableSpec.publicationName.get()}-${target.targetName}"
|
||||
pom {
|
||||
name = "${executableSpec.publicationName.get()}-${target.targetName}"
|
||||
url = executableSpec.website
|
||||
artifact(configuration.singleFile) {
|
||||
classifier = null
|
||||
extension = if (target.os.isWindows) "exe" else "bin"
|
||||
builtBy(configuration)
|
||||
}
|
||||
description =
|
||||
executableSpec.documentationName.map { name ->
|
||||
buildString {
|
||||
append("Native $name executable for ${target.os.displayName}/${target.arch}")
|
||||
if (target.musl) {
|
||||
append(" and statically linked to musl")
|
||||
}
|
||||
append(".")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
// need to put in `afterEvaluate` because `artifactId` cannot be set lazily.
|
||||
project.afterEvaluate {
|
||||
create<MavenPublication>("macExecutableAmd64") {
|
||||
artifactId = "${executableSpec.publicationName.get()}-macos-amd64"
|
||||
artifact(stagedMacAmd64Executable.singleFile) {
|
||||
classifier = null
|
||||
extension = "bin"
|
||||
builtBy(stagedMacAmd64Executable)
|
||||
}
|
||||
pom {
|
||||
name = "${executableSpec.publicationName.get()}-macos-amd64"
|
||||
url = executableSpec.website
|
||||
description =
|
||||
executableSpec.documentationName.map { name ->
|
||||
"Native $name executable for macOS/amd64."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
create<MavenPublication>("macExecutableAarch64") {
|
||||
artifactId = "${executableSpec.publicationName.get()}-macos-aarch64"
|
||||
artifact(stagedMacAarch64Executable.singleFile) {
|
||||
classifier = null
|
||||
extension = "bin"
|
||||
builtBy(stagedMacAarch64Executable)
|
||||
}
|
||||
pom {
|
||||
name = "${executableSpec.publicationName.get()}-macos-aarch64"
|
||||
url = executableSpec.website
|
||||
description =
|
||||
executableSpec.documentationName.map { name ->
|
||||
"Native $name executable for macOS/aarch64."
|
||||
}
|
||||
}
|
||||
configurePublication(Target.MacosAarch64, stagedMacAarch64Executable)
|
||||
}
|
||||
|
||||
create<MavenPublication>("linuxExecutableAmd64") {
|
||||
artifactId = "${executableSpec.publicationName.get()}-linux-amd64"
|
||||
artifact(stagedLinuxAmd64Executable.singleFile) {
|
||||
classifier = null
|
||||
extension = "bin"
|
||||
builtBy(stagedLinuxAmd64Executable)
|
||||
}
|
||||
pom {
|
||||
name = "${executableSpec.publicationName.get()}-linux-amd64"
|
||||
url = executableSpec.website
|
||||
description =
|
||||
executableSpec.documentationName.map { name ->
|
||||
"Native $name executable for linux/amd64."
|
||||
}
|
||||
}
|
||||
configurePublication(Target.LinuxAmd64, stagedLinuxAmd64Executable)
|
||||
}
|
||||
|
||||
create<MavenPublication>("linuxExecutableAarch64") {
|
||||
artifactId = "${executableSpec.publicationName.get()}-linux-aarch64"
|
||||
artifact(stagedLinuxAarch64Executable.singleFile) {
|
||||
classifier = null
|
||||
extension = "bin"
|
||||
builtBy(stagedLinuxAarch64Executable)
|
||||
}
|
||||
pom {
|
||||
name = "${executableSpec.publicationName.get()}-linux-aarch64"
|
||||
url = executableSpec.website
|
||||
description =
|
||||
executableSpec.documentationName.map { name ->
|
||||
"Native $name executable for linux/aarch64."
|
||||
}
|
||||
}
|
||||
configurePublication(Target.LinuxAarch64, stagedLinuxAarch64Executable)
|
||||
}
|
||||
|
||||
create<MavenPublication>("alpineLinuxExecutableAmd64") {
|
||||
artifactId = "${executableSpec.publicationName.get()}-alpine-linux-amd64"
|
||||
artifact(stagedAlpineLinuxAmd64Executable.singleFile) {
|
||||
classifier = null
|
||||
extension = "bin"
|
||||
builtBy(stagedAlpineLinuxAmd64Executable)
|
||||
}
|
||||
pom {
|
||||
name = "${executableSpec.publicationName.get()}-alpine-linux-amd64"
|
||||
url = executableSpec.website
|
||||
description =
|
||||
executableSpec.documentationName.map { name ->
|
||||
"Native $name executable for linux/amd64 and statically linked to musl."
|
||||
}
|
||||
}
|
||||
configurePublication(Target.AlpineLinuxAmd64, stagedAlpineLinuxAmd64Executable)
|
||||
}
|
||||
|
||||
create<MavenPublication>("windowsExecutableAmd64") {
|
||||
artifactId = "${executableSpec.publicationName.get()}-windows-amd64"
|
||||
artifact(stagedWindowsAmd64Executable.singleFile) {
|
||||
classifier = null
|
||||
extension = "exe"
|
||||
builtBy(stagedWindowsAmd64Executable)
|
||||
}
|
||||
pom {
|
||||
name = "${executableSpec.publicationName.get()}-windows-amd64"
|
||||
url = executableSpec.website
|
||||
description =
|
||||
executableSpec.documentationName.map { name ->
|
||||
"Native $name executable for windows/amd64."
|
||||
}
|
||||
}
|
||||
configurePublication(Target.WindowsAmd64, stagedWindowsAmd64Executable)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -331,7 +264,6 @@ signing {
|
||||
sign(publishing.publications["linuxExecutableAarch64"])
|
||||
sign(publishing.publications["linuxExecutableAmd64"])
|
||||
sign(publishing.publications["macExecutableAarch64"])
|
||||
sign(publishing.publications["macExecutableAmd64"])
|
||||
sign(publishing.publications["alpineLinuxExecutableAmd64"])
|
||||
sign(publishing.publications["windowsExecutableAmd64"])
|
||||
}
|
||||
|
||||
@@ -13,29 +13,27 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
val assembleNativeMacOsAarch64 by tasks.registering { group = "build" }
|
||||
val assembleNativeMacOsAarch64 = tasks.register("assembleNativeMacOsAarch64") { group = "build" }
|
||||
|
||||
val assembleNativeMacOsAmd64 by tasks.registering { group = "build" }
|
||||
val assembleNativeLinuxAarch64 = tasks.register("assembleNativeLinuxAarch64") { group = "build" }
|
||||
|
||||
val assembleNativeLinuxAarch64 by tasks.registering { group = "build" }
|
||||
val assembleNativeLinuxAmd64 = tasks.register("assembleNativeLinuxAmd64") { group = "build" }
|
||||
|
||||
val assembleNativeLinuxAmd64 by tasks.registering { group = "build" }
|
||||
val assembleNativeAlpineLinuxAmd64 =
|
||||
tasks.register("assembleNativeAlpineLinuxAmd64") { group = "build" }
|
||||
|
||||
val assembleNativeAlpineLinuxAmd64 by tasks.registering { group = "build" }
|
||||
val assembleNativeWindowsAmd64 = tasks.register("assembleNativeWindowsAmd64") { group = "build" }
|
||||
|
||||
val assembleNativeWindowsAmd64 by tasks.registering { group = "build" }
|
||||
val testNativeMacOsAarch64 = tasks.register("testNativeMacOsAarch64") { group = "verification" }
|
||||
|
||||
val testNativeMacOsAarch64 by tasks.registering { group = "verification" }
|
||||
val testNativeLinuxAarch64 = tasks.register("testNativeLinuxAarch64") { group = "verification" }
|
||||
|
||||
val testNativeMacOsAmd64 by tasks.registering { group = "verification" }
|
||||
val testNativeLinuxAmd64 = tasks.register("testNativeLinuxAmd64") { group = "verification" }
|
||||
|
||||
val testNativeLinuxAarch64 by tasks.registering { group = "verification" }
|
||||
val testNativeAlpineLinuxAmd64 =
|
||||
tasks.register("testNativeAlpineLinuxAmd64") { group = "verification" }
|
||||
|
||||
val testNativeLinuxAmd64 by tasks.registering { group = "verification" }
|
||||
|
||||
val testNativeAlpineLinuxAmd64 by tasks.registering { group = "verification" }
|
||||
|
||||
val testNativeWindowsAmd64 by tasks.registering { group = "verification" }
|
||||
val testNativeWindowsAmd64 = tasks.register("testNativeWindowsAmd64") { group = "verification" }
|
||||
|
||||
val buildInfo = project.extensions.getByType<BuildInfo>()
|
||||
|
||||
@@ -44,79 +42,55 @@ private fun <T : Task> Task.wraps(other: TaskProvider<T>) {
|
||||
outputs.files(other)
|
||||
}
|
||||
|
||||
val assembleNative by tasks.registering {
|
||||
group = "build"
|
||||
val assembleNative =
|
||||
tasks.register("assembleNative") {
|
||||
group = "build"
|
||||
|
||||
if (!buildInfo.isCrossArchSupported && buildInfo.isCrossArch) {
|
||||
throw GradleException("Cross-arch builds are not supported on ${buildInfo.os.name}")
|
||||
}
|
||||
|
||||
when {
|
||||
buildInfo.os.isMacOsX && buildInfo.targetArch == "aarch64" -> {
|
||||
wraps(assembleNativeMacOsAarch64)
|
||||
}
|
||||
buildInfo.os.isMacOsX && buildInfo.targetArch == "amd64" -> {
|
||||
wraps(assembleNativeMacOsAmd64)
|
||||
}
|
||||
buildInfo.os.isLinux && buildInfo.targetArch == "aarch64" -> {
|
||||
wraps(assembleNativeLinuxAarch64)
|
||||
}
|
||||
buildInfo.os.isLinux && buildInfo.targetArch == "amd64" -> {
|
||||
if (buildInfo.musl) wraps(assembleNativeAlpineLinuxAmd64) else wraps(assembleNativeLinuxAmd64)
|
||||
}
|
||||
buildInfo.os.isWindows && buildInfo.targetArch == "amd64" -> {
|
||||
wraps(assembleNativeWindowsAmd64)
|
||||
}
|
||||
else -> {
|
||||
@Suppress("DuplicatedCode")
|
||||
if (!buildInfo.isCrossArchSupported && buildInfo.isCrossArch) {
|
||||
doLast {
|
||||
throw GradleException(
|
||||
"Cannot build targeting ${buildInfo.os.name}/${buildInfo.targetArch} with musl=${buildInfo.musl}"
|
||||
)
|
||||
throw GradleException("Cross-arch builds are not supported on ${buildInfo.os.name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val testNative by tasks.registering {
|
||||
group = "verification"
|
||||
dependsOn(assembleNative)
|
||||
|
||||
if (!buildInfo.isCrossArchSupported && buildInfo.isCrossArch) {
|
||||
throw GradleException("Cross-arch builds are not supported on ${buildInfo.os.name}")
|
||||
@Suppress("DuplicatedCode")
|
||||
when (buildInfo.targetMachine) {
|
||||
Target.MacosAarch64 -> wraps(assembleNativeMacOsAarch64)
|
||||
Target.LinuxAarch64 -> wraps(assembleNativeLinuxAarch64)
|
||||
Target.LinuxAmd64 -> wraps(assembleNativeLinuxAmd64)
|
||||
Target.AlpineLinuxAmd64 -> wraps(assembleNativeAlpineLinuxAmd64)
|
||||
Target.WindowsAmd64 -> wraps(assembleNativeWindowsAmd64)
|
||||
}
|
||||
}
|
||||
|
||||
when {
|
||||
buildInfo.os.isMacOsX && buildInfo.targetArch == "aarch64" -> {
|
||||
dependsOn(testNativeMacOsAarch64)
|
||||
val testNative =
|
||||
tasks.register("testNative") {
|
||||
group = "verification"
|
||||
dependsOn(assembleNative)
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
if (!buildInfo.isCrossArchSupported && buildInfo.isCrossArch) {
|
||||
throw GradleException("Cross-arch builds are not supported on ${buildInfo.os.name}")
|
||||
}
|
||||
buildInfo.os.isMacOsX && buildInfo.targetArch == "amd64" -> {
|
||||
dependsOn(testNativeMacOsAmd64)
|
||||
}
|
||||
buildInfo.os.isLinux && buildInfo.targetArch == "aarch64" -> {
|
||||
dependsOn(testNativeLinuxAarch64)
|
||||
}
|
||||
buildInfo.os.isLinux && buildInfo.targetArch == "amd64" -> {
|
||||
if (buildInfo.musl) dependsOn(testNativeAlpineLinuxAmd64) else dependsOn(testNativeLinuxAmd64)
|
||||
}
|
||||
buildInfo.os.isWindows && buildInfo.targetArch == "amd64" -> {
|
||||
dependsOn(testNativeWindowsAmd64)
|
||||
}
|
||||
else -> {
|
||||
doLast {
|
||||
throw GradleException(
|
||||
"Cannot build targeting ${buildInfo.os.name}/${buildInfo.targetArch} with musl=${buildInfo.musl}"
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
when (buildInfo.targetMachine) {
|
||||
Target.MacosAarch64 -> wraps(testNativeMacOsAarch64)
|
||||
Target.LinuxAarch64 -> wraps(testNativeLinuxAarch64)
|
||||
Target.LinuxAmd64 -> wraps(testNativeLinuxAmd64)
|
||||
Target.AlpineLinuxAmd64 -> wraps(testNativeAlpineLinuxAmd64)
|
||||
Target.WindowsAmd64 -> wraps(testNativeWindowsAmd64)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val checkNative by tasks.registering {
|
||||
group = "verification"
|
||||
dependsOn(testNative)
|
||||
}
|
||||
val checkNative =
|
||||
tasks.register("checkNative") {
|
||||
group = "verification"
|
||||
dependsOn(testNative)
|
||||
}
|
||||
|
||||
val buildNative by tasks.registering {
|
||||
group = "build"
|
||||
dependsOn(checkNative)
|
||||
}
|
||||
val buildNative =
|
||||
tasks.register("buildNative") {
|
||||
group = "build"
|
||||
dependsOn(checkNative)
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import javax.inject.Inject
|
||||
import org.gradle.accessors.dm.LibrariesForLibs
|
||||
import org.gradle.api.tasks.options.Option
|
||||
import org.gradle.process.ExecOperations
|
||||
|
||||
// Source bundles for the native variants of a project, one for each `Target`.
|
||||
//
|
||||
// Each bundle consists of the sources of everything that is compiled into the native image or
|
||||
// library of that target:
|
||||
// * the project's own sources, and those of its first-party dependencies;
|
||||
// * the sources of third-party dependencies that have classes in the image;
|
||||
// * the Java sources of the JDK, SubstrateVM, and Graal classes that are in the image;
|
||||
// * the C sources of the JDK and SubstrateVM native libraries that are linked into the image;
|
||||
// * for statically linked musl executables, the sources of musl and zlib.
|
||||
//
|
||||
// Which classes are in the image is read from the lists in `build/compiled-classes` (download them
|
||||
// with the `downloadCompiledClasses` task). native-image produces these lists, and CI uploads them
|
||||
// as `compiled-classes-<project>-*` artifacts. This way, the source bundle of any target can be
|
||||
// built on any machine.
|
||||
//
|
||||
// Sources of the JDK, SubstrateVM, and Graal come from the OpenJDK and Graal repositories at the
|
||||
// tags matching the GraalVM that is used to build, because GraalVM doesn't ship all of the Java
|
||||
// sources (or any of the C sources).
|
||||
plugins { id("pklSourceBundle") }
|
||||
|
||||
val libs = the<LibrariesForLibs>()
|
||||
val sourceBundleSpec = extensions.getByType<SourceBundleSpec>()
|
||||
|
||||
val openJdkSourcesTarball: Configuration = configurations.create("openJdkSourcesTarball")
|
||||
val graalSourcesTarball: Configuration = configurations.create("graalSourcesTarball")
|
||||
|
||||
val muslSourcesTarball: Configuration = configurations.create("muslSourcesTarball")
|
||||
val zlibSourcesTarball: Configuration = configurations.create("zlibSourcesTarball")
|
||||
|
||||
// CI builds musl and zlib from source for statically linked executables; use the same versions.
|
||||
fun muslToolchainVersion(name: String): String {
|
||||
val script = rootProject.file(".github/scripts/install_musl.sh").readText()
|
||||
return Regex("${name}_VERSION=\"([^\"]+)\"").find(script)?.groupValues?.get(1)
|
||||
?: throw GradleException("Could not find ${name}_VERSION in .github/scripts/install_musl.sh")
|
||||
}
|
||||
|
||||
// The "GitHub tag archives" and "musl releases" repositories are declared in settings.gradle.kts;
|
||||
// declaring repositories in a project would stop it from using the ones declared in settings.
|
||||
dependencies {
|
||||
val jdkVersion = libs.versions.graalVmJdkVersion.get()
|
||||
openJdkSourcesTarball("openjdk:jdk${jdkVersion.substringBefore('.')}u:jdk-$jdkVersion-ga")
|
||||
graalSourcesTarball("oracle:graal:vm-${libs.versions.graalVm.get()}")
|
||||
muslSourcesTarball("musl:musl:${muslToolchainVersion("MUSL")}")
|
||||
zlibSourcesTarball("madler:zlib:v${muslToolchainVersion("ZLIB")}")
|
||||
}
|
||||
|
||||
// native-image's class path can differ from the runtime class path (e.g. `nativeImageClasspath` in
|
||||
// `pklNativeExecutable`), so resolve the sources of what's actually compiled in.
|
||||
val resolveNativeSourcesJars =
|
||||
tasks.register<ResolveSourcesJars>("resolveNativeSourcesJars") {
|
||||
configuration.set(
|
||||
provider {
|
||||
configurations.findByName("nativeImageClasspath")
|
||||
?: configurations.getByName("runtimeClasspath")
|
||||
}
|
||||
)
|
||||
outputDir.set(layout.buildDirectory.dir("resolveNativeSourcesJars"))
|
||||
}
|
||||
|
||||
// The JDK native libraries that native-image links into the image.
|
||||
val openJdkNativeLibraries = listOf("java", "net", "nio", "zip", "extnet", "management_ext")
|
||||
|
||||
// Platform-specific directories in OpenJDK's `src/<module>/<platform>/`.
|
||||
val Target.OS.openJdkPlatforms: List<String>
|
||||
get() = buildList {
|
||||
add("share")
|
||||
if (!isWindows) add("unix")
|
||||
add(
|
||||
when {
|
||||
isMacOS -> "macosx"
|
||||
isWindows -> "windows"
|
||||
else -> "linux"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// The `substratevm/src/com.oracle.svm.native.*` projects that apply to the OS.
|
||||
val Target.OS.svmNativeProjects: List<String>
|
||||
get() = buildList {
|
||||
add("libchelper")
|
||||
when {
|
||||
isMacOS -> {
|
||||
add("darwin")
|
||||
add("jvm.posix")
|
||||
}
|
||||
isLinux -> {
|
||||
add("libcontainer")
|
||||
add("jvm.posix")
|
||||
}
|
||||
isWindows -> add("jvm.windows")
|
||||
}
|
||||
}
|
||||
|
||||
val sourceBundleTasks =
|
||||
Target.entries.map { target ->
|
||||
val variant = target.name
|
||||
val outputDir = layout.buildDirectory.dir("tmp/nativeImageSources/${target.targetName}")
|
||||
val compiledClassesFile =
|
||||
layout.buildDirectory.file("compiled-classes/${target.targetName}.txt")
|
||||
|
||||
val nativeImageSourcesJar =
|
||||
tasks.register<NativeImageSourcesJar>("nativeImageSourcesJar$variant") {
|
||||
compiledClasses = compiledClassesFile
|
||||
openJdkTarball.from(openJdkSourcesTarball)
|
||||
graalTarball.from(graalSourcesTarball)
|
||||
openJdkPlatforms.addAll(target.os.openJdkPlatforms)
|
||||
outputJar = outputDir.map { it.file("native-image-sources.jar") }
|
||||
}
|
||||
|
||||
val openJdkNativeSourcesJar =
|
||||
tasks.register<Zip>("openJdkNativeSourcesJar$variant") {
|
||||
archiveFileName = "openjdk-native-sources.jar"
|
||||
destinationDirectory = outputDir
|
||||
includeEmptyDirs = false
|
||||
|
||||
from(provider { tarTree(resources.gzip(openJdkSourcesTarball.singleFile)) }) {
|
||||
for (platform in target.os.openJdkPlatforms) {
|
||||
include("*/src/*/$platform/native/include/**")
|
||||
for (library in openJdkNativeLibraries) {
|
||||
include("*/src/*/$platform/native/lib$library/**")
|
||||
}
|
||||
}
|
||||
// drop the `<repo>-<tag>/` root directory
|
||||
eachFile {
|
||||
relativePath = RelativePath(true, *relativePath.segments.drop(1).toTypedArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val graalNativeSourcesJar =
|
||||
tasks.register<Zip>("graalNativeSourcesJar$variant") {
|
||||
archiveFileName = "graal-native-sources.jar"
|
||||
destinationDirectory = outputDir
|
||||
includeEmptyDirs = false
|
||||
|
||||
from(provider { tarTree(resources.gzip(graalSourcesTarball.singleFile)) }) {
|
||||
for (svmProject in target.os.svmNativeProjects) {
|
||||
include("*/substratevm/src/com.oracle.svm.native.$svmProject/**")
|
||||
}
|
||||
eachFile {
|
||||
relativePath = RelativePath(true, *relativePath.segments.drop(1).toTypedArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Executables for musl targets are statically linked, so musl and zlib are part of them.
|
||||
val staticLibcSources =
|
||||
if (target.musl) {
|
||||
val muslArch =
|
||||
when (target.arch) {
|
||||
Target.Arch.AMD64 -> "x86_64"
|
||||
Target.Arch.AARCH64 -> "aarch64"
|
||||
}
|
||||
|
||||
val muslSourcesJar =
|
||||
tasks.register<Zip>("muslSourcesJar$variant") {
|
||||
archiveFileName = "musl-sources.jar"
|
||||
destinationDirectory = outputDir
|
||||
includeEmptyDirs = false
|
||||
|
||||
from(provider { tarTree(resources.gzip(muslSourcesTarball.singleFile)) }) {
|
||||
// skip the other architectures
|
||||
include("*/src/**", "*/include/**", "*/crt/**", "*/ldso/**", "*/compat/**")
|
||||
include("*/arch/generic/**", "*/arch/$muslArch/**")
|
||||
include("*/COPYRIGHT", "*/VERSION", "*/WHATSNEW")
|
||||
eachFile {
|
||||
relativePath = RelativePath(true, *relativePath.segments.drop(1).toTypedArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val zlibSourcesJar =
|
||||
tasks.register<Zip>("zlibSourcesJar$variant") {
|
||||
archiveFileName = "zlib-sources.jar"
|
||||
destinationDirectory = outputDir
|
||||
includeEmptyDirs = false
|
||||
|
||||
from(provider { tarTree(resources.gzip(zlibSourcesTarball.singleFile)) }) {
|
||||
exclude("*/.github/**")
|
||||
eachFile {
|
||||
relativePath = RelativePath(true, *relativePath.segments.drop(1).toTypedArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceBundleSpec.staticMuslLibc.map { isStatic ->
|
||||
if (isStatic) files(muslSourcesJar, zlibSourcesJar) else files()
|
||||
}
|
||||
} else {
|
||||
provider { files() }
|
||||
}
|
||||
|
||||
tasks.register<BuildSourceBundle>("sourceBundle$variant") {
|
||||
group = "build"
|
||||
description = "Builds the source bundle of ${project.name} for ${target.targetName}."
|
||||
inputJars.from(
|
||||
sourceBundleSpec.firstPartyJars,
|
||||
sourceBundleSpec.extraNativeSources,
|
||||
nativeImageSourcesJar,
|
||||
openJdkNativeSourcesJar,
|
||||
graalNativeSourcesJar,
|
||||
staticLibcSources,
|
||||
)
|
||||
// Only include sources of dependency classes that are compiled into the image.
|
||||
filteredInputJars.from(resolveNativeSourcesJars.map { fileTree(it.outputDir) })
|
||||
compiledClasses = compiledClassesFile
|
||||
outputZip =
|
||||
layout.buildDirectory.file(
|
||||
"sourceBundle/${project.name}-${project.version}-${target.targetName}-sourcebundle.zip"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named("sourceBundle") { dependsOn(sourceBundleTasks) }
|
||||
|
||||
/**
|
||||
* Downloads the lists of classes compiled into native images, which CI uploads as
|
||||
* `compiled-classes-<project>-<os>-<arch>` artifacts, into `build/compiled-classes`.
|
||||
*
|
||||
* Requires the GitHub CLI (`gh`) to be installed and authenticated.
|
||||
*/
|
||||
abstract class DownloadCompiledClasses : DefaultTask() {
|
||||
@get:Option(option = "run-id", description = "ID of the GitHub Actions run to download from.")
|
||||
@get:Input
|
||||
abstract val runId: Property<String>
|
||||
|
||||
@get:Option(option = "repo", description = "GitHub repository that the run belongs to.")
|
||||
@get:Input
|
||||
abstract val repo: Property<String>
|
||||
|
||||
/** The project whose artifacts to download (named `compiled-classes-<project>-*`). */
|
||||
@get:Input abstract val projectName: Property<String>
|
||||
|
||||
@get:OutputDirectory abstract val destinationDir: DirectoryProperty
|
||||
|
||||
@get:Inject protected abstract val execOperations: ExecOperations
|
||||
|
||||
init {
|
||||
group = "build"
|
||||
description = "Downloads the compiled classes lists that CI produced into the build directory."
|
||||
repo.convention("apple/pkl")
|
||||
// always re-download; this task is meant to be run manually
|
||||
outputs.upToDateWhen { false }
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
@Suppress("unused")
|
||||
fun download() {
|
||||
if (!runId.isPresent) {
|
||||
throw GradleException("Specify the run to download from, e.g. `--run-id=<id>`")
|
||||
}
|
||||
val name = projectName.get()
|
||||
val downloadDir = temporaryDir.resolve("download")
|
||||
downloadDir.deleteRecursively()
|
||||
downloadDir.mkdirs()
|
||||
|
||||
execOperations.exec {
|
||||
commandLine(
|
||||
"gh",
|
||||
"run",
|
||||
"download",
|
||||
runId.get(),
|
||||
"--repo",
|
||||
repo.get(),
|
||||
"--pattern",
|
||||
"compiled-classes-$name-*",
|
||||
"--dir",
|
||||
downloadDir.absolutePath,
|
||||
)
|
||||
}
|
||||
|
||||
// Files are named `<project>-<version>-<target name>.txt`; keep only the target name.
|
||||
val fileName = Regex("^${Regex.escape(name)}-\\d+(?:\\.\\d+)*(?:-SNAPSHOT)?-(.+\\.txt)$")
|
||||
val downloaded =
|
||||
downloadDir
|
||||
.walkTopDown()
|
||||
.filter { it.isFile }
|
||||
.associate { file ->
|
||||
val target =
|
||||
fileName.matchEntire(file.name)?.groupValues?.get(1)
|
||||
?: throw GradleException("Unexpected file in downloaded artifacts: $file")
|
||||
target to file
|
||||
}
|
||||
if (downloaded.isEmpty()) {
|
||||
throw GradleException("Run ${runId.get()} has no `compiled-classes-$name-*` artifacts")
|
||||
}
|
||||
|
||||
val destination = destinationDir.get().asFile
|
||||
destination.mkdirs()
|
||||
// remove lists of targets that are no longer built
|
||||
destination.listFiles { f -> f.extension == "txt" }?.forEach { it.delete() }
|
||||
for ((target, file) in downloaded) {
|
||||
file.copyTo(destination.resolve(target))
|
||||
}
|
||||
logger.lifecycle("Downloaded ${downloaded.keys.sorted()} into $destination")
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register<DownloadCompiledClasses>("downloadCompiledClasses") {
|
||||
projectName = project.name
|
||||
destinationDir = layout.buildDirectory.dir("compiled-classes")
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
plugins { id("pklAllProjects") }
|
||||
|
||||
// Sources jars of the first-party projects that this project depends on.
|
||||
// Ideally we'd configure this automatically based on project dependencies.
|
||||
val firstPartySourcesJars: Configuration = configurations.create("firstPartySourcesJars")
|
||||
|
||||
// Sources jars of third-party dependencies. Project dependencies are not covered by this; they
|
||||
// are added to `firstPartySourcesJars` instead.
|
||||
val resolveSourcesJars =
|
||||
tasks.register<ResolveSourcesJars>("resolveSourcesJars") {
|
||||
configuration.set(configurations.named("runtimeClasspath"))
|
||||
outputDir.set(layout.buildDirectory.dir("resolveSourcesJars"))
|
||||
}
|
||||
|
||||
val sourceBundleSpec = extensions.create<SourceBundleSpec>("sourceBundleSpec")
|
||||
|
||||
sourceBundleSpec.staticMuslLibc.convention(false)
|
||||
|
||||
plugins.withId("pklJavaLibrary") { sourceBundleSpec.firstPartyJars.from(tasks.named("sourcesJar")) }
|
||||
|
||||
plugins.withId("pklKotlinLibrary") {
|
||||
sourceBundleSpec.firstPartyJars.from(tasks.named("sourcesJar"))
|
||||
}
|
||||
|
||||
sourceBundleSpec.firstPartyJars.from(firstPartySourcesJars)
|
||||
|
||||
sourceBundleSpec.dependencyJars.from(resolveSourcesJars.map { fileTree(it.outputDir) })
|
||||
|
||||
// Builds every source bundle of this project; there is one for each variant that the project is
|
||||
// distributed as. The variants are registered by `pklJvmSourceBundle` and `pklNativeSourceBundle`.
|
||||
tasks.register("sourceBundle") {
|
||||
group = "build"
|
||||
description = "Builds the source bundles of all variants of this project."
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
plugins { id("com.diffplug.spotless") }
|
||||
|
||||
val pklFormatter by configurations.creating
|
||||
val pklFormatter = configurations.create("pklFormatter")
|
||||
|
||||
dependencies { pklFormatter(rootProject.project("pkl-formatter")) }
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
::===----------------------------------------------------------------------===//
|
||||
:: Copyright © $YEAR Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
::
|
||||
:: Licensed under the Apache License, Version 2.0 (the "License");
|
||||
:: you may not use this file except in compliance with the License.
|
||||
:: You may obtain a copy of the License at
|
||||
::
|
||||
:: https://www.apache.org/licenses/LICENSE-2.0
|
||||
::
|
||||
:: Unless required by applicable law or agreed to in writing, software
|
||||
:: distributed under the License is distributed on an "AS IS" BASIS,
|
||||
:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
:: See the License for the specific language governing permissions and
|
||||
:: limitations under the License.
|
||||
::===----------------------------------------------------------------------===//
|
||||
@@ -0,0 +1,15 @@
|
||||
#===----------------------------------------------------------------------===//
|
||||
# Copyright © $YEAR Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#===----------------------------------------------------------------------===//
|
||||
+4
-3
@@ -37,7 +37,8 @@ nexusPublishing {
|
||||
}
|
||||
}
|
||||
|
||||
val configureLateInitAnnotation by tasks.registering(ConfigureLateInitAnnotation::class)
|
||||
val configureLateInitAnnotation =
|
||||
tasks.register<ConfigureLateInitAnnotation>("configureLateInitAnnotation")
|
||||
|
||||
idea {
|
||||
project {
|
||||
@@ -53,9 +54,9 @@ idea {
|
||||
}
|
||||
}
|
||||
|
||||
val clean by tasks.existing { delete(layout.buildDirectory) }
|
||||
val clean = tasks.named("clean") { delete(layout.buildDirectory) }
|
||||
|
||||
val printVersion by tasks.registering { doFirst { println(buildInfo.pklVersion) } }
|
||||
val printVersion = tasks.register("printVersion") { doFirst { println(buildInfo.pklVersion) } }
|
||||
|
||||
val message =
|
||||
"""
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
name: main
|
||||
title: Main Project
|
||||
version: 0.32.0-dev
|
||||
version: 0.34.0-dev
|
||||
prerelease: true
|
||||
nav:
|
||||
- nav.adoc
|
||||
@@ -1,8 +0,0 @@
|
||||
= Community
|
||||
:uri-github-issue: https://github.com/apple/pkl/issues
|
||||
:uri-github-discussions: https://github.com/apple/pkl/discussions
|
||||
|
||||
We'd love to hear from you!
|
||||
|
||||
* Create an {uri-github-issue}[issue]
|
||||
* Ask questions on {uri-github-discussions}[GitHub Discussions]
|
||||
@@ -4,6 +4,7 @@
|
||||
* xref:kotlin-binding:index.adoc[Kotlin]
|
||||
* xref:swift:ROOT:index.adoc[Swift]
|
||||
* xref:go:ROOT:index.adoc[Go]
|
||||
* xref:libpkl:index.adoc[libpkl]
|
||||
* xref:bindings-specification:index.adoc[Specification]
|
||||
** xref:bindings-specification:message-passing-api.adoc[Message Passing API]
|
||||
** xref:bindings-specification:binary-encoding.adoc[Pkl Binary Encoding]
|
||||
@@ -3,7 +3,7 @@
|
||||
// the following attributes must be updated immediately before a release
|
||||
|
||||
// pkl version corresponding to current git commit without -dev suffix or git hash
|
||||
:pkl-version-no-suffix: 0.32.0
|
||||
:pkl-version-no-suffix: 0.34.0
|
||||
// tells whether pkl version corresponding to current git commit
|
||||
// is a release version (:is-release-version: '') or dev version (:!is-release-version:)
|
||||
:!is-release-version:
|
||||
@@ -74,8 +74,9 @@ endif::[]
|
||||
:uri-stdlib-jsonnetModule: {uri-pkl-stdlib-docs}/jsonnet
|
||||
:uri-stdlib-reflectModule: {uri-pkl-stdlib-docs}/reflect
|
||||
:uri-stdlib-mathModule: {uri-pkl-stdlib-docs}/math
|
||||
:uri-stdlib-netModule: {uri-pkl-stdlib-docs}/net
|
||||
:uri-stdlib-net-Url: {uri-stdlib-netModule}/Url
|
||||
:uri-stdlib-xmlModule: {uri-pkl-stdlib-docs}/xml
|
||||
:uri-stdlib-protobufModule: {uri-pkl-stdlib-docs}/protobuf
|
||||
:uri-stdlib-pklbinaryModule: {uri-pkl-stdlib-docs}/pklbinary
|
||||
:uri-stdlib-yamlModule: {uri-pkl-stdlib-docs}/yaml
|
||||
:uri-stdlib-YamlParser: {uri-stdlib-yamlModule}/Parser
|
||||
|
||||
@@ -46,7 +46,7 @@ The first slot of the array designates the value's type.
|
||||
The remaining slots have fixed meanings depending on the type.
|
||||
Additional slots may be added to types in future Pkl releases. Decoders *must* be designed to defensively discard values beyond the number of known slots for a type or provide meaningful error messages.
|
||||
|
||||
The array's length is the number of slots that are filled. For example, xref:{uri-stdlib-List}[List] is encoded as an MessagePack array with two elements.
|
||||
The array's length is the number of slots that are filled. For example, link:{uri-stdlib-List}[List] is encoded as an MessagePack array with two elements.
|
||||
|
||||
|===
|
||||
|Pkl type |Slot 1 2+|Slot 2 2+|Slot 3 2+|Slot 4
|
||||
|
||||
@@ -7,7 +7,10 @@ Pkl can be embedded within any host application.
|
||||
The host application has access to low level controls.
|
||||
It is able to manage the lifecycle of evaluators, as well as provide custom modules and resources to Pkl.
|
||||
|
||||
Currently, Pkl must be embedded as a child process, by shelling out to the CLI using the xref:pkl-cli:index.adoc#command-server[`pkl server`] command. In the future, a C library will also be provided.
|
||||
Pkl can be embedded in one of two ways:
|
||||
|
||||
. By spawning xref:pkl-cli:index.adoc#command-server[`pkl server`] as a subprocess, and communicating with it via STDIN/STDOUT.
|
||||
. By linking to the xref:libpkl:index.adoc[libpkl library].
|
||||
|
||||
When embedded, communication between a host application and Pkl happens via message passing.
|
||||
The message passing specification can be found in xref:message-passing-api.adoc[].
|
||||
@@ -20,7 +23,7 @@ NOTE: Pkl's Java and Kotlin libraries binds to Pkl directly, and do not use mess
|
||||
|
||||
A language binding for Pkl should generally have the following components:
|
||||
|
||||
. A client that spawns `pkl server`, and talks to it using message passing.
|
||||
. A client that either spawns `pkl server` or links to `libpkl`, and talks to it using message passing.
|
||||
. A deserializer that turns xref:binary-encoding.adoc[pkl binary encoding] into a structure in the host language.
|
||||
. A code generator that transforms Pkl schemas into schemas written in the host language.
|
||||
The code generator is mostly written in Pkl, with a lightweight executable that acts as the glue layer.
|
||||
|
||||
@@ -22,6 +22,24 @@ Default: (not set) +
|
||||
Flag that indicates to generate config classes for use with Spring Boot.
|
||||
====
|
||||
|
||||
.--spring-boot-configuration-properties
|
||||
[%collapsible]
|
||||
====
|
||||
Default: (none) +
|
||||
Example: `my.mod#Server=server` +
|
||||
Explicitly configures which generated classes receive the Spring Boot `@ConfigurationProperties` annotation, and with what prefix.
|
||||
Only applies if `--generate-spring-boot` is set.
|
||||
|
||||
The key is the Pkl class name (for example, `my.mod` for a module class, or `my.mod#Server` for a class within a module), and the value is the annotation's prefix.
|
||||
An empty value generates a bare `@ConfigurationProperties` annotation.
|
||||
|
||||
If this option is omitted, the annotations are inferred from the structure of the input modules.
|
||||
If this option is set, no annotations are inferred, and only the specified classes are annotated.
|
||||
|
||||
Repeat this option to define multiple mappings.
|
||||
Values must be valid Spring Boot configuration properties prefixes.
|
||||
====
|
||||
|
||||
.--implement-serializable
|
||||
[%collapsible]
|
||||
====
|
||||
|
||||
@@ -2100,6 +2100,8 @@ The xref:blog:ROOT:class-as-a-function.adoc[Class-as-a-function] pattern may be
|
||||
TIP: In most cases, methods without parameters should not be defined.
|
||||
Instead, use <<fixed-properties,`fixed` properties>> on the module or class.
|
||||
|
||||
Methods may also be <<generic-methods,generic>>.
|
||||
|
||||
[[modules]]
|
||||
== Modules
|
||||
|
||||
@@ -2750,7 +2752,6 @@ The standard library provides these renderers:
|
||||
* link:{uri-stdlib-baseModule}/PcfRenderer[PcfRenderer]
|
||||
* link:{uri-stdlib-baseModule}/PListRenderer[PListRenderer]
|
||||
* link:{uri-stdlib-baseModule}/PropertiesRenderer[PropertiesRenderer]
|
||||
* link:{uri-stdlib-protobufModule}/Renderer[protobuf.Renderer]
|
||||
* link:{uri-stdlib-xmlModule}/Renderer[xml.Renderer]
|
||||
* link:{uri-stdlib-baseModule}/YamlRenderer[YamlRenderer]
|
||||
|
||||
@@ -3282,7 +3283,8 @@ This section discusses language features that are generally more relevant to tem
|
||||
<<projects,Projects>> +
|
||||
<<external-readers,External Readers>> +
|
||||
<<mirroring_packages,Mirroring packages>> +
|
||||
<<references,References>>
|
||||
<<references,References>> +
|
||||
<<generic-methods,Generic Methods>>
|
||||
|
||||
[[meaning-of-new]]
|
||||
=== Meaning of `new`
|
||||
@@ -3361,6 +3363,7 @@ The value will not have property `isPredatory = true` as the `default` property
|
||||
<3> Assigning a `new` explicitly-typed value to a `Mapping` entry.
|
||||
The value will not have property `isPredatory = true` as the `default` property of the `Mapping` is not used.
|
||||
|
||||
[[implicitly-typed-new]]
|
||||
==== Implicitly Typed `new`
|
||||
|
||||
When using the implicitly typed `new` invocation, there is no explicit parent value to amend.
|
||||
@@ -3368,6 +3371,8 @@ In these cases, Pkl infers the amend operation's parent value based on context:
|
||||
|
||||
* When assigning to a declared property, the property's default value is amended (<<amend-null, including `null`>>).
|
||||
If there is no type associated with the property, an empty `Dynamic` object is amended.
|
||||
* In method argument, method return value, and let binding positions, the annotated type's default value is amended.
|
||||
If there is no type annotation, an empty `Dynamic` object is amended.
|
||||
* When assigning to an entry (e.g. a `Mapping` member) or element (e.g. a `Listing` member), the enclosing object's `default` property is applied to the corresponding index or key, respectively, to produce the value to be amended.
|
||||
* In other cases, evaluation fails with the error message "Cannot tell which parent to amend".
|
||||
|
||||
@@ -3379,29 +3384,29 @@ In this case, the argument's type should be specified explicitly.
|
||||
class Bird {
|
||||
name: String
|
||||
|
||||
function listHatchlings(items: Listing<String>): Listing<String> = new {
|
||||
function listHatchlings(items: Listing<String>): Listing<String> = new { // <1>
|
||||
for (item in items) {
|
||||
"\(name):\(item)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedProperty: Bird = new { // <1>
|
||||
typedProperty: Bird = new { // <2>
|
||||
name = "Swift"
|
||||
}
|
||||
|
||||
untypedProperty = new { // <2>
|
||||
untypedProperty = new { // <3>
|
||||
hello = "world"
|
||||
}
|
||||
|
||||
typedListing: Listing<Bird> = new {
|
||||
new { // <3>
|
||||
new { // <4>
|
||||
name = "Kite"
|
||||
}
|
||||
}
|
||||
|
||||
untypedListing: Listing = new {
|
||||
new { // <4>
|
||||
new { // <5>
|
||||
hello = "there"
|
||||
}
|
||||
}
|
||||
@@ -3410,13 +3415,13 @@ typedMapping: Mapping<String, Bird> = new {
|
||||
default { entryKey ->
|
||||
name = entryKey
|
||||
}
|
||||
["Saltmarsh Sparrow"] = new { // <5>
|
||||
["Saltmarsh Sparrow"] = new { // <6>
|
||||
name = "Sharp-tailed Sparrow"
|
||||
}
|
||||
}
|
||||
|
||||
amendedMapping = (typedMapping) {
|
||||
["Saltmarsh Sparrow"] = new {} // <6>
|
||||
["Saltmarsh Sparrow"] = new {} // <7>
|
||||
}
|
||||
|
||||
class Aviary {
|
||||
@@ -3426,22 +3431,27 @@ class Aviary {
|
||||
}
|
||||
|
||||
aviary: Aviary = new {
|
||||
birds = new { // <7>
|
||||
birds = new { // <8>
|
||||
new { name = "Kiwi" }
|
||||
}
|
||||
}
|
||||
|
||||
swiftHatchlings = typedProperty.listHatchlings(new { "Poppy"; "Chirpy" }) // <8>
|
||||
----
|
||||
swiftHatchlings = typedProperty.listHatchlings(new { "Poppy"; "Chirpy" }) // <9>
|
||||
|
||||
<1> Assignment to a property with an explicitly declared type, amending `new Bird {}`.
|
||||
<2> Assignment to an undeclared property in module context, amending `new Dynamic {}`.
|
||||
<3> `Listing` element creation, amending implicit `default`, `new Bird {}`.
|
||||
<4> `Listing` element creation, amending implicit `default`, `new Dynamic {}`.
|
||||
<5> `Mapping` value assignment, amending the result of applying `default` to `"Saltmarsh Sparrow"`, `new Bird { name = "Saltmarsh Sparrow" }`.
|
||||
<6> `Mapping` value assignment _replacing_ the parent's entry, amending the result of applying `default` to `"Saltmarsh Sparrow"`, `new Bird { name = "Saltmarsh Sparrow" }`.
|
||||
<7> Amending the property default value `new Listing { new Bird { name = "Osprey" } }`; the result contains both birds.
|
||||
<8> Error: Cannot tell which parent to amend.
|
||||
pigeonHatchlings =
|
||||
let (hatchlingNames: Listing<String> = new { "Polly"; "Daisy" }) // <10>
|
||||
new Bird { name = "Pigeon" }.listHatchlings(hatchlingNames)
|
||||
----
|
||||
<1> Instantiation of a method return value, amending the return type's default value `new Listing<String> {}`.
|
||||
<2> Assignment to a property with an explicitly declared type, amending `new Bird {}`.
|
||||
<3> Assignment to an undeclared property in module context, amending `new Dynamic {}`.
|
||||
<4> `Listing` element creation, amending implicit `default`, `new Bird {}`.
|
||||
<5> `Listing` element creation, amending implicit `default`, `new Dynamic {}`.
|
||||
<6> `Mapping` value assignment, amending the result of applying `default` to `"Saltmarsh Sparrow"`, `new Bird { name = "Saltmarsh Sparrow" }`.
|
||||
<7> `Mapping` value assignment _replacing_ the parent's entry, amending the result of applying `default` to `"Saltmarsh Sparrow"`, `new Bird { name = "Saltmarsh Sparrow" }`.
|
||||
<8> Amending the property default value `new Listing { new Bird { name = "Osprey" } }`; the result contains both birds.
|
||||
<9> Instantiation of a method argument, amending the parameter type's default value `new Listing<String> {}`.
|
||||
<10> Instantiation inside a <<let-expressions,`let` binding>>, amending the binding's type's default value `new Listing<String> {}`.
|
||||
|
||||
[[let-expressions]]
|
||||
=== Let Expressions
|
||||
@@ -5994,32 +6004,13 @@ There are some restrictions on properties that may be referenced; attempting to
|
||||
* Properties originally defined in `external` classes (this only includes `Module.output`)
|
||||
|
||||
pkldoc:Reference[pkl:ref] instances are created using the constructor method pkldoc:#Reference()[pkl:ref].
|
||||
This method accepts three parameters:
|
||||
This method accepts two type arguments and two parameters:
|
||||
|
||||
* `domain` - An instance of a `ref.Domain` subclass; its type becomes the `D` type argument of the resulting `Reference` instance.
|
||||
* `class` - A `Class` instance that will be the referent type of the returned `Reference`; it is the `T` type argument of the resulting instance.
|
||||
* `D` - The type of the reference's domain. Must be a subclass of `ref.Domain`.
|
||||
* `Referent` - The type the reference refers to.
|
||||
* `domain` - An instance of `D` that becomes the domain resulting `Reference` instance.
|
||||
* `data` - A domain-specific value used to identify the root or context of the reference.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
It is only possible to construct new references with a referent type that is a simple (non-generic) class type.
|
||||
To obtain a reference to values of other types (generic, typealias, nullable, union, etc.), libraries must wrap those types in a class:
|
||||
|
||||
[source%parsed,pkl]
|
||||
----
|
||||
import "pkl:ref"
|
||||
|
||||
class TypeHolder {
|
||||
$: *Listing<String>? | Mapping<UInt, String> // <1>
|
||||
}
|
||||
|
||||
local r: ref.Reference<MyDomain, Listing<String>? | Mapping<UInt, String>> =
|
||||
ref.Reference(myDomain, TypeHolder, null).$ // <2>
|
||||
----
|
||||
<1> Define a "holder" class with a property of the desired type.
|
||||
<2> Access the `$` property of the reference to get a reference to the desired non-class type.
|
||||
====
|
||||
|
||||
Here's a realistic example of how references might be used:
|
||||
|
||||
[source,pkl]
|
||||
@@ -6027,7 +6018,7 @@ Here's a realistic example of how references might be used:
|
||||
----
|
||||
import "pkl:ref"
|
||||
|
||||
class Domain extends ref.Domain { // <1>
|
||||
class Domain extends ref.AbstractDomain { // <1>
|
||||
function renderReference(reference: ref.Reference<Domain, Any>): String =
|
||||
if (reference.getData() is Task)
|
||||
let (path = reference.getPath().map((access) -> access.property ?? access.key.toString()))
|
||||
@@ -6038,18 +6029,20 @@ class Domain extends ref.Domain { // <1>
|
||||
|
||||
typealias Ref<T> = ref.Reference<Domain, T> // <2>
|
||||
|
||||
abstract class Task { // <3>
|
||||
const function Ref<T>(data: Any): Ref<T> = ref.Reference<Domain, T>(new Domain {}, data) // <3>
|
||||
|
||||
abstract class Task { // <4>
|
||||
name: String
|
||||
}
|
||||
|
||||
class TaskA extends Task {
|
||||
input1: String | Ref<String> // <4>
|
||||
input1: String | Ref<String> // <5>
|
||||
input2: Int | Ref<Int>
|
||||
|
||||
hidden fixed outputs: Ref<TaskAOutputs> = ref.Reference(new Domain {}, TaskAOutputs, this) // <5>
|
||||
hidden fixed outputs: Ref<TaskAOutputs> = Ref<TaskAOutputs>(this) // <6>
|
||||
}
|
||||
|
||||
abstract class TaskAOutputs { // <6>
|
||||
abstract class TaskAOutputs { // <7>
|
||||
foo: String
|
||||
bar: Int
|
||||
}
|
||||
@@ -6059,24 +6052,25 @@ class TaskB extends Task {
|
||||
inputY: Int | Ref<Int>
|
||||
}
|
||||
|
||||
tasks: Listing<Task>(isDistinctBy((it) -> it.name)) // <7>
|
||||
tasks: Listing<Task>(isDistinctBy((it) -> it.name)) // <8>
|
||||
|
||||
output {
|
||||
renderer {
|
||||
converters {
|
||||
[ref.Reference] = (it) -> it.toString() // <8>
|
||||
[ref.Reference] = (it) -> it.toString() // <9>
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Define a `ref.Domain` subclass that determines how `Reference` instances are rendered as strings.
|
||||
<2> For convenience, create a type alias for references of this domain.
|
||||
<3> Create a new base class that can be referenced. `Task` instances will be identified by their `name` value.
|
||||
<4> In a specific `Task` subclass, input values accept both literal values and references to the same type.
|
||||
<5> `Task` subclasses that provide output values define an `outputs` property where the referent type is `TaskAOutputs`.
|
||||
<6> The `TaskAOutputs` class defines the possible runtime outputs of `TaskA` tasks. It is `abstract` and never instantiated, only used for producing references.
|
||||
<7> Users provide `Task` instances with unique `name` values.
|
||||
<8> Delegate to `Domain` for rendering `Reference` values.
|
||||
<3> For convenience, wrap the `Reference` constructor to pass appropriate the domain type and value.
|
||||
<4> Create a new base class that can be referenced. `Task` instances will be identified by their `name` value.
|
||||
<5> In a specific `Task` subclass, input values accept both literal values and references to the same type.
|
||||
<6> `Task` subclasses that provide output values define an `outputs` property where the referent type is `TaskAOutputs`.
|
||||
<7> The `TaskAOutputs` class defines the possible runtime outputs of `TaskA` tasks. It is `abstract` and never instantiated, only used for producing references.
|
||||
<8> Users provide `Task` instances with unique `name` values.
|
||||
<9> Delegate to `Domain` for rendering `Reference` values.
|
||||
|
||||
[source,pkl]
|
||||
.myWorkflow.pkl
|
||||
@@ -6119,3 +6113,37 @@ tasks {
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[[generic-methods]]
|
||||
=== Generic Methods
|
||||
|
||||
Methods may be declared as "generic", with types that are themselves parameters.
|
||||
Method type parameters may be used within the method anywhere else a type may be used: in a parameter's type, in the return type, or within the method body (e.g. <<defining-objects,object instantiation>>, <<type-tests,type tests>>, <<type-casts,type casts>>).
|
||||
|
||||
When calling a generic method, a caller may manually specify type arguments by writing `methodName<TypeArg1, TypeArg2>(arg1, arg2)`.
|
||||
When no type arguments are provided, type checks of parameter types behave like <<unknown-type,`unknown`>> and always succeed.
|
||||
|
||||
[source,pkl]
|
||||
----
|
||||
class Pigeon {
|
||||
name: String
|
||||
}
|
||||
|
||||
class Hawk {
|
||||
name: String
|
||||
}
|
||||
|
||||
function makeFlock<Bird>(names: Listing<String>): Listing<Bird> = new {
|
||||
for (n in names) {
|
||||
new { name = n }
|
||||
}
|
||||
}
|
||||
|
||||
pigeonFlock = makeFlock<Pigeon>(new { "Alice"; "Bill"; "Ruth" }) // <1>
|
||||
hawkFlock = makeFlock<Hawk>(new { "Florence"; "Holly"; "Jim" }) // <2>
|
||||
----
|
||||
<1> Result: a `Listing` where every element is a `Pigeon`.
|
||||
<2> Result: a `Listing` where every element is a `Hawk`.
|
||||
|
||||
NOTE: A `<` following an identifier is parsed as the start of a type argument list when it is well-formed and directly followed by `(`.
|
||||
Otherwise, it is parsed as the less-than operator; for example, `foo<bar>(baz)` is a method call with type arguments, whereas `foo<bar + 5>(baz)` is a comparison.
|
||||
@@ -360,7 +360,7 @@ at pkl.base#Module.output.text (https://github.com/apple/pkl/blob/e4d8c882d/stdl
|
||||
<6> What Pkl evaluated to discover the error.
|
||||
|
||||
When Pkl prints source locations, it also prints clickable links for easy access.
|
||||
For local files, it generates a link for your development environment (https://pkl-lang.org/main/current/pkl-cli/index.html#settings-file[configurable in `+~/.pkl/settings.pkl+`]).
|
||||
For local files, it generates a link for your development environment (https://pkl-lang.org/main/current/pkl-cli/index.html#settings-file[configurable in `+~/.config/pkl/settings.pkl+`]).
|
||||
For packages imported from elsewhere, if available, Pkl produces `https://` links to their repository.
|
||||
|
||||
Pkl complains about a _type constraint_.
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
= libpkl
|
||||
include::ROOT:partial$component-attributes.adoc[]
|
||||
|
||||
libpkl is a C library for driving Pkl evaluation.
|
||||
|
||||
It is designed to be a low-level API that interacts with Pkl via the xref:bindings-specification:message-passing-api.adoc[message passing API].
|
||||
It is meant to be paired with higher level logic written in a host language within the context of a Pkl language binding library.
|
||||
The higher level logic deals with implementing the evaluator API, and serializing/deserializing messages according to the message passing API.
|
||||
|
||||
== Installing libpkl
|
||||
|
||||
Currently, we do not publish libpkl.
|
||||
Therefore, libpkl needs to be built from source, by cloning this repository and running the steps laid out in <<build-from-source>>.
|
||||
|
||||
Once built, it can be <<linking-to-libpkl,linked against>>, either statically or dynamically.
|
||||
|
||||
[[linking-to-libpkl]]
|
||||
== Linking to libpkl
|
||||
|
||||
It is possible to link to libpkl either as a static library or a dynamic library.
|
||||
|
||||
Statically linking to libpkl means that it is possible to copy libpkl's library code directly into your own library or executable at compile time, meaning that it is self-contained.
|
||||
However, this impacts the binary size of your application, as libpkl's library files are around 100MB.
|
||||
|
||||
On the other hand, dynamically linking to libpkl has negligible impact on binary size, but requires that end users have libpkl installed on their OS.
|
||||
|
||||
=== Linking to the static vs. shared library
|
||||
|
||||
The simplest way to configure your compiler to link to either the static or the shared library is to use https://en.wikipedia.org/wiki/Pkg-config[pkg-config].
|
||||
|
||||
The libpkl library ships with two pkg-config files: `libpkl.pc`, and `libpkl-static.pc`.
|
||||
|
||||
The normal `libpkl.pc` links to the shared library.
|
||||
|
||||
To unambiguously link to the static library, use `libpkl-static.pc`.
|
||||
|
||||
For example, when using `cc`:
|
||||
|
||||
[source,shell]
|
||||
----
|
||||
# Link against the shared library
|
||||
cc -o myprogram myprogram.c $(pkg-config --libs --cflags libpkl)
|
||||
|
||||
# Link against the static library
|
||||
cc -o myprogram myprogram.c $(pkg-config --static --libs --cflags libpkl-static)
|
||||
----
|
||||
|
||||
|
||||
If libpkl is installed in a non-system directory, set `PKG_CONFIG_PATH` to the subpath `lib/pkgconfig` within the directory that contains libpkl. For example, given libpkl directory `/path/to/libpkl`, the environment variable should have `/path/to/libpkl/lib/pkgconfig`.
|
||||
|
||||
NOTE: Windows can use pkg-config through the https://www.msys2.org/docs/pkgconfig/[MSYS2 toolchain]. Using pkg-config on Windows implies compiling with gcc-style compiler like https://code.visualstudio.com/docs/cpp/config-mingw[MinGW], instead of MSVC.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
It's also possible to link to the static library with the normal `libpkl.pc`.
|
||||
|
||||
For example:
|
||||
|
||||
[source,shell]
|
||||
----
|
||||
cc -Wl,-Bstatic $(pkg-config --libs --cflags libpkl)
|
||||
----
|
||||
====
|
||||
|
||||
[[build-from-source]]
|
||||
== Building libpkl from source
|
||||
|
||||
To build libpkl from source, the apple/pkl repository first must be cloned down to a host OS.
|
||||
|
||||
Build machine requirements:
|
||||
|
||||
* Java 21 or higher
|
||||
* zlib
|
||||
* C compiler
|
||||
** `cc` (gcc, clang) toolchain on Unix
|
||||
** MSVC toolchain on Windows
|
||||
|
||||
Example:
|
||||
|
||||
[source,shell]
|
||||
----
|
||||
git clone git@github.com:apple/pkl.git
|
||||
cd pkl/
|
||||
./gradlew libpkl:buildNative
|
||||
----
|
||||
|
||||
This results in two directories of interest:
|
||||
|
||||
* `libpkl/build/distributions/`: directory containing archives (`.zip` on Windows, `.tar.gz` on macOS/Linux) of C libraries.
|
||||
* `libpkl/build/native-libs/<os>-<arch>`: directory containing the built library files (the inner structure of the archive).
|
||||
|
||||
Cross-compilation is not supported.
|
||||
|
||||
=== Distribution layout
|
||||
|
||||
The built distribution archive contains both statically linked and dynamically linked binaries for each OS.
|
||||
|
||||
The archive has the following structure:
|
||||
|
||||
[source]
|
||||
----
|
||||
.
|
||||
└── libpkl-<version>-<os>-<arch>
|
||||
├── include
|
||||
│ └── pkl.h
|
||||
├── lib
|
||||
│ ├── libpkl.<shared library extension>
|
||||
│ ├── libpkl.<static library extension>
|
||||
│ └── pkgconfig
|
||||
│ ├── libpkl-static.pc
|
||||
│ └── libpkl.pc
|
||||
├── LICENSE.txt
|
||||
├── README.md
|
||||
└── THIRD-PARTY-NOTICES.txt
|
||||
----
|
||||
|
||||
The file extensions are as follows:
|
||||
|===
|
||||
|OS |Static Library Extension |Shared library extension
|
||||
|
||||
|macOS
|
||||
|`.a`
|
||||
|`.dylib`
|
||||
|
||||
|Linux
|
||||
|`.a`
|
||||
|`.so`
|
||||
|
||||
|Windows
|
||||
|`.lib`
|
||||
|`.dll`
|
||||
|===
|
||||
|
||||
== ABI compatibility
|
||||
|
||||
libpkl is ABI compatible across Pkl versions.
|
||||
The underlying Pkl version can be accessed using the `pkl_version()` method.
|
||||
@@ -4,7 +4,6 @@ include::ROOT:partial$component-attributes.adoc[]
|
||||
:uri-mise: https://mise.jdx.dev
|
||||
:uri-winget: https://learn.microsoft.com/en-us/windows/package-manager/
|
||||
|
||||
:uri-pkl-macos-amd64-download: {uri-sonatype-snapshot-download}&a=pkl-cli-macos-amd64&e=bin
|
||||
:uri-pkl-macos-aarch64-download: {uri-sonatype-snapshot-download}&a=pkl-cli-macos-aarch64&e=bin
|
||||
:uri-pkl-linux-amd64-download: {uri-sonatype-snapshot-download}&a=pkl-cli-linux-amd64&e=bin
|
||||
:uri-pkl-linux-aarch64-download: {uri-sonatype-snapshot-download}&a=pkl-cli-linux-aarch64&e=bin
|
||||
@@ -13,7 +12,6 @@ include::ROOT:partial$component-attributes.adoc[]
|
||||
:uri-pkl-java-download: {uri-sonatype-snapshot-download}&a=pkl-cli-java&e=jar
|
||||
|
||||
ifdef::is-release-version[]
|
||||
:uri-pkl-macos-amd64-download: {github-releases}/pkl-macos-amd64
|
||||
:uri-pkl-macos-aarch64-download: {github-releases}/pkl-macos-aarch64
|
||||
:uri-pkl-linux-amd64-download: {github-releases}/pkl-linux-amd64
|
||||
:uri-pkl-linux-aarch64-download: {github-releases}/pkl-linux-aarch64
|
||||
@@ -36,7 +34,7 @@ For interactive development, the CLI includes a Read-Eval-Print Loop (REPL).
|
||||
|
||||
The CLI comes in multiple flavors:
|
||||
|
||||
* Native macOS executable for amd64 (tested on macOS 10.15)
|
||||
* Native macOS executable for aarch64 (tested on macOS 10.15)
|
||||
* Native Linux executable for amd64
|
||||
* Native Linux executable for aarch64
|
||||
* Native Alpine Linux executable for amd64 (cross-compiled and tested on Oracle Linux 8)
|
||||
@@ -140,8 +138,6 @@ Development and release versions can be downloaded and installed manually.
|
||||
|
||||
=== macOS Executable
|
||||
|
||||
On aarch64:
|
||||
|
||||
[source,shell]
|
||||
[subs="+attributes"]
|
||||
----
|
||||
@@ -150,16 +146,6 @@ chmod +x pkl
|
||||
./pkl --version
|
||||
----
|
||||
|
||||
On amd64:
|
||||
|
||||
[source,shell]
|
||||
[subs="+attributes"]
|
||||
----
|
||||
curl -L -o pkl '{uri-pkl-macos-amd64-download}'
|
||||
chmod +x pkl
|
||||
./pkl --version
|
||||
----
|
||||
|
||||
This should print something similar to:
|
||||
|
||||
[source]
|
||||
@@ -321,7 +307,6 @@ The default output renderer for a module supports the following formats:
|
||||
* `pcf`
|
||||
* `plist`
|
||||
* `properties`
|
||||
* `textproto`
|
||||
* `xml`
|
||||
* `yaml`
|
||||
|
||||
@@ -464,6 +449,24 @@ When enabled, type constraint failures will show intermediate values in the asse
|
||||
Use `--no-power-assertions` to disable this feature if you prefer simpler output or better performance.
|
||||
====
|
||||
|
||||
[[profile-cpu-output]]
|
||||
.--profile-cpu-output
|
||||
[%collapsible]
|
||||
====
|
||||
Default: (none) +
|
||||
The file to write profiler output to. Omitting this option disables CPU profiling entirely. +
|
||||
|
||||
The CPU profiler writes output in https://github.com/google/pprof[pprof] format.
|
||||
====
|
||||
|
||||
[[profile-cpu-sample-period]]
|
||||
.--profile-cpu-sample-period
|
||||
[%collapsible]
|
||||
====
|
||||
Default: 10 +
|
||||
The sampling duration for the CPU sampler, in milliseconds.
|
||||
====
|
||||
|
||||
This command also takes <<common-options, common options>>.
|
||||
|
||||
[[command-server]]
|
||||
@@ -684,6 +687,12 @@ Example: `--test-reporter minimal` +
|
||||
Which test reporter to use for CLI output. Possible values are `spec` and `minimal`.
|
||||
====
|
||||
|
||||
.--install
|
||||
[%collapsible]
|
||||
====
|
||||
Install the built package into the module cache dir.
|
||||
====
|
||||
|
||||
This command also takes <<common-options,common options>>.
|
||||
|
||||
[[command-project-resolve]]
|
||||
@@ -1385,14 +1394,24 @@ it works as follows:
|
||||
The Pkl settings file allows to customize the CLI experience.
|
||||
|
||||
A settings file is a Pkl module amending the `pkl.settings` standard library module.
|
||||
Its default location is `~/.pkl/settings.pkl`.
|
||||
|
||||
Unless configured explicitly, Pkl will look in the following locations:
|
||||
|
||||
. `$XDG_CONFIG_HOME/pkl/settings.pkl`
|
||||
. `%APPDATA%/pkl/settings.pkl` (on Windows only)
|
||||
. `~/.config/pkl/settings.pkl`
|
||||
. Subdirectory `pkl/settings.pkl` within one of the paths described by `$XDG_CONFIG_DIRS`
|
||||
. `/etc/xdg/pkl/settings.pkl`
|
||||
. `~/.pkl/settings.pkl` (legacy location used by Pkl 0.32 and lower)
|
||||
|
||||
To use a different settings file, set the `--settings` command line option, for example `--settings mysettings.pkl`.
|
||||
To enforce default settings, use `--settings pkl:settings`.
|
||||
|
||||
The settings file is also honored by (and configurable through) the Gradle plugin and `CliEvaluator` API.
|
||||
|
||||
Here is a typical settings file:
|
||||
|
||||
.~/.pkl/settings.pkl
|
||||
.~/.config/pkl/settings.pkl
|
||||
[source%parsed,{pkl}]
|
||||
----
|
||||
amends "pkl:settings" // <1>
|
||||
@@ -1414,10 +1433,19 @@ When making TLS requests, Pkl comes with its own set of {uri-certificates}[CA ce
|
||||
These certificates can be overridden via either of the two options:
|
||||
|
||||
- Set them directly via the CLI option `--ca-certificates <path>`.
|
||||
- Add them to a directory at path `~/.pkl/cacerts/`.
|
||||
- Add them to a user directory.
|
||||
|
||||
If CA certificates are not explicitly configured, Pkl will look in the following locations:
|
||||
|
||||
. `$XDG_CONFIG_HOME/pkl/cacerts`
|
||||
. `%APPDATA%/pkl/cacerts` (on Windows only)
|
||||
. `~/.config/pkl/cacerts`
|
||||
. Subdirectory `pkl/cacerts` within one of the paths described by `$XDG_CONFIG_DIRS`
|
||||
. `/etc/xdg/pkl/cacerts`
|
||||
. `~/.pkl/cacerts` (legacy location used by Pkl 0.32 and lower)
|
||||
|
||||
Both these options will *replace* the default CA certificates bundled with Pkl. +
|
||||
The CLI option takes precedence over the certificates in `~/.pkl/cacerts/`. +
|
||||
The CLI option takes precedence over the certificates in the cacerts directory. +
|
||||
Certificates need to be X.509 certificates in PEM format.
|
||||
|
||||
[[http-proxy]]
|
||||
|
||||
@@ -36,9 +36,15 @@ Possible values:
|
||||
.--cache-dir
|
||||
[%collapsible]
|
||||
====
|
||||
Default: `~/.pkl/cache` +
|
||||
Example: `/path/to/module/cache/` +
|
||||
|
||||
The cache directory for storing packages.
|
||||
|
||||
If unset, defaults to the following locations:
|
||||
|
||||
. `$XDG_CACHE_HOME/pkl`
|
||||
. `$LOCALAPPDATA/pkl/Cache` (on Windows only)
|
||||
. `~/.cache/pkl` (if `$XDG_CACHE_HOME` and `$LOCALAPPDATA` are both unset)
|
||||
====
|
||||
|
||||
.--no-cache
|
||||
@@ -97,7 +103,7 @@ Any symlinks are resolved before this check is performed.
|
||||
Default: (none) +
|
||||
Example: `mySettings.pkl` +
|
||||
File path of the Pkl settings file to use.
|
||||
If not set, `~/.pkl/settings.pkl` or defaults specified in the `pkl.settings` standard library module are used.
|
||||
If not set, `~/.config/pkl/settings.pkl` on Unix or `%APPDATA%/pkl/settings.pkl` on Windows (or the legacy `~/.pkl/settings.pkl`), or defaults specified in the `pkl.settings` standard library module are used.
|
||||
====
|
||||
|
||||
.-t, --timeout
|
||||
|
||||
@@ -5,7 +5,6 @@ include::ROOT:partial$component-attributes.adoc[]
|
||||
:uri-DocPackageInfo: {uri-pkl-stdlib-docs}/DocPackageInfo/
|
||||
:uri-CliDocGenerator: {uri-pkl-doc-main-sources}/CliDocGenerator.kt
|
||||
:uri-DocGenerator: {uri-pkl-doc-main-sources}/DocGenerator.kt
|
||||
:uri-pkldoc-macos-amd64-download: {uri-sonatype-snapshot-download}&a=pkldoc-macos-amd64&e=bin
|
||||
:uri-pkldoc-macos-aarch64-download: {uri-sonatype-snapshot-download}&a=pkldoc-macos-aarch64&e=bin
|
||||
:uri-pkldoc-linux-amd64-download: {uri-sonatype-snapshot-download}&a=pkldoc-linux-amd64&e=bin
|
||||
:uri-pkldoc-linux-aarch64-download: {uri-sonatype-snapshot-download}&a=pkldoc-linux-aarch64&e=bin
|
||||
@@ -14,7 +13,6 @@ include::ROOT:partial$component-attributes.adoc[]
|
||||
:uri-pkldoc-java-download: {uri-sonatype-snapshot-download}&a=pkldoc-java&e=jar
|
||||
|
||||
ifdef::is-release-version[]
|
||||
:uri-pkldoc-macos-amd64-download: {github-releases}/pkldoc-macos-amd64
|
||||
:uri-pkldoc-macos-aarch64-download: {github-releases}/pkldoc-macos-aarch64
|
||||
:uri-pkldoc-linux-amd64-download: {github-releases}/pkldoc-linux-amd64
|
||||
:uri-pkldoc-linux-aarch64-download: {github-releases}/pkldoc-linux-aarch64
|
||||
@@ -172,7 +170,7 @@ endif::[]
|
||||
|
||||
The CLI comes in multiple flavors:
|
||||
|
||||
* Native macOS executable for amd64 (tested on macOS 10.15)
|
||||
* Native macOS executable for aarch64 (tested on macOS 10.15)
|
||||
* Native Linux executable for amd64
|
||||
* Native Linux executable for aarch64
|
||||
* Native Alpine Linux executable for amd64 (cross-compiled and tested on Oracle Linux 8)
|
||||
@@ -192,7 +190,6 @@ However, it requires a Java 17 (or higher) runtime on the system path, and has a
|
||||
Download links:
|
||||
|
||||
* macOS aarch64: {uri-pkldoc-macos-aarch64-download}
|
||||
* macOS amd64: {uri-pkldoc-macos-amd64-download}
|
||||
* Linux aarch64: {uri-pkldoc-linux-aarch64-download}
|
||||
* Linux amd64: {uri-pkldoc-linux-amd64-download}
|
||||
* Alpine Linux amd64: {uri-pkldoc-alpine-download}
|
||||
|
||||
@@ -152,7 +152,6 @@ The default output renderer for a module supports the following formats:
|
||||
* `"pcf"`
|
||||
* `"plist"`
|
||||
* `"properties"`
|
||||
* `"textproto"`
|
||||
* `"xml"`
|
||||
* `"yaml"`
|
||||
====
|
||||
@@ -694,6 +693,13 @@ Example: `reporter = "minimal"` +
|
||||
Which test reporter to use for CLI output. Possible values are `"spec"` and `"minimal"`.
|
||||
====
|
||||
|
||||
.install: Property<Boolean>
|
||||
[%collapsible]
|
||||
====
|
||||
Default: `false` +
|
||||
Install the built package into the module cache dir.
|
||||
====
|
||||
|
||||
Common properties:
|
||||
|
||||
include::../partials/gradle-common-properties.adoc[]
|
||||
|
||||
@@ -36,6 +36,21 @@ Example: `generateSpringBootConfig = true` +
|
||||
Whether to generate config classes for use with Spring Boot.
|
||||
====
|
||||
|
||||
.springBootConfigurationProperties: MapProperty<String, String>
|
||||
[%collapsible]
|
||||
====
|
||||
Default: `[:]` +
|
||||
Example: `springBootConfigurationProperties = ["my.mod": "", "my.mod#Server": "server"]` +
|
||||
Explicitly configures which generated classes receive the Spring Boot `@ConfigurationProperties` annotation, and with what prefix.
|
||||
Only applies if `generateSpringBootConfig` is `true`.
|
||||
|
||||
The key is the Pkl class name (for example, `my.mod` for a module class, or `my.mod#Server` for a class within a module), and the value is the annotation's prefix.
|
||||
An empty value generates a bare `@ConfigurationProperties` annotation.
|
||||
|
||||
If this property is empty, the annotations are inferred from the structure of the input modules.
|
||||
If this property is set, no annotations are inferred, and only the specified classes are annotated.
|
||||
====
|
||||
|
||||
.implementSerializable: Property<Boolean>
|
||||
[%collapsible]
|
||||
====
|
||||
|
||||
@@ -64,7 +64,9 @@ Default: `null` +
|
||||
Example 1: `moduleCacheDir = layout.buildDirectory.dir("pkl-module-cache")` +
|
||||
Example 2: `moduleCacheDir.fileValue file("/absolute/path/to/cache")` +
|
||||
The cache directory for storing packages.
|
||||
If `null`, defaults to `~/.pkl/cache`.
|
||||
If `null`, defaults to `~/.cache/pkl` on Unix or `%LOCALAPPDATA%/pkl/Cache` on Windows.
|
||||
|
||||
This setting can also be configured using the `$XDG_CACHE_HOME` environment variable.
|
||||
====
|
||||
|
||||
.color: Property<Boolean>
|
||||
|
||||
@@ -69,7 +69,7 @@ Example: `settingsModule = layout.projectDirectory.file("mySettings.pkl")` +
|
||||
The Pkl settings module to use.
|
||||
This property accepts the same input types as the `sourceModules` property.
|
||||
|
||||
If `null`, `~/.pkl/settings.pkl` or defaults specified in the `pkl.settings` standard library module are used.
|
||||
If `null`, `~/.config/pkl/settings.pkl` on Unix or `%APPDATA%/pkl/settings.pkl` on Windows (or the legacy `~/.pkl/settings.pkl`), or defaults specified in the `pkl.settings` standard library module are used.
|
||||
====
|
||||
|
||||
include::../partials/gradle-common-properties.adoc[]
|
||||
@@ -1,6 +1,6 @@
|
||||
= Pkl 0.32 Release Notes
|
||||
:version: 0.32
|
||||
:version-minor: 0.32.0
|
||||
:version-minor: 0.32.2
|
||||
:release-date: July 8th, 2026
|
||||
:version-next: 0.33
|
||||
:version-next-date: October 2026
|
||||
@@ -62,7 +62,7 @@ Library authors describe types as `ref.Reference<D, T>`, where `D` is the _domai
|
||||
----
|
||||
import "pkl:ref"
|
||||
|
||||
class MyDomain extends ref.Domain { // <1>
|
||||
class MyDomain extends ref.AbstractDomain { // <1>
|
||||
function renderReference(ref: ref.Reference<MyDomain, Any>): String =
|
||||
"${ " + ref.getData() + "." + ref.getPath().join(".") + " }"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,738 @@
|
||||
= Pkl 0.33 Release Notes
|
||||
:version: 0.33
|
||||
:version-minor: 0.33.0
|
||||
:release-date: November 5th, 2026
|
||||
:version-next: 0.34
|
||||
:version-next-date: February 2027
|
||||
|
||||
include::partial$intro.adoc[]
|
||||
|
||||
== Highlights [small]#💖#
|
||||
|
||||
[[generic-methods]]
|
||||
=== Generic Methods
|
||||
|
||||
Support for generic methods has been greatly expanded (https://github.com/apple/pkl/pull/1849[#1849], https://github.com/apple/pkl/pull/1940[#1940]).
|
||||
|
||||
A generic method is a method that accepts types as arguments.
|
||||
This is a powerful tool that allows writing business logic that works over a variety of types.
|
||||
|
||||
For example, this code declares two parameters: type parameter `T`, and value parameter `arg`.
|
||||
|
||||
[source,pkl]
|
||||
----
|
||||
function identity<T>(arg: T): T = arg
|
||||
----
|
||||
|
||||
Generic methods currently already exist in a limited sense:
|
||||
|
||||
. Only the standard library can declare generic methods.
|
||||
. It is not possible to pass in type arguments when calling generic methods.
|
||||
|
||||
In Pkl 0.33, both of these restrictions are lifted.
|
||||
|
||||
To pass type arguments into a generic method, add the type surrounded by angle brackets:
|
||||
|
||||
[source,pkl]
|
||||
----
|
||||
result = identity<String>("Hello, World!")
|
||||
----
|
||||
|
||||
If type arguments are omitted entirely, it behaves as calling the method with the `unknown` type.
|
||||
The following two are logically identical:
|
||||
|
||||
[source,pkl]
|
||||
----
|
||||
result = identity("Hello, World!")
|
||||
result2 = identity<unknown>("Hello, World!")
|
||||
----
|
||||
|
||||
This is a <<parser-rule-change-for-comparison,breaking change>>: in some cases, code that parses as a comparison expression now parses as a method call.
|
||||
|
||||
To read more about generic methods, consult the xref:language-reference:index.adoc#generic-methods[language reference].
|
||||
|
||||
To learn more about the design of generic method calls, consult https://github.com/apple/pkl-evolution/blob/main/spices/SPICE-0032-generic-methods.adoc[SPICE-0032].
|
||||
|
||||
=== New standard library module: `pkl:net`
|
||||
|
||||
A new standard library module is introduced called pkldoc:#[pkl:net], for working with network types (https://github.com/apple/pkl/pull/1834[#1834]).
|
||||
|
||||
Its centerpiece is pkldoc:Url[pkl:net], a URL as defined by https://www.rfc-editor.org/rfc/rfc3986[RFC 3986].
|
||||
A string is parsed with pkldoc:#Url()[pkl:net], which accepts an absolute URL as well as a relative reference, and which throws if the string cannot be parsed:
|
||||
|
||||
[source,pkl]
|
||||
----
|
||||
import "pkl:net"
|
||||
|
||||
local url = net.Url("https://user:pw@example.com:8080/a/b c?q=1&q=2#f")
|
||||
|
||||
scheme = url.scheme // "https"
|
||||
rawUserInfo = url.rawUserInfo // "user:pw"
|
||||
authority = url.authority // "user:pw@example.com:8080"
|
||||
rawHost = url.rawHost // "example.com"
|
||||
rawPort = url.rawPort // "8080"
|
||||
port = url.port // 8080
|
||||
rawPath = url.rawPath // "/a/b%20c"
|
||||
path = url.path // "/a/b c"
|
||||
pathSegments = url.pathSegments // List("a", "b c")
|
||||
rawQuery = url.rawQuery // "q=1&q=2"
|
||||
queryParameters = url.queryParameters // Mapping { ["q"] = Listing { "1"; "2" } }
|
||||
rawFragment = url.rawFragment // "f"
|
||||
----
|
||||
|
||||
Parsing is lenient: whatever cannot appear literally in a component is percent-encoded as its UTF-8 bytes, and whatever is already encoded is kept as written.
|
||||
Every component is held percent-encoded in its `raw` property, whether the input was encoded or not; the percent-decoded form of each is available as `userInfo`, `host`, `path`, `query`, and `fragment`.
|
||||
The derived `pathSegments` and `queryParameters` are percent-decoded too.
|
||||
|
||||
A URL can also be built from its components, or derived from another URL by amending it.
|
||||
A component given this way has to be percent-encoded already:
|
||||
|
||||
[source,pkl]
|
||||
----
|
||||
built = new net.Url {
|
||||
scheme = "https"
|
||||
rawHost = "example.com"
|
||||
rawPath = "/a%20b"
|
||||
}.toString() // "https://example.com/a%20b"
|
||||
|
||||
local base = net.Url("https://example.com/a?q=1")
|
||||
|
||||
amended = (base) { rawQuery = null }.toString() // "https://example.com/a"
|
||||
----
|
||||
|
||||
Each component is type-checked, so a URL built this way cannot be malformed.
|
||||
For example, a `rawPort` can only hold digits, only a URL with a `rawHost` can have one, and a `rawPath` of `"/a b"` is rejected.
|
||||
The decoded components are `fixed`, so they cannot be amended.
|
||||
|
||||
A relative reference can be resolved against an absolute URL, which also removes dot segments from the result's path:
|
||||
|
||||
[source,pkl]
|
||||
----
|
||||
resolved = net.Url("https://example.com/a/b").resolve("../c").toString() // "https://example.com/c"
|
||||
----
|
||||
|
||||
Comparing two URLs with `==` compares their components as written.
|
||||
To instead compare them as the *syntax-based normalization* of RFC 3986, where scheme and host are case-insensitive and percent-encoding is normalized, use `equals()`:
|
||||
|
||||
[source,pkl]
|
||||
----
|
||||
same = net.Url("HTTPS://Example.com/%7Euser").equals(net.Url("https://example.com/~user")) // true
|
||||
----
|
||||
|
||||
The module also provides standalone helpers: pkldoc:#encodeUrlComponent()[pkl:net] and pkldoc:#decodeUrlComponent()[pkl:net] for percent-encoding, and pkldoc:#buildQuery()[pkl:net] and pkldoc:#parseQuery()[pkl:net] for `application/x-www-form-urlencoded` queries.
|
||||
|
||||
Alongside this module, `pkl:base` gains pkldoc:String#isValidUrl[], which tells whether a string is a valid absolute URL.
|
||||
It is stricter than `net.Url()`, which also parses a relative reference, and which percent-encodes whatever cannot appear literally in a URL component.
|
||||
The `Uri` typealias is now defined in terms of it, which is a <<uri-validates,breaking change>>.
|
||||
|
||||
=== New standard library module: `pkl:syntax`
|
||||
|
||||
A new standard library module is introduced called pkldoc:#[pkl:syntax], for working with Pkl source code (https://github.com/apple/pkl/pull/1821[#1821], https://github.com/apple/pkl/pull/1964[#1964], https://github.com/apple/pkl/pull/1955[#1955]).
|
||||
|
||||
It has three parts:
|
||||
|
||||
* pkldoc:Parser[pkl:syntax] parses a string or resource into a syntax tree, as either a module or an expression.
|
||||
* pkldoc:Node[pkl:syntax] is a node of that tree.
|
||||
It has a `type`, `children`, the verbatim source `text`, and the source `span`.
|
||||
Nodes can be searched, folded, and replaced with the `find*`, `fold`, and `replace*` methods.
|
||||
* pkldoc:FormattingRenderer[pkl:syntax] renders a tree back to Pkl source code, using the canonical Pkl formatter.
|
||||
|
||||
[source,pkl]
|
||||
----
|
||||
import "pkl:syntax"
|
||||
|
||||
local myModule = new syntax.Parser {}.parseModule("""
|
||||
class Point {
|
||||
x: Int
|
||||
y: Int
|
||||
}
|
||||
""")
|
||||
|
||||
className = myModule.findChild((n) -> n.type == "class").findChildOfType("identifier").text // "Point"
|
||||
|
||||
ifCount = myModule.fold(0, (acc, n) -> if (n.type == "if_expr") acc + 1 else acc) // 0
|
||||
----
|
||||
|
||||
=== C Library (libpkl)
|
||||
|
||||
A new C library, libpkl, is introduced (https://github.com/apple/pkl/pull/1238[#1238], https://github.com/apple/pkl/pull/1799[#1799], https://github.com/apple/pkl/pull/1801[#1801], https://github.com/apple/pkl/pull/1805[#1805], https://github.com/apple/pkl/pull/1812[#1812]).
|
||||
|
||||
One of Pkl's selling points is the ability to embed the language into another runtime as a library.
|
||||
However, currently, callers from non-JVM languages must spawn Pkl as a child process.
|
||||
This introduces a system dependency on downstream users, and also introduces complexity and overhead for the operating system.
|
||||
|
||||
To address this pain point, a new library called libpkl is released for the C language.
|
||||
|
||||
This is a low-level library meant to be called into from language binding libraries.
|
||||
It exposes the xref:bindings-specification:message-passing-api.adoc[message passing API] through a small C interface (`pkl.h`).
|
||||
|
||||
Archives containing the header, both static and dynamic (shared) libraries, and `pkg-config` files are published for each supported OS and architecture, as part of the GitHub release.
|
||||
|
||||
To read more about libpkl, consult the xref:libpkl:index.adoc[libpkl documentation].
|
||||
|
||||
To read more about the design of this feature, consult https://github.com/apple/pkl-evolution/blob/main/spices/SPICE-0015-c-library-for-pkl.adoc[SPICE-0015].
|
||||
|
||||
Thank you to https://github.com/KushalP[@KushalP] for their contributions to this feature!
|
||||
|
||||
=== CPU Profiler
|
||||
|
||||
The Pkl CLI can now profile where an evaluation spends its CPU time (https://github.com/apple/pkl/pull/1823[#1823], https://github.com/apple/pkl/pull/1931[#1931]).
|
||||
|
||||
Profiling is enabled by passing `--profile-cpu-output`, which sets the file to write the profile to.
|
||||
The profile is written in https://github.com/google/pprof[pprof] format, so it can be explored with any tool that reads it, such as `go tool pprof`.
|
||||
|
||||
[source,shell,opts=novalidate]
|
||||
----
|
||||
$ pkl eval --profile-cpu-output profile.pb.gz myConfig.pkl
|
||||
$ go tool pprof -http=:8080 profile.pb.gz
|
||||
----
|
||||
|
||||
Profiling is only exposed through the CLI.
|
||||
For more details, consult xref:pkl-cli:index.adoc#profile-cpu-output[the CLI documentation].
|
||||
|
||||
== Noteworthy [small]#🎶#
|
||||
|
||||
=== `this` as a type
|
||||
|
||||
The `this` keyword can now be used as a type (https://github.com/apple/pkl/pull/1708[#1708], https://github.com/apple/pkl/pull/1962[#1962]).
|
||||
|
||||
A self type is the type of the object that a member is accessed on.
|
||||
Self types are useful when building class hierarchies, because they let a member refer to the _same_ type as the object it belongs to, even after the class is extended.
|
||||
|
||||
Currently, Pkl has a self-type, `module`, but it specifically describes the type of the module object.
|
||||
There is no equivalent for regular classes nor objects.
|
||||
|
||||
To address this shortcoming, the `this` keyword is now also a type.
|
||||
It refers to the self-type of the directly enclosing object, class, or module.
|
||||
|
||||
When a class is extended, `this` in an inherited member refers to the subclass, not to the class that declares the member:
|
||||
|
||||
[source,pkl]
|
||||
----
|
||||
open class Node {
|
||||
parent: this?
|
||||
children: List<this>
|
||||
}
|
||||
|
||||
class SpecialNode extends Node {
|
||||
special: Boolean
|
||||
}
|
||||
|
||||
node: SpecialNode = new {
|
||||
special = true
|
||||
parent = new SpecialNode { special = false; children = List() } // <1>
|
||||
children = List(new SpecialNode { special = true; children = List() })
|
||||
}
|
||||
|
||||
bad: SpecialNode = new {
|
||||
special = true
|
||||
parent = new Node {} // <2>
|
||||
}
|
||||
----
|
||||
<1> `parent` has type `SpecialNode?`.
|
||||
<2> Error: `Node` is not a `SpecialNode`.
|
||||
|
||||
The `this` type can be used in properties, method parameters and return types, function types, and type arguments.
|
||||
It cannot be used in a type alias body.
|
||||
|
||||
The `this` type has also been <<this-adoption,adopted in the standard library>>.
|
||||
|
||||
To read more about the design, see https://github.com/apple/pkl-evolution/blob/main/spices/SPICE-0029-self-type.adoc[SPICE-0029].
|
||||
|
||||
=== Better `new {}` inference
|
||||
|
||||
The `new {}` expression can now correctly infer its parent in more positions (https://github.com/apple/pkl/pull/1768[#1768], https://github.com/apple/pkl/pull/1824[#1824], https://github.com/apple/pkl/pull/1845[#1845], https://github.com/apple/pkl/pull/1847[#1847], https://github.com/apple/pkl/pull/1857[#1857]).
|
||||
|
||||
An xref:language-reference:index.adoc#implicitly-typed-new[implicitly typed `new {}` expression] tells Pkl to infer the parent when amending.
|
||||
When used inside a property, for example, it amends the class property default.
|
||||
|
||||
Using `new {}` in the following positions currently throw, and will work in Pkl 0.33:
|
||||
|
||||
* Method arguments
|
||||
* Let bindings
|
||||
|
||||
[source,pkl]
|
||||
----
|
||||
class Bird {
|
||||
name: String
|
||||
}
|
||||
|
||||
function greet(bird: Bird): String = "Hello, \(bird.name)"
|
||||
|
||||
greeting1 = greet(new { name = "Pigeon" }) // <1>
|
||||
|
||||
greeting2 =
|
||||
let (bird: Bird = new { name = "Parrot" }) // <2>
|
||||
greet(bird)
|
||||
----
|
||||
<1> The argument is inferred to be a `Bird`.
|
||||
<2> The `let` binding is inferred to be a `Bird`.
|
||||
|
||||
Inference is not available where the parameter type is not known ahead of time.
|
||||
Additionally, it cannot be used for constructor methods like `List()`, generic methods like `Pair()`, and `Function.apply()`.
|
||||
|
||||
It is an error for the parameter type to be `this` or `module`.
|
||||
|
||||
=== Support for parsing `.netrc` files into HTTP headers
|
||||
|
||||
Pkl now supports parsing `.netrc` files to configure HTTP Basic authentication headers (https://github.com/apple/pkl/pull/1817[#1817], https://github.com/apple/pkl/pull/1854[#1854]).
|
||||
|
||||
A new method is introduced called `netRcHeaders()` on class `pkl.EvaluatorSettings#Http`.
|
||||
|
||||
It can be used to read credentials directly from a local `.netrc` file and mapped to HTTP `Authorization` headers:
|
||||
|
||||
.~/.config/pkl/settings.pkl
|
||||
[source,pkl]
|
||||
----
|
||||
amends "pkl:settings"
|
||||
|
||||
http {
|
||||
headers = netRcHeaders(read("/Users/johnappleseed/.netrc"))
|
||||
}
|
||||
----
|
||||
|
||||
It follows standard `curl` parsing rules, supporting comments, quoted values, and escaped characters.
|
||||
|
||||
Thank you to https://github.com/ayashjorden[@ayashjorden] for their contributions to this feature!
|
||||
|
||||
=== CLI Changes
|
||||
|
||||
==== Package installation to the local cache
|
||||
|
||||
The `pkl project package` command has gained a new `--install` flag that triggers installation of the built package(s) into the module cache (https://github.com/apple/pkl/pull/1795[#1795]).
|
||||
|
||||
Local cache installation is useful for local end-to-end testing of dependency trees.
|
||||
This functionality is also available via pkl-gradle.
|
||||
|
||||
|
||||
==== Default file locations
|
||||
|
||||
For new setups, the CLI no longer stores anything under `~/.pkl` (https://github.com/apple/pkl/pull/1809[#1809]).
|
||||
|
||||
It uses XDG-style locations on Unix and Known Folder locations on Windows:
|
||||
|
||||
[cols="1,2,2,2",options="header"]
|
||||
|===
|
||||
| Concern | Unix (Linux/macOS) | Windows | Legacy fallback
|
||||
|
||||
| Package cache
|
||||
| `~/.cache/pkl`
|
||||
| `$LOCALAPPDATA/pkl/Cache`
|
||||
| none
|
||||
|
||||
| Settings file
|
||||
| `~/.config/pkl/settings.pkl`
|
||||
| `$APPDATA/pkl/settings.pkl`
|
||||
| `~/.pkl/settings.pkl`
|
||||
|
||||
| CA certificates
|
||||
| `~/.config/pkl/cacerts`
|
||||
| `$APPDATA/pkl/cacerts`
|
||||
| `~/.pkl/cacerts`
|
||||
|
||||
| REPL history
|
||||
| `~/.local/state/pkl/repl-history`
|
||||
| `$LOCALAPPDATA/pkl/repl-history`
|
||||
| none
|
||||
|===
|
||||
|
||||
On every OS, these locations can be overridden with XDG-style env vars.
|
||||
For example, setting `XDG_CACHE_HOME` will configure the cache directory.
|
||||
|
||||
Note that the existing `~/.pkl/cache` directory is ignored, so Pkl will download packages to populate its cache if configured to do so.
|
||||
|
||||
To learn more about this design, consult https://github.com/apple/pkl-evolution/blob/main/spices/SPICE-0031-os-base-directories.adoc[SPICE-0031].
|
||||
|
||||
Thank you to https://github.com/florinungur[@florinungur] for their contributions to this feature!
|
||||
|
||||
=== Java Library Changes
|
||||
|
||||
==== `pkl.net#Url` maps to `java.net.URI`
|
||||
|
||||
The JVM bindings now understand the new `pkl.net#Url` class (https://github.com/apple/pkl/pull/1834[#1834]).
|
||||
|
||||
The Java and Kotlin code generators generate a `java.net.URI` for a property whose type is pkldoc:Url[pkl:net].
|
||||
|
||||
Two conversions are added to `org.pkl.config.java.mapper.Conversions`, both part of the preconfigured set:
|
||||
|
||||
* `pUrlToString` -- gives the same result as `Url.toString()`
|
||||
* `pUrlToURI` -- throws `ConversionException` if the URL cannot be represented as a `java.net.URI`
|
||||
|
||||
=== Standard Library Changes
|
||||
|
||||
==== `pkl:net`
|
||||
|
||||
* New module introduced.
|
||||
|
||||
==== `pkl:syntax`
|
||||
|
||||
* New module introduced.
|
||||
|
||||
==== `pkl:base`
|
||||
|
||||
* New property: pkldoc:String#isValidUrl[]
|
||||
* Changed typealias: pkldoc:Uri[] is now `String(isValidUrl)` (<<uri-validates,breaking change>>)
|
||||
* [[this-adoption]] Properties refined with `this` type
|
||||
** pkldoc:Any.getClass()[] now returns `Class<this>`
|
||||
** pkldoc:Any.ifNonNull()[] now accepts an argument of type `(this) \-> Result`
|
||||
|
||||
==== `pkl:ref`
|
||||
|
||||
* Renamed class: `Domain` is now `AbstractDomain` (<<reference-changes,breaking change>>)
|
||||
* Changed method: `Reference()` now takes type arguments instead of a `Class` parameter (<<reference-changes,breaking change>>)
|
||||
* pkldoc:AbstractDomain.renderReference()[pkl:ref] now accepts a `Reference<this, Any>`
|
||||
* No longer considered experimental.
|
||||
|
||||
==== `pkl:protobuf`
|
||||
|
||||
* Module deprecated for removal in Pkl 0.34 (https://github.com/apple/pkl/pull/1778[#1778]).
|
||||
|
||||
== Breaking Changes [small]#💔#
|
||||
|
||||
=== Removal of macOS amd64 (Intel) Binaries
|
||||
|
||||
Native `pkl` and `pkldoc` binaries for Intel Mac systems are no longer published with Pkl releases (https://github.com/apple/pkl/pull/1769[#1769]).
|
||||
|
||||
To continue running new Pkl releases on these systems, use an appropriate Java runtime and the `jpkl` and `jpkldoc` Java executables.
|
||||
|
||||
=== Type check changes for `Class<T>`
|
||||
|
||||
Currently, type arguments to the `Class` type are erased.
|
||||
Any `Class` value would typecheck against `Class<T>` for any value of `T`.
|
||||
|
||||
In Pkl 0.33, this erasure has been removed.
|
||||
A `Class` value typechecked against `Class<T>` must be a subclass of `T` (https://github.com/apple/pkl/pull/1698[#1698]).
|
||||
|
||||
If `T` does not resolve to a class type (i.e. it is a union type, nullable type, string literal type, parameterized type, or `nothing`), the type check will always fail.
|
||||
|
||||
=== Standard library properties marked `const`
|
||||
|
||||
Some properties within the `pkl:math` and `pkl:release` modules have been marked `const` (https://github.com/apple/pkl/pull/1864[#1864]).
|
||||
|
||||
Currently, module properties in `pkl:math` and `pkl:release` can be overridden by amending the module.
|
||||
Doing this makes little sense; these values are constants and overriding them by amending their module would be odd.
|
||||
|
||||
The following properties are now `const` and cannot be amended:
|
||||
|
||||
* pkldoc:minInt[pkl:math]
|
||||
* pkldoc:maxInt[pkl:math]
|
||||
* pkldoc:maxInt8[pkl:math]
|
||||
* pkldoc:maxInt16[pkl:math]
|
||||
* pkldoc:maxInt32[pkl:math]
|
||||
* pkldoc:maxUInt[pkl:math]
|
||||
* pkldoc:maxUInt8[pkl:math]
|
||||
* pkldoc:maxUInt16[pkl:math]
|
||||
* pkldoc:maxUInt32[pkl:math]
|
||||
* pkldoc:minFiniteFloat[pkl:math]
|
||||
* pkldoc:maxFiniteFloat[pkl:math]
|
||||
* pkldoc:minPositiveFloat[pkl:math]
|
||||
* pkldoc:e[pkl:math]
|
||||
* pkldoc:pi[pkl:math]
|
||||
* pkldoc:current[pkl:release]
|
||||
|
||||
This is a breaking change for code that looks like this:
|
||||
[source,pkl]
|
||||
----
|
||||
bogusMath = (import("pkl:math")) {
|
||||
minInt = -1
|
||||
}
|
||||
----
|
||||
|
||||
=== Changes to `join` method on `List`, `Set`, and `Listing`
|
||||
|
||||
The `join` method in `List`, `Set`, and `Listing` will now call the `toString()` method if overridden on an object (https://github.com/apple/pkl/pull/1872[#1872]).
|
||||
|
||||
This is a breaking change, but is also considered a bugfix.
|
||||
|
||||
The following snippet will produce different results:
|
||||
|
||||
[source,pkl]
|
||||
----
|
||||
class Bird {
|
||||
name: String
|
||||
|
||||
function toString() = "{ My name is \(name) }"
|
||||
}
|
||||
|
||||
hidden birds: Listing<Bird> = new {
|
||||
new { name = "Osprey" }
|
||||
new { name = "Eagle" }
|
||||
}
|
||||
|
||||
res = birds.join("\n")
|
||||
----
|
||||
|
||||
Currently, Pkl ignores the `toString()` override, and produces:
|
||||
|
||||
[source,pkl]
|
||||
----
|
||||
res = """
|
||||
new Bird { name = "Osprey" }
|
||||
new Bird { name = "Eagle" }
|
||||
"""
|
||||
----
|
||||
|
||||
In Pkl 0.33, the overridden `toString()` will be called, and produces:
|
||||
|
||||
[source,pkl]
|
||||
----
|
||||
res = """
|
||||
{ My name is Osprey }
|
||||
{ My name is Eagle }
|
||||
"""
|
||||
----
|
||||
|
||||
[[uri-validates]]
|
||||
=== `Uri` validates its value
|
||||
|
||||
The pkldoc:Uri[] typealias in `pkl:base` is now `String(isValidUrl)` instead of a plain `String` (https://github.com/apple/pkl/pull/1834[#1834]).
|
||||
|
||||
A value typed as `Uri` must be a valid absolute URL according to https://www.rfc-editor.org/rfc/rfc3986[RFC 3986].
|
||||
This means that a relative reference, or a URL containing a character that must be percent-encoded, no longer type-checks:
|
||||
|
||||
[source,pkl]
|
||||
----
|
||||
good: Uri = "https://example.com/some%20path"
|
||||
|
||||
bad1: Uri = "./some/path" // throws: not absolute
|
||||
bad2: Uri = "https://example.com/some path" // throws: space must be percent-encoded
|
||||
----
|
||||
|
||||
This affects every standard library member typed as `Uri`, among them pkldoc:Resource#uri[], pkldoc:#importGraph()[pkl:analyze], and the URI-valued properties of pkldoc:#[pkl:Project], pkldoc:#[pkl:EvaluatorSettings] and pkldoc:#[pkl:DocPackageInfo].
|
||||
|
||||
As a consequence, an invalid URI is now reported as a type constraint violation at the point where it is set, rather than as an error raised later by whatever consumes it.
|
||||
|
||||
[[reference-changes]]
|
||||
=== Changes to `Reference` construction
|
||||
|
||||
As part of the introduction of generic methods, the `Reference()` constructor method in `pkl:ref` takes the domain and referent types as type arguments instead of taking a `Class` parameter (https://github.com/apple/pkl/pull/1849[#1849]).
|
||||
This also makes it possible to construct references to non-class types, such as `Listing<String>` or `String | Int`.
|
||||
|
||||
The `ref.Domain` class has been renamed to `ref.AbstractDomain`.
|
||||
|
||||
[source,pkl]
|
||||
----
|
||||
import "pkl:ref"
|
||||
|
||||
class MyDomain extends ref.AbstractDomain { // <1>
|
||||
function renderReference(reference: ref.Reference<MyDomain, Any>): String = "..."
|
||||
}
|
||||
|
||||
// current
|
||||
myRef = ref.Reference(new MyDomain {}, MyReferent, myData)
|
||||
|
||||
// new
|
||||
myRef = ref.Reference<MyDomain, MyReferent>(new MyDomain {}, myData) // <2>
|
||||
----
|
||||
<1> In Pkl 0.32, `extends ref.Domain`.
|
||||
<2> Constraints in the referent type of a `Reference` type annotation are no longer an error, they are ignored.
|
||||
|
||||
[#parser-rule-change-for-comparison]
|
||||
=== Parser rule changes for comparison
|
||||
|
||||
To support <<generic-methods,generic method calls>>, the syntax for Pkl has changed to permit passing type arguments when calling a method.
|
||||
|
||||
This purposefully introduces an ambiguity to the grammar: the following expression can be parsed as either a generic method call, or as nested comparison expressions:
|
||||
|
||||
[source,{pkl-expr}]
|
||||
----
|
||||
foo < Int > (5)
|
||||
----
|
||||
|
||||
To resolve such ambiguities, such code is preferably parsed as a generic method call.
|
||||
|
||||
This contrived example is nonsensical today, because it is an error to compare a boolean.
|
||||
However, there are times when such code might be written in a well-formed way, for example:
|
||||
|
||||
[source,pkl]
|
||||
----
|
||||
res = foo(
|
||||
bar < baz,
|
||||
qux > (corge + 3)
|
||||
)
|
||||
----
|
||||
|
||||
In Pkl 0.33, this above snippet will interpret `bar` as a generic method call.
|
||||
To restore the old meaning, wrap the left-hand side with parentheses:
|
||||
|
||||
[source,diff]
|
||||
----
|
||||
res = foo(
|
||||
- bar < baz,
|
||||
+ (bar) < baz,
|
||||
qux > (corge + 3)
|
||||
)
|
||||
----
|
||||
|
||||
=== `test.catch()` and `test.catchOrNull()` force their result
|
||||
|
||||
The pkldoc:#catch()[pkl:test] and pkldoc:#catchOrNull()[pkl:test] methods now fully evaluate the value returned by the function they are given (https://github.com/apple/pkl/pull/1910[#1910]).
|
||||
|
||||
Currently, an error nested inside the returned value was not caught.
|
||||
It surfaced later, when the value was used.
|
||||
In Pkl 0.33, it is caught, and `catch()` returns its message.
|
||||
|
||||
=== Abstract methods must be implemented in extending non-abstract classes/modules
|
||||
|
||||
When extending an abstract class or module, it is now an error if any abstract methods are not implemented.
|
||||
|
||||
In the following snippet, class `Swallow` is invalid because it does not implement `canCarry`:
|
||||
|
||||
[source,pkl]
|
||||
----
|
||||
abstract class Bird {
|
||||
abstract function canCarry(fruit: Fruit): Boolean
|
||||
}
|
||||
|
||||
class Swallow extends Bird {
|
||||
type: "European" | "African"
|
||||
}
|
||||
----
|
||||
|
||||
=== Removal of `module` type in const scopes
|
||||
|
||||
The `module` type should no longer be used inside typealiases, class bodies, annotation bodies, and `const` scopes.
|
||||
Such usages will emit a deprecation warning, and will become a full error in Pkl 0.34 (https://github.com/apple/pkl/pull/1708[#1708]).
|
||||
|
||||
The `module` type is a self-type, which refers to the `this` of the module.
|
||||
When a module is extended, the `module` type refers to the extending module.
|
||||
On the other hand, the definition of classes and typealiases cannot be changed by an extending module.
|
||||
|
||||
The following snippet is misleading, because the module property `friend` _is_ a self-type, whereas class property `Feature.animal` is not.
|
||||
|
||||
.Bird.pkl
|
||||
[source,pkl]
|
||||
----
|
||||
open module Bird
|
||||
|
||||
friend: module? // <1>
|
||||
|
||||
class Feature {
|
||||
animal: module? // <2>
|
||||
}
|
||||
----
|
||||
<1> Self-type
|
||||
<2> Not a self-type
|
||||
|
||||
To have clearer semantics, the `module` type can no longer be used inside const scopes.
|
||||
|
||||
In most cases, such code should be replaced with a self-import.
|
||||
|
||||
.Bird.pkl
|
||||
[source,pkl]
|
||||
----
|
||||
open module Bird
|
||||
|
||||
import "Bird.pkl" // <1>
|
||||
|
||||
class Feature {
|
||||
animal: Bird // <2>
|
||||
}
|
||||
----
|
||||
<1> Self-import to use `Bird` as a type
|
||||
|
||||
=== Protobuf module deprecated
|
||||
|
||||
The `pkl:protobuf` module is deprecated without replacement (https://github.com/apple/pkl/pull/1778[#1778]).
|
||||
|
||||
The protobuf module renders Pkl data into the "textproto" format.
|
||||
However, Pkl is more expressive than protobuf, which means that Pkl's renderer needs to make rendering choices that are bespoke to Pkl's protobuf renderer.
|
||||
|
||||
For example, there is no native equivalent for Pkl's `null` literal.
|
||||
To address this, Pkl's protobuf renderer renders these as a "null" scalar.
|
||||
|
||||
These rendering choices are somewhat ad-hoc and likely to break from one usage to the next.
|
||||
|
||||
Users of protobuf are encouraged render Pkl into https://protobuf.dev/programming-guides/json/[ProtoJSON] instead.
|
||||
|
||||
== Miscellaneous [small]#🐸#
|
||||
|
||||
* The `abstract` modifier is now a no-op for properties. (https://github.com/apple/pkl/pull/1781[#1781]).
|
||||
* Abstract methods in non-abstract classes and modules are now rejected (https://github.com/apple/pkl/pull/1647[#1647], https://github.com/apple/pkl/pull/1780[#1780]).
|
||||
* Enforce that non-abstract classes implement all abstract methods from superclasses (https://github.com/apple/pkl/pull/1785[#1785]).
|
||||
* Introduce a new internal representation for type metadata (https://github.com/apple/pkl/pull/1838[#1838]).
|
||||
* Improve error message when amending a property with no value set (https://github.com/apple/pkl/pull/1867[#1867]).
|
||||
* Use `EnumSet` for token categories (https://github.com/apple/pkl/pull/1803[#1803]).
|
||||
* pkldoc:String.toInt()[] now supports all hexadecimal, octal, and binary formats (https://github.com/apple/pkl/pull/1808[#1808]).
|
||||
* Handle conditional pluralization in error message (https://github.com/apple/pkl/pull/1848[#1848]).
|
||||
* Avoid using Java `Object.toString()` to present language-facing string representations (https://github.com/apple/pkl/pull/1850[#1850]).
|
||||
* Unify internal construction of objects (https://github.com/apple/pkl/pull/1873[#1873]).
|
||||
* Use module cache directory configured via the CLI or Gradle while evaluating `PklProject` and `settings.pkl` files (https://github.com/apple/pkl/pull/1900[#1900]).
|
||||
* Typed values are rendered with more helpful type names in error messages and pkldoc:Any.toString()[] (https://github.com/apple/pkl/pull/1412[#1412]).
|
||||
* Add support for customizing spring boot `@ConfigurationProperties` in generated Java/Kotlin code (https://github.com/apple/pkl/pull/1897[#1897]).
|
||||
* Improve error message when instantiating a class that cannot be instantiated (https://github.com/apple/pkl/pull/1916[#1916]).
|
||||
* Improve error messages when a type alias fails to type check (https://github.com/apple/pkl/pull/1914[#1914]).
|
||||
* Add descriptions and groups to the tasks created by the pkl-gradle plugin (https://github.com/apple/pkl/pull/190[#190]).
|
||||
* Pkldoc no longer truncates the names of modules that do not start with the package's module name prefix (https://github.com/apple/pkl/pull/1909[#1909]).
|
||||
* Reject `file:` URIs that include an authority (https://github.com/apple/pkl/pull/629[#629]).
|
||||
* Upgrade to GraalVM 25.2.4 (https://github.com/apple/pkl/pull/1804[#1804]).
|
||||
* Improve performance of reading `const` members (https://github.com/apple/pkl/pull/1835[#1835]).
|
||||
* Improve performance of `Listing` and `Mapping` type casts (https://github.com/apple/pkl/pull/1837[#1837]).
|
||||
* Improve performance of lexical frame resolution (https://github.com/apple/pkl/pull/1842[#1842]).
|
||||
* Improve performance of amending `null` parents (https://github.com/apple/pkl/pull/1836[#1836]).
|
||||
* Improve performance of annotations without object bodies (https://github.com/apple/pkl/pull/1843[#1843]).
|
||||
* Improve performance of `for` and `when` generators (https://github.com/apple/pkl/pull/1844[#1844]).
|
||||
* Improve performance of parent property and subscript access (https://github.com/apple/pkl/pull/1853[#1853]).
|
||||
* Improve performance of typechecking (https://github.com/apple/pkl/pull/1871[#1871]).
|
||||
* Improve performance of string literal union types in more cases (https://github.com/apple/pkl/pull/1875[#1875]).
|
||||
* Improve performance of iterating object members (https://github.com/apple/pkl/pull/1879[#1879]). Thank you to https://github.com/odenix[@odenix] for their contributions to this feature!
|
||||
* Fix some partial evaluation issues in the GraalVM compiler (https://github.com/apple/pkl/pull/1944[#1944]).
|
||||
|
||||
== Bug Fixes [small]#🐜#
|
||||
|
||||
* Fix incorrect behavior when objects are partially forced (https://github.com/apple/pkl/pull/1790[#1790]).
|
||||
* Fix issues publishing pkl-gradle (https://github.com/apple/pkl/pull/1777[#1777], https://github.com/apple/pkl/pull/1807[#1807]).
|
||||
* Fix resolution of the `module` type in typealias bodies (https://github.com/apple/pkl/pull/1796[#1796]).
|
||||
* Don't fail when loading known-malformed CA certs (https://github.com/apple/pkl/pull/1806[#1806]).
|
||||
* Fix resolution of type names in typealias constraints (https://github.com/apple/pkl/pull/1826[#1826]).
|
||||
* Fix parsing of block comments at the end of a file (https://github.com/apple/pkl/pull/1828[#1828]).
|
||||
* Sanitize block comment endings in generated Kotlin doc comments (https://github.com/apple/pkl/pull/1830[#1830]).
|
||||
* Enforce max collection size when decoding pkl-binary data (https://github.com/apple/pkl/pull/1831[#1831]).
|
||||
* Correctly omit glob imports from reflected imports (https://github.com/apple/pkl/pull/1816[#1816]).
|
||||
* Escape characters that cannot appear literally in rendered YAML (https://github.com/apple/pkl/pull/1846[#1846]).
|
||||
* Fix invalid prefix when generating spring boot config (https://github.com/apple/pkl/pull/1862[#1862]).
|
||||
* Fix `pkl run` behaviors where (sub)commands amend `pkl:Command` without extending it (https://github.com/apple/pkl/pull/1882[#1882]).
|
||||
* Fix pkldoc:String.endsWith()[] for overlapping regex matches (https://github.com/apple/pkl/pull/1880[#1880], https://github.com/apple/pkl/pull/1884[#1884]).
|
||||
* Fix leak of external reader processes in the language binding server (https://github.com/apple/pkl/pull/1885[#1885]).
|
||||
* Fix decoding of `Regex` values from pkl-binary data (https://github.com/apple/pkl/pull/1895[#1895]).
|
||||
* Fix `pkl eval` of dependency notation source modules when using multiple file output (https://github.com/apple/pkl/pull/1942[#1942]).
|
||||
* Report an error, instead of overflowing the stack, when parsing or formatting code that is nested too deeply (https://github.com/apple/pkl/pull/1918[#1918], https://github.com/apple/pkl/pull/1953[#1953]).
|
||||
* Fix type check of the `module` type when it is defined in a parent member body (https://github.com/apple/pkl/pull/1936[#1936]).
|
||||
* Fix type check of subscript key types for `Map` and `Mapping` references (https://github.com/apple/pkl/pull/1933[#1933]).
|
||||
* Fix `hashCode()` and `==` inconsistency for negative zero in `Duration` and `DataSize` (https://github.com/apple/pkl/pull/1932[#1932]).
|
||||
* Fix `hashCode()` inconsistency with `==` for `Listing`, `Duration`, and `DataSize` (https://github.com/apple/pkl/pull/1922[#1922]).
|
||||
* Fix `toString()` and `value` of `Duration` and `DataSize` values that are too large to fit in a `Long` (https://github.com/apple/pkl/pull/1935[#1935]).
|
||||
* Fix iteration over `Dynamic` objects that have a negative key, such as `new Dynamic { [-1] = 2 }` (https://github.com/apple/pkl/pull/1948[#1948]).
|
||||
* Fix enforcement of `const` member access inside methods (https://github.com/apple/pkl/pull/1945[#1945]).
|
||||
* Fix parameterized type checks in type alias constraints (https://github.com/apple/pkl/pull/1906[#1906]).
|
||||
* Fix resolution of qualified type names (https://github.com/apple/pkl/pull/1915[#1915]).
|
||||
* Fix lookaround regexes in pkldoc:String.lastIndexOf()[] and pkldoc:String.replaceLast()[] (https://github.com/apple/pkl/pull/1908[#1908]).
|
||||
* Fix formatter instability (https://github.com/apple/pkl/pull/1911[#1911]).
|
||||
* Improve normalization of URIs (https://github.com/apple/pkl/pull/1937[#1937], https://github.com/apple/pkl/pull/1954[#1954]).
|
||||
* Missing const checks which allow accessing non-const members (https://github.com/apple/pkl/issues/1963[#1963]).
|
||||
|
||||
== Contributors [small]#🙏#
|
||||
|
||||
We would like to thank the contributors to this release (in alphabetical order):
|
||||
|
||||
* https://github.com/AAGAM17[@AAGAM17]
|
||||
* https://github.com/aashish254[@aashish254]
|
||||
* https://github.com/ayashjorden[@ayashjorden]
|
||||
* https://github.com/bioball[@bioball]
|
||||
* https://github.com/Carpe-Wang[@Carpe-Wang]
|
||||
* https://github.com/dylanpulver[@dylanpulver]
|
||||
* https://github.com/florinungur[@florinungur]
|
||||
* https://github.com/goutamadwant[@goutamadwant]
|
||||
* https://github.com/henriquejsza[@henriquejsza]
|
||||
* https://github.com/HT154[@HT154]
|
||||
* https://github.com/KushalP[@KushalP]
|
||||
* https://github.com/kwy404[@kwy404]
|
||||
* https://github.com/mayc7n[@mayc7n]
|
||||
* https://github.com/netvl[@netvl]
|
||||
* https://github.com/odenix[@odenix]
|
||||
* https://github.com/protobufel2[@protobufel2]
|
||||
* https://github.com/RushikeshGandhmal[@RushikeshGandhmal]
|
||||
* https://github.com/sergiosalvi[@sergiosalvi]
|
||||
* https://github.com/sgammon[@sgammon]
|
||||
* https://github.com/stackoverflow[@stackoverflow]
|
||||
* https://github.com/vinayakj592[@vinayakj592]
|
||||
@@ -0,0 +1,47 @@
|
||||
= Pkl 0.34 Release Notes
|
||||
|
||||
:version: 0.34
|
||||
:version-minor: 0.34.0
|
||||
:release-date: TBD
|
||||
:version-next: 0.35.0
|
||||
:version-next-date: June 2027
|
||||
|
||||
link:partial$intro.adoc[role=include]
|
||||
|
||||
== Highlights [small]#💖#
|
||||
|
||||
News you don't want to miss.
|
||||
|
||||
=== XXX
|
||||
|
||||
== Noteworthy [small]#🎶#
|
||||
|
||||
Ready when you need them.
|
||||
|
||||
=== XXX
|
||||
|
||||
== Breaking Changes [small]#💔#
|
||||
|
||||
Things to watch out for when upgrading.
|
||||
|
||||
=== XXX
|
||||
|
||||
== Miscellaneous [small]#🐸#
|
||||
|
||||
* XXX
|
||||
* XXX
|
||||
|
||||
== Bug Fixes [small]#🐜#
|
||||
|
||||
* XXX
|
||||
* XXX
|
||||
|
||||
== Security Fixes [small]#🔒#
|
||||
|
||||
The following security vulnerabilities were fixed:
|
||||
|
||||
== Contributors [small]#🙏#
|
||||
|
||||
We would like to thank the contributors to this release (in alphabetical order):
|
||||
|
||||
* XXX
|
||||
@@ -1,8 +1,54 @@
|
||||
= Changelog
|
||||
include::ROOT:partial$component-attributes.adoc[]
|
||||
|
||||
[[release-0.34.0]]
|
||||
== 0.34.0 (UNRELEASED)
|
||||
|
||||
xref:0.34.adoc[Release Notes]
|
||||
|
||||
[[release-0.33.0]]
|
||||
== 0.33.0 (2026-11-05)
|
||||
|
||||
xref:0.33.adoc[Release Notes]
|
||||
|
||||
[[release-0.32.2]]
|
||||
== 0.32.2 (2026-10-21)
|
||||
|
||||
=== Security Fixes [small]#🔒#
|
||||
|
||||
* Update fallback CA certificate trust and remove trust for the issuer of compromised intermediate CAs (https://github.com/apple/pkl/pull/1923[#1923]).
|
||||
|
||||
=== Fixes
|
||||
|
||||
* Fix rendering of stack traces for evaluated multi-line expressions (https://github.com/apple/pkl/pull/1793[#1793]).
|
||||
|
||||
=== Contributors ❤️
|
||||
|
||||
Thank you to all the contributors for this release!
|
||||
|
||||
* https://github.com/HT154[@HT154]
|
||||
* https://github.com/protobufel2[@protobufel2]
|
||||
|
||||
[[release-0.32.1]]
|
||||
== 0.32.1 (2026-07-23)
|
||||
|
||||
=== Fixes
|
||||
|
||||
* Fix closing intermediate response bodies when following HTTP redirects (https://github.com/apple/pkl/pull/1749[#1749]).
|
||||
* Fix `eval -x` of local members (https://github.com/apple/pkl/pull/1760[#1760]).
|
||||
* Fix variable resolution of object body params (https://github.com/apple/pkl/pull/1788[#1788]).
|
||||
|
||||
=== Contributors ❤️
|
||||
|
||||
Thank you to all the contributors for this release!
|
||||
|
||||
* https://github.com/bioball[@bioball]
|
||||
* https://github.com/KushalP[@KushalP]
|
||||
|
||||
[[release-0.32.0]]
|
||||
== 0.32.0 (UNRELEASED)
|
||||
== 0.32.0 (2026-07-08)
|
||||
|
||||
xref:0.32.adoc[Release Notes]
|
||||
|
||||
[[release-0.31.1]]
|
||||
== 0.31.1 (2026-03-26)
|
||||
@@ -38,6 +84,7 @@ Thank you to all the contributors for this release!
|
||||
== 0.31.0 (2026-02-26)
|
||||
|
||||
xref:0.31.adoc[Release Notes]
|
||||
|
||||
[[release-0.30.2]]
|
||||
|
||||
== 0.30.2 (2025-12-15)
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
The Pkl team aims to release a new version of Pkl in February, June, and October of each year.
|
||||
|
||||
* xref:0.34.adoc[0.34 Release Notes]
|
||||
* xref:0.33.adoc[0.33 Release Notes]
|
||||
* xref:0.32.adoc[0.32 Release Notes]
|
||||
* xref:0.31.adoc[0.31 Release Notes]
|
||||
* xref:0.30.adoc[0.30 Release Notes]
|
||||
|
||||
@@ -5,47 +5,39 @@
|
||||
:version-next: XXX (e.g., 0.10)
|
||||
:version-next-date: XXX (e.g., July 2018)
|
||||
|
||||
include::partial$intro.adoc[]
|
||||
link:partial$intro.adoc[role=include]
|
||||
|
||||
== Highlights [small]#💖#
|
||||
|
||||
News you don't want to miss.
|
||||
|
||||
.XXX
|
||||
[%collapsible]
|
||||
====
|
||||
XXX
|
||||
====
|
||||
=== XXX
|
||||
|
||||
== Noteworthy [small]#🎶#
|
||||
|
||||
Ready when you need them.
|
||||
|
||||
.XXX
|
||||
[%collapsible]
|
||||
====
|
||||
XXX
|
||||
====
|
||||
=== XXX
|
||||
|
||||
== Breaking Changes [small]#💔#
|
||||
|
||||
Things to watch out for when upgrading.
|
||||
|
||||
.XXX
|
||||
[%collapsible]
|
||||
====
|
||||
XXX
|
||||
====
|
||||
=== XXX
|
||||
|
||||
== Work In Progress [small]#🚆#
|
||||
== Miscellaneous [small]#🐸#
|
||||
|
||||
They missed the train but deserve a mention.
|
||||
* XXX
|
||||
* XXX
|
||||
|
||||
.XXX
|
||||
[%collapsible]
|
||||
====
|
||||
XXX
|
||||
====
|
||||
== Bug Fixes [small]#🐜#
|
||||
|
||||
* XXX
|
||||
* XXX
|
||||
|
||||
== Security Fixes [small]#🔒#
|
||||
|
||||
The following security vulnerabilities were fixed:
|
||||
|
||||
== Contributors [small]#🙏#
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
**** xref:kotlin-binding:pkl-config-kotlin.adoc[pkl-config-kotlin Library]
|
||||
*** xref:swift:ROOT:index.adoc[Swift]
|
||||
*** xref:go:ROOT:index.adoc[Go]
|
||||
*** xref:libpkl:index.adoc[libpkl]
|
||||
*** xref:bindings-specification:index.adoc[Specification]
|
||||
**** xref:bindings-specification:message-passing-api.adoc[Message Passing API]
|
||||
**** xref:bindings-specification:binary-encoding.adoc[Pkl Binary Encoding]
|
||||
@@ -41,6 +42,8 @@
|
||||
* xref:ROOT:evolution-and-roadmap.adoc[Evolution and Roadmap]
|
||||
|
||||
* xref:release-notes:index.adoc[Release Notes]
|
||||
** xref:release-notes:0.34.adoc[0.34 Release Notes]
|
||||
** xref:release-notes:0.33.adoc[0.33 Release Notes]
|
||||
** xref:release-notes:0.32.adoc[0.32 Release Notes]
|
||||
** xref:release-notes:0.31.adoc[0.31 Release Notes]
|
||||
** xref:release-notes:0.30.adoc[0.30 Release Notes]
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
# suppress inspection "UnusedProperty" for whole file
|
||||
|
||||
group=org.pkl-lang
|
||||
version=0.32.0
|
||||
version=0.34.0
|
||||
|
||||
# google-java-format requires jdk.compiler exports
|
||||
org.gradle.jvmargs= \
|
||||
|
||||
+32
-19
@@ -3,46 +3,50 @@ assertj = "3.27.7"
|
||||
checksumPlugin = "1.4.0"
|
||||
# 5.0.3 is the last version compatible with Kotlin 2.2
|
||||
clikt = "5.0.3"
|
||||
commonMark = "0.29.0"
|
||||
commonMark = "0.30.0"
|
||||
downloadTaskPlugin = "5.7.0"
|
||||
eclipseCdtFormat = "12.2"
|
||||
errorProne = "2.50.0"
|
||||
errorPronePlugin = "5.1.0"
|
||||
geantyref = "2.0.1"
|
||||
#noinspection UnusedVersionCatalogEntry
|
||||
googleJavaFormat = "1.35.0"
|
||||
# 25.0.2 no longer supports macos-x64
|
||||
graalVm = "25.0.1"
|
||||
graalVm = "25.2.4"
|
||||
graalVmJdkVersion = "25.0.4"
|
||||
#noinspection UnusedVersionCatalogEntry
|
||||
graalVmJdkVersion = "25.0.1"
|
||||
graalVmInnovation = "2"
|
||||
# slightly hacky but convenient place so we remember to update the checksum
|
||||
# pklGraalVm.gradle.kts assumes this entry exists
|
||||
#noinspection UnusedVersionCatalogEntry
|
||||
graalVmSha256-macos-x64 = "a3d895b4cd1c783badbd277ec70409806bd4102fca0d2a60dbaeb0bab41aec30"
|
||||
graalVmSha256-macos-x64 = "unavailable"
|
||||
#noinspection UnusedVersionCatalogEntry
|
||||
graalVmSha256-macos-aarch64 = "066339f24a8ab5c161548491a9400f7344e7761a1e46f8979e76c7ef11d5bc76"
|
||||
graalVmSha256-macos-aarch64 = "507330fff8907de51b8621c95a21a491fa9b0d8c240de184594f40f83add3bfa"
|
||||
#noinspection UnusedVersionCatalogEntry
|
||||
graalVmSha256-linux-x64 = "01e39fe1a87f28b842a3e4e3b77be9b544dca3a58fa6e93b924a6106c8bac7fb"
|
||||
graalVmSha256-linux-x64 = "3f4a89de8eaa96f2ed677f09957c7e872cd8467aad3537f8b5394c1b8c4b942e"
|
||||
#noinspection UnusedVersionCatalogEntry
|
||||
graalVmSha256-linux-aarch64 = "7aa0b9935a80e67f37c6025678393dbd123bb6f2226811decbc1a13093fc8ae2"
|
||||
graalVmSha256-linux-aarch64 = "22286f7ecd21b9aedb3226b9bf797469e1bd3eefc491e12ef3dd49b452d230b7"
|
||||
#noinspection UnusedVersionCatalogEntry
|
||||
graalVmSha256-windows-x64 = "fde83c5ceec2c75560c747ccd9f314f90e4cf5c5287416e67c4ce442e344ca27"
|
||||
graalVmSha256-windows-x64 = "789d2af1c06c3c24f402d2d4a711bdbb19b36f7d8c74afe6a959492fd121ef33"
|
||||
# pklGraalVm.gradle.kts assumes this entry exists
|
||||
#noinspection UnusedVersionCatalogEntry
|
||||
graalVmSha256-windows-aarch64 = "unavailable"
|
||||
ideaExtPlugin = "1.4.1"
|
||||
javaPoet = "0.16.0"
|
||||
javaPoet = "0.17.0"
|
||||
javaxInject = "1"
|
||||
jextractPlugin = "1.+"
|
||||
jimfs = "1.3.1"
|
||||
jline = "4.2.1"
|
||||
jline = "4.4.6"
|
||||
jmh = "1.37"
|
||||
jmhPlugin = "0.7.3"
|
||||
jna = "5.19.1"
|
||||
jspecify = "1.0.0"
|
||||
junit = "6.1.0"
|
||||
junit = "6.1.3"
|
||||
# 1.7+ generates much more verbose code
|
||||
kotlinPoet = "1.6.0"
|
||||
kotlinStdLib = "2.2.21"
|
||||
#noinspection UnusedVersionCatalogEntry
|
||||
kotlinTarget = "2.2"
|
||||
kotlinToolchain = "2.3.21"
|
||||
kotlinToolchain = "2.4.0"
|
||||
kotlinxHtml = "0.12.0"
|
||||
# 1.9.0 is the last version compatible with Kotlin 2.2
|
||||
kotlinxSerialization = "1.9.0"
|
||||
@@ -56,12 +60,14 @@ msgpack = "0.9.12"
|
||||
nexusPublishPlugin = "2.0.0"
|
||||
nullaway = "0.13.7"
|
||||
nullawayPlugin = "3.0.0"
|
||||
nuValidator = "26.6.24"
|
||||
nuValidator = "26.7.3"
|
||||
paguro = "3.10.3"
|
||||
shadowPlugin = "9.4.2"
|
||||
slf4j = "2.0.18"
|
||||
protobufPlugin = "0.10.0"
|
||||
protobuf = "4.35.1"
|
||||
shadowPlugin = "9.5.1"
|
||||
slf4j = "2.0.20"
|
||||
snakeYaml = "3.0.1"
|
||||
spotlessPlugin = "8.7.0"
|
||||
spotlessPlugin = "8.8.0"
|
||||
wiremock = "3.13.2"
|
||||
|
||||
[libraries] # ordered alphabetically
|
||||
@@ -85,6 +91,8 @@ jimfs = { group = "com.google.jimfs", name = "jimfs", version.ref = "jimfs" }
|
||||
jlineReader = { group = "org.jline", name = "jline-reader", version.ref = "jline" }
|
||||
jlineTerminal = { group = "org.jline", name = "jline-terminal", version.ref = "jline" }
|
||||
jlineTerminalJni = { group = "org.jline", name = "jline-terminal-jni", version.ref = "jline" }
|
||||
jna = { group = "net.java.dev.jna", name = "jna", version.ref = "jna" }
|
||||
jnaPlatform = { group = "net.java.dev.jna", name = "jna-platform", version.ref = "jna" }
|
||||
#noinspection UnusedVersionCatalogEntry
|
||||
jspecify = { group = "org.jspecify", name = "jspecify", version.ref = "jspecify" }
|
||||
junitApi = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junit" }
|
||||
@@ -111,16 +119,19 @@ nullaway = { group = "com.uber.nullaway", name = "nullaway", version.ref = "null
|
||||
nullawayPlugin = { group = "net.ltgt.gradle", name = "gradle-nullaway-plugin", version.ref = "nullawayPlugin" }
|
||||
# to be replaced with https://github.com/usethesource/capsule or https://github.com/lacuna/bifurcan
|
||||
paguro = { group = "org.organicdesign", name = "Paguro", version.ref = "paguro" }
|
||||
protobuf = { group = "com.google.protobuf", name = "protobuf-java", version.ref = "protobuf" }
|
||||
protoc = { group = "com.google.protobuf", name = "protoc", version.ref = "protobuf" }
|
||||
pklConfigJavaAll025 = { group = "org.pkl-lang", name = "pkl-config-java-all", version = "0.25.0" }
|
||||
shadowPlugin = { group = "com.gradleup.shadow", name = "com.gradleup.shadow.gradle.plugin", version.ref = "shadowPlugin" }
|
||||
slf4jApi = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" }
|
||||
slf4jSimple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" }
|
||||
snakeYaml = { group = "org.snakeyaml", name = "snakeyaml-engine", version.ref = "snakeYaml" }
|
||||
spotlessPlugin = { group = "com.diffplug.spotless", name = "spotless-plugin-gradle", version.ref = "spotlessPlugin"}
|
||||
svm = { group = "org.graalvm.nativeimage", name = "svm", version.ref = "graalVm" }
|
||||
svm = { group = "org.graalvm.nativeimage", name = "svm", version.ref = "graalVmJdkVersion" }
|
||||
truffleApi = { group = "org.graalvm.truffle", name = "truffle-api", version.ref = "graalVm" }
|
||||
truffleProfiler = { group = "org.graalvm.tools", name = "profiler-tool", version.ref = "graalVm" }
|
||||
truffleDslProcessor = { group = "org.graalvm.truffle", name = "truffle-dsl-processor", version.ref = "graalVm" }
|
||||
truffleSvm = { group = "org.graalvm.nativeimage", name = "truffle-runtime-svm", version.ref = "graalVm" }
|
||||
truffleSvm = { group = "org.graalvm.nativeimage", name = "truffle-runtime-svm", version.ref = "graalVmJdkVersion" }
|
||||
truffleRuntime = { group = "org.graalvm.truffle", name = "truffle-runtime", version.ref = "graalVm" }
|
||||
wiremock = { group = "org.wiremock", name = "wiremock", version.ref = "wiremock" }
|
||||
|
||||
@@ -129,9 +140,11 @@ checksum = { id = "org.gradle.crypto.checksum", version.ref = "checksumPlugin" }
|
||||
#noinspection UnusedVersionCatalogEntry
|
||||
errorProne = { id = "net.ltgt.errorprone", version.ref = "errorPronePlugin" }
|
||||
ideaExt = { id = "org.jetbrains.gradle.plugin.idea-ext", version.ref = "ideaExtPlugin" }
|
||||
jextract = { id = "de.infolektuell.jextract", version.ref = "jextractPlugin" }
|
||||
jmh = { id = "me.champeau.jmh", version.ref = "jmhPlugin" }
|
||||
kotlinxSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinToolchain" }
|
||||
nexusPublish = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "nexusPublishPlugin" }
|
||||
#noinspection UnusedVersionCatalogEntry
|
||||
nullaway = { id = "net.ltgt.nullaway", version.ref = "nullawayPlugin" }
|
||||
shadow = { id = "com.gradleup.shadow", version.ref = "shadowPlugin" }
|
||||
protobuf = { id = "com.google.protobuf", version.ref = "protobufPlugin" }
|
||||
Vendored
BIN
Binary file not shown.
+2
-2
@@ -1,7 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=bbaeb2fef8710818cf0e261201dab964c572f92b942812df0c3620d62a529a01
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip
|
||||
distributionSha256Sum=dce76f55f8e251a3a1f130eb120f30b3d271de2b76c9b0729d316b5a1b6dc01f
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.1-bin.zip
|
||||
networkTimeout=10000
|
||||
retries=0
|
||||
retryBackOffMs=500
|
||||
|
||||
Vendored
+41
-11
@@ -26,6 +26,33 @@
|
||||
@rem Set local scope for the variables, and ensure extensions are enabled
|
||||
setlocal EnableExtensions
|
||||
|
||||
@rem Catch executions from older scripts and ensure they exit cleanly.
|
||||
@rem This can be removed once we can be reasonably confident that few people
|
||||
@rem will be migrating directly to this new wrapper.
|
||||
goto afterSafetyNet
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
goto exitWithErrorLevel
|
||||
:afterSafetyNet
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
@@ -45,13 +72,14 @@ set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
1>&2 echo.
|
||||
1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
1>&2 echo.
|
||||
1>&2 echo Please set the JAVA_HOME variable in your environment to match the
|
||||
1>&2 echo location of your Java installation.
|
||||
|
||||
"%COMSPEC%" /c exit 1
|
||||
goto exitWithErrorLevel
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
@@ -59,13 +87,14 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
1>&2 echo.
|
||||
1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
1>&2 echo.
|
||||
1>&2 echo Please set the JAVA_HOME variable in your environment to match the
|
||||
1>&2 echo location of your Java installation.
|
||||
|
||||
"%COMSPEC%" /c exit 1
|
||||
goto exitWithErrorLevel
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
@@ -75,8 +104,9 @@ echo location of your Java installation. 1>&2
|
||||
@rem Execute gradlew
|
||||
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
|
||||
@rem which allows us to clear the local environment before executing the java command
|
||||
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
|
||||
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel & goto exitWithErrorLevel
|
||||
|
||||
@rem This label must not be changed. We rely on old scripts being able to jump to this point.
|
||||
:exitWithErrorLevel
|
||||
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
|
||||
"%COMSPEC%" /c exit %ERRORLEVEL%
|
||||
+3
-3
@@ -2,15 +2,15 @@
|
||||
"catalogs": {},
|
||||
"aliases": {
|
||||
"pkl": {
|
||||
"script-ref": "org.pkl-lang:pkl-cli-java:0.31.1",
|
||||
"script-ref": "org.pkl-lang:pkl-cli-java:0.33.0",
|
||||
"java-agents": []
|
||||
},
|
||||
"pkl-codegen-java": {
|
||||
"script-ref": "org.pkl-lang:pkl-codegen-java:0.31.1",
|
||||
"script-ref": "org.pkl-lang:pkl-codegen-java:0.33.0",
|
||||
"java-agents": []
|
||||
},
|
||||
"pkl-codegen-kotlin": {
|
||||
"script-ref": "org.pkl-lang:pkl-codegen-kotlin:0.31.1",
|
||||
"script-ref": "org.pkl-lang:pkl-codegen-kotlin:0.33.0",
|
||||
"java-agents": []
|
||||
}
|
||||
},
|
||||
|
||||
@@ -0,0 +1,567 @@
|
||||
/*
|
||||
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
import org.gradle.kotlin.dsl.filter
|
||||
|
||||
plugins {
|
||||
id("pklAllProjects")
|
||||
id("pklGraalVm")
|
||||
id("pklJavaLibrary")
|
||||
id("pklNativeLifecycle")
|
||||
id("pklNativeSourceBundle")
|
||||
}
|
||||
|
||||
val stagedMacAarch64NativeLibrary: Configuration =
|
||||
configurations.create("stagedMacAarch64NativeLibrary")
|
||||
val stagedLinuxAmd64NativeLibrary: Configuration =
|
||||
configurations.create("stagedLinuxAmd64NativeLibrary")
|
||||
val stagedLinuxAarch64NativeLibrary: Configuration =
|
||||
configurations.create("stagedLinuxAarch64NativeLibrary")
|
||||
val stagedAlpineLinuxAmd64NativeLibrary: Configuration =
|
||||
configurations.create("stagedAlpineLinuxAmd64NativeLibrary")
|
||||
val stagedWindowsAmd64NativeLibrary: Configuration =
|
||||
configurations.create("stagedWindowsAmd64NativeLibrary")
|
||||
|
||||
val nativeTestSourceSet: SourceSet = sourceSets.create("nativeTest")
|
||||
|
||||
val nativeTestImplementation: Configuration =
|
||||
configurations.getByName("nativeTestImplementation") {
|
||||
extendsFrom(configurations.testImplementation.get())
|
||||
}
|
||||
|
||||
val nativeTestRuntimeOnly: Configuration =
|
||||
configurations.getByName("nativeTestRuntimeOnly") {
|
||||
extendsFrom(configurations.testRuntimeOnly.get())
|
||||
}
|
||||
|
||||
val externalReaderFixtureConfiguration = configurations.create("externalReaderFixture")
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.graalSdk)
|
||||
|
||||
implementation(projects.pklCore)
|
||||
implementation(projects.pklServer)
|
||||
|
||||
implementation(libs.msgpack)
|
||||
implementation(libs.truffleApi)
|
||||
implementation(libs.truffleRuntime)
|
||||
|
||||
nativeTestImplementation(projects.pklCommonsTest)
|
||||
nativeTestImplementation(libs.jna)
|
||||
nativeTestImplementation(libs.jnaPlatform)
|
||||
nativeTestRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
|
||||
externalReaderFixtureConfiguration(project(":pkl-core", "externalReaderFixture"))
|
||||
|
||||
// firstPartySourcesJars(project(":pkl-formatter", "sourcesJar")) // TODO when syntax merges
|
||||
firstPartySourcesJars(project(":pkl-core", "sourcesJar"))
|
||||
firstPartySourcesJars(project(":pkl-parser", "sourcesJar"))
|
||||
firstPartySourcesJars(project(":pkl-server", "sourcesJar"))
|
||||
}
|
||||
|
||||
tasks.withType(CCompile::class) {
|
||||
if (buildInfo.targetMachine.os.isMacOS) {
|
||||
arch = buildInfo.targetMachine.arch
|
||||
}
|
||||
}
|
||||
|
||||
private fun NativeImageBuild.configure(target: Target) {
|
||||
arch = target.arch
|
||||
|
||||
if (target.arch == Target.Arch.AARCH64) {
|
||||
dependsOn(":installGraalVmAarch64")
|
||||
} else {
|
||||
dependsOn(":installGraalVmAmd64")
|
||||
}
|
||||
|
||||
outputDir = target.tempOutputDir
|
||||
outputName = "libpkl_internal"
|
||||
|
||||
classpath.from(sourceSets.main.map { it.output })
|
||||
classpath.from(
|
||||
project(":pkl-commons-cli").extensions.getByType(SourceSetContainer::class)["svm"].output
|
||||
)
|
||||
classpath.from(configurations.runtimeClasspath)
|
||||
|
||||
sharedLibrary = true
|
||||
val scriptName = if (buildInfo.os.isWindows) "build_windows.bat" else "build_unix.sh"
|
||||
nativeCompilerPath = projectDir.resolve("scripts/${scriptName}")
|
||||
|
||||
if (!buildInfo.os.isWindows) {
|
||||
// build_unix.sh extracts the object files of every archive it links against into a loose
|
||||
// `objects/` dir so that `buildStaticLibrary` can merge them into a single, self-contained
|
||||
// static library.
|
||||
//
|
||||
// Don't need to do this for Windows because it produces a merged archive.
|
||||
additionalOutputDirectoryNames.add("objects")
|
||||
}
|
||||
}
|
||||
|
||||
val macNativeImageAarch64 =
|
||||
tasks.register<NativeImageBuild>("macNativeImageAarch64") {
|
||||
configure(Target.MacosAarch64)
|
||||
// macOS only supports 16K page size
|
||||
extraNativeImageArgs = buildList {
|
||||
add("-H:PageSize=16384")
|
||||
if (buildInfo.isCiBuild) {
|
||||
add("-J-Xmx12g")
|
||||
add("-H:+DeadlockWatchdogExitOnTimeout")
|
||||
add("-H:DeadlockWatchdogInterval=10")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val linuxNativeImageAmd64 =
|
||||
tasks.register<NativeImageBuild>("linuxNativeImageAmd64") { configure(Target.LinuxAmd64) }
|
||||
|
||||
val linuxNativeImageAarch64 =
|
||||
tasks.register<NativeImageBuild>("linuxNativeImageAarch64") {
|
||||
configure(Target.LinuxAarch64)
|
||||
extraNativeImageArgs =
|
||||
listOf(
|
||||
// Ensure compatibility for kernels with page size set to 4k, 16k and 64k
|
||||
// (e.g. Raspberry Pi 5, Asahi Linux)
|
||||
"-H:PageSize=65536"
|
||||
)
|
||||
}
|
||||
|
||||
val alpineLinuxNativeImageAmd64 =
|
||||
tasks.register<NativeImageBuild>("alpineLinuxNativeImageAmd64") {
|
||||
configure(Target.AlpineLinuxAmd64)
|
||||
extraNativeImageArgs.addAll("--libc=musl")
|
||||
}
|
||||
|
||||
val windowsNativeImageAmd64 =
|
||||
tasks.register<NativeImageBuild>("windowsNativeImageAmd64") {
|
||||
configure(Target.WindowsAmd64)
|
||||
extraNativeImageArgs.addAll("-Dfile.encoding=UTF-8", "-H:-CheckToolchain")
|
||||
}
|
||||
|
||||
val nativeImageBuildTask =
|
||||
when (buildInfo.targetMachine) {
|
||||
Target.MacosAarch64 -> macNativeImageAarch64
|
||||
Target.LinuxAarch64 -> linuxNativeImageAarch64
|
||||
Target.LinuxAmd64 -> linuxNativeImageAmd64
|
||||
Target.WindowsAmd64 -> windowsNativeImageAmd64
|
||||
Target.AlpineLinuxAmd64 -> alpineLinuxNativeImageAmd64
|
||||
}
|
||||
|
||||
val buildNativeImageLibrary =
|
||||
tasks.register("buildNativeImageLibrary") {
|
||||
group = "build"
|
||||
dependsOn(nativeImageBuildTask)
|
||||
outputs.files(nativeImageBuildTask)
|
||||
}
|
||||
|
||||
val buildPklObjectFile =
|
||||
tasks.register<CCompile>("buildPklObjectFile") {
|
||||
dependsOn(buildNativeImageLibrary)
|
||||
includeDirs.from(buildInfo.targetMachine.tempOutputDir)
|
||||
includeDirs.from(file("src/main/c/include/"))
|
||||
sourceFiles.from(file("src/main/c/pkl.c"))
|
||||
positionIndependentCode = true
|
||||
if (buildInfo.os.isWindows) {
|
||||
// Match the dynamic CRT (ucrt/msvcrt) that native-image's bundled JDK objects expect -
|
||||
// otherwise linking pkl.obj together with the merged libpkl_internal_s.lib archive hits a
|
||||
// LIBCMT/MSVCRT default-lib conflict and unresolved __imp_* CRT import symbols.
|
||||
runtimeLibrary = "MD"
|
||||
}
|
||||
|
||||
outputDir =
|
||||
layout.buildDirectory.dir("tmp/compileC/${name}/${buildInfo.targetMachine.targetName}")
|
||||
|
||||
val versionMacro =
|
||||
if (buildInfo.targetMachine.os.isWindows) "\\\"${buildInfo.pklVersion}\\\""
|
||||
else "\"${buildInfo.pklVersion}\""
|
||||
defines.put("PKL_VERSION", versionMacro)
|
||||
}
|
||||
|
||||
val buildStaticLibrary =
|
||||
tasks.register<CArchive>("buildStaticLibrary") {
|
||||
dependsOn(buildNativeImageLibrary, buildPklObjectFile)
|
||||
|
||||
val targetMachine = buildInfo.targetMachine
|
||||
val objectExtension = targetMachine.os.objectFileExtension
|
||||
val pklObjectFile = buildPklObjectFile.flatMap {
|
||||
it.outputDir.map { dir -> dir.file("pkl.${objectExtension}") }
|
||||
}
|
||||
|
||||
if (targetMachine.os.isWindows) {
|
||||
// build_windows.bat produces a single self-contained merged archive
|
||||
// (libpkl_internal_s.lib) directly, rather than extracting loose .obj members into an
|
||||
// "objects" directory the way build_unix.sh does - pull that in instead.
|
||||
objectFiles.from(
|
||||
buildNativeImageLibrary.map { task ->
|
||||
task.outputs.files.find {
|
||||
it.name == "libpkl_internal_s.${targetMachine.os.staticLibraryExtension}"
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
objectFiles.from(
|
||||
fileTree(targetMachine.tempOutputDir.get().dir("objects")).matching {
|
||||
include("**/*.${objectExtension}")
|
||||
}
|
||||
)
|
||||
}
|
||||
objectFiles.from(pklObjectFile)
|
||||
|
||||
outputFile =
|
||||
targetMachine.outputDir.map { dir ->
|
||||
dir.file("lib/libpkl.${targetMachine.os.staticLibraryExtension}")
|
||||
}
|
||||
}
|
||||
|
||||
val buildSharedLibrary =
|
||||
tasks.register<CCompile>("buildSharedLibrary") {
|
||||
dependsOn(buildNativeImageLibrary, buildPklObjectFile)
|
||||
|
||||
link = true
|
||||
val targetMachine = buildInfo.targetMachine
|
||||
|
||||
val libraryFile = buildPklObjectFile.flatMap { task ->
|
||||
task.outputDir.map { dir -> dir.file("pkl.${targetMachine.os.objectFileExtension}") }
|
||||
}
|
||||
|
||||
val extension = targetMachine.os.sharedLibraryExtension
|
||||
val staticLib = buildNativeImageLibrary.map { task ->
|
||||
// link against the self-contained static archive (produced by build_unix.sh /
|
||||
// build_windows.bat) rather than the shared library, so libpkl doesn't carry a runtime
|
||||
// dependency on a separate libpkl_internal shared library.
|
||||
val fileName =
|
||||
if (targetMachine.os.isWindows)
|
||||
"libpkl_internal_s.${targetMachine.os.staticLibraryExtension}"
|
||||
else "libpkl_internal.${targetMachine.os.staticLibraryExtension}"
|
||||
task.outputs.files.find { it.name == fileName }
|
||||
}
|
||||
|
||||
sourceFiles.from(libraryFile)
|
||||
includeDirs.from(file("src/main/c/"))
|
||||
libraryFiles.from(staticLib)
|
||||
positionIndependentCode = true
|
||||
sharedLibrary = true
|
||||
|
||||
outputFile = targetMachine.outputDir.map { it.file("lib/libpkl.${extension}") }
|
||||
|
||||
if (buildInfo.os.isMacOS) {
|
||||
frameworks.addAll("Foundation", "CoreServices")
|
||||
linkerFlags.addAll("-current_version", project.version.toString().substringBefore("-"))
|
||||
linkerFlags.addAll("-compatibility_version", "0.1.0")
|
||||
// prevent JNI symbols from being exported and clobbering the symbol table
|
||||
val exportedSymbolsFile = file("src/main/c/pkl.exported_symbols")
|
||||
inputs.file(exportedSymbolsFile)
|
||||
linkerFlags.addAll("-exported_symbols_list", exportedSymbolsFile.absolutePath)
|
||||
compilerArgs.addAll("-install_name", "@rpath/libpkl.dylib")
|
||||
}
|
||||
|
||||
if (targetMachine.os.isWindows) {
|
||||
// Without an explicit /IMPLIB, MSVC would name the DLL's import library "libpkl.lib" —
|
||||
// the same path buildStaticLibrary writes its static archive to.
|
||||
linkerFlags.add(
|
||||
targetMachine.libraryDir.map { dir -> "/IMPLIB:${dir.file("libpkl_dll.lib").asFile}" }
|
||||
)
|
||||
} else {
|
||||
libraries.add("z")
|
||||
libraries.add("pthread")
|
||||
}
|
||||
|
||||
if (targetMachine.os.isLinux) {
|
||||
libraries.add("dl")
|
||||
// prevent JNI symbols from being exported and clobbering the symbol table
|
||||
val versionScriptFile = file("src/main/c/pkl.map")
|
||||
inputs.file(versionScriptFile)
|
||||
linkerFlags.add("--version-script=${versionScriptFile.absolutePath}")
|
||||
}
|
||||
}
|
||||
|
||||
// Source bundles for each target are registered by `pklNativeSourceBundle`. libpkl additionally
|
||||
// includes its own C sources, which are the same for all targets.
|
||||
val libpklNativeSourcesJar =
|
||||
tasks.register<Zip>("libpklNativeSourcesJar") {
|
||||
archiveFileName = "libpkl-native-sources.jar"
|
||||
destinationDirectory = layout.buildDirectory.dir("tmp/nativeImageSources")
|
||||
from("src/main/c")
|
||||
}
|
||||
|
||||
sourceBundleSpec { extraNativeSources.from(libpklNativeSourcesJar) }
|
||||
|
||||
val Target.outputDir
|
||||
get() = layout.buildDirectory.dir("native-libs/$targetName")
|
||||
|
||||
val Target.libraryDir
|
||||
get() = layout.buildDirectory.dir("native-libs/$targetName/lib")
|
||||
|
||||
val Target.tempOutputDir
|
||||
get() = layout.buildDirectory.dir("tmp/native-libs/$targetName")
|
||||
|
||||
val distTar =
|
||||
tasks.register<Tar>("distTar") {
|
||||
val isUnix = buildInfo.os.isMacOS || buildInfo.os.isLinux
|
||||
onlyIf { isUnix }
|
||||
dependsOn(buildSharedLibrary, buildStaticLibrary, processFiles)
|
||||
compression = Compression.GZIP
|
||||
val baseName = "libpkl-${buildInfo.pklVersionNonUnique}-${buildInfo.targetMachine.targetName}"
|
||||
archiveFileName = "${baseName}.tar.gz"
|
||||
from(buildInfo.targetMachine.outputDir)
|
||||
into(baseName)
|
||||
}
|
||||
|
||||
val distZip =
|
||||
tasks.register<Zip>("distZip") {
|
||||
val isWindows = buildInfo.os.isWindows
|
||||
onlyIf { isWindows }
|
||||
dependsOn(buildSharedLibrary, buildStaticLibrary, processFiles)
|
||||
val baseName = "libpkl-${buildInfo.pklVersionNonUnique}-${buildInfo.targetMachine.targetName}"
|
||||
archiveFileName = "${baseName}.zip"
|
||||
from(buildInfo.targetMachine.outputDir)
|
||||
into(baseName)
|
||||
}
|
||||
|
||||
tasks.assembleNative { dependsOn(distZip, distTar, processFiles) }
|
||||
|
||||
val processFiles =
|
||||
tasks.register<Copy>("processFiles") {
|
||||
inputs.property("version", buildInfo.pklVersion)
|
||||
inputs.dir("src/main/c")
|
||||
inputs.dir("src/main/files")
|
||||
from(fileTree("src/main/files"))
|
||||
from(fileTree("src/main/c") { exclude("*.{c,map,exported_symbols}") })
|
||||
includeEmptyDirs = true
|
||||
into(buildInfo.targetMachine.outputDir)
|
||||
|
||||
val tokens = buildMap {
|
||||
this["version"] = buildInfo.pklVersion
|
||||
this["static_lib_filename"] = "libpkl.${buildInfo.os.staticLibraryExtension}"
|
||||
this["extra_static_libs"] =
|
||||
when {
|
||||
buildInfo.os.isMacOS -> " -lz -framework Foundation -framework CoreServices"
|
||||
buildInfo.os.isWindows -> ""
|
||||
else -> " -lz"
|
||||
}
|
||||
this["extra_shared_libs"] =
|
||||
when {
|
||||
buildInfo.os.isMacOS -> " -lpkl"
|
||||
buildInfo.os.isWindows -> " -l:libpkl_dll.lib"
|
||||
else -> " -lpkl"
|
||||
}
|
||||
}
|
||||
filesMatching("**/libpkl*.pc") { filter<ReplaceTokens>("tokens" to tokens) }
|
||||
}
|
||||
|
||||
val testNativeJava =
|
||||
tasks.register<Test>("testNativeJava") {
|
||||
dependsOn(tasks.assembleNative)
|
||||
dependsOn(externalReaderFixtureConfiguration)
|
||||
|
||||
description = "Test native libraries from Java"
|
||||
group = "verification"
|
||||
|
||||
testClassesDirs = nativeTestSourceSet.output.classesDirs
|
||||
classpath = nativeTestSourceSet.runtimeClasspath
|
||||
|
||||
// It's not good enough to just provide `jna.library.path` on Linux; need to also provide
|
||||
// `LD_LIBRARY_PATH` or `java.library.path` so that transitive libraries can be loaded.
|
||||
jvmArgumentProviders.add(
|
||||
CommandLineArgumentProvider {
|
||||
listOf(
|
||||
"-Djna.library.path=" + buildInfo.targetMachine.libraryDir.get().asFile.absolutePath,
|
||||
"-Djava.library.path=" + buildInfo.targetMachine.libraryDir.get().asFile.absolutePath,
|
||||
"--enable-native-access=ALL-UNNAMED",
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
environment("LD_LIBRARY_PATH", buildInfo.targetMachine.libraryDir.get().asFile.absolutePath)
|
||||
|
||||
useJUnitPlatform()
|
||||
|
||||
systemProperty(
|
||||
"org.pkl.core.testExternalReaderPath",
|
||||
externalReaderFixtureConfiguration.files.single().absolutePath,
|
||||
)
|
||||
}
|
||||
|
||||
// link to static library
|
||||
val compileNativeTestStatic =
|
||||
tasks.register<CCompile>("compileNativeTestStatic") {
|
||||
description = "Build C test with static library"
|
||||
group = "verification"
|
||||
|
||||
dependsOn(tasks.assembleNative)
|
||||
|
||||
val libDir = buildInfo.targetMachine.libraryDir.get().asFile
|
||||
val testSrc = file("src/nativeTest/c/test_pkl.c")
|
||||
|
||||
link = true
|
||||
sourceFiles.from(testSrc)
|
||||
includeDirs.from(buildInfo.targetMachine.outputDir.map { it.file("include") })
|
||||
// Match the dynamic CRT (ucrt/msvcrt) that libpkl.lib's bundled JDK objects expect -
|
||||
// otherwise this defaults to the static CRT (LIBCMT), causing a defaultlib conflict and
|
||||
// unresolved __imp_* CRT import symbols, same as buildPklObjectFile.
|
||||
if (buildInfo.os.isWindows) {
|
||||
runtimeLibrary = "MD"
|
||||
}
|
||||
outputFile =
|
||||
layout.buildDirectory.file(
|
||||
"native-test/${buildInfo.targetMachine.targetName}/test_pkl_static"
|
||||
)
|
||||
|
||||
libraryFiles.from("${libDir.absolutePath}/libpkl.${buildInfo.os.staticLibraryExtension}")
|
||||
|
||||
if (!buildInfo.os.isWindows) {
|
||||
libraries.add("pthread")
|
||||
libraries.add("z")
|
||||
}
|
||||
|
||||
if (buildInfo.os.isLinux) {
|
||||
libraries.add("dl")
|
||||
}
|
||||
|
||||
if (buildInfo.os.isMacOS) {
|
||||
frameworks.addAll("Foundation", "CoreServices")
|
||||
}
|
||||
}
|
||||
|
||||
// link to dynamic library
|
||||
val compileNativeTestDynamic =
|
||||
tasks.register<CCompile>("compileNativeTestDynamic") {
|
||||
description = "Build C test with dynamic library"
|
||||
group = "verification"
|
||||
|
||||
dependsOn(tasks.assembleNative)
|
||||
|
||||
val testSrc = file("src/nativeTest/c/test_pkl.c")
|
||||
|
||||
link = true
|
||||
sourceFiles.from(testSrc)
|
||||
includeDirs.from(buildInfo.targetMachine.outputDir.map { it.file("include") })
|
||||
outputFile =
|
||||
layout.buildDirectory.file(
|
||||
"native-test/${buildInfo.targetMachine.targetName}/test_pkl_dynamic"
|
||||
)
|
||||
|
||||
libraryPaths.from(buildInfo.targetMachine.libraryDir)
|
||||
|
||||
if (buildInfo.os.isWindows) {
|
||||
// "pkl" would resolve to libpkl.lib, which is buildStaticLibrary's static archive, not
|
||||
// the DLL's import library — link against the import library directly instead.
|
||||
libraryFiles.from(buildInfo.targetMachine.libraryDir.map { it.file("libpkl_dll.lib") })
|
||||
} else {
|
||||
libraries.add("pkl")
|
||||
// Bake the library's location into the test executable so the dynamic loader can find
|
||||
// libpkl.so/libpkl.dylib without needing LD_LIBRARY_PATH/DYLD_LIBRARY_PATH set at run time.
|
||||
linkerFlags.add("-rpath")
|
||||
linkerFlags.add(buildInfo.targetMachine.libraryDir.get().asFile.absolutePath)
|
||||
}
|
||||
|
||||
if (buildInfo.os.isLinux) {
|
||||
libraries.add("dl")
|
||||
}
|
||||
}
|
||||
|
||||
val buildNativeTestC =
|
||||
tasks.register("buildNativeTestC") {
|
||||
description = "Build both static and dynamic C tests"
|
||||
group = "verification"
|
||||
|
||||
dependsOn(compileNativeTestStatic, compileNativeTestDynamic)
|
||||
}
|
||||
|
||||
fun Task.configureDummyOutput() {
|
||||
group = "verification"
|
||||
|
||||
// dummy output to satisfy up-to-date check
|
||||
val outputFile = layout.buildDirectory.file(name)
|
||||
outputs.file(outputFile)
|
||||
|
||||
doFirst { outputFile.get().asFile.delete() }
|
||||
doLast { outputFile.get().asFile.writeText("OK") }
|
||||
}
|
||||
|
||||
val testNativeCStatic =
|
||||
tasks.register<Exec>("testNativeCStatic") {
|
||||
configureDummyOutput()
|
||||
group = "verification"
|
||||
dependsOn(compileNativeTestStatic)
|
||||
|
||||
val testDir = layout.buildDirectory.dir("native-test").get().asFile
|
||||
|
||||
workingDir = projectDir
|
||||
commandLine(compileNativeTestStatic.get().outputFile.get().asFile.absolutePath)
|
||||
}
|
||||
|
||||
val testNativeCDynamic =
|
||||
tasks.register<Exec>("testNativeCDynamic") {
|
||||
configureDummyOutput()
|
||||
group = "verification"
|
||||
|
||||
dependsOn(compileNativeTestDynamic)
|
||||
|
||||
val testDir = layout.buildDirectory.dir("native-test").get().asFile
|
||||
|
||||
workingDir = projectDir
|
||||
commandLine(compileNativeTestDynamic.get().outputFile.get().asFile.absolutePath)
|
||||
|
||||
if (buildInfo.os.isWindows) {
|
||||
// Windows has no rpath equivalent — the DLL loader only searches the exe's own
|
||||
// directory and PATH, so libpkl.dll must be made findable via PATH.
|
||||
doFirst {
|
||||
environment(
|
||||
"PATH",
|
||||
"${buildInfo.targetMachine.libraryDir.get().asFile.absolutePath};${System.getenv("PATH")}",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.testNative {
|
||||
dependsOn(testNativeCStatic, testNativeCDynamic)
|
||||
|
||||
// can't run libraries from other arch in Java
|
||||
if (!buildInfo.isCrossArch) {
|
||||
dependsOn(testNativeJava)
|
||||
}
|
||||
}
|
||||
|
||||
spotless {
|
||||
cpp {
|
||||
licenseHeaderFile(
|
||||
rootProject.file("build-logic/src/main/resources/license-header.star-block.txt"),
|
||||
"// ",
|
||||
)
|
||||
target("src/*/c/**/*.c", "src/*/c/**/*.h")
|
||||
eclipseCdt(libs.versions.eclipseCdtFormat.get())
|
||||
}
|
||||
shell {
|
||||
licenseHeaderFile(
|
||||
rootProject.file("build-logic/src/main/resources/license-header.hash-comment.txt"),
|
||||
"## build_",
|
||||
)
|
||||
// skip shebang
|
||||
.skipLinesMatching("^#!.+?$")
|
||||
|
||||
target("scripts/*.sh")
|
||||
}
|
||||
format("bat") {
|
||||
target("scripts/*.bat")
|
||||
licenseHeaderFile(
|
||||
rootProject.file("build-logic/src/main/resources/license-header.batch-script.txt"),
|
||||
"@echo",
|
||||
)
|
||||
}
|
||||
}
|
||||
Executable
+116
@@ -0,0 +1,116 @@
|
||||
#!/bin/bash
|
||||
#===----------------------------------------------------------------------===//
|
||||
# Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#===----------------------------------------------------------------------===//
|
||||
## build_unix.sh
|
||||
# Adapted from https://github.com/oracle/graal/issues/3053#issuecomment-1866735057
|
||||
# This script intercepts compiler arguments from graalvm native shared images
|
||||
# and additionally generates a static library.
|
||||
#
|
||||
# Use with --native-compiler-path=${pathToThisScript}
|
||||
|
||||
set -e
|
||||
|
||||
# Detect OS
|
||||
OS=$(uname -s)
|
||||
case "$OS" in
|
||||
Darwin)
|
||||
SHARED_LIB_EXT="dylib"
|
||||
;;
|
||||
Linux)
|
||||
SHARED_LIB_EXT="so"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported OS: $OS"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Determine the project name and output path based on the output library argument
|
||||
for arg in "$@"; do
|
||||
if [[ "$arg" == *."$SHARED_LIB_EXT" ]]; then
|
||||
OUTPUT_PATH=$(dirname "$arg")
|
||||
LIB_NAME=$(basename "${arg%."$SHARED_LIB_EXT"}")
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Do a simple forward for any calls that are used to compile individual C files
|
||||
if [[ -z $LIB_NAME ]]; then
|
||||
cc "$@"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Create a debug log in $output/logs
|
||||
LOG_PATH="${OUTPUT_PATH}/logs"
|
||||
LOG_FILE="${LOG_PATH}/compiler_commands.txt"
|
||||
mkdir -p "$LOG_PATH"
|
||||
|
||||
WORKINGDIR=${PWD}
|
||||
echo "Working directory: ${WORKINGDIR}" > "${LOG_FILE}"
|
||||
echo "Output path: ${OUTPUT_PATH}" >> "${LOG_FILE}"
|
||||
echo "Library name: ${LIB_NAME}" >> "${LOG_FILE}"
|
||||
echo "OS: ${OS}" >> "${LOG_FILE}"
|
||||
|
||||
echo "=====================================================" >> "${LOG_FILE}"
|
||||
echo " SHARED LIBRARY " >> "${LOG_FILE}"
|
||||
echo "=====================================================" >> "${LOG_FILE}"
|
||||
|
||||
CC_ARGS=( "$@" )
|
||||
echo "cc ${CC_ARGS[*]@Q}" >> "${LOG_FILE}"
|
||||
cc "${CC_ARGS[@]}"
|
||||
|
||||
echo "=====================================================" >> "${LOG_FILE}"
|
||||
echo " STATIC LIBRARY " >> "${LOG_FILE}"
|
||||
echo "=====================================================" >> "${LOG_FILE}"
|
||||
# To create a single static library we need to call 'ar -r' on all .o files.
|
||||
# In order to also include all static library dependencies, we can first extract the
|
||||
# .o files and then include them as well.
|
||||
echo "======= Source archives" >> "${LOG_FILE}"
|
||||
OBJECTS=${OUTPUT_PATH}/objects
|
||||
rm -rf "${OBJECTS}"
|
||||
mkdir "${OBJECTS}"
|
||||
|
||||
# Remove existing archive to avoid "fat file" errors
|
||||
ARCHIVE_FILE="${OUTPUT_PATH}/${LIB_NAME}.a"
|
||||
rm -f "${ARCHIVE_FILE}"
|
||||
|
||||
AR_ARGS="-rcs ${ARCHIVE_FILE} ${OBJECTS}/*.o"
|
||||
for arg in "$@"
|
||||
do
|
||||
if [[ $arg =~ .*\.(a)$ ]]; then
|
||||
# extract the objects (.o) of each archive (.a) into
|
||||
# separate directories to avoid naming collisions
|
||||
echo "$arg" >> "${LOG_FILE}"
|
||||
ARCHIVE_DIR=${OBJECTS}/$(basename "${arg%.a}")
|
||||
mkdir "${ARCHIVE_DIR}"
|
||||
cp "$arg" "${ARCHIVE_DIR}"
|
||||
cd "${ARCHIVE_DIR}" || exit
|
||||
ar -x "$arg"
|
||||
cd "${WORKINGDIR}" || exit
|
||||
AR_ARGS+=" ${ARCHIVE_DIR}/*.o"
|
||||
fi
|
||||
if [[ $arg =~ .*\.(o)$ ]]; then
|
||||
cp "$arg" "${OBJECTS}"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "======= Objects" >> "${LOG_FILE}"
|
||||
find "${OBJECTS}" -name "*.o" >> "${LOG_FILE}"
|
||||
|
||||
echo "======= Archive command" >> "${LOG_FILE}"
|
||||
echo "ar $AR_ARGS" >> "${LOG_FILE}"
|
||||
# shellcheck disable=SC2086
|
||||
ar $AR_ARGS
|
||||
@@ -0,0 +1,85 @@
|
||||
::===----------------------------------------------------------------------===//
|
||||
:: Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
::
|
||||
:: Licensed under the Apache License, Version 2.0 (the "License");
|
||||
:: you may not use this file except in compliance with the License.
|
||||
:: You may obtain a copy of the License at
|
||||
::
|
||||
:: https://www.apache.org/licenses/LICENSE-2.0
|
||||
::
|
||||
:: Unless required by applicable law or agreed to in writing, software
|
||||
:: distributed under the License is distributed on an "AS IS" BASIS,
|
||||
:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
:: See the License for the specific language governing permissions and
|
||||
:: limitations under the License.
|
||||
::===----------------------------------------------------------------------===//
|
||||
@echo off
|
||||
setlocal EnableDelayedExpansion
|
||||
|
||||
REM Adapted from https://github.com/oracle/graal/issues/3053#issuecomment-1866735057
|
||||
REM This script intercepts compiler arguments generated by GraalVM's native-image for
|
||||
REM creating a shared library, and additionally generates a self-contained static library.
|
||||
REM
|
||||
REM Use with --native-compiler-path=${pathToThisScript}.bat
|
||||
|
||||
REM Determine the library name and output path based on the .dll argument. MSVC's output-naming
|
||||
REM flag is passed as `/Fe<path>` with no space between the flag and the path, so strip that
|
||||
REM prefix before parsing the path - otherwise ~dp/~n misparse the combined token.
|
||||
set LIB_NAME=
|
||||
set OUTPUT_PATH=
|
||||
for %%P in (%*) do (
|
||||
echo %%P | findstr /C:".dll" 1>nul
|
||||
if !errorlevel!==0 (
|
||||
set "ARG=%%~P"
|
||||
if "!ARG:~0,3!"=="/Fe" set "ARG=!ARG:~3!"
|
||||
for %%F in ("!ARG!") do (
|
||||
set LIB_NAME=%%~nF
|
||||
set OUTPUT_PATH=%%~dpF
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
REM Do a simple forward for any calls that are used to compile individual C files
|
||||
IF "%LIB_NAME%"=="" (
|
||||
cmd /c cl %*
|
||||
exit /b
|
||||
)
|
||||
|
||||
REM Setup log path and log file
|
||||
set LOG_PATH=%OUTPUT_PATH%logs
|
||||
set LOG_FILE=%LOG_PATH%\compiler_commands.txt
|
||||
if not exist %LOG_PATH% mkdir %LOG_PATH%
|
||||
|
||||
echo Working directory: %CD% > %LOG_FILE%
|
||||
echo Output path: %OUTPUT_PATH% >> %LOG_FILE%
|
||||
echo Library name: %LIB_NAME% >> %LOG_FILE%
|
||||
|
||||
echo ===================================================== >> %LOG_FILE%
|
||||
echo SHARED LIBRARY >> %LOG_FILE%
|
||||
echo ===================================================== >> %LOG_FILE%
|
||||
set CL_ARGS=%*
|
||||
echo cl.exe %CL_ARGS% >> %LOG_FILE%
|
||||
cmd /c cl.exe %CL_ARGS%
|
||||
|
||||
echo ===================================================== >> %LOG_FILE%
|
||||
echo STATIC LIBRARY >> %LOG_FILE%
|
||||
echo ===================================================== >> %LOG_FILE%
|
||||
REM lib.exe can combine .obj and .lib inputs directly into a single output archive, so unlike
|
||||
REM build_unix.sh we don't need to manually extract members from input archives first - we just
|
||||
REM collect every .obj/.lib argument that was passed to the shared-library link and re-archive
|
||||
REM them together into one self-contained static library with no runtime DLL dependency.
|
||||
REM We don't want to overwrite the import library needed to link against the DLL, so we append
|
||||
REM "_s" to indicate that it is the static variant.
|
||||
set STATIC_INPUTS=
|
||||
for %%P in (%*) do (
|
||||
set ARG=%%P
|
||||
if /I "!ARG:~-4!"==".obj" set STATIC_INPUTS=!STATIC_INPUTS! %%P
|
||||
if /I "!ARG:~-4!"==".lib" set STATIC_INPUTS=!STATIC_INPUTS! %%P
|
||||
)
|
||||
|
||||
echo ======= Static inputs >> %LOG_FILE%
|
||||
echo %STATIC_INPUTS% >> %LOG_FILE%
|
||||
|
||||
set LIB_ARGS=/OUT:%OUTPUT_PATH%%LIB_NAME%_s.lib %STATIC_INPUTS%
|
||||
echo lib.exe %LIB_ARGS% >> %LOG_FILE%
|
||||
cmd /c lib.exe %LIB_ARGS%
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
// pkl.h
|
||||
#ifndef PKL_H
|
||||
#define PKL_H
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define PKL_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define PKL_EXPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
#define PKL_ERR_THREAD 1 /* Called using the same pexec_t but from a different thread */
|
||||
#define PKL_ERR_PROTOCOL 2 /* Failed to decode a message */
|
||||
|
||||
/** Error details that occurred during a method call */
|
||||
typedef struct {
|
||||
char *message;
|
||||
} pkl_error_t;
|
||||
|
||||
/**
|
||||
* Pkl executor instance that manages communication with the Pkl runtime.
|
||||
*
|
||||
* Instances should be created via `pkl_init` and destroyed via `pkl_close`.
|
||||
*
|
||||
* All calls using this executor should be synchronized in the same thread.
|
||||
*/
|
||||
typedef struct __pkl_exec_t pkl_exec_t;
|
||||
|
||||
/**
|
||||
* The callback that gets called when a message is received from Pkl.
|
||||
*
|
||||
* Messages must be deserialized to Pkl's Message Passing API:
|
||||
* https://pkl-lang.org/main/current/bindings-specification/message-passing-api.html
|
||||
*
|
||||
* @param[in] length The length of the message bytes
|
||||
* @param[in] message The message itself
|
||||
* @param[in] userData User-defined data passed in from pkl_init.
|
||||
*/
|
||||
typedef void (*pkl_message_response_handler)(unsigned int length, char *message,
|
||||
void *userData);
|
||||
|
||||
/**
|
||||
* Initializes and allocates a Pkl executor, writing it to the slot pointed by `exec`.
|
||||
*
|
||||
* To clean up resources allocated by the executor, use `pkl_close()`.
|
||||
*
|
||||
* All calls using this executor should come from the same thread.
|
||||
*
|
||||
* @param[in] handler The callback that gets called when a message is received from Pkl.
|
||||
* @param[in] userData User-defined data that gets passed to handler.
|
||||
* @param[out] exec The pointer to write the created pkl_exec_t to.
|
||||
* @param[out] error The pointer to write error details to. Can optionally be `NULL`.
|
||||
*
|
||||
* @return 0 on success, non-zero on failure.
|
||||
*/
|
||||
PKL_EXPORT int pkl_init(pkl_message_response_handler handler, void *userData,
|
||||
pkl_exec_t **exec, pkl_error_t *error);
|
||||
|
||||
/**
|
||||
* Send a message to Pkl, providing the length and a pointer to the first byte.
|
||||
*
|
||||
* Messages must be serialized according to Pkl's Message Passing API:
|
||||
* https://pkl-lang.org/main/current/bindings-specification/message-passing-api.html
|
||||
*
|
||||
* If a message is incorrectly serialized, returns `PKL_ERR_PROTOCOL`.
|
||||
* If called from a different thread than `pkl_exec_t`'s originating thread, returns
|
||||
* `PKL_ERR_THREAD`.
|
||||
*
|
||||
* @param[in] pexec The Pkl executor instance.
|
||||
* @param[in] length The length of the message, in bytes.
|
||||
* @param[in] message The message to send to Pkl.
|
||||
* @param[out] error The pointer to write error details to. Can optionally be `NULL`.
|
||||
*
|
||||
* @return 0 on success, and non-zero otherwise.
|
||||
*/
|
||||
PKL_EXPORT int pkl_send_message(const pkl_exec_t *pexec, unsigned int length,
|
||||
char *message, pkl_error_t *error);
|
||||
|
||||
/**
|
||||
* Cleans up any resources that were created as part of the `pkl_init` process
|
||||
* for our `pkl_exec_t` instance.
|
||||
*
|
||||
* If called from a different thread than `pkl_exec_t`'s originating thread, returns
|
||||
* `PKL_ERR_THREAD`.
|
||||
*
|
||||
* @param[in] pexec The Pkl executor instance.
|
||||
* @param[out] error The pointer to write error details to. Can optionally be `NULL`.
|
||||
*
|
||||
* @return 0 on success, -1 if `pexec` is `NULL`, and an error code otherwise.
|
||||
*/
|
||||
PKL_EXPORT int pkl_close(pkl_exec_t *pexec, pkl_error_t *error);
|
||||
|
||||
/**
|
||||
* Returns a null-terminated string indicating Pkl's version.
|
||||
*/
|
||||
PKL_EXPORT const char* pkl_version();
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
// pkl.c
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
#include <graal_isolate.h>
|
||||
#include <libpkl_internal.h>
|
||||
|
||||
#include <pkl.h>
|
||||
|
||||
#ifndef PKL_VERSION
|
||||
#define PKL_VERSION "0.0.0"
|
||||
#endif
|
||||
|
||||
// ReSharper disable once CppClassNeverUsed
|
||||
struct __pkl_exec_t {
|
||||
graal_isolate_t *isolate;
|
||||
graal_isolatethread_t *isolateThread;
|
||||
|
||||
/** The caller-supplied handler/userData from pkl_init, invoked only from `queue_thread`. */
|
||||
pkl_message_response_handler handler;
|
||||
|
||||
void *userData;
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE queue_thread;
|
||||
#else
|
||||
pthread_t queue_thread;
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* Polls for messages coming from Pkl and sends them back to the handler.
|
||||
*
|
||||
* Ensures that calls into Pkl are wrapped with graal_attach_thread/graal_detach_thread, and
|
||||
* calls back to the handler are _not_ inside the GraalVM thread context.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
static DWORD WINAPI pkl_dispatch_worker(LPVOID arg) {
|
||||
#else
|
||||
static void* pkl_dispatch_worker(void *arg) {
|
||||
#endif
|
||||
const pkl_exec_t *pexec = (pkl_exec_t*) arg;
|
||||
|
||||
for (;;) {
|
||||
graal_isolatethread_t *thread;
|
||||
if (graal_attach_thread(pexec->isolate, &thread) != 0) {
|
||||
fprintf(stderr,
|
||||
"fatal: failed to attach response dispatch thread to isolate.\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
char *message = NULL;
|
||||
const int length = pkl_internal_poll_response(thread, &message);
|
||||
|
||||
if (graal_detach_thread(thread) != 0) {
|
||||
fprintf(stderr,
|
||||
"fatal: failed to detach response dispatch thread from isolate.\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
if (length < 0) {
|
||||
// pkl_internal_server_stop has been called and the queue is drained
|
||||
break;
|
||||
}
|
||||
pexec->handler(length, message, pexec->userData);
|
||||
// message was allocated on the Java side via UnmanagedMemory.malloc, which maps onto
|
||||
// the platform's malloc/free; ownership passes to us here.
|
||||
free(message);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
return 0;
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int pkl_start_dispatch_worker(pkl_exec_t *pexec, pkl_error_t *error) {
|
||||
#ifdef _WIN32
|
||||
pexec->queue_thread = CreateThread(NULL, 0, pkl_dispatch_worker, pexec, 0, NULL);
|
||||
if (pexec->queue_thread == NULL) {
|
||||
#else
|
||||
if (pthread_create(&pexec->queue_thread, NULL, pkl_dispatch_worker, pexec)
|
||||
!= 0) {
|
||||
#endif
|
||||
if (error != NULL) {
|
||||
error->message = "Failed to create response dispatch thread";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pkl_stop_dispatch_worker(pkl_exec_t *pexec) {
|
||||
#ifdef _WIN32
|
||||
WaitForSingleObject(pexec->queue_thread, INFINITE);
|
||||
CloseHandle(pexec->queue_thread);
|
||||
#else
|
||||
pthread_join(pexec->queue_thread, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
int pkl_init(pkl_message_response_handler handler, void *userData,
|
||||
pkl_exec_t **exec, pkl_error_t *error) {
|
||||
if (handler == NULL) {
|
||||
if (error != NULL) {
|
||||
error->message = "handler is null";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (exec == NULL) {
|
||||
if (error != NULL) {
|
||||
error->message = "exec is null";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
pkl_exec_t *pexec = calloc(1, sizeof(pkl_exec_t));
|
||||
if (pexec == NULL) {
|
||||
fprintf(stderr, "failed to allocate pkl_exec_t\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
pexec->handler = handler;
|
||||
pexec->userData = userData;
|
||||
|
||||
graal_isolate_t *isolate;
|
||||
graal_isolatethread_t *isolateThread;
|
||||
|
||||
if (graal_create_isolate(NULL, &isolate, &isolateThread) != 0) {
|
||||
if (error != NULL) {
|
||||
error->message = "Failed to create graal isolate thread";
|
||||
}
|
||||
free(pexec);
|
||||
*exec = NULL;
|
||||
return -1;
|
||||
}
|
||||
pexec->isolate = isolate;
|
||||
pexec->isolateThread = isolateThread;
|
||||
|
||||
if (pkl_start_dispatch_worker(pexec, error) != 0) {
|
||||
graal_tear_down_isolate(isolateThread);
|
||||
free(pexec);
|
||||
*exec = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
pkl_internal_server_start(isolateThread);
|
||||
|
||||
*exec = pexec;
|
||||
if (error != NULL) {
|
||||
error->message = NULL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pkl_send_message(const pkl_exec_t *pexec, const unsigned int length,
|
||||
char *message, pkl_error_t *error) {
|
||||
if (pexec == NULL) {
|
||||
if (error != NULL) {
|
||||
error->message = "pexec is null";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (message == NULL) {
|
||||
if (error != NULL) {
|
||||
error->message = "message is null";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
graal_isolatethread_t *thread = graal_get_current_thread(pexec->isolate);
|
||||
if (thread != pexec->isolateThread) {
|
||||
if (error != NULL) {
|
||||
error->message =
|
||||
"called into pkl_send_message from different thread";
|
||||
}
|
||||
return PKL_ERR_THREAD;
|
||||
}
|
||||
|
||||
char *errormessage = NULL;
|
||||
const int resp = pkl_internal_send_message(thread, (int) length, message,
|
||||
&errormessage);
|
||||
|
||||
if (resp != 0) {
|
||||
if (error != NULL) {
|
||||
error->message = errormessage;
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
if (error != NULL) {
|
||||
error->message = NULL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pkl_close(pkl_exec_t *pexec, pkl_error_t *error) {
|
||||
if (pexec == NULL) {
|
||||
if (error != NULL) {
|
||||
error->message = "pexec is null";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
graal_isolatethread_t *thread = graal_get_current_thread(pexec->isolate);
|
||||
if (thread != pexec->isolateThread) {
|
||||
if (error != NULL) {
|
||||
error->message = "called into pkl_close from different thread";
|
||||
}
|
||||
return PKL_ERR_THREAD;
|
||||
}
|
||||
|
||||
// pkl_internal_server_stop unblocks queue_thread's pending/next poll call, so the join
|
||||
// below is guaranteed to return; only then is it safe to tear down the isolate.
|
||||
pkl_internal_server_stop(thread);
|
||||
pkl_stop_dispatch_worker(pexec);
|
||||
|
||||
if (graal_tear_down_isolate(thread) != 0) {
|
||||
fprintf(stderr, "fatal: failed to tear down graal isolate.\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
free(pexec);
|
||||
if (error != NULL) {
|
||||
error->message = NULL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* pkl_version() {
|
||||
return PKL_VERSION;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
_pkl_init
|
||||
_pkl_send_message
|
||||
_pkl_close
|
||||
_pkl_version
|
||||
@@ -0,0 +1,9 @@
|
||||
PKL {
|
||||
global:
|
||||
pkl_init;
|
||||
pkl_send_message;
|
||||
pkl_close;
|
||||
pkl_version;
|
||||
local:
|
||||
*;
|
||||
};
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../../../LICENSE.txt
|
||||
@@ -0,0 +1,5 @@
|
||||
# libpkl
|
||||
|
||||
libpkl is a native C library for calling into Pkl via its message passing API.
|
||||
|
||||
For full usage instructions, visit https://pkl-lang.org/main/current/libpkl/index.html
|
||||
@@ -0,0 +1,437 @@
|
||||
1) Graal Sdk (https://github.com/oracle/graal)
|
||||
POM License: Universal Permissive License, Version 1.0 - http://opensource.org/licenses/UPL
|
||||
|
||||
Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
|
||||
The Universal Permissive License (UPL), Version 1.0
|
||||
|
||||
Subject to the condition set forth below, permission is hereby granted to any person obtaining a copy of this software, associated documentation and/or data (collectively the "Software"), free of charge and under any and all copyright rights in the Software, and any and all patent rights owned or freely licensable by each licensor hereunder covering either (i) the unmodified Software as contributed to or provided by such licensor, or (ii) the Larger Works (as defined below), to deal in both
|
||||
|
||||
(a) the Software, and
|
||||
|
||||
(b) any piece of software and/or hardware listed in the lrgrwrks.txt file if one is included with the Software (each a "Larger Work" to which the Software is contributed by such licensors),
|
||||
|
||||
without restriction, including without limitation the rights to copy, create derivative works of, display, perform, and distribute the Software and make, use, sell, offer for sale, import, export, have made, and have sold the Software and the Larger Work(s), and to sublicense the foregoing rights on either these or other terms.
|
||||
|
||||
This license is subject to the following condition:
|
||||
|
||||
The above copyright notice and either this complete permission notice or at a minimum a reference to the UPL must be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
----
|
||||
|
||||
2) Truffle API (http://openjdk.java.net/projects/graal)
|
||||
POM License: Universal Permissive License, Version 1.0 - http://opensource.org/licenses/UPL
|
||||
|
||||
Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
|
||||
The Universal Permissive License (UPL), Version 1.0
|
||||
|
||||
Subject to the condition set forth below, permission is hereby granted to any person obtaining a copy of this software, associated documentation and/or data (collectively the "Software"), free of charge and under any and all copyright rights in the Software, and any and all patent rights owned or freely licensable by each licensor hereunder covering either (i) the unmodified Software as contributed to or provided by such licensor, or (ii) the Larger Works (as defined below), to deal in both
|
||||
|
||||
(a) the Software, and
|
||||
|
||||
(b) any piece of software and/or hardware listed in the lrgrwrks.txt file if one is included with the Software (each a "Larger Work" to which the Software is contributed by such licensors),
|
||||
|
||||
without restriction, including without limitation the rights to copy, create derivative works of, display, perform, and distribute the Software and make, use, sell, offer for sale, import, export, have made, and have sold the Software and the Larger Work(s), and to sublicense the foregoing rights on either these or other terms.
|
||||
|
||||
This license is subject to the following condition:
|
||||
|
||||
The above copyright notice and either this complete permission notice or at a minimum a reference to the UPL must be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
----
|
||||
|
||||
3) SubstrateVM (https://github.com/oracle/graal)
|
||||
POM License: GPL 2 with Classpath Exception
|
||||
|
||||
The GNU General Public License (GPL)
|
||||
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this license
|
||||
document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your freedom to share
|
||||
and change it. By contrast, the GNU General Public License is intended to
|
||||
guarantee your freedom to share and change free software--to make sure the
|
||||
software is free for all its users. This General Public License applies to
|
||||
most of the Free Software Foundation's software and to any other program whose
|
||||
authors commit to using it. (Some other Free Software Foundation software is
|
||||
covered by the GNU Library General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not price. Our
|
||||
General Public Licenses are designed to make sure that you have the freedom to
|
||||
distribute copies of free software (and charge for this service if you wish),
|
||||
that you receive source code or can get it if you want it, that you can change
|
||||
the software or use pieces of it in new free programs; and that you know you
|
||||
can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid anyone to deny
|
||||
you these rights or to ask you to surrender the rights. These restrictions
|
||||
translate to certain responsibilities for you if you distribute copies of the
|
||||
software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether gratis or for
|
||||
a fee, you must give the recipients all the rights that you have. You must
|
||||
make sure that they, too, receive or can get the source code. And you must
|
||||
show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and (2)
|
||||
offer you this license which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain that
|
||||
everyone understands that there is no warranty for this free software. If the
|
||||
software is modified by someone else and passed on, we want its recipients to
|
||||
know that what they have is not the original, so that any problems introduced
|
||||
by others will not reflect on the original authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software patents. We
|
||||
wish to avoid the danger that redistributors of a free program will
|
||||
individually obtain patent licenses, in effect making the program proprietary.
|
||||
To prevent this, we have made it clear that any patent must be licensed for
|
||||
everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and modification
|
||||
follow.
|
||||
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains a notice
|
||||
placed by the copyright holder saying it may be distributed under the terms of
|
||||
this General Public License. The "Program", below, refers to any such program
|
||||
or work, and a "work based on the Program" means either the Program or any
|
||||
derivative work under copyright law: that is to say, a work containing the
|
||||
Program or a portion of it, either verbatim or with modifications and/or
|
||||
translated into another language. (Hereinafter, translation is included
|
||||
without limitation in the term "modification".) Each licensee is addressed as
|
||||
"you".
|
||||
|
||||
Activities other than copying, distribution and modification are not covered by
|
||||
this License; they are outside its scope. The act of running the Program is
|
||||
not restricted, and the output from the Program is covered only if its contents
|
||||
constitute a work based on the Program (independent of having been made by
|
||||
running the Program). Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's source code as
|
||||
you receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice and
|
||||
disclaimer of warranty; keep intact all the notices that refer to this License
|
||||
and to the absence of any warranty; and give any other recipients of the
|
||||
Program a copy of this License along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and you may
|
||||
at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion of it, thus
|
||||
forming a work based on the Program, and copy and distribute such modifications
|
||||
or work under the terms of Section 1 above, provided that you also meet all of
|
||||
these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices stating
|
||||
that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in whole or
|
||||
in part contains or is derived from the Program or any part thereof, to be
|
||||
licensed as a whole at no charge to all third parties under the terms of
|
||||
this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively when run,
|
||||
you must cause it, when started running for such interactive use in the
|
||||
most ordinary way, to print or display an announcement including an
|
||||
appropriate copyright notice and a notice that there is no warranty (or
|
||||
else, saying that you provide a warranty) and that users may redistribute
|
||||
the program under these conditions, and telling the user how to view a copy
|
||||
of this License. (Exception: if the Program itself is interactive but does
|
||||
not normally print such an announcement, your work based on the Program is
|
||||
not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If identifiable
|
||||
sections of that work are not derived from the Program, and can be reasonably
|
||||
considered independent and separate works in themselves, then this License, and
|
||||
its terms, do not apply to those sections when you distribute them as separate
|
||||
works. But when you distribute the same sections as part of a whole which is a
|
||||
work based on the Program, the distribution of the whole must be on the terms
|
||||
of this License, whose permissions for other licensees extend to the entire
|
||||
whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest your
|
||||
rights to work written entirely by you; rather, the intent is to exercise the
|
||||
right to control the distribution of derivative or collective works based on
|
||||
the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program with the
|
||||
Program (or with a work based on the Program) on a volume of a storage or
|
||||
distribution medium does not bring the other work under the scope of this
|
||||
License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it, under
|
||||
Section 2) in object code or executable form under the terms of Sections 1 and
|
||||
2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable source
|
||||
code, which must be distributed under the terms of Sections 1 and 2 above
|
||||
on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three years, to
|
||||
give any third party, for a charge no more than your cost of physically
|
||||
performing source distribution, a complete machine-readable copy of the
|
||||
corresponding source code, to be distributed under the terms of Sections 1
|
||||
and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer to
|
||||
distribute corresponding source code. (This alternative is allowed only
|
||||
for noncommercial distribution and only if you received the program in
|
||||
object code or executable form with such an offer, in accord with
|
||||
Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for making
|
||||
modifications to it. For an executable work, complete source code means all
|
||||
the source code for all modules it contains, plus any associated interface
|
||||
definition files, plus the scripts used to control compilation and installation
|
||||
of the executable. However, as a special exception, the source code
|
||||
distributed need not include anything that is normally distributed (in either
|
||||
source or binary form) with the major components (compiler, kernel, and so on)
|
||||
of the operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the source
|
||||
code from the same place counts as distribution of the source code, even though
|
||||
third parties are not compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program except as
|
||||
expressly provided under this License. Any attempt otherwise to copy, modify,
|
||||
sublicense or distribute the Program is void, and will automatically terminate
|
||||
your rights under this License. However, parties who have received copies, or
|
||||
rights, from you under this License will not have their licenses terminated so
|
||||
long as such parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not signed it.
|
||||
However, nothing else grants you permission to modify or distribute the Program
|
||||
or its derivative works. These actions are prohibited by law if you do not
|
||||
accept this License. Therefore, by modifying or distributing the Program (or
|
||||
any work based on the Program), you indicate your acceptance of this License to
|
||||
do so, and all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the Program),
|
||||
the recipient automatically receives a license from the original licensor to
|
||||
copy, distribute or modify the Program subject to these terms and conditions.
|
||||
You may not impose any further restrictions on the recipients' exercise of the
|
||||
rights granted herein. You are not responsible for enforcing compliance by
|
||||
third parties to this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues), conditions
|
||||
are imposed on you (whether by court order, agreement or otherwise) that
|
||||
contradict the conditions of this License, they do not excuse you from the
|
||||
conditions of this License. If you cannot distribute so as to satisfy
|
||||
simultaneously your obligations under this License and any other pertinent
|
||||
obligations, then as a consequence you may not distribute the Program at all.
|
||||
For example, if a patent license would not permit royalty-free redistribution
|
||||
of the Program by all those who receive copies directly or indirectly through
|
||||
you, then the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply and
|
||||
the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any patents or
|
||||
other property right claims or to contest validity of any such claims; this
|
||||
section has the sole purpose of protecting the integrity of the free software
|
||||
distribution system, which is implemented by public license practices. Many
|
||||
people have made generous contributions to the wide range of software
|
||||
distributed through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing to
|
||||
distribute software through any other system and a licensee cannot impose that
|
||||
choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to be a
|
||||
consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in certain
|
||||
countries either by patents or by copyrighted interfaces, the original
|
||||
copyright holder who places the Program under this License may add an explicit
|
||||
geographical distribution limitation excluding those countries, so that
|
||||
distribution is permitted only in or among countries not thus excluded. In
|
||||
such case, this License incorporates the limitation as if written in the body
|
||||
of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions of the
|
||||
General Public License from time to time. Such new versions will be similar in
|
||||
spirit to the present version, but may differ in detail to address new problems
|
||||
or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any later
|
||||
version", you have the option of following the terms and conditions either of
|
||||
that version or of any later version published by the Free Software Foundation.
|
||||
If the Program does not specify a version number of this License, you may
|
||||
choose any version ever published by the Free Software Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free programs
|
||||
whose distribution conditions are different, write to the author to ask for
|
||||
permission. For software which is copyrighted by the Free Software Foundation,
|
||||
write to the Free Software Foundation; we sometimes make exceptions for this.
|
||||
Our decision will be guided by the two goals of preserving the free status of
|
||||
all derivatives of our free software and of promoting the sharing and reuse of
|
||||
software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
|
||||
THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
|
||||
STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE
|
||||
PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
|
||||
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE,
|
||||
YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL
|
||||
ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE
|
||||
PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR
|
||||
INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA
|
||||
BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER
|
||||
OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest possible
|
||||
use to the public, the best way to achieve this is to make it free software
|
||||
which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest to attach
|
||||
them to the start of each source file to most effectively convey the exclusion
|
||||
of warranty; and each file should have at least the "copyright" line and a
|
||||
pointer to where the full notice is found.
|
||||
|
||||
One line to give the program's name and a brief idea of what it does.
|
||||
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation; either version 2 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this when it
|
||||
starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author Gnomovision comes
|
||||
with ABSOLUTELY NO WARRANTY; for details type 'show w'. This is free
|
||||
software, and you are welcome to redistribute it under certain conditions;
|
||||
type 'show c' for details.
|
||||
|
||||
The hypothetical commands 'show w' and 'show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may be
|
||||
called something other than 'show w' and 'show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary. Here
|
||||
is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
'Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
signature of Ty Coon, 1 April 1989
|
||||
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Library General Public
|
||||
License instead of this License.
|
||||
|
||||
|
||||
"CLASSPATH" EXCEPTION TO THE GPL
|
||||
|
||||
Certain source files distributed by Oracle America and/or its affiliates
|
||||
are subject to the following clarification and special exception to the GPLv2,
|
||||
based on the GNU Project exception for its Classpath libraries, known as the
|
||||
GNU Classpath Exception, but only where Oracle has expressly included in the
|
||||
particular source file's header the words "Oracle designates this particular
|
||||
file as subject to the "Classpath" exception as provided by Oracle in the
|
||||
LICENSE file that accompanied this code."
|
||||
|
||||
You should also note that Oracle includes multiple, independent programs in
|
||||
this software package. Some of those programs are provided under licenses
|
||||
deemed incompatible with the GPLv2 by the Free Software Foundation and others.
|
||||
For example, the package includes programs licensed under the Apache License,
|
||||
Version 2.0. Such programs are licensed to you under their original licenses.
|
||||
|
||||
Oracle facilitates your further distribution of this package by adding the
|
||||
Classpath Exception to the necessary parts of its GPLv2 code, which permits you
|
||||
to use that code in combination with other independent modules not licensed
|
||||
under the GPLv2. However, note that this would not permit you to commingle code
|
||||
under an incompatible license with Oracle's GPLv2 licensed code by, for
|
||||
example, cutting and pasting such code into a file also containing Oracle's
|
||||
GPLv2 licensed code and then distributing the result.
|
||||
|
||||
Additionally, if you were to remove the Classpath Exception from any of the
|
||||
files to which it applies and distribute the result, you would likely be
|
||||
required to license some or all of the other code in that distribution under
|
||||
the GPLv2 as well, and since the GPLv2 is incompatible with the license terms
|
||||
of some items included in the distribution by Oracle, removing the Classpath
|
||||
Exception could therefore effectively compromise your ability to further
|
||||
distribute the package.
|
||||
|
||||
Proceed with caution and we recommend that you obtain the advice of a lawyer
|
||||
skilled in open source matters before removing the Classpath Exception or
|
||||
making modifications to this package which may subsequently be redistributed
|
||||
and/or involve the use of third party software.
|
||||
|
||||
CLASSPATH EXCEPTION
|
||||
|
||||
Linking this library statically or dynamically with other modules is making a
|
||||
combined work based on this library. Thus, the terms and conditions of the GNU
|
||||
General Public License version 2 cover the whole combination.
|
||||
|
||||
As a special exception, the copyright holders of this library give you
|
||||
permission to link this library with independent modules to produce an
|
||||
executable, regardless of the license terms of these independent modules, and
|
||||
to copy and distribute the resulting executable under terms of your choice,
|
||||
provided that you also meet, for each linked independent module, the terms and
|
||||
conditions of the license of that module. An independent module is a module
|
||||
which is not derived from or based on this library. If you modify this library,
|
||||
you may extend this exception to your version of the library, but you are not
|
||||
obligated to do so. If you do not wish to do so, delete this exception
|
||||
statement from your version.
|
||||
|
||||
----
|
||||
|
||||
4) msgpack-core (https://msgpack.org/)
|
||||
POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
The Apache License
|
||||
Copyright © 2016 Sadayuki Furuhashi, Muga Nishizawa, Taro L. Saito, Mitsunori Komatsu, Ozawa Tsuyoshi
|
||||
Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License.You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||
@@ -0,0 +1,11 @@
|
||||
prefix=${pcfiledir}/../..
|
||||
exec_prefix=${prefix}
|
||||
libdir=${exec_prefix}/lib
|
||||
includedir=${prefix}/include
|
||||
|
||||
Name: libpkl
|
||||
Description: C library for calling into Pkl's message passing API.
|
||||
URL: https://pkl-lang.org/main/current/libpkl/index.html
|
||||
Version: @version@
|
||||
Libs: ${libdir}/@static_lib_filename@@extra_static_libs@
|
||||
Cflags: -I${includedir}
|
||||
@@ -0,0 +1,11 @@
|
||||
prefix=${pcfiledir}/../..
|
||||
exec_prefix=${prefix}
|
||||
libdir=${exec_prefix}/lib
|
||||
includedir=${prefix}/include
|
||||
|
||||
Name: libpkl
|
||||
Description: C library for calling into Pkl's message passing API.
|
||||
URL: https://pkl-lang.org/main/current/libpkl/index.html
|
||||
Version: @version@
|
||||
Libs: -L${libdir}@extra_shared_libs@
|
||||
Cflags: -I${includedir}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.libpkl;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import org.graalvm.nativeimage.IsolateThread;
|
||||
import org.graalvm.nativeimage.UnmanagedMemory;
|
||||
import org.graalvm.nativeimage.c.function.CEntryPoint;
|
||||
import org.graalvm.nativeimage.c.type.CCharPointer;
|
||||
import org.graalvm.nativeimage.c.type.CCharPointerPointer;
|
||||
import org.graalvm.nativeimage.c.type.CTypeConversion;
|
||||
import org.graalvm.word.WordFactory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.pkl.core.messaging.MessageTransports.Logger;
|
||||
import org.pkl.core.messaging.ProtocolException;
|
||||
import org.pkl.server.Server;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class LibPklInternal {
|
||||
private static final Logger logger = new LibPklLogger();
|
||||
private static final NativeTransport transport =
|
||||
new NativeTransport(logger, LibPklInternal::handleSendMessageToNative);
|
||||
private static @Nullable Server server;
|
||||
|
||||
private static final BlockingQueue<byte[]> responseQueue = new LinkedBlockingQueue<>();
|
||||
|
||||
// sentinel identity, not contents, marks the end of the stream - see pklInternalPollResponse
|
||||
private static final byte[] STOP_SENTINEL = new byte[0];
|
||||
|
||||
// keep in sync with values defined in pkl.h
|
||||
static final int PKL_ERR_PROTOCOL = 2;
|
||||
|
||||
private LibPklInternal() {}
|
||||
|
||||
@CEntryPoint(name = "pkl_internal_send_message")
|
||||
public static int pklInternalSendMessage(
|
||||
IsolateThread thread, int length, CCharPointer ptr, CCharPointerPointer errorMessage) {
|
||||
try {
|
||||
transport.sendMessage(length, ptr);
|
||||
return 0;
|
||||
} catch (ProtocolException e) {
|
||||
@SuppressWarnings("resource")
|
||||
var cString =
|
||||
CTypeConversion.toCString("Invalid encoding: %s".formatted(e.getMessage())).get();
|
||||
errorMessage.write(cString);
|
||||
return PKL_ERR_PROTOCOL;
|
||||
}
|
||||
}
|
||||
|
||||
@CEntryPoint(name = "pkl_internal_server_start")
|
||||
public static void pklInternalServerStart(IsolateThread thread) {
|
||||
server = new Server(transport);
|
||||
server.start();
|
||||
}
|
||||
|
||||
@CEntryPoint(name = "pkl_internal_server_stop")
|
||||
public static void pklInternalServerStop(IsolateThread thread) {
|
||||
assert server != null;
|
||||
server.close();
|
||||
// wakes up (and eventually stops) whatever thread is blocked in pkl_internal_poll_response
|
||||
responseQueue.add(STOP_SENTINEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks until a response is available, copies it into unmanaged memory, and writes its address
|
||||
* to {@code outMessage}. The caller (pkl.c) takes ownership of the returned buffer and must free
|
||||
* it with the C library's {@code free}.
|
||||
*
|
||||
* @return the length of the response in bytes, or a negative value once the server has been
|
||||
* stopped and no further responses will ever be produced.
|
||||
*/
|
||||
@CEntryPoint(name = "pkl_internal_poll_response")
|
||||
public static int pklInternalPollResponse(IsolateThread thread, CCharPointerPointer outMessage) {
|
||||
byte[] bytes;
|
||||
try {
|
||||
bytes = responseQueue.take();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
outMessage.write(WordFactory.nullPointer());
|
||||
return -1;
|
||||
}
|
||||
// identity check, not content check: a genuine zero-length message is never this instance
|
||||
if (bytes == STOP_SENTINEL) {
|
||||
outMessage.write(WordFactory.nullPointer());
|
||||
return -1;
|
||||
}
|
||||
var buf = UnmanagedMemory.<CCharPointer>malloc(WordFactory.unsigned(bytes.length));
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
buf.write(i, bytes[i]);
|
||||
}
|
||||
outMessage.write(buf);
|
||||
return bytes.length;
|
||||
}
|
||||
|
||||
public static void handleSendMessageToNative(byte[] bytes) {
|
||||
responseQueue.add(bytes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.libpkl;
|
||||
|
||||
import java.util.Objects;
|
||||
import org.pkl.core.messaging.MessageTransports.Logger;
|
||||
|
||||
public class LibPklLogger implements Logger {
|
||||
|
||||
@Override
|
||||
public void log(String msg) {
|
||||
if (Objects.equals(System.getenv("PKL_DEBUG"), "1")) {
|
||||
System.err.println("[libpkl] " + msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.libpkl;
|
||||
|
||||
import java.io.InputStream;
|
||||
import org.graalvm.nativeimage.c.type.CCharPointer;
|
||||
|
||||
public class NativeInputStream extends InputStream {
|
||||
private int offset;
|
||||
private final int length;
|
||||
private final CCharPointer ptr;
|
||||
|
||||
public NativeInputStream(int length, CCharPointer ptr) {
|
||||
super();
|
||||
this.length = length;
|
||||
this.ptr = ptr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() {
|
||||
if (available() <= 0) {
|
||||
return -1;
|
||||
}
|
||||
var result = ptr.read(offset);
|
||||
offset++;
|
||||
return result & 0xFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() {
|
||||
return length - offset;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.libpkl;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.function.Consumer;
|
||||
import org.graalvm.nativeimage.c.type.CCharPointer;
|
||||
import org.msgpack.core.MessagePack;
|
||||
import org.pkl.core.PklBugException;
|
||||
import org.pkl.core.messaging.Message;
|
||||
import org.pkl.core.messaging.MessageTransports.AbstractMessageTransport;
|
||||
import org.pkl.core.messaging.MessageTransports.Logger;
|
||||
import org.pkl.core.messaging.ProtocolException;
|
||||
import org.pkl.server.ServerMessagePackDecoder;
|
||||
import org.pkl.server.ServerMessagePackEncoder;
|
||||
|
||||
public class NativeTransport extends AbstractMessageTransport {
|
||||
private final Consumer<byte[]> sendMessageToNative;
|
||||
|
||||
protected NativeTransport(Logger logger, Consumer<byte[]> sendMessageToNative) {
|
||||
super(logger);
|
||||
this.sendMessageToNative = sendMessageToNative;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStart() {}
|
||||
|
||||
@Override
|
||||
protected void doClose() {}
|
||||
|
||||
@Override
|
||||
protected void doSend(Message message) throws ProtocolException {
|
||||
try (var os = new ByteArrayOutputStream();
|
||||
var packer = MessagePack.newDefaultPacker(os)) {
|
||||
var encoder = new ServerMessagePackEncoder(packer);
|
||||
encoder.encode(message);
|
||||
sendMessageToNative.accept(os.toByteArray());
|
||||
} catch (IOException e) {
|
||||
// impossible; no IO happens during packing
|
||||
throw PklBugException.unreachableCode();
|
||||
} catch (ProtocolException e) {
|
||||
// should never happen; messages coming from Pkl should always be well-formed.
|
||||
log("Unexpected protocol exception: " + e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public void sendMessage(int length, CCharPointer ptr) throws ProtocolException {
|
||||
if (length == 0) {
|
||||
throw new ProtocolException("Unexpected end of input; 0 message bytes");
|
||||
}
|
||||
try (var is = new NativeInputStream(length, ptr);
|
||||
var unpacker = MessagePack.newDefaultUnpacker(is)) {
|
||||
var message = new ServerMessagePackDecoder(unpacker).decode();
|
||||
// guaranteed by `length == 0` check above
|
||||
assert message != null;
|
||||
accept(message);
|
||||
} catch (IOException e) {
|
||||
// impossible; no IO happens during unpacking
|
||||
throw PklBugException.unreachableCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
@NullMarked
|
||||
package org.pkl.core.stdlib.protobuf;
|
||||
package org.pkl.libpkl;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
// test_pkl.c
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "pkl.h"
|
||||
|
||||
// Callback for receiving messages
|
||||
void test_message_handler(unsigned int length, char *message, void *userData) {
|
||||
printf("Received message of length %d\n", length);
|
||||
// Store message in userData for verification
|
||||
if (userData != NULL) {
|
||||
char **stored = (char**) userData;
|
||||
*stored = malloc(length + 1);
|
||||
memcpy(*stored, message, length);
|
||||
(*stored)[length] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
// Test pkl_version
|
||||
void test_version() {
|
||||
const char *version = pkl_version();
|
||||
assert(version != NULL);
|
||||
printf("✓ pkl_version: %s\n", version);
|
||||
}
|
||||
|
||||
// Test pkl_init and pkl_close
|
||||
void test_init_close() {
|
||||
pkl_error_t err = { 0 };
|
||||
pkl_exec_t *exec = NULL;
|
||||
int result = pkl_init(test_message_handler, NULL, &exec, &err);
|
||||
assert(result == 0);
|
||||
assert(exec != NULL);
|
||||
|
||||
result = pkl_close(exec, &err);
|
||||
assert(result == 0);
|
||||
printf("✓ pkl_init/close succeeded\n");
|
||||
}
|
||||
|
||||
// Test error handling
|
||||
void test_null_send_message() {
|
||||
pkl_error_t err = { 0 };
|
||||
int result = pkl_send_message(NULL, 0, NULL, &err);
|
||||
assert(result == -1);
|
||||
|
||||
result = pkl_close(NULL, &err);
|
||||
assert(result == -1);
|
||||
printf("✓ Error handling works\n");
|
||||
}
|
||||
|
||||
// A zero-length message decodes to nothing and must be reported as an error, not silently accepted.
|
||||
void test_empty_message() {
|
||||
pkl_error_t err = { 0 };
|
||||
pkl_exec_t *exec = NULL;
|
||||
assert(pkl_init(test_message_handler, NULL, &exec, &err) == 0);
|
||||
|
||||
char dummy = 0;
|
||||
int result = pkl_send_message(exec, 0, &dummy, &err);
|
||||
assert(result == PKL_ERR_PROTOCOL);
|
||||
|
||||
assert(pkl_close(exec, &err) == 0);
|
||||
printf("✓ Empty message rejected\n");
|
||||
}
|
||||
|
||||
void test_garbage_send_message() {
|
||||
pkl_error_t err = { 0 };
|
||||
pkl_exec_t *exec = NULL;
|
||||
assert(pkl_init(test_message_handler, NULL, &exec, &err) == 0);
|
||||
|
||||
char *message = (char*) (unsigned char[] ) { 0x41, 0x42, 0x43 };
|
||||
int result = pkl_send_message(exec, 3, message, &err);
|
||||
assert(result == PKL_ERR_PROTOCOL);
|
||||
|
||||
result = pkl_close(exec, &err);
|
||||
assert(result == 0);
|
||||
printf("✓ Garbage message becomes protocol exception\n");
|
||||
}
|
||||
|
||||
int main() {
|
||||
printf("Running libpkl C tests...\n\n");
|
||||
|
||||
test_version();
|
||||
test_init_close();
|
||||
test_null_send_message();
|
||||
test_empty_message();
|
||||
test_garbage_send_message();
|
||||
|
||||
printf("\nAll tests passed!\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.libpkl
|
||||
|
||||
import com.sun.jna.Callback
|
||||
import com.sun.jna.Library
|
||||
import com.sun.jna.Native
|
||||
import com.sun.jna.Pointer
|
||||
import com.sun.jna.Structure
|
||||
import com.sun.jna.ptr.PointerByReference
|
||||
|
||||
@Suppress("FunctionName")
|
||||
interface LibPklJNA : Library {
|
||||
companion object {
|
||||
val INSTANCE: LibPklJNA = Native.load("pkl", LibPklJNA::class.java)
|
||||
}
|
||||
|
||||
@Structure.FieldOrder("message")
|
||||
class PklError : Structure() {
|
||||
@JvmField var message: String? = null
|
||||
}
|
||||
|
||||
interface PklMessageResponseHandler : Callback {
|
||||
fun invoke(length: Int, message: Pointer, userData: Pointer?)
|
||||
}
|
||||
|
||||
fun pkl_init(
|
||||
handler: PklMessageResponseHandler,
|
||||
userData: Pointer?,
|
||||
exec: PointerByReference,
|
||||
error: PklError,
|
||||
): Int
|
||||
|
||||
fun pkl_send_message(pexec: Pointer, length: Int, message: ByteArray, error: PklError): Int
|
||||
|
||||
fun pkl_close(pexec: Pointer, error: PklError): Int
|
||||
|
||||
fun pkl_version(): String
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.libpkl
|
||||
|
||||
import com.sun.jna.Pointer
|
||||
import com.sun.jna.ptr.PointerByReference
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.msgpack.core.MessagePack
|
||||
import org.pkl.core.messaging.Message
|
||||
import org.pkl.core.messaging.MessageTransports
|
||||
import org.pkl.server.ServerMessagePackDecoder
|
||||
import org.pkl.server.ServerMessagePackEncoder
|
||||
|
||||
class LibPklMessageTransport : MessageTransports.AbstractMessageTransport({}) {
|
||||
private val messageResponseHandler: LibPklJNA.PklMessageResponseHandler =
|
||||
object : LibPklJNA.PklMessageResponseHandler {
|
||||
override fun invoke(length: Int, message: Pointer, userData: Pointer?) {
|
||||
val message = decode(message.getByteArray(0, length))
|
||||
accept(message)
|
||||
}
|
||||
}
|
||||
|
||||
var pexec: Pointer? = null
|
||||
|
||||
override fun doStart() {
|
||||
val execRef = PointerByReference()
|
||||
val error = LibPklJNA.PklError()
|
||||
val result = LibPklJNA.INSTANCE.pkl_init(messageResponseHandler, Pointer.NULL, execRef, error)
|
||||
assertThat(result).`as` { "Failed to call pkl_init: ${error.message}" }.isEqualTo(0)
|
||||
pexec = execRef.value
|
||||
assertThat(pexec).isNotNull()
|
||||
}
|
||||
|
||||
override fun doClose() {
|
||||
val error = LibPklJNA.PklError()
|
||||
val result = LibPklJNA.INSTANCE.pkl_close(pexec!!, error)
|
||||
assertThat(result).`as` { "Failed to call pkl_close: ${error.message}" }.isEqualTo(0)
|
||||
}
|
||||
|
||||
override fun doSend(message: Message) {
|
||||
val bytes = encode(message)
|
||||
val error = LibPklJNA.PklError()
|
||||
val result = LibPklJNA.INSTANCE.pkl_send_message(pexec!!, bytes.size, bytes, error)
|
||||
assertThat(result).`as` { "Failed to call pkl_send_message: ${error.message}" }.isEqualTo(0)
|
||||
}
|
||||
|
||||
private fun encode(message: Message): ByteArray {
|
||||
val packer = MessagePack.newDefaultBufferPacker()
|
||||
val encoder = ServerMessagePackEncoder(packer)
|
||||
encoder.encode(message)
|
||||
return packer.toByteArray()
|
||||
}
|
||||
|
||||
private fun decode(receivedBytes: ByteArray): Message {
|
||||
val unpacker = MessagePack.newDefaultUnpacker(receivedBytes)
|
||||
return ServerMessagePackDecoder(unpacker).decode()!!
|
||||
}
|
||||
}
|
||||
Loaded 100 of 1184 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user