Compare commits

..
11 Commits
Author SHA1 Message Date
Jen Basch 6a3722a471 Prepare 0.29.1 release 2025-08-27 13:46:31 -07:00
Daniel Chao 2a7195125c Prepare 0.29.1 release (#1191) 2025-08-27 13:32:08 -07:00
Artem Yarmoliuk f0896ba16f Fix shell completion for paths (#1161) 2025-08-26 10:50:08 -07:00
Daniel Chao 9f7997bbc4 Fix missing resources in native pkldoc, and disable test mode (#1175)
This fixes two issues:

1. Test mode is enabled in pkldoc without the ability to turn it off
2. Native pkldoc is missing required resources

This also adds tests for both `jpkldoc` and `pkldoc`.
2025-08-26 10:49:56 -07:00
Daniel Chao 245b53bc8a Add docs for installing via winget (#1171) 2025-08-26 10:49:45 -07:00
Daniel Chao 9a92d75fcb Fix escaping in yaml strings (#1165)
The backslash needs to be escaped when rendering double-quoted YAML strings.

In addition, this escapes the following characters:

* next line (0x85)
* nbsp (0xa0)
2025-08-26 10:49:39 -07:00
Daniel Chao 419127fb0d Fix encoding for mapping with local members (#1152)
Fixes an issue where local members are included in
the mapping entry count.

Also: avoid re-computing Mapping.length
2025-08-26 10:49:20 -07:00
Jen Basch f7951510b9 Correctly handle EOF after unmatched backtick (#1187) 2025-08-25 14:35:12 -07:00
Daniel Chao 1b49ec9422 Fix download links (#1162)
* Snapshot repo has changed
* Fix a bug where pkl-codegen-java download link points to Sonatype instead of GitHub

Not in the PR: we also need to fix the snapshot download location;
but haven't figured out yet what the correct link is.
2025-07-31 08:44:22 -07:00
Dan Chao bdabcea216 Add link to 0.29 release notes 2025-07-24 10:55:05 -07:00
Islon Scherer 5f00f9c82e Prepare 0.29.0 release 2025-07-24 19:07:40 +02:00
3589 changed files with 24257 additions and 230532 deletions

No files matched your search

+168
View File
@@ -0,0 +1,168 @@
//===----------------------------------------------------------------------===//
// Copyright © 2024 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 gets rendered to .circleci/config.yml via git hook.
amends "package://pkg.pkl-lang.org/pkl-project-commons/pkl.impl.circleci@1.2.0#/PklCI.pkl"
import "jobs/BuildNativeJob.pkl"
import "jobs/GradleCheckJob.pkl"
import "jobs/DeployJob.pkl"
import "jobs/SimpleGradleJob.pkl"
local prbJobs: Listing<String> = gradleCheckJobs.keys.toListing()
local buildAndTestJobs = (prbJobs) {
"bench"
"gradle-compatibility"
...buildNativeJobs.keys.filter((it) -> it.endsWith("snapshot"))
}
local releaseJobs = (prbJobs) {
"bench"
"gradle-compatibility"
...buildNativeJobs.keys.filter((it) -> it.endsWith("release"))
}
prb {
jobs = prbJobs
}
main {
jobs {
...buildAndTestJobs
new {
["deploy-snapshot"] {
requires = buildAndTestJobs
context = "pkl-maven-release"
}
}
}
}
release {
jobs {
...releaseJobs
// do GitHub release first because we can overwrite the tag.
// publishing to Maven Central is final.
new {
["github-release"] {
requires = releaseJobs
context = "pkl-github-release"
}
}
new {
["deploy-release"] {
requires { "github-release" }
context = "pkl-maven-release"
}
}
}
}
releaseBranch {
jobs = releaseJobs
}
triggerDocsBuild = "both"
triggerPackageDocsBuild = "release"
local buildNativeJobs: Mapping<String, BuildNativeJob> = new {
for (_dist in List("release", "snapshot")) {
for (_project in List("pkl-cli", "pkl-doc")) {
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-alpine-amd64-\(_dist)"] {
arch = "amd64"
os = "linux"
musl = true
isRelease = _dist == "release"
project = _project
}
["\(_project)-windows-amd64-\(_dist)"] {
arch = "amd64"
os = "windows"
isRelease = _dist == "release"
project = _project
}
}
}
}
local gradleCheckJobs: Mapping<String, GradleCheckJob> = new {
["gradle-check"] {
javaVersion = "21.0"
isRelease = false
os = "linux"
}
["gradle-check-windows"] {
javaVersion = "21.0"
isRelease = false
os = "windows"
}
}
jobs {
for (jobName, job in buildNativeJobs) {
[jobName] = job.job
}
for (jobName, job in gradleCheckJobs) {
[jobName] = job.job
}
["bench"] = new SimpleGradleJob { command = "bench:jmh" }.job
["gradle-compatibility"] = new SimpleGradleJob {
name = "gradle compatibility"
command = #"""
:pkl-gradle:build \
:pkl-gradle:compatibilityTestReleases
"""#
}.job
["deploy-snapshot"] = new DeployJob { command = "publishToSonatype" }.job
["deploy-release"] = new DeployJob {
isRelease = true
command = "publishToSonatype closeAndReleaseSonatypeStagingRepository"
}.job
["github-release"] {
docker {
new { image = "maniator/gh:v2.40.1" }
}
steps {
new AttachWorkspaceStep { at = "." }
new RunStep {
name = "Publish release on GitHub"
command = #"""
# exclude build_artifacts.txt from publish
rm -f */build/executable/*.build_artifacts.txt
find */build/executable/* -type d | xargs rm -rf
gh release create "${CIRCLE_TAG}" \
--title "${CIRCLE_TAG}" \
--target "${CIRCLE_SHA1}" \
--verify-tag \
--notes "Release notes: https://pkl-lang.org/main/current/release-notes/changelog.html#release-${CIRCLE_TAG}" \
--repo "${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}" \
*/build/executable/*
"""#
}
}
}
}
+1632
View File
File diff suppressed because it is too large. Load diff
+192
View File
@@ -0,0 +1,192 @@
//===----------------------------------------------------------------------===//
// Copyright © 2024 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.
//===----------------------------------------------------------------------===//
/// Builds the native `pkl` CLI
extends "GradleJob.pkl"
import "package://pkg.pkl-lang.org/pkl-pantry/com.circleci.v2@1.5.0#/Config.pkl"
/// The architecture to use
arch: "amd64"|"aarch64"
/// Whether to link to musl. Otherwise, links to glibc.
musl: Boolean = false
/// The Gradle project under which to generate the executable
project: String
javaVersion = "21.0"
extraGradleArgs {
when (os == "macOS" && arch == "amd64") {
"-Dpkl.targetArch=\(arch)"
}
when (musl) {
"-Dpkl.musl=true"
}
}
local setupLinuxEnvironment: Config.RunStep =
let (muslVersion = "1.2.2")
let (zlibVersion = "1.2.13")
new {
name = "Set up environment"
shell = "#!/bin/bash -exo pipefail"
command = new Listing {
#"""
sed -ie '/\[ol8_codeready_builder\]/,/^$/s/enabled=0/enabled=1/g' /etc/yum.repos.d/oracle-linux-ol8.repo \
&& microdnf -y install util-linux tree coreutils-single findutils curl tar gzip git zlib-devel gcc-c++ make openssl glibc-langpack-en libstdc++-static \
&& microdnf clean all \
&& rm -rf /var/cache/dnf
# install jdk
curl -Lf \
https://github.com/adoptium/temurin\#(module.majorJdkVersion)-binaries/releases/download/\#(module.jdkGitHubReleaseName)/OpenJDK\#(module.majorJdkVersion)U-jdk_\#(if (arch == "amd64") "x64" else "aarch64")_linux_hotspot_\#(module.jdkVersionAlt).tar.gz -o /tmp/jdk.tar.gz
mkdir /jdk \
&& cd /jdk \
&& cat /tmp/jdk.tar.gz | tar --strip-components=1 -xzC .
mkdir -p ~/staticdeps/bin
cp /usr/lib/gcc/\#(if (arch == "amd64") "x86_64" else "aarch64")-redhat-linux/8/libstdc++.a ~/staticdeps
# install zlib
if [[ ! -f ~/staticdeps/include/zlib.h ]]; then
curl -Lf https://github.com/madler/zlib/releases/download/v\#(zlibVersion)/zlib-\#(zlibVersion).tar.gz -o /tmp/zlib.tar.gz
mkdir -p /tmp/dep_zlib-\#(zlibVersion) \
&& cd /tmp/dep_zlib-\#(zlibVersion) \
&& cat /tmp/zlib.tar.gz | tar --strip-components=1 -xzC . \
&& echo "zlib-\#(zlibVersion): configure..." && ./configure --static --prefix="$HOME"/staticdeps > /dev/null \
&& echo "zlib-\#(zlibVersion): make..." && make -s -j4 \
&& echo "zlib-\#(zlibVersion): make install..." && make -s install \
&& rm -rf /tmp/dep_zlib-\#(zlibVersion)
fi
"""#
// don't need musl on aarch because GraalVM only supports musl builds on x86
when (arch == "amd64") {
#"""
# install musl
if [[ ! -f ~/staticdeps/bin/x86_64-linux-musl-gcc ]]; then
curl -Lf https://musl.libc.org/releases/musl-\#(muslVersion).tar.gz -o /tmp/musl.tar.gz
mkdir -p /tmp/dep_musl-\#(muslVersion) \
&& cd /tmp/dep_musl-\#(muslVersion) \
&& cat /tmp/musl.tar.gz | tar --strip-components=1 -xzC . \
&& echo "musl-\#(muslVersion): configure..." && ./configure --disable-shared --prefix="$HOME"/staticdeps > /dev/null \
&& echo "musl-\#(muslVersion): make..." && make -s -j4 \
&& echo "musl-\#(muslVersion): make install..." && make -s install \
&& rm -rf /tmp/dep_musl-\#(muslVersion)
# native-image expects to find an executable at this path.
ln -s ~/staticdeps/bin/musl-gcc ~/staticdeps/bin/x86_64-linux-musl-gcc
fi
"""#
}
}.join("\n\n")
}
local setupMacEnvironment: Config.RunStep =
new {
name = "Set up environment"
shell = "#!/bin/bash -exo pipefail"
command =
#"""
# install jdk
curl -Lf \
https://github.com/adoptium/temurin\#(module.majorJdkVersion)-binaries/releases/download/\#(module.jdkGitHubReleaseName)/OpenJDK\#(module.majorJdkVersion)U-jdk_\#(if (arch == "amd64") "x64" else "aarch64")_mac_hotspot_\#(module.jdkVersionAlt).tar.gz -o /tmp/jdk.tar.gz
mkdir $HOME/jdk \
&& cd $HOME/jdk \
&& cat /tmp/jdk.tar.gz | tar --strip-components=1 -xzC .
"""#
}
steps {
when (os == "linux") {
new Config.RestoreCacheStep {
name = "Restore static deps from cache"
key = "staticdeps-\(arch)"
}
setupLinuxEnvironment
new Config.SaveCacheStep {
name = "Save statics deps to cache"
key = "staticdeps-\(arch)"
paths {
"~/staticdeps"
}
}
}
when (os == "macOS") {
when (arch == "amd64") {
new Config.RunStep {
name = "Installing Rosetta 2"
command = """
/usr/sbin/softwareupdate --install-rosetta --agree-to-license
"""
}
}
setupMacEnvironment
}
new Config.RunStep {
name = "gradle buildNative"
when (module.os == "windows") {
shell = "bash.exe"
}
command = #"""
export PATH=~/staticdeps/bin:$PATH
./gradlew \#(module.gradleArgs) \#(project):buildNative
"""#
}
new Config.PersistToWorkspaceStep {
root = "."
paths {
"\(project)/build/executable/"
}
}
}
job {
when (os == "macOS") {
macos {
xcode = "15.3.0"
}
resource_class = "m2pro.large"
environment {
["JAVA_HOME"] = "/Users/distiller/jdk/Contents/Home"
}
}
when (os == "linux") {
docker = new Listing<Config.DockerImage> {
new {
image = if (arch == "aarch64") "arm64v8/oraclelinux:8-slim" else "oraclelinux:8-slim"
}
}
environment {
["JAVA_HOME"] = "/jdk"
}
resource_class = if (arch == "aarch64") "arm.xlarge" else "xlarge"
}
when (os == "windows") {
machine {
image = "windows-server-2022-gui:current"
}
resource_class = "windows.large"
environment {
["JAVA_HOME"] = "/jdk"
}
}
}
+43
View File
@@ -0,0 +1,43 @@
//===----------------------------------------------------------------------===//
// Copyright © 2024 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.
//===----------------------------------------------------------------------===//
extends "GradleJob.pkl"
import "package://pkg.pkl-lang.org/pkl-pantry/com.circleci.v2@1.5.0#/Config.pkl"
local self = this
javaVersion = "21.0"
command: String
os = "linux"
steps {
new Config.AttachWorkspaceStep { at = "." }
new Config.RunStep {
command = "./gradlew \(self.gradleArgs) \(module.command)"
}
// add Java executables to workspace so they get published as a GitHub release
new Config.PersistToWorkspaceStep {
root = "."
paths {
"pkl-cli/build/executable/"
"pkl-doc/build/executable/"
"pkl-codegen-java/build/executable/"
"pkl-codegen-kotlin/build/executable/"
}
}
}
+25
View File
@@ -0,0 +1,25 @@
//===----------------------------------------------------------------------===//
// Copyright © 2024 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.
//===----------------------------------------------------------------------===//
extends "GradleJob.pkl"
import "package://pkg.pkl-lang.org/pkl-pantry/com.circleci.v2@1.5.0#/Config.pkl"
steps {
new Config.RunStep {
name = "gradle check"
command = "./gradlew \(module.gradleArgs) check"
}
}
+104
View File
@@ -0,0 +1,104 @@
//===----------------------------------------------------------------------===//
// Copyright © 2024 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.
//===----------------------------------------------------------------------===//
abstract module GradleJob
import "package://pkg.pkl-lang.org/pkl-pantry/com.circleci.v2@1.5.0#/Config.pkl"
import "package://pkg.pkl-lang.org/pkl-pantry/pkl.experimental.uri@1.0.3#/URI.pkl"
/// Whether this is a release build or not.
isRelease: Boolean = false
/// The OS to run on
os: "macOS"|"linux"|"windows"
/// The version of Java to use.
javaVersion: "17.0"|"21.0"
fixed javaVersionFull =
if (javaVersion == "17.0") "17.0.9+9"
else "21.0.5+11"
fixed jdkVersionAlt = javaVersionFull.replaceLast("+", "_")
fixed majorJdkVersion = javaVersionFull.split(".").first
fixed jdkGitHubReleaseName =
let (ver =
// 17.0.9+9 is missing some binaries (see https://github.com/adoptium/adoptium-support/issues/994)
if (javaVersionFull == "17.0.9+9" && os == "windows") "jdk-17.0.9+9.1"
else "jdk-\(javaVersionFull)"
)
URI.encodeComponent(ver)
fixed gradleArgs = new Listing {
"--info"
"--stacktrace"
"-DtestReportsDir=${HOME}/test-results"
"-DpklMultiJdkTesting=true"
when (isRelease) {
"-DreleaseBuild=true"
}
...extraGradleArgs
}.join(" ")
extraGradleArgs: Listing<String>
steps: Listing<Config.Step>
job: Config.Job = new {
environment {
["LANG"] = "en_US.UTF-8"
when (os == "windows") {
["JAVA_HOME"] = "/jdk"
}
}
when (os == "linux") {
docker {
new {
image = "cimg/openjdk:\(javaVersion)"
}
}
}
when (os == "windows") {
machine {
image = "windows-server-2022-gui:current"
}
resource_class = "windows.large"
}
steps {
"checkout"
when (os == "windows") {
new Config.RunStep {
name = "Set up environment"
shell = "bash.exe"
command = #"""
# install jdk
curl -Lf \
https://github.com/adoptium/temurin\#(majorJdkVersion)-binaries/releases/download/\#(jdkGitHubReleaseName)/OpenJDK\#(majorJdkVersion)U-jdk_x64_windows_hotspot_\#(jdkVersionAlt).zip -o /tmp/jdk.zip
unzip /tmp/jdk.zip -d /tmp/jdk \
&& cd /tmp/jdk/jdk-* \
&& mkdir /jdk \
&& cp -r . /jdk
"""#
}
}
...module.steps
new Config.StoreTestResults {
path = "~/test-results"
}
}
}
+35
View File
@@ -0,0 +1,35 @@
//===----------------------------------------------------------------------===//
// Copyright © 2024 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.
//===----------------------------------------------------------------------===//
extends "GradleJob.pkl"
import "package://pkg.pkl-lang.org/pkl-pantry/com.circleci.v2@1.5.0#/Config.pkl"
name: String = command
command: String
os = "linux"
javaVersion = "21.0"
steps {
new Config.RunStep {
name = module.name
command = """
./gradlew \(module.gradleArgs) \(module.command)
"""
}
}
-2
View File
@@ -1,4 +1,2 @@
# Auto-format Kotlin code
816cd483c8adf4c04e14236c173a1dc6bd2579ea
# Format Kotlin code again
2b3603b544bae2ad87374b65afaf8ac018216261
-1
View File
@@ -1,6 +1,5 @@
# linguist-generated would suppress files in diffs
**/src/test/files/** linguist-vendored
.github/workflows/* linguist-generated
/docs/** linguist-documentation
+3 -3
View File
@@ -2,7 +2,7 @@
files=`git diff --cached --name-status`
if [[ $files =~ .github/* ]]; then
pkl eval --project-dir .github/ -m .github .github/index.pkl
git add .github/workflows/
if [[ $files =~ .circleci/config.pkl ]]; then
pkl eval .circleci/config.pkl -o .circleci/config.yml
git add .circleci/config.yml
fi
-10
View File
@@ -1,10 +0,0 @@
amends "pkl:Project"
dependencies {
["pkl.impl.ghactions"] {
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"
}
}
-40
View File
@@ -1,40 +0,0 @@
{
"schemaVersion": 1,
"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.9.0",
"checksums": {
"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.2",
"checksums": {
"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.1",
"checksums": {
"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.9",
"checksums": {
"sha256": "467b91559f31ad7401c78d3df9569b5ec7aa4e1b49209f9f22d02bd72971e0bd"
}
},
"package://pkg.pkl-lang.org/pkl-pantry/com.github.dependabot@1": {
"type": "remote",
"uri": "projectpackage://pkg.pkl-lang.org/pkl-pantry/com.github.dependabot@1.0.4",
"checksums": {
"sha256": "c7391119f946d7761d0ca0cc358ed8fe2bdfc691411087ccac89637bd96fec4a"
}
}
}
}
-25
View File
@@ -1,25 +0,0 @@
version: 2
updates:
- package-ecosystem: gradle
cooldown:
default-days: 7
directory: /
schedule:
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: quarterly
-261
View File
@@ -1,261 +0,0 @@
amends "@pkl.impl.ghactions/PklCI.pkl"
import "@gha/Workflow.pkl"
import "jobs/BuildJavaExecutableJob.pkl"
import "jobs/BuildNativeJob.pkl"
import "jobs/DeployJob.pkl"
import "jobs/GithubRelease.pkl"
import "jobs/GradleJob.pkl"
import "jobs/PklJob.pkl"
import "jobs/SimpleGradleJob.pkl"
triggerDocsBuild = "both"
testReports {
junit {
"**/build/test-results/**/*.xml"
}
html {
"**/build/reports/tests/**/*"
}
excludeJobs {
"bench"
"github-release"
"dependency-submission"
"dependency-review"
Regex("deploy-.*")
}
}
local baseGradleCheck: SimpleGradleJob = new {
isRelease = false
command = "check"
fetchDepth = 0
}
local gradleCheck = (baseGradleCheck) {
os = "linux"
}
local gradleCheckWindows = (baseGradleCheck) {
os = "windows"
}
local typealias PklJobs = Mapping<String, PklJob | *Workflow.Job>
local toWorkflowJobs: (PklJobs) -> Workflow.Jobs = (it) -> new Workflow.Jobs {
for (k, v in it) {
when (v is PklJob) {
[k] = v.job
} else {
[k] = v
}
}
}
local gradleCheckJobs: PklJobs = new {
["gradle-check"] = gradleCheck
["gradle-check-windows"] = gradleCheckWindows
}
local buildNativeJobs: Mapping<String, BuildNativeJob> = new {
for (_dist in List("release", "snapshot")) {
for (_project in List("pkl-cli", "pkl-doc", "libpkl")) {
for (_arch in List("amd64", "aarch64")) {
["\(_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/**/*"
}
}
}
}
local buildNativeSnapshots = buildNativeJobs.toMap().filter((key, _) -> key.endsWith("snapshot"))
local buildNativeReleases = buildNativeJobs.toMap().filter((key, _) -> key.endsWith("release"))
local benchmarkJob: SimpleGradleJob = new { command = "bench:jmh" }
local gradleCompatibilityJob: SimpleGradleJob = new {
command = ":pkl-gradle:build :pkl-gradle:compatibilityTestReleases"
fetchDepth = 0
}
local buildJavaExecutableJob: BuildJavaExecutableJob = new {
fetchDepth = 0
}
local buildAndTestJobs: PklJobs = new {
...gradleCheckJobs
["bench"] = benchmarkJob
["gradle-compatibility"] = gradleCompatibilityJob
["java-executables-snapshot"] = (buildJavaExecutableJob) { isRelease = false }
...buildNativeSnapshots
}
local releaseJobs: PklJobs = new {
...gradleCheckJobs
["bench"] = benchmarkJob
["gradle-compatibility"] = gradleCompatibilityJob
["java-executables-release"] = (buildJavaExecutableJob) { isRelease = true }
...buildNativeReleases
}
// By default, just run ./gradlew check on linux.
// Trigger other checks based on GitHub PR description. Examples:
//
// * [windows] -- Test on Windows
// * [native] -- Test all native builds
// * [native-pkl-cli] -- Test all pkl-cli os/arch pairs
// * [native-pkl-cli-macos] -- Test pkl-cli on macOS
prb {
local prbJobs: Mapping<String, GradleJob> = new {
["gradle-check"] = gradleCheck
["gradle-check-windows"] = (gradleCheckWindows) {
`if` = "contains(github.event.pull_request.body, '[windows]')"
}
for (jobName, job in buildNativeSnapshots) {
[jobName] = (job) {
local tags = new Listing {
"[native]"
"[native-\(job.project)]"
"[native-\(job.project)-\(job.os)]"
"[native-\(job.project)-\(job.os)-\(job.arch)]"
"[native-\(job.project)-\(job.os)-\(job.arch)]"
when (job.musl) {
"[native-\(job.project)-alpine-\(job.os)-\(job.arch)]"
}
}
`if` =
tags
.toList()
.map((it) -> "contains(github.event.pull_request.body, '\(it)')")
.join(" || ")
}
}
}
local prbJobs2 = (prbJobs) {
[[true]] {
// better SLA when not running on nightly
nightlyMacOS = false
}
}
jobs = prbJobs2 |> toWorkflowJobs
}
build {
jobs = buildAndTestJobs |> toWorkflowJobs
}
main {
jobs =
(buildAndTestJobs) {
["deploy-snapshot"] = (
new DeployJob {
extraGradleArgs {
"--no-parallel"
}
command = "publishToSonatype"
}
) {
needs = buildAndTestJobs.keys.toListing()
}
["dependency-submission"] {
`runs-on` = "ubuntu-latest"
permissions {
contents = "write"
}
steps {
module.catalog.`actions/checkout@v6`
(module.catalog.`actions/setup-java@v5`) {
with {
`java-version` = "25"
distribution = "temurin"
}
}
(module.catalog.`gradle/actions/dependency-submission@v6`) {
with {
// language=regexp
`dependency-graph-include-configurations` =
".*[rR]untimeClasspath|.*[cC]ompileClasspath"
}
}
}
}
} |> toWorkflowJobs
}
releaseBranch {
jobs = releaseJobs |> toWorkflowJobs
}
release {
jobs =
(releaseJobs) {
["deploy-release"] = (
new DeployJob {
isRelease = true
command = "publishToSonatype closeAndReleaseSonatypeStagingRepository"
}
) {
needs = releaseJobs.keys.toListing()
}
["github-release"] = new GithubRelease {
needs = releaseJobs.keys.toListing()
}
} |> toWorkflowJobs
}
dependabot {
updates {
new {
`package-ecosystem` = "gradle"
schedule {
interval = "quarterly"
}
cooldown {
`default-days` = 7
}
directory = "/"
}
}
}
codeql {
scans {
new {
language = "java-kotlin"
buildMode = "autobuild"
}
new {
language = "javascript-typescript"
buildMode = "none"
}
}
}
-31
View File
@@ -1,31 +0,0 @@
extends "GradleJob.pkl"
import "@gha/catalog.pkl"
// Keep this in sync with projects that build java executables
local projects: List<String> = List("pkl-doc", "pkl-cli", "pkl-codegen-java", "pkl-codegen-kotlin")
local command =
new Listing<String> {
"./gradlew"
module.gradleArgs
for (project in projects) {
// NOTE: `build` doesn't build native executables
"\(project):build"
}
}.join(" ")
steps {
new {
name = "gradle build java executables"
shell = "bash"
run = command
}
(catalog.`actions/upload-artifact@v5`) {
name = "Upload executable artifacts"
with {
name = "deploy-artifacts-java-executables"
path = "*/build/executable/**/*"
}
}
}
-92
View File
@@ -1,92 +0,0 @@
extends "GradleJob.pkl"
import "@gha/catalog.pkl"
/// Whether to link to musl. Otherwise, links to glibc.
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 (musl) {
"-Dpkl.musl=true"
}
}
preSteps {
when (os == "linux" && !musl) {
new {
name = "Install deps"
run =
"dnf install -y git binutils gcc glibc-devel zlib-devel libstdc++-static glibc-langpack-en"
}
}
}
steps {
when (musl) {
new {
name = "Install musl and zlib"
run = read("../scripts/install_musl.sh").text
}
}
// workaround for https://github.com/actions/checkout/issues/1048
when (os == "linux" && !musl) {
new {
name = "Fix git ownership"
// language=bash
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 built artifacts"
with {
name =
if (musl)
"deploy-artifacts-\(project)-alpine-\(module.os)-\(module.arch)"
else
"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 = 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/**/*"
}
}
}
fixed job {
when (os == "linux" && !musl) {
container {
image = "redhat/ubi8:8.10"
}
}
}
-31
View File
@@ -1,31 +0,0 @@
extends "GradleJob.pkl"
import "@gha/catalog.pkl"
import "@gha/Workflow.pkl"
import "@pkl.impl.ghactions/helpers.pkl"
local self = this
command: String
arch = "amd64"
os = "linux"
steps {
catalog.`actions/checkout@v6`
(catalog.`actions/download-artifact@v6`) {
with {
pattern = "deploy-artifacts-**"
`merge-multiple` = true
}
}
new Workflow.Step {
run = "./gradlew \(self.gradleArgs) \(module.command)"
}
|> helpers.withMavenPublishSecrets
}
fixed job {
environment = "maven-release"
}
-42
View File
@@ -1,42 +0,0 @@
module GithubRelease
extends "PklJob.pkl"
import "@gha/catalog.pkl"
import "@gha/context.pkl"
fixed job {
`runs-on` = "ubuntu-latest"
permissions {
contents = "write"
}
needs = module.needs
steps {
(catalog.`actions/download-artifact@v6`) {
with {
pattern = "deploy-artifacts-**"
`merge-multiple` = true
}
}
new {
name = "Publish release on GitHub"
env {
["GH_TOKEN"] = context.github.token
["TAG_NAME"] = context.github.refName
["GIT_SHA"] = context.github.sha
["GH_REPO"] = context.github.repository
}
// language=bash
run =
#"""
gh release create ${TAG_NAME} \
--draft \
--title "${TAG_NAME}" \
--target "${GIT_SHA}" \
--verify-tag \
--notes "Release notes: https://pkl-lang.org/main/current/release-notes/changelog.html#release-${TAG_NAME}" \
*/build/executable/*
"""#
}
}
}
-120
View File
@@ -1,120 +0,0 @@
abstract module GradleJob
extends "PklJob.pkl"
import "@gha/Workflow.pkl"
import "@gha/context.pkl"
import "@pkl.impl.ghactions/catalog.pkl"
/// Whether this is a release build or not.
isRelease: Boolean = false
/// The architecture to use
arch: "amd64" | "aarch64" = "amd64"
/// The OS to run on.
os: "macOS" | "linux" | "windows" = "linux"
// TODO flip this to `true` when nightly macOS is available
/// Whether to run on nightly macOS.
nightlyMacOS: Boolean(implies(os == "macOS")) = false
extraGradleArgs: Listing<String>
steps: Listing<Workflow.Step>
preSteps: Listing<Workflow.Step>
/// The fetch depth to use when doing a git checkout.
fetchDepth: Int?
fixed gradleArgs =
new Listing {
"$DEBUG_ARGS"
"--no-daemon"
"-DpklMultiJdkTesting=true"
when (isRelease) {
"-DreleaseBuild=true"
}
...extraGradleArgs
}.join(" ")
fixed job {
env {
["LANG"] = "en_US.UTF-8"
when (os == "windows") {
["JAVA_HOME"] = "/jdk"
}
}
when (os == "macOS") {
`if` =
let (cond = "github.repository_owner == 'apple'")
if (super.`if` != null)
"(\(super.`if`)) && \(cond)"
else
cond
}
`runs-on` =
if (os == "linux" && arch == "amd64")
"ubuntu-latest"
else if (os == "linux" && arch == "aarch64")
"ubuntu-24.04-arm"
else if (os == "windows")
"windows-latest"
else
new Listing {
"self-hosted"
"macos"
// Currently have a bug where macOS runners have `cc` from XCode 26 toolchain but have
// default SDK set to 27.
// TODO remove me once this issue is fixed.
"tahoe"
when (nightlyMacOS) {
"nightly"
}
}
steps {
...preSteps
// full checkout (needed for spotless)
(catalog.`actions/checkout@v6`) {
when (fetchDepth != null) {
with {
`fetch-depth` = fetchDepth
}
}
}
(catalog.`actions/setup-java@v5`) {
with {
`java-version` = "25"
distribution = "temurin"
architecture =
if (arch == "amd64")
"x64"
else
"aarch64"
}
}
(catalog.`gradle/actions/setup-gradle@v5`) {
when (isRelease) {
with {
`cache-disabled` = true
}
}
}
new {
name = "Set DEBUG_ARGS env var"
env {
["RUNNER_DEBUG"] = context.runner.debug
}
shell = "bash"
// language=bash
run =
"""
if [[ "$RUNNER_DEBUG" -eq 1 ]]; then
echo "DEBUG_ARGS=--info --stacktrace" >> $GITHUB_ENV
fi
"""
}
...module.steps
}
}
-29
View File
@@ -1,29 +0,0 @@
abstract module PklJob
extends "@pkl.impl.ghactions/jobs/PklJob.pkl"
/// Identify any jobs that must complete successfully before this job will run.
///
/// It can be a string or array of strings.
/// If a job fails or is skipped, all jobs that need it are skipped unless the jobs use a conditional expression that
/// causes the job to continue.
/// If a run contains a series of jobs that need each other, a failure or skip applies to all jobs in the dependency
/// chain from the point of failure or skip onwards. If you would like a job to run even if a job it is dependent on
/// did not succeed, use the `always()` conditional expression in `jobs.<job_id>.if`.
///
/// See: <https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idneeds>
needs: (String | *Listing<String>)?
/// A conditional to prevent a job from running unless a condition is met.
///
/// You can use any supported context and expression to create a conditional.
/// For more information on which contexts are supported in this key, see
/// [Contexts reference](https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#context-availability).
@SourceCode { language = "GithubExpressionLanguage" }
`if`: String?
/// The underlying workflow job
fixed job {
`if` = module.`if`
needs = module.needs
}
-18
View File
@@ -1,18 +0,0 @@
extends "GradleJob.pkl"
name: String = command
command: String
os = "linux"
steps {
new {
name = module.name
shell = "bash"
run =
"""
./gradlew \(module.gradleArgs) \(module.command)
"""
}
}
-75
View File
@@ -1,75 +0,0 @@
set -e
mkdir -p ~/staticdeps/
ZLIB_VERSION="1.2.13"
MUSL_VERSION="9fa28ece75d8a2191de7c5bb53bed224c5947417" # v1.2.6
# install zlib
if [[ ! -f ~/staticdeps/include/zlib.h ]]; then
# Download zlib tarball and signature
curl -Lf "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz" -o /tmp/zlib.tar.gz
curl -Lf "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz.asc" -o /tmp/zlib.tar.gz.asc
# Import zlib GPG key
gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 5ED46A6721D365587791E2AA783FCD8E58BCAFBA
# Verify GPG signature
echo "Verifying zlib GPG signature..."
gpg --verify /tmp/zlib.tar.gz.asc /tmp/zlib.tar.gz
mkdir -p "/tmp/dep_zlib-${ZLIB_VERSION}"
cd "/tmp/dep_zlib-${ZLIB_VERSION}"
# shellcheck disable=SC2002
cat /tmp/zlib.tar.gz | tar --strip-components=1 -xzC .
echo "zlib-${ZLIB_VERSION}: configure..."
./configure --static --prefix="$HOME"/staticdeps > /dev/null
echo "zlib-${ZLIB_VERSION}: make..."
make -s -j4
echo "zlib-${ZLIB_VERSION}: make install..."
make -s install
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 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
echo "musl-${MUSL_VERSION}: make..."
make -s -j4
echo "musl-${MUSL_VERSION}: make install..."
make -s install
rm -rf "/tmp/dep_musl-${MUSL_VERSION}"
# native-image expects to find an executable at this path.
ln -s ~/staticdeps/bin/musl-gcc ~/staticdeps/bin/x86_64-linux-musl-gcc
fi
echo "${HOME}/staticdeps/bin" >> "$GITHUB_PATH"
-166
View File
@@ -1,166 +0,0 @@
>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
-177
View File
@@ -1,177 +0,0 @@
>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
-319
View File
@@ -1,319 +0,0 @@
>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
-42
View File
@@ -1,42 +0,0 @@
#file: noinspection MandatoryParamsAbsent,UndefinedAction
# This is a fake workflow that never runs.
# It's used to pin actions to specific git SHAs when generating actual workflows.
# It also gets updated by dependabot (see .github/dependabot.yml).
# Generated from Workflow.pkl. DO NOT EDIT.
name: __lockfile__
'on':
push:
branches-ignore:
- '**'
tags-ignore:
- '**'
permissions: {}
jobs:
locks:
if: 'false'
runs-on: nothing
steps:
- name: EnricoMi/publish-unit-test-result-action@v2
uses: EnricoMi/publish-unit-test-result-action@d0a4676d0e0b938bc201470d88276b7c74c712b3 # v2
- name: actions/checkout@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@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@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4
- name: github/codeql-action/init@v4
uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4
- name: gradle/actions/dependency-submission@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
File diff suppressed because it is too large. Load diff
-62
View File
@@ -1,62 +0,0 @@
# Generated from Workflow.pkl. DO NOT EDIT.
'on':
pull_request:
branches:
- main
push:
branches:
- main
schedule:
- cron: 29 17 * * 4
jobs:
analyze-actions:
name: Analyze (actions)
permissions:
security-events: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false
- uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4
with:
languages: actions
build-mode: none
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4
with:
category: /language:actions
analyze-java-kotlin:
name: Analyze (java-kotlin)
permissions:
security-events: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false
- uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4
with:
languages: java-kotlin
build-mode: autobuild
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4
with:
category: /language:java-kotlin
analyze-javascript-typescript:
name: Analyze (javascript-typescript)
permissions:
security-events: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false
- uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4
with:
languages: javascript-typescript
build-mode: none
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4
with:
category: /language:javascript-typescript
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
-34
View File
@@ -1,34 +0,0 @@
# Generated from Workflow.pkl. DO NOT EDIT.
name: PR Test Reports
'on':
workflow_run:
types:
- completed
workflows:
- Pull Request
permissions:
contents: read
jobs:
test-results:
name: Test Results
if: github.event.workflow_run.conclusion == 'success' || github.event.workflow_run.conclusion == 'failure'
permissions:
actions: read
checks: write
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: dawidd6/action-download-artifact@ac66b43f0e6a346234dd65d4d0c8fbb31cb316e5 # v11
with:
path: artifacts
name: test-results-.*
name_is_regexp: true
run_id: ${{ github.event.workflow_run.id }}
- name: Publish test results
uses: EnricoMi/publish-unit-test-result-action@d0a4676d0e0b938bc201470d88276b7c74c712b3 # v2
with:
commit: ${{ github.event.workflow_run.head_sha }}
comment_mode: 'off'
files: artifacts/**/*.xml
event_file: artifacts/test-results-event-file/event.json
event_name: ${{ github.event.workflow_run.event }}
-1
View File
@@ -15,7 +15,6 @@ testgenerated/
!.idea/runConfigurations/
!.idea/scopes/
!.idea/vcs.xml
.intellijPlatform/
.vscode/
+10 -1
View File
@@ -10,6 +10,16 @@
<option name="INSERT_INNER_CLASS_IMPORTS" value="true" />
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
<value />
</option>
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="" withSubpackages="true" static="true" />
<emptyLine />
<package name="" withSubpackages="true" static="false" />
</value>
</option>
<option name="RIGHT_MARGIN" value="100" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
@@ -53,7 +63,6 @@
</option>
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="" withSubpackages="true" static="false" module="true" />
<package name="" withSubpackages="true" static="true" />
<emptyLine />
<package name="" withSubpackages="true" static="false" />
+1 -35
View File
@@ -12,35 +12,6 @@
<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>
<RegExpInspectionConfiguration>
<option name="name" value="PklCliDirectProjectEvaluatorSettingsAccess" />
<option name="suppressId" value="PklCliDirectProjectEvaluatorSettingsAccess" />
<option name="uuid" value="dd497f47-d38f-3fab-9ed7-eabe699620c8" />
<option name="patterns">
<list>
<InspectionPattern>
<option name="regExp" value="project\?\.evaluatorSettings" />
<option name="_fileType" value="Kotlin" />
<option name="searchContext" value="ANY" />
<option name="replacement" value="evaluatorSettings" />
</InspectionPattern>
<InspectionPattern>
<option name="regExp" value="project\?\.resolvedEvaluatorSettings" />
<option name="_fileType" value="Kotlin" />
<option name="searchContext" value="ANY" />
<option name="replacement" value="evaluatorSettings" />
</InspectionPattern>
</list>
</option>
</RegExpInspectionConfiguration>
</list>
</option>
</inspection_tool>
<inspection_tool class="FieldAccessNotGuarded" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="FieldMayBeFinal" enabled="true" level="INFORMATION" enabled_by_default="true">
<scope name="AllExceptTruffleAst" level="WARNING" enabled="true" />
</inspection_tool>
@@ -97,15 +68,10 @@
<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" />
<option name="processComments" value="true" />
</inspection_tool>
<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>
-24
View File
@@ -1,24 +0,0 @@
# 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)
+77
View File
@@ -0,0 +1,77 @@
## 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 -20
View File
@@ -13,11 +13,11 @@ To import the project into IntelliJ, go to File->Open and select the project's r
If the project is opened but not imported, look for a popup in the lower right corner
and click its "Import Gradle Project" link.
. (recommended) Install {uri-gng}[gng] +
_gng_ enables you to run Gradle commands with `gw` (instead of `./gradlew`) from any subdirectory.
_gng_ enables to run Gradle commands with `gw` (instead of `./gradlew`) from any subdirectory.
. (recommended) Set up Git ignore-revs +
`git config blame.ignoreRevsFile .git-blame-ignore-revs`
. (recommended) Install {uri-jenv}[jenv] and plugins +
_jenv_ uses specific JDK versions in certain subdirectories. _Pkl_ comes with a `.java-version` file specifying JDK 21. +
_jenv_ use specific JDK versions in certain subdirectories. _Pkl_ comes with a `.java-version` file specifying JDK 21. +
Enable _jenv_ plugins for better handling by `gradle`:
+
[source,shell]
@@ -53,6 +53,7 @@ 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
@@ -73,7 +74,9 @@ gw wrapper --gradle-version [version] --gradle-distribution-sha256-sum [sha]
. (optional) Update _gradle/libs.version.toml_
based on version information from https://search.maven.org, https://plugins.gradle.org, and GitHub repos
. Run `gw updateDependencyLocks`
. Validate changes with `gw build buildNative`
. Review and commit the updated dependency lock files
== Code Generation
@@ -87,35 +90,20 @@ This will listen on port 5005.
Example: `./gradlew test -Djvmdebug=true`
== Snippet Test Plugin
There is an IntelliJ plugin meant for development on the Pkl project itself located in https://github.com/apple/pkl-project-commons[pkl-project-commons].
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.
For automated build setup examples see our https://github.com/apple/pkl/blob/main/.circleci/[CircleCI] jobs like our https://github.com/apple/pkl/blob/main/.circleci/jobs/BuildNativeJob.pkl[BuildNativeJob.pkl], where we build Pkl automatically.
=== Truffle
* http://ssw.jku.at/Research/Projects/JVM/Truffle.html[Homepage]
* https://github.com/graalvm/truffle[GitHub]
* http://lafo.ssw.uni-linz.ac.at/javadoc/truffle/latest/[Javadoc]
* https://mail.openjdk.org/pipermail/graal-dev/[Mailing List]
* http://mail.openjdk.java.net/pipermail/graal-dev/[Mailing List]
* https://medium.com/@octskyward/graal-truffle-134d8f28fb69#.2db370y2g[Graal & Truffle (Article)]
* https://comserv.cs.ut.ee/home/files/Pool_ComputerScience_2016.pdf?study=ATILoputoo&reference=6319668E7151D556131810BC3F4A627D7FEF5F3B[Truffle Overview (see chapter 1)]
* https://gist.github.com/smarr/d1f8f2101b5cc8e14e12[Truffle: Languages and Material]
* https://github.com/smarr/truffle-notes[Truffle Notes]
* https://www.graalvm.org/latest/graalvm-as-a-platform/language-implementation-framework/[Truffle Language Implementation Framework]
* https://wiki.openjdk.java.net/display/Graal/Truffle+FAQ+and+Guidelines[Truffle FAQ]
=== Other Config Languages
-1
View File
@@ -8,5 +8,4 @@ See link:CONTRIBUTING.adoc[] for general contribution guidelines.
* https://github.com/bioball[Daniel Chao]
* https://github.com/stackoverflow[Islon Scherer]
* https://github.com/HT154[Jen Basch]
* https://github.com/holzensp[Philip Hölzenspies]
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright © 2024-2026 Apple Inc. and the Pkl project authors
Copyright © 2024-2025 Apple Inc. and the Pkl project authors
Portions of this software were originally based on 'SnakeYAML' developed by Andrey Somov.
+6 -15
View File
@@ -12,7 +12,7 @@
:uri-installation: https://pkl-lang.org/main/current/pkl-cli/index.html#installation
:uri-lang-reference: https://pkl-lang.org/main/current/language-reference/index.html
:uri-ci-artifacts: https://s01.oss.sonatype.org/content/groups/public/org/pkl-lang/
:uri-ci-pipeline: https://github.com/apple/pkl/actions
:uri-ci-pipeline: https://app.circleci.com/pipelines/github/apple/pkl
A configuration as code language with rich validation and tooling.
@@ -37,10 +37,7 @@ We'd love to hear from you!
* Create an {uri-github-issue}[issue]
* Ask a question on {uri-github-discussions}[GitHub Discussions]
== Development
image:https://github.com/apple/pkl/actions/workflows/main.yml/badge.svg?style=svg["Build (main)", link="https://github.com/apple/pkl/actions/workflows/main.yml"]
== Development image:https://circleci.com/gh/apple/pkl.svg?style=svg["Apple", link="https://app.circleci.com/pipelines/github/apple/pkl"]
* link:CONTRIBUTING.adoc[] for tips on pull requests and filing issues
* link:DEVELOPMENT.adoc[] for build instructions
* {uri-ci-artifacts}[Sonatype Repository] for the artifacts/binaries built by our {uri-ci-pipeline}[CI pipelines] (and those of our other tools and packages repositories).
@@ -63,9 +60,6 @@ image:https://github.com/apple/pkl/actions/workflows/main.yml/badge.svg?style=sv
|https://github.com/apple/pkl-go-examples[`apple/pkl-go-examples`]
|Examples for using Pkl within Go applications
|https://github.com/apple/highlightjs-pkl[`apple/highlightjs-pkl`]
|Highlight.js syntax highlighting for Pkl
|https://github.com/apple/pkl-intellij[`apple/pkl-intellij`]
|JetBrains editor plugins providing Pkl language support
@@ -82,7 +76,7 @@ image:https://github.com/apple/pkl/actions/workflows/main.yml/badge.svg?style=sv
|The pkl-lang.org website
|https://github.com/apple/pkl-lsp[`apple/pkl-lsp`]
|Language server for Pkl, implementing the server-side of the Language Server Protocol
| Language server for Pkl, implementing the server-side of the Language Server Protocol
|https://github.com/apple/pkl-neovim[`apple/pkl-neovim`]
|Pkl language support for Neovim
@@ -96,9 +90,6 @@ image:https://github.com/apple/pkl/actions/workflows/main.yml/badge.svg?style=sv
|https://github.com/apple/pkl-project-commons[`apple/pkl-project-commons`]
|Utility libraries for Pkl
|https://github.com/apple/pkl-readers[`apple/pkl-readers`]
|Shared Pkl https://pkl-lang.org/main/current/language-reference/index.html#external-readers[external reader] tools
|https://github.com/apple/pkl-spring[`apple/pkl-spring`]
|Spring Boot extension for configuring Boot apps with Pkl
@@ -114,9 +105,9 @@ image:https://github.com/apple/pkl/actions/workflows/main.yml/badge.svg?style=sv
|https://github.com/apple/pkl.tmbundle[`apple/pkl.tmbundle`]
|TextMate bundle for Pkl
|https://github.com/apple/rules_pkl[`apple/rules_pkl`]
| Bazel build rules for Pkl
|https://github.com/apple/tree-sitter-pkl[`apple/tree-sitter-pkl`]
|Tree-sitter parser for Pkl
|https://github.com/apple/rules_pkl[`apple/rules_pkl`]
|Bazel build rules for Pkl
|===
-98
View File
@@ -363,101 +363,3 @@ 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.
+9 -19
View File
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 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.
@@ -14,13 +14,13 @@
* limitations under the License.
*/
plugins {
id("pklAllProjects")
id("pklJavaLibrary")
pklAllProjects
pklJavaLibrary
id("me.champeau.jmh")
}
val truffle: Configuration = configurations.create("truffle")
val graal: Configuration = configurations.create("graal")
val truffle: Configuration by configurations.creating
val graal: Configuration by configurations.creating
dependencies {
jmh(projects.pklCore)
@@ -31,17 +31,13 @@ dependencies {
}
jmh {
// includes = listOf("ParserBenchmark")
// include = ["fib_class_java"]
// include = ["fib_class_constrained1", "fib_class_constrained2"]
jmhVersion.set(libs.versions.jmh)
// jvmArgsAppend = "-Dgraal.TruffleCompilationExceptionsAreFatal=true " +
// "-Dgraal.Dump=Truffle,TruffleTree -Dgraal.TraceTruffleCompilation=true " +
// "-Dgraal.TruffleFunctionInlining=false"
val graalVm =
when (buildInfo.arch) {
Target.Arch.AARCH64 -> buildInfo.graalVmAarch64
Target.Arch.AMD64 -> buildInfo.graalVmAmd64
}
jvm.set("${graalVm.baseDir}/bin/java")
jvm.set("${buildInfo.graalVmAmd64.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(
@@ -55,13 +51,7 @@ jmh {
// synchronizeIterations = false
}
tasks.named("jmh") {
if (buildInfo.arch == Target.Arch.AARCH64) {
dependsOn(":installGraalVmAarch64")
} else if (buildInfo.arch == Target.Arch.AMD64) {
dependsOn(":installGraalVmAmd64")
}
}
tasks.named("jmh") { 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
+54
View File
@@ -0,0 +1,54 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
net.bytebuddy:byte-buddy:1.15.11=jmh,jmhRuntimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.sf.jopt-simple:jopt-simple:5.0.4=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath
org.apache.commons:commons-math3:3.6.1=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath
org.apiguardian:apiguardian-api:1.1.2=jmhCompileClasspath,jmhImplementationDependenciesMetadata,testCompileClasspath,testImplementationDependenciesMetadata
org.assertj:assertj-core:3.27.3=jmh,jmhRuntimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.graalvm.compiler:compiler:24.1.2=graal
org.graalvm.polyglot:polyglot:24.1.2=jmh,jmhRuntimeClasspath,truffle
org.graalvm.sdk:collections:24.1.2=graal,jmh,jmhRuntimeClasspath,truffle
org.graalvm.sdk:graal-sdk:24.1.2=jmh,jmhRuntimeClasspath
org.graalvm.sdk:nativeimage:24.1.2=jmh,jmhRuntimeClasspath,truffle
org.graalvm.sdk:word:24.1.2=graal,jmh,jmhRuntimeClasspath,truffle
org.graalvm.truffle:truffle-api:24.1.2=jmh,jmhRuntimeClasspath,truffle
org.graalvm.truffle:truffle-compiler:24.1.2=graal
org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-build-common:2.0.21=kotlinBuildToolsApiClasspath
org.jetbrains.kotlin:kotlin-build-tools-api:2.0.21=kotlinBuildToolsApiClasspath
org.jetbrains.kotlin:kotlin-build-tools-impl:2.0.21=kotlinBuildToolsApiClasspath
org.jetbrains.kotlin:kotlin-compiler-embeddable:2.0.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-compiler-runner:2.0.21=kotlinBuildToolsApiClasspath
org.jetbrains.kotlin:kotlin-daemon-client:2.0.21=kotlinBuildToolsApiClasspath
org.jetbrains.kotlin:kotlin-daemon-embeddable:2.0.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:2.0.21=kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-native-prebuilt:2.0.21=kotlinNativeBundleConfiguration
org.jetbrains.kotlin:kotlin-reflect:2.0.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-script-runtime:2.0.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathJmh,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-scripting-common:2.0.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathJmh,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:2.0.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathJmh,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:2.0.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathJmh,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
org.jetbrains.kotlin:kotlin-scripting-jvm:2.0.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathJmh,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.0.21=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.21=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib:2.0.21=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathJmh,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.4=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.jetbrains:annotations:13.0=jmh,jmhCompileClasspath,jmhRuntimeClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathJmh,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-api:5.13.3=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:5.13.3=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-params:5.13.3=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.junit.platform:junit-platform-commons:1.13.3=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.junit.platform:junit-platform-engine:1.13.3=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-launcher:1.13.3=testRuntimeClasspath
org.junit:junit-bom:5.13.3=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.msgpack:msgpack-core:0.9.8=jmh,jmhRuntimeClasspath
org.openjdk.jmh:jmh-core:1.37=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath
org.openjdk.jmh:jmh-generator-asm:1.37=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath
org.openjdk.jmh:jmh-generator-bytecode:1.37=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath
org.openjdk.jmh:jmh-generator-reflection:1.37=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath
org.opentest4j:opentest4j:1.3.0=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.organicdesign:Paguro:3.10.3=jmh,jmhRuntimeClasspath
org.ow2.asm:asm:9.0=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath
org.snakeyaml:snakeyaml-engine:2.10=jmh,jmhRuntimeClasspath
empty=annotationProcessor,apiDependenciesMetadata,compileClasspath,compileOnlyDependenciesMetadata,implementationDependenciesMetadata,intransitiveDependenciesMetadata,jmhAnnotationProcessor,jmhApiDependenciesMetadata,jmhCompileOnlyDependenciesMetadata,jmhIntransitiveDependenciesMetadata,jmhKotlinScriptDefExtensions,kotlinCompilerPluginClasspath,kotlinNativeCompilerPluginClasspath,kotlinScriptDefExtensions,runtimeClasspath,sourcesJar,testAnnotationProcessor,testApiDependenciesMetadata,testCompileOnlyDependenciesMetadata,testIntransitiveDependenciesMetadata,testKotlinScriptDefExtensions
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024 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.
@@ -23,7 +23,6 @@ import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.util.TempFile;
import org.openjdk.jmh.util.TempFileManager;
import org.pkl.core.evaluatorSettings.TraceMode;
import org.pkl.core.http.HttpClient;
import org.pkl.core.module.ModuleKeyFactories;
import org.pkl.core.repl.ReplRequest;
@@ -52,8 +51,7 @@ public class ListSort {
null,
IoUtils.getCurrentWorkingDir(),
StackFrameTransformers.defaultTransformer,
false,
TraceMode.COMPACT);
false);
private static final List<Object> list = new ArrayList<>(100000);
static {
@@ -1,111 +0,0 @@
/*
* 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+/")
}
-89
View File
@@ -1,89 +0,0 @@
/*
* 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)
}
}
}
-410
View File
@@ -1,410 +0,0 @@
/*
* 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())
}
}
@@ -1,76 +0,0 @@
/*
* 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 groovy.util.Node
import groovy.xml.XmlParser
import groovy.xml.XmlUtil
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
abstract class ConfigureLateInitAnnotation : DefaultTask() {
private val miscXmlFile = project.rootProject.file(".idea/misc.xml")
init {
inputs.file(miscXmlFile)
outputs.file(miscXmlFile)
}
@TaskAction
fun run() {
val annotationName = "org.pkl.core.util.LateInit"
if (!miscXmlFile.exists()) {
miscXmlFile.writeText(
"""
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
</project>
"""
.trimIndent()
.trim()
)
}
val root = XmlParser().parse(miscXmlFile)
fun Node.childNodes() = children().filterIsInstance<Node>()
var entryPointsManager =
root.childNodes().find {
it.name() == "component" && it.attribute("name") == "EntryPointsManager"
}
if (entryPointsManager == null) {
entryPointsManager = root.appendNode("component", mapOf("name" to "EntryPointsManager"))
}
var writeAnnotations = entryPointsManager.childNodes().find { it.name() == "writeAnnotations" }
if (writeAnnotations == null) {
writeAnnotations = entryPointsManager.appendNode("writeAnnotations")
}
val alreadyExists =
writeAnnotations.childNodes().any {
it.name() == "writeAnnotation" && it.attribute("name") == annotationName
}
if (!alreadyExists) {
writeAnnotations.appendNode("writeAnnotation", mapOf("name" to annotationName))
miscXmlFile.writeText(XmlUtil.serialize(root))
logger.lifecycle("Updated .idea/misc.xml")
} else {
logger.info("$annotationName is already configured in .idea/misc.xml")
}
}
}
@@ -1,150 +0,0 @@
/*
* 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.nio.file.AtomicMoveNotSupportedException
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import java.util.*
import java.util.zip.ZipInputStream
import javax.inject.Inject
import kotlin.io.path.createDirectories
import org.gradle.api.DefaultTask
import org.gradle.api.internal.file.FileOperations
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import org.gradle.process.ExecOperations
abstract class InstallGraalVm
@Inject
constructor(
private val fileOperations: FileOperations,
private val execOperations: ExecOperations,
) : DefaultTask() {
@get:Input abstract val graalVm: Property<BuildInfo.GraalVm>
init {
@Suppress("LeakingThis") onlyIf("GraalVM not installed") { !graalVm.get().installDir.exists() }
}
@TaskAction
@Suppress("unused")
fun run() {
// minimize chance of corruption by extract-to-random-dir-and-publish
val distroDir = Paths.get(graalVm.get().homeDir, UUID.randomUUID().toString())
try {
distroDir.createDirectories()
println("Extracting ${graalVm.get().downloadFile} into $distroDir")
val os = org.gradle.internal.os.OperatingSystem.current()
if (os.isWindows) extractZip(distroDir) else extractTarGz(distroDir)
val distroBinDir =
if (os.isMacOsX) distroDir.resolve("Contents/Home/bin") else distroDir.resolve("bin")
println("Installing native-image into $distroDir")
val gvmVersionMajor =
requireNotNull(graalVm.get().version.split(".").first().toIntOrNull()) {
"Invalid GraalVM JDK version: ${graalVm.get().graalVmJdkVersion}"
}
if (gvmVersionMajor < 24) {
execOperations.exec {
val executableName = if (os.isWindows) "gu.cmd" else "gu"
executable = distroBinDir.resolve(executableName).toString()
args("install", "--no-progress", "native-image")
}
}
publishInstallDir(distroDir, os)
} catch (e: Exception) {
try {
fileOperations.delete(distroDir)
} catch (ignored: Exception) {}
throw e
}
}
private fun extractTarGz(distroDir: Path) {
// faster and more reliable than Gradle's `copy { from tarTree() }`
execOperations.exec {
workingDir = distroDir.toFile()
executable = "tar"
args("--strip-components=1", "-xzf", graalVm.get().downloadFile)
}
}
private fun extractZip(distroDir: Path) {
val targetDir = distroDir.toAbsolutePath().normalize()
ZipInputStream(graalVm.get().downloadFile.inputStream().buffered()).use { zipInput ->
var entry = zipInput.nextEntry
while (entry != null) {
try {
val strippedPath = stripFirstPathComponent(entry.name)
if (strippedPath != null) {
val target = targetDir.resolve(strippedPath).normalize()
require(target.startsWith(targetDir)) {
"GraalVM archive entry escapes destination directory: ${entry.name}"
}
if (entry.isDirectory) {
target.createDirectories()
} else {
target.parent?.createDirectories()
Files.copy(zipInput, target)
}
}
} finally {
zipInput.closeEntry()
}
entry = zipInput.nextEntry
}
}
}
private fun stripFirstPathComponent(path: String): String? {
val normalizedPath = path.replace('\\', '/').trimStart('/')
val separatorIndex = normalizedPath.indexOf('/')
if (separatorIndex == -1) return null
return normalizedPath.substring(separatorIndex + 1).takeIf { it.isNotEmpty() }
}
private fun publishInstallDir(distroDir: Path, os: org.gradle.internal.os.OperatingSystem) {
if (os.isWindows) {
println("Installing ${graalVm.get().installDir} from $distroDir")
moveAtomicallyOrRegularly(distroDir, graalVm.get().installDir.toPath())
return
}
println("Creating symlink ${graalVm.get().installDir} for $distroDir")
val tempLink = Paths.get(graalVm.get().homeDir, UUID.randomUUID().toString())
Files.createSymbolicLink(tempLink, distroDir)
try {
moveAtomicallyOrRegularly(tempLink, graalVm.get().installDir.toPath())
} catch (e: Exception) {
try {
fileOperations.delete(tempLink.toFile())
} catch (ignored: Exception) {}
throw e
}
}
private fun moveAtomicallyOrRegularly(source: Path, target: Path) {
try {
Files.move(source, target, StandardCopyOption.ATOMIC_MOVE)
} catch (e: AtomicMoveNotSupportedException) {
Files.move(source, target)
}
}
}
@@ -1,340 +0,0 @@
/*
* Copyright © 2025-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.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.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
abstract class NativeImageBuildService : BuildService<BuildServiceParameters.None>
abstract class NativeImageBuild : DefaultTask() {
@get:Input abstract val outputName: Property<String>
@get:Input abstract val extraNativeImageArgs: ListProperty<String>
@get:Input abstract val arch: Property<Target.Arch>
/**
* 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
@get:Input abstract val envVars: MapProperty<String, String>
@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) {
Target.Arch.AMD64 -> buildInfo.graalVmAmd64
Target.Arch.AARCH64 -> buildInfo.graalVmAarch64
}
}
private val buildInfo: BuildInfo = project.extensions.getByType(BuildInfo::class.java)
private val nativeImageCommandName =
if (buildInfo.os.isWindows) "native-image.cmd" else "native-image"
private val nativeImageExecutable = graalVm.map { "${it.baseDir}/bin/$nativeImageCommandName" }
private val extraArgsFromProperties by lazy {
System.getProperties()
.filter { it.key.toString().startsWith("pkl.native") }
.map { "${it.key}=${it.value}".substring("pkl.native".length) }
}
private val buildService =
project.gradle.sharedServices.registerIfAbsent(
"nativeImageBuildService",
NativeImageBuildService::class,
) {
maxParallelUsages.set(1)
}
init {
// ensure native-image builds run in serial (prevent `gw buildNative` from consuming all host
// CPU resources).
usesService(buildService)
sharedLibrary.convention(false)
outputDir.convention(project.layout.buildDirectory.dir("executable"))
group = "build"
inputs
.files(classpath)
.withPropertyName("runtimeClasspath")
.withNormalizer(ClasspathNormalizer::class)
inputs
.files(nativeImageExecutable)
.withPropertyName("graalVmNativeImage")
.withPathSensitivity(PathSensitivity.ABSOLUTE)
if (nativeCompilerPath.isPresent) {
inputs.file(nativeCompilerPath)
}
}
@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() {
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(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")
if (mainClass.isPresent) {
add("-H:Class=${mainClass.get()}")
}
if (sharedLibrary.get()) {
add("--shared")
}
add("-o")
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")
add("-H:+EnforceMaxRuntimeCompileMethods")
add("--enable-url-protocols=http,https")
add("-H:+ReportExceptionStackTraces")
// disable automatic support for JVM CLI options (puts our main class in full control of
// argument parsing)
add("-H:-ParseRuntimeOptions")
if (!buildInfo.isReleaseBuild) {
// disable all optimizations
add("-O0")
// generate debugging information
add("-g")
}
if (buildInfo.isNativeArch) {
add("-march=native")
} else {
add("-march=compatibility")
}
// native-image rejects non-existing class path entries -> filter
add("--class-path")
val pathInput = classpath.filter {
it.exists() && !exclusions.any { exclude -> it.name.contains(exclude) }
}
add(pathInput.asPath)
// make sure dev machine stays responsive (15% slowdown on my laptop)
val processors =
Runtime.getRuntime().availableProcessors() /
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" })
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_"
}
}
@@ -1,109 +0,0 @@
/*
* 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"
)
}
}
@@ -1,58 +0,0 @@
/*
* Copyright © 2025-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 com.diffplug.spotless.FormatterFunc
import com.diffplug.spotless.FormatterStep
import java.io.Serial
import java.io.Serializable
import java.net.URLClassLoader
import org.gradle.api.artifacts.Configuration
class PklFormatterStep(@Transient private val configuration: Configuration) : Serializable {
companion object {
@Serial private const val serialVersionUID: Long = 1L
}
fun create(): FormatterStep {
return FormatterStep.createLazy(
"pkl",
{ PklFormatterStep(configuration) },
{ PklFormatterFunc(configuration) },
)
}
}
class PklFormatterFunc(@Transient private val configuration: Configuration) :
FormatterFunc, Serializable {
companion object {
@Serial private const val serialVersionUID: Long = 1L
}
private val classLoader by lazy {
val urls = configuration.files.map { it.toURI().toURL() }
// Use the platform classloader as parent to isolate from Gradle's classloader
URLClassLoader(urls.toTypedArray(), ClassLoader.getPlatformClassLoader())
}
private val formatterClass by lazy { classLoader.loadClass("org.pkl.formatter.Formatter") }
private val formatMethod by lazy { formatterClass.getMethod("format", String::class.java) }
private val formatterInstance by lazy { formatterClass.getConstructor().newInstance() }
override fun apply(input: String): String {
return formatMethod(formatterInstance, input) as String
}
}
@@ -1,153 +0,0 @@
/*
* 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.nio.charset.StandardCharsets
import java.util.Base64
import org.gradle.api.Project
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.publish.maven.tasks.AbstractPublishToMaven
import org.gradle.api.publish.maven.tasks.GenerateMavenPom
import org.gradle.kotlin.dsl.*
import org.gradle.plugins.signing.SigningExtension
/** Configures common POM metadata (licenses, developers, SCM, etc.) for all Pkl publications. */
fun Project.configurePklPomMetadata() {
extensions.configure<PublishingExtension> {
publications.withType<MavenPublication>().configureEach {
pom {
name.set(artifactId)
licenses {
license {
name.set("The Apache Software License, Version 2.0")
url.set("https://github.com/apple/pkl/blob/main/LICENSE.txt")
}
}
developers {
developer {
id.set("pkl-authors")
name.set("The Pkl Authors")
email.set("pkl-oss@group.apple.com")
}
}
scm {
connection.set("scm:git:git://github.com/apple/pkl.git")
developerConnection.set("scm:git:ssh://github.com/apple/pkl.git")
val buildInfo = extensions.getByType<BuildInfo>()
url.set("https://github.com/apple/pkl/tree/${buildInfo.commitish}")
}
issueManagement {
system.set("GitHub Issues")
url.set("https://github.com/apple/pkl/issues")
}
ciManagement {
system.set("GitHub Actions")
url.set("https://github.com/apple/pkl/actions")
}
}
}
}
}
/**
* 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 } }
val validatePom =
tasks.register("validatePom") {
val outputFile =
layout.buildDirectory.file("validatePom") // dummy output to satisfy up-to-date check
dependsOn(generatePomTasks)
inputs.files(pomFiles)
outputs.file(outputFile)
onlyIf { pomFiles.get().isNotEmpty() }
doLast {
outputFile.get().asFile.delete()
val pomFile = pomFiles.get().single()
assert(pomFile.exists())
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()
)
}
}
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()
)
}
}
outputFile.get().asFile.writeText("OK")
}
}
tasks.named("publish") { dependsOn(validatePom) }
}
/** Configures signing for Pkl publications. */
fun Project.configurePklSigning() {
// Workaround for maven publish plugin not setting up dependencies correctly.
// Taken from https://github.com/gradle/gradle/issues/26091#issuecomment-1798137734
val dependsOnTasks = mutableListOf<String>()
tasks.withType<AbstractPublishToMaven>().configureEach {
dependsOnTasks.add(name.replace("publish", "sign").replaceAfter("Publication", ""))
dependsOn(dependsOnTasks)
}
extensions.configure<SigningExtension> {
// provided as env vars `ORG_GRADLE_PROJECT_signingKey` and
// `ORG_GRADLE_PROJECT_signingPassword` in CI.
val signingKey =
(findProperty("signingKey") as String?)?.let {
Base64.getDecoder().decode(it).toString(StandardCharsets.US_ASCII)
}
val signingPassword = findProperty("signingPassword") as String?
if (signingKey != null && signingPassword != null) {
useInMemoryPgpKeys(signingKey, signingPassword)
}
extensions.getByType<PublishingExtension>().publications.findByName("library")?.let { sign(it) }
}
}
@@ -1,77 +0,0 @@
/*
* 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 com.diffplug.spotless.FormatterFunc
import com.diffplug.spotless.FormatterStep
import java.io.File
import java.io.Serial
import java.io.Serializable
/**
* A Spotless [FormatterStep] that suppresses formatting changes where the only difference between
* the formatted output and the file's content in the upstream base ref is the license header year.
*
* Avoids an issue where, in the process of working on the codebase:
* 1. A file is modified.
* 2. Spotless formats the file, and also updates the copyright year.
* 3. The original modification is reverted.
* 4. Spotless formats the file again, but now the copyright year is the updated year.
*/
class RevertYearOnlyChangesStep(private val repoRoot: File, private val ratchetFrom: String) :
Serializable {
companion object {
@Serial private const val serialVersionUID: Long = 1L
}
fun create(): FormatterStep =
FormatterStep.createLazy(
"revertYearOnlyChanges",
{ this },
{ RevertYearOnlyChangesFunc(repoRoot, ratchetFrom) },
)
}
class RevertYearOnlyChangesFunc(private val repoRoot: File, private val ratchetFrom: String) :
FormatterFunc.NeedsFile, Serializable {
companion object {
@Serial private const val serialVersionUID: Long = 1L
// Matches "Copyright © 2024" or "Copyright © 2024-2025"
private val YEAR_REGEX = Regex("""(Copyright © )\d{4}(-\d{4})?""")
}
override fun applyWithFile(unix: String, file: File): String {
val relativePath = repoRoot.toPath().relativize(file.toPath()).toString()
val upstreamContent = gitShow(ratchetFrom, relativePath) ?: return unix
val normalizedRaw = YEAR_REGEX.replace(unix, "\$1YEAR")
val normalizedUpstream = YEAR_REGEX.replace(upstreamContent, "\$1YEAR")
return if (normalizedRaw == normalizedUpstream) {
// Only the year changed — return the upstream content
upstreamContent
} else {
unix
}
}
private fun gitShow(ref: String, path: String): String? {
val process =
ProcessBuilder("git", "show", "$ref:$path")
.directory(repoRoot)
.redirectErrorStream(true)
.start()
val output = process.inputStream.readBytes().toString(Charsets.UTF_8)
return if (process.waitFor() == 0) output.replace("\r\n", "\n") else null
}
}
@@ -1,43 +0,0 @@
/*
* 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>
}
-110
View File
@@ -1,110 +0,0 @@
/*
* 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
}
}
@@ -1,49 +0,0 @@
/*
* Copyright © 2025-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 net.ltgt.gradle.errorprone.CheckSeverity
import net.ltgt.gradle.errorprone.errorprone
import net.ltgt.gradle.nullaway.nullaway
import org.gradle.accessors.dm.LibrariesForLibs
import org.gradle.api.tasks.compile.JavaCompile
plugins {
`java-library`
id("net.ltgt.errorprone")
id("net.ltgt.nullaway")
}
val libs = the<LibrariesForLibs>()
dependencies {
api(libs.jspecify)
errorprone(libs.errorProne)
errorprone(libs.nullaway)
}
nullaway { onlyNullMarked = true }
tasks.withType<JavaCompile>().configureEach {
options.errorprone.disableAllChecks = true
options.errorprone.check("StringCaseLocaleUsage", CheckSeverity.ERROR)
options.errorprone.check("GuardedBy", CheckSeverity.ERROR)
options.errorprone.nullaway {
error()
onlyNullMarked = true
jspecifyMode = true
// honor assert x != null in addition to Objects.requireNonNull(x)
assertsEnabled = true
}
}
@@ -1,35 +0,0 @@
/*
* 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) }
@@ -1,114 +0,0 @@
/*
* 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.accessors.dm.LibrariesForLibs
import org.gradle.api.GradleException
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.kotlin
import org.gradle.kotlin.dsl.the
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
plugins {
java
kotlin("jvm")
id("com.diffplug.spotless")
}
val buildInfo = project.extensions.getByType<BuildInfo>()
val libs = the<LibrariesForLibs>()
kotlin {
jvmToolchain {
languageVersion.set(buildInfo.jdkToolchainVersion)
vendor.set(buildInfo.jdkVendor)
}
compilerOptions {
val kotlinTarget = KotlinVersion.fromVersion(libs.versions.kotlinTarget.get())
languageVersion.set(kotlinTarget)
apiVersion.set(kotlinTarget)
jvmTarget = JvmTarget.fromTarget(buildInfo.jvmTarget.toString())
freeCompilerArgs.addAll(
"-jvm-default=no-compatibility", // was: -Xjvm-default=all
"-Xjdk-release=${buildInfo.jvmTarget}",
)
}
}
spotless {
val revertYearOnlyChanges = RevertYearOnlyChangesStep(rootProject.rootDir, ratchetFrom!!).create()
kotlin {
addStep(revertYearOnlyChanges)
ktfmt(libs.versions.ktfmt.get()).googleStyle()
target("src/*/kotlin/**/*.kt")
licenseHeaderFile(
rootProject.file("build-logic/src/main/resources/license-header.star-block.txt")
)
}
}
/**
* Kotlin modules to guard: fail the build if any dependency resolves to a version higher than
* `libs.versions.kotlinTarget`. This includes versions introduced via direct declarations, BOMs,
* version catalogs, or constraints.
*/
val guardedKotlinModules = setOf(libs.kotlinStdLib.get().module, libs.kotlinReflect.get().module)
/**
* Classpath configurations where the above rule applies. Kept narrow to avoid interfering with
* Gradle/Kotlin plugin internal configurations.
*/
val guardedConfigurations =
setOf(
configurations.compileClasspath,
configurations.runtimeClasspath,
configurations.testCompileClasspath,
configurations.testRuntimeClasspath,
)
guardedConfigurations.forEach { configuration ->
configuration.configure {
incoming.afterResolve {
resolutionResult.allComponents.forEach { component ->
val moduleVersion = component.moduleVersion ?: return@forEach
if (
moduleVersion.module in guardedKotlinModules &&
moduleVersion.version.exceedsKotlinTarget()
) {
throw GradleException(
"Resolved ${moduleVersion.module}:${moduleVersion.version} on configuration $name, " +
"which exceeds the allowed Kotlin version ($kotlinTargetVersion)"
)
}
}
}
}
}
// also works for version ranges like: [2.3.0,)
val kotlinVersionRegex = Regex("""(\d+)\.(\d+)(?:\.\d+)?""")
val kotlinTargetVersion = libs.versions.kotlinTarget.get()
val targetMajor = kotlinTargetVersion.substringBefore('.').toInt()
val targetMinor = kotlinTargetVersion.substringAfter('.').toInt()
fun String.exceedsKotlinTarget(): Boolean {
val version =
kotlinVersionRegex.find(this) ?: throw GradleException("Could not parse Kotlin version: $this")
val major = version.groupValues[1].toInt()
val minor = version.groupValues[2].toInt()
return major > targetMajor || (major == targetMajor && minor > targetMinor)
}
@@ -1,29 +0,0 @@
/*
* 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 org.gradle.accessors.dm.LibrariesForLibs
plugins { id("pklJavaLibrary") }
val buildInfo = project.extensions.getByType<BuildInfo>()
val libs = the<LibrariesForLibs>()
dependencies {
// Kotlin libraries typically expose stdlib types in their public APIs.
// Therefore, the stdlib must be available on the consumer's compile classpath,
// and "implementation" is not sufficient.
api(libs.kotlinStdLib)
}
@@ -1,270 +0,0 @@
/*
* Copyright © 2025-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.lang.Runtime.Version
import kotlin.io.path.createDirectories
import kotlin.io.path.writeText
import org.gradle.accessors.dm.LibrariesForLibs
plugins {
id("pklGraalVm")
id("pklJavaLibrary")
id("pklNativeLifecycle")
id("pklNativeSourceBundle")
id("pklPublishLibrary")
id("com.gradleup.shadow")
}
// assumes that `pklJavaExecutable` is also applied
val executableSpec = project.extensions.getByType<ExecutableSpec>()
val buildInfo = project.extensions.getByType<BuildInfo>()
// musl executables are linked with `--static`, so musl and zlib become part of the executable
extensions.getByType<SourceBundleSpec>().staticMuslLibc = true
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(target: Target) =
files(
layout.buildDirectory.dir("executable").map { dir ->
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(Target.MacosAarch64))
stagedLinuxAarch64Executable(executableFile(Target.LinuxAarch64))
stagedLinuxAmd64Executable(executableFile(Target.LinuxAmd64))
stagedAlpineLinuxAmd64Executable(executableFile(Target.AlpineLinuxAmd64))
stagedWindowsAmd64Executable(executableFile(Target.WindowsAmd64))
}
private fun NativeImageBuild.configure(target: Target) {
arch = target.arch
outputName = executableSpec.name.map { "$it-${target.targetName}" }
mainClass = executableSpec.mainClass
if (target.arch == Target.Arch.AARCH64) {
dependsOn(":installGraalVmAarch64")
} else {
dependsOn(":installGraalVmAmd64")
}
classpath.from(sourceSets.main.map { it.output })
classpath.from(
project(":pkl-commons-cli").extensions.getByType(SourceSetContainer::class)["svm"].output
)
classpath.from(nativeImageClasspath)
}
val macExecutableAarch64 =
tasks.register<NativeImageBuild>("macExecutableAarch64") { configure(Target.MacosAarch64) }
val linuxExecutableAmd64 =
tasks.register<NativeImageBuild>("linuxExecutableAmd64") { configure(Target.LinuxAmd64) }
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 =
tasks.register<NativeImageBuild>("alpineExecutableAmd64") {
configure(Target.AlpineLinuxAmd64)
extraNativeImageArgs.addAll("--static", "--libc=musl")
}
val windowsExecutableAmd64 =
tasks.register<NativeImageBuild>("windowsExecutableAmd64") {
configure(Target.WindowsAmd64)
extraNativeImageArgs.add("-Dfile.encoding=UTF-8")
}
val assembleNative = tasks.named("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)
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'"
)
}
outputFile.get().asFile.toPath().apply {
try {
parent.createDirectories()
} catch (_: java.nio.file.FileAlreadyExistsException) {}
writeText("OK")
}
}
}
val requiredGlibcVersion: Version = Version.parse("2.17")
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>) {
dependsOn(other)
outputs.files(other)
}
val testNative = tasks.named("testNative") { dependsOn(testStartNativeExecutable, checkGlibc) }
val assembleNativeMacOsAarch64 =
tasks.named("assembleNativeMacOsAarch64") { wraps(macExecutableAarch64) }
val assembleNativeLinuxAarch64 =
tasks.named("assembleNativeLinuxAarch64") { wraps(linuxExecutableAarch64) }
val assembleNativeLinuxAmd64 =
tasks.named("assembleNativeLinuxAmd64") { wraps(linuxExecutableAmd64) }
val assembleNativeAlpineLinuxAmd64 =
tasks.named("assembleNativeAlpineLinuxAmd64") { wraps(alpineExecutableAmd64) }
val assembleNativeWindowsAmd64 =
tasks.named("assembleNativeWindowsAmd64") { 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>("macExecutableAarch64") {
configurePublication(Target.MacosAarch64, stagedMacAarch64Executable)
}
create<MavenPublication>("linuxExecutableAmd64") {
configurePublication(Target.LinuxAmd64, stagedLinuxAmd64Executable)
}
create<MavenPublication>("linuxExecutableAarch64") {
configurePublication(Target.LinuxAarch64, stagedLinuxAarch64Executable)
}
create<MavenPublication>("alpineLinuxExecutableAmd64") {
configurePublication(Target.AlpineLinuxAmd64, stagedAlpineLinuxAmd64Executable)
}
create<MavenPublication>("windowsExecutableAmd64") {
configurePublication(Target.WindowsAmd64, stagedWindowsAmd64Executable)
}
}
}
}
signing {
project.afterEvaluate {
sign(publishing.publications["linuxExecutableAarch64"])
sign(publishing.publications["linuxExecutableAmd64"])
sign(publishing.publications["macExecutableAarch64"])
sign(publishing.publications["alpineLinuxExecutableAmd64"])
sign(publishing.publications["windowsExecutableAmd64"])
}
}
@@ -1,96 +0,0 @@
/*
* 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.
*/
val assembleNativeMacOsAarch64 = tasks.register("assembleNativeMacOsAarch64") { group = "build" }
val assembleNativeLinuxAarch64 = tasks.register("assembleNativeLinuxAarch64") { group = "build" }
val assembleNativeLinuxAmd64 = tasks.register("assembleNativeLinuxAmd64") { group = "build" }
val assembleNativeAlpineLinuxAmd64 =
tasks.register("assembleNativeAlpineLinuxAmd64") { group = "build" }
val assembleNativeWindowsAmd64 = tasks.register("assembleNativeWindowsAmd64") { group = "build" }
val testNativeMacOsAarch64 = tasks.register("testNativeMacOsAarch64") { group = "verification" }
val testNativeLinuxAarch64 = tasks.register("testNativeLinuxAarch64") { group = "verification" }
val testNativeLinuxAmd64 = tasks.register("testNativeLinuxAmd64") { group = "verification" }
val testNativeAlpineLinuxAmd64 =
tasks.register("testNativeAlpineLinuxAmd64") { group = "verification" }
val testNativeWindowsAmd64 = tasks.register("testNativeWindowsAmd64") { group = "verification" }
val buildInfo = project.extensions.getByType<BuildInfo>()
private fun <T : Task> Task.wraps(other: TaskProvider<T>) {
dependsOn(other)
outputs.files(other)
}
val assembleNative =
tasks.register("assembleNative") {
group = "build"
@Suppress("DuplicatedCode")
if (!buildInfo.isCrossArchSupported && buildInfo.isCrossArch) {
doLast {
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)
}
}
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}")
}
@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 =
tasks.register("checkNative") {
group = "verification"
dependsOn(testNative)
}
val buildNative =
tasks.register("buildNative") {
group = "build"
dependsOn(checkNative)
}
@@ -1,322 +0,0 @@
/*
* 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")
}
@@ -1,38 +0,0 @@
/*
* 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.
*/
plugins {
`maven-publish`
signing
}
publishing {
publications {
components.findByName("java")?.let { javaComponent ->
create<MavenPublication>("library") { from(javaComponent) }
}
}
}
configurePklPomMetadata()
configurePomValidation()
configurePklSigning()
artifacts {
project.tasks.findByName("javadocJar")?.let { archives(it) }
project.tasks.findByName("sourcesJar")?.let { archives(it) }
}
@@ -1,49 +0,0 @@
/*
* 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."
}
@@ -1,39 +0,0 @@
/*
* Copyright © 2025-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("com.diffplug.spotless") }
val pklFormatter = configurations.create("pklFormatter")
dependencies { pklFormatter(rootProject.project("pkl-formatter")) }
spotless {
format("pkl") {
target("**/*.pkl")
addStep(PklFormatterStep(pklFormatter).create())
licenseHeaderFile(
rootProject.file("build-logic/src/main/resources/license-header.line-comment.txt"),
"/// ",
)
// disable ratcheting for Pkl sources
// make any change to pkl-formatter reformat the stdlib
ratchetFrom = null
}
}
for (taskName in
listOf("spotlessPkl", "spotlessPklApply", "spotlessPklCheck", "spotlessPklDiagnose")) {
tasks.named(taskName) { dependsOn(":pkl-formatter:assemble") }
}
@@ -1,15 +0,0 @@
::===----------------------------------------------------------------------===//
:: 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.
::===----------------------------------------------------------------------===//
@@ -1,15 +0,0 @@
#===----------------------------------------------------------------------===//
# 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.
#===----------------------------------------------------------------------===//
+7 -10
View File
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 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.
@@ -14,14 +14,15 @@
* limitations under the License.
*/
// https://youtrack.jetbrains.com/issue/KTIJ-19369
@file:Suppress("DSL_SCOPE_VIOLATION")
import org.jetbrains.gradle.ext.ActionDelegationConfig
import org.jetbrains.gradle.ext.ActionDelegationConfig.TestRunner.PLATFORM
import org.jetbrains.gradle.ext.ProjectSettings
import org.jetbrains.gradle.ext.taskTriggers
plugins {
id("pklAllProjects")
id("pklGraalVm")
pklAllProjects
pklGraalVm
alias(libs.plugins.ideaExt)
alias(libs.plugins.jmh) apply false
@@ -37,9 +38,6 @@ nexusPublishing {
}
}
val configureLateInitAnnotation =
tasks.register<ConfigureLateInitAnnotation>("configureLateInitAnnotation")
idea {
project {
this as ExtensionAware
@@ -49,14 +47,13 @@ idea {
delegateBuildRunToGradle = true
testRunner = PLATFORM
}
taskTriggers.afterSync(configureLateInitAnnotation)
}
}
}
val clean = tasks.named("clean") { delete(layout.buildDirectory) }
val clean by tasks.existing { delete(layout.buildDirectory) }
val printVersion = tasks.register("printVersion") { doFirst { println(buildInfo.pklVersion) } }
val printVersion by tasks.registering { doFirst { println(buildInfo.pklVersion) } }
val message =
"""
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 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.
@@ -20,16 +20,11 @@ plugins {
`jvm-toolchains`
}
/**
* To avoid the provisioning of multiple JDKs and other build issues, keep this value in sync with
* the JVM toolchain versions in `BuildInfo.kt` and `gradle-daemon-jvm.properties`.
*/
val toolchainVersion = 25
// Keep this in sync with the constants in `BuildInfo.kt` (those are not addressable here).
val toolchainVersion = 21
dependencies {
implementation(libs.downloadTaskPlugin)
implementation(libs.errorPronePlugin)
implementation(libs.nullawayPlugin)
implementation(libs.spotlessPlugin)
implementation(libs.kotlinPlugin) { exclude(module = "kotlin-android-extensions") }
implementation(libs.shadowPlugin)
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 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.
@@ -15,7 +15,7 @@
*/
@file:Suppress("UnstableApiUsage")
rootProject.name = "build-logic"
rootProject.name = "buildSrc"
pluginManagement {
repositories {
@@ -24,7 +24,7 @@ pluginManagement {
}
}
plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" }
plugins { id("org.gradle.toolchains.foojay-resolver-convention") }
// makes ~/.gradle/init.gradle unnecessary and ~/.gradle/gradle.properties optional
dependencyResolutionManagement {
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 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.
@@ -15,10 +15,7 @@
*/
@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
@@ -27,7 +24,6 @@ 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
@@ -35,7 +31,7 @@ import org.gradle.process.CommandLineArgumentProvider
/**
* JVM bytecode target; this is pinned at a reasonable version, because downstream JVM projects
* which consume Pkl will need a minimum bytecode level at or above this one.
* which consume Pkl will need a minimum Bytecode level at or above this one.
*
* Kotlin and Java need matching bytecode targets, so this is expressed as a build setting and
* constant default. To override, pass `-DpklJdkToolchain=X` to the Gradle command line, where X is
@@ -44,13 +40,10 @@ import org.gradle.process.CommandLineArgumentProvider
const val PKL_JVM_TARGET_DEFAULT_MAXIMUM = 17
/**
* The Pkl build requires JDK 25+; otherwise, NullAway will not work correctly.
*
* This is a build-time requirement, not a runtime requirement. To avoid the provisioning of
* multiple JDKs and other build issues, keep this value in sync with the JVM toolchain versions in
* `build-logic/build.gradle.kts` and `gradle-daemon-jvm.properties`.
* The Pkl build requires JDK 21+ to build, because JDK 17 is no longer within the default set of
* supported JDKs for GraalVM. This is a build-time requirement, not a runtime requirement.
*/
const val PKL_JDK_VERSION_MIN = 25
const val PKL_JDK_VERSION_MIN = 21
/**
* The JDK minimum is set to match the bytecode minimum, to guarantee that fat JARs work against the
@@ -59,43 +52,44 @@ const val PKL_JDK_VERSION_MIN = 25
const val PKL_TEST_JDK_MINIMUM = PKL_JVM_TARGET_DEFAULT_MAXIMUM
/**
* Maximum JDK version which Pkl is tested with; this should be bumped when new JDK releases are
* issued.
* Maximum JDK version which Pkl is tested with; this should be bumped when new JDK stable releases
* are issued. At the time of this writing, JDK 23 is the latest available release.
*/
const val PKL_TEST_JDK_MAXIMUM = 26
const val PKL_TEST_JDK_MAXIMUM = 23
/**
* If `true`, all JDK releases between [PKL_TEST_JDK_MINIMUM] and [PKL_TEST_JDK_MAXIMUM] are tested.
* If `false`, only LTS releases within that range are tested. To override, pass
* `-DpklTestAllJdks=true` on the Gradle command line.
* Test the full suite of JDKs between [PKL_TEST_JDK_MINIMUM] and [PKL_TEST_JDK_MAXIMUM]; if this is
* set to `false` (or overridden on the command line), only LTS releases are tested by default.
*/
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 osName: String,
val arch: String,
val version: String,
val graalJdkVersion: String,
) {
inner class GraalVm(val arch: String) {
val homeDir: String by lazy {
System.getenv("GRAALVM_HOME") ?: "${System.getProperty("user.home")}/.graalvm"
}
val graalVmJdkVersion: String by lazy { libs.findVersion("graalVmJdkVersion").get().toString() }
val graalVmInnovation: String by lazy { libs.findVersion("graalVmInnovation").get().toString() }
val baseName: String by lazy {
"graalvm-community-jdk-${graalVmJdkVersion}_${osName}-${arch}_bin"
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 graalMajorVersion: String = graalJdkVersion.split(".").first()
val baseName: String by lazy { "graalvm-jdk-${graalVmJdkVersion}_${osName}-${arch}_bin" }
val downloadUrl: String by lazy {
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 jdkMajor = graalVmJdkVersion.takeWhile { it != '.' }
val extension = if (os.isWindows) "zip" else "tar.gz"
"https://download.oracle.com/graalvm/$jdkMajor/archive/$baseName.$extension"
}
val downloadFile: File by lazy {
@@ -106,50 +100,35 @@ open class BuildInfo(private val project: Project) {
val installDir: File by lazy { File(homeDir, baseName) }
val baseDir: String by lazy {
if (os.isMacOS) "$installDir/Contents/Home" else installDir.toString()
if (os.isMacOsX) "$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: Arch by lazy { System.getProperty("pkl.targetArch")?.let(Arch::fromName) ?: arch }
val targetArch by lazy { System.getProperty("pkl.targetArch") ?: 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.isMacOS }
val isCrossArchSupported by lazy { os.isMacOsX }
/** Whether to build native executables using the musl toolchain or not. */
val musl: Boolean by lazy { java.lang.Boolean.getBoolean("pkl.musl") }
val arch: Arch by lazy {
/** Same logic as [org.gradle.internal.os.OperatingSystem#arch], which is protected. */
val arch: String by lazy {
when (val arch = System.getProperty("os.arch")) {
"x86_64",
"amd64" -> Arch.AMD64
"aarch64" -> Arch.AARCH64
else -> throw GradleException("Cannot build Pkl on arch: $arch")
"x86" -> "i386"
"x86_64" -> "amd64"
"powerpc" -> "ppc"
else -> arch
}
}
val graalVmAarch64: GraalVm by lazy { createGraalVm("aarch64") }
val graalVmAarch64: GraalVm = GraalVm("aarch64")
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 graalVmAmd64: GraalVm = GraalVm("x64")
val isCiBuild: Boolean by lazy { System.getenv("CI") != null }
@@ -217,32 +196,17 @@ open class BuildInfo(private val project: Project) {
}
val testJdkVendors: Sequence<JvmVendorSpec> by lazy {
// By default, only Adoptium is tested during multi-JDK testing. Flip `-DpklTestAllVendors=true`
// to additionally test against GraalVM and Oracle.
// By default, only OpenJDK is tested during multi-JDK testing. Flip `-DpklTestAllVendors=true`
// to additionally test against a suite of JDK vendors, including Azul, Oracle, and GraalVM.
when (System.getProperty("pklTestAllVendors")?.toBoolean()) {
true -> sequenceOf(JvmVendorSpec.ADOPTIUM, JvmVendorSpec.GRAAL_VM, JvmVendorSpec.ORACLE)
else -> sequenceOf(JvmVendorSpec.ADOPTIUM)
}
}
private val isArmWindows: Boolean
get() {
if (!os.isWindows) {
return false
}
// System.getProperty("os.arch") returns the architecture of the JVM, not the host OS.
val procArch = System.getenv("PROCESSOR_ARCHITECTURE")
return "ARM64".equals(procArch, ignoreCase = true)
}
// Assembles a collection of JDK versions which tests can be run against, considering ancillary
// parameters like `testAllJdks` and `testExperimentalJdks`.
val jdkTestRange: Collection<JavaLanguageVersion> by lazy {
if (isArmWindows) {
// Java toolchains does not work on ARM windows: https://github.com/gradle/gradle/issues/29807
// prevent creating tasks to test different JDKs if developing on a Windows ARM machine.
return@lazy listOf()
}
JavaVersionRange.inclusive(jdkTestFloor, jdkTestCeiling).toList()
}
@@ -298,21 +262,12 @@ open class BuildInfo(private val project: Project) {
val namer = testNamer(baseNameProvider)
val applyConfig: MultiJdkTestConfigurator = { (version, jdk) ->
// 1) copy configurations from the template task
dependsOn(templateTask)
templateTask.get().let { template ->
// copy explicit dependencies not inferred from task inputs
dependsOn(template.dependsOn)
classpath = template.classpath
testClassesDirs = template.testClassesDirs
jvmArgs.addAll(template.jvmArgs)
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") }
}
}
)
jvmArgumentProviders.addAll(template.jvmArgumentProviders)
forkEvery = template.forkEvery
maxParallelForks = template.maxParallelForks
minHeapSize = template.minHeapSize
@@ -334,8 +289,8 @@ open class BuildInfo(private val project: Project) {
// multiply out by jdk vendor
testJdkVendors.map { vendor -> (targetVersion to vendor) }
}
.mapNotNull { (jdkTarget, vendor) ->
if (jdkToolchainVersion == jdkTarget) {
.map { (jdkTarget, vendor) ->
if (jdkToolchainVersion == jdkTarget)
tasks.register(namer(jdkTarget, vendor)) {
// alias to `test`
dependsOn(templateTask)
@@ -343,24 +298,20 @@ open class BuildInfo(private val project: Project) {
description =
"Alias for regular '${baseNameProvider()}' task, on JDK ${jdkTarget.asInt()}"
}
} else {
// Always register and enable the task so it can be run explicitly,
// but only return it if it should be included in "check".
val task =
tasks.register(namer(jdkTarget, vendor.takeIf { isMultiVendor }), Test::class) {
group = Category.VERIFICATION
description = "Run tests against JDK ${jdkTarget.asInt()}"
applyConfig(jdkTarget to toolchains.launcherFor { languageVersion = jdkTarget })
// fix: on jdk17, we must force the polyglot module on to the modulepath
if (jdkTarget.asInt() == 17)
jvmArgumentProviders.add(
CommandLineArgumentProvider {
buildList { listOf("--add-modules=org.graalvm.polyglot") }
}
)
}
task.takeIf { jdkTarget.isEnabled }
}
else
tasks.register(namer(jdkTarget, vendor.takeIf { isMultiVendor }), Test::class) {
enabled = jdkTarget.isEnabled
group = Category.VERIFICATION
description = "Run tests against JDK ${jdkTarget.asInt()}"
applyConfig(jdkTarget to toolchains.launcherFor { languageVersion = jdkTarget })
// fix: on jdk17, we must force the polyglot module on to the modulepath
if (jdkTarget.asInt() == 17)
jvmArgumentProviders.add(
CommandLineArgumentProvider {
buildList { listOf("--add-modules=org.graalvm.polyglot") }
}
)
}
}
.toList()
}
@@ -388,18 +339,12 @@ open class BuildInfo(private val project: Project) {
}
val hasMuslToolchain: Boolean by lazy {
// see .github/scripts/install_musl.sh
// see "install musl" in .circleci/jobs/BuildNativeJob.pkl
File(System.getProperty("user.home"), "staticdeps/bin/x86_64-linux-musl-gcc").exists()
}
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}")
}
val os: org.gradle.internal.os.OperatingSystem by lazy {
org.gradle.internal.os.OperatingSystem.current()
}
// could be `commitId: Provider<String> = project.provider { ... }`
@@ -407,7 +352,7 @@ open class BuildInfo(private val project: Project) {
// allow -DcommitId=abc123 for build environments that don't have git.
System.getProperty("commitId").let { if (it != null) return@lazy it }
// only run command once per build invocation
if (project.path == project.rootProject.path) {
if (project === project.rootProject) {
val process =
ProcessBuilder()
.command("git", "rev-parse", "--short", "HEAD")
@@ -455,12 +400,3 @@ 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")
}
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 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.
@@ -36,7 +36,6 @@ abstract class ExecutableJar : DefaultTask() {
@get:Input abstract val jvmArgs: ListProperty<String>
@TaskAction
@Suppress("unused")
fun buildJar() {
val inFile = inJar.get().asFile
val outFile = outJar.get().asFile
@@ -1,5 +1,5 @@
/*
* Copyright © 2025-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2025 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.
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024 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.
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024 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.
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024 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.
@@ -0,0 +1,91 @@
/*
* Copyright © 2024-2025 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.nio.file.Files
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import java.util.*
import javax.inject.Inject
import kotlin.io.path.createDirectories
import org.gradle.api.DefaultTask
import org.gradle.api.internal.file.FileOperations
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import org.gradle.process.ExecOperations
abstract class InstallGraalVm
@Inject
constructor(
private val fileOperations: FileOperations,
private val execOperations: ExecOperations,
) : DefaultTask() {
@get:Input abstract val graalVm: Property<BuildInfo.GraalVm>
init {
@Suppress("LeakingThis") onlyIf("GraalVM not installed") { !graalVm.get().installDir.exists() }
}
@TaskAction
@Suppress("unused")
fun run() {
// minimize chance of corruption by extract-to-random-dir-and-flip-symlink
val distroDir = Paths.get(graalVm.get().homeDir, UUID.randomUUID().toString())
try {
distroDir.createDirectories()
println("Extracting ${graalVm.get().downloadFile} into $distroDir")
// faster and more reliable than Gradle's `copy { from tarTree() }`
execOperations.exec {
workingDir = distroDir.toFile()
executable = "tar"
args("--strip-components=1", "-xzf", graalVm.get().downloadFile)
}
val os = org.gradle.internal.os.OperatingSystem.current()
val distroBinDir =
if (os.isMacOsX) distroDir.resolve("Contents/Home/bin") else distroDir.resolve("bin")
println("Installing native-image into $distroDir")
val gvmVersionMajor =
requireNotNull(graalVm.get().version.split(".").first().toIntOrNull()) {
"Invalid GraalVM JDK version: ${graalVm.get().graalVmJdkVersion}"
}
if (gvmVersionMajor < 24) {
execOperations.exec {
val executableName = if (os.isWindows) "gu.cmd" else "gu"
executable = distroBinDir.resolve(executableName).toString()
args("install", "--no-progress", "native-image")
}
}
println("Creating symlink ${graalVm.get().installDir} for $distroDir")
val tempLink = Paths.get(graalVm.get().homeDir, UUID.randomUUID().toString())
Files.createSymbolicLink(tempLink, distroDir)
try {
Files.move(tempLink, graalVm.get().installDir.toPath(), StandardCopyOption.ATOMIC_MOVE)
} catch (e: Exception) {
try {
fileOperations.delete(tempLink.toFile())
} catch (ignored: Exception) {}
throw e
}
} catch (e: Exception) {
try {
fileOperations.delete(distroDir)
} catch (ignored: Exception) {}
throw e
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 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.
@@ -27,7 +27,6 @@ private val ltsReleases =
JavaLanguageVersion.of(11),
JavaLanguageVersion.of(17),
JavaLanguageVersion.of(21),
JavaLanguageVersion.of(25),
)
/** Describes an inclusive range of JVM versions, based on the [JavaLanguageVersion] type. */
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 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.
@@ -57,7 +57,7 @@ open class MergeSourcesJars : DefaultTask() {
// a word or a period character. should catch most cases.
val importPattern =
Pattern.compile(
"(?<!([\\w.]))(" + relocatedPkgs.keys.joinToString("|") { it.replace(".", "\\.") } + ")"
"(?<!(\\w|\\.))(" + relocatedPkgs.keys.joinToString("|") { it.replace(".", "\\.") } + ")"
)
val sourceFileExts = sourceFileExtensions.get()
@@ -70,7 +70,7 @@ open class MergeSourcesJars : DefaultTask() {
val details = this
if (details.isDirectory) return@visit
var path = details.relativePath.parent!!.pathString
var path = details.relativePath.parent.pathString
val relocatedPath = relocatedPaths.keys.find { path.startsWith(it) }
if (relocatedPath != null) {
path = path.replace(relocatedPath, relocatedPaths.getValue(relocatedPath))
@@ -101,7 +101,7 @@ open class MergeSourcesJars : DefaultTask() {
project.zipTree(jar).visit {
val details = this
if (details.isDirectory) return@visit // avoid adding empty dirs
result.add(details.relativePath.parent!!.pathString)
result.add(details.relativePath.parent.pathString)
}
}
return result
@@ -0,0 +1,169 @@
/*
* Copyright © 2025 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.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
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.InputFiles
import org.gradle.api.tasks.OutputFile
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 extraNativeImageArgs: ListProperty<String>
@get:Input abstract val arch: Property<Architecture>
@get:Input abstract val mainClass: Property<String>
@get:InputFiles abstract val classpath: ConfigurableFileCollection
private val outputDir = project.layout.buildDirectory.dir("executable")
@get:OutputFile val outputFile = outputDir.flatMap { it.file(imageName) }
@get:Inject protected abstract val execOperations: ExecOperations
private val graalVm: Provider<BuildInfo.GraalVm> =
arch.map { a ->
when (a) {
Architecture.AMD64 -> buildInfo.graalVmAmd64
Architecture.AARCH64 -> buildInfo.graalVmAarch64
}
}
private val buildInfo: BuildInfo = project.extensions.getByType(BuildInfo::class.java)
private val nativeImageCommandName =
if (buildInfo.os.isWindows) "native-image.cmd" else "native-image"
private val nativeImageExecutable = graalVm.map { "${it.baseDir}/bin/$nativeImageCommandName" }
private val extraArgsFromProperties by lazy {
System.getProperties()
.filter { it.key.toString().startsWith("pkl.native") }
.map { "${it.key}=${it.value}".substring("pkl.native".length) }
}
private val buildService =
project.gradle.sharedServices.registerIfAbsent(
"nativeImageBuildService",
NativeImageBuildService::class,
) {
maxParallelUsages.set(1)
}
init {
// ensure native-image builds run in serial (prevent `gw buildNative` from consuming all host
// CPU resources).
usesService(buildService)
group = "build"
inputs
.files(classpath)
.withPropertyName("runtimeClasspath")
.withNormalizer(ClasspathNormalizer::class)
inputs
.files(nativeImageExecutable)
.withPropertyName("graalVmNativeImage")
.withPathSensitivity(PathSensitivity.ABSOLUTE)
}
@TaskAction
protected fun run() {
execOperations.exec {
val exclusions =
listOf(buildInfo.libs.findLibrary("graalSdk").get()).map { it.get().module.name }
executable = nativeImageExecutable.get()
workingDir(outputDir)
args = buildList {
// must be emitted before any experimental options are used
add("-H:+UnlockExperimentalVMOptions")
// 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")
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()}")
add("-o")
add(imageName.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")
add("-H:+EnforceMaxRuntimeCompileMethods")
add("--enable-url-protocols=http,https")
add("-H:+ReportExceptionStackTraces")
// 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")
}
if (buildInfo.isNativeArch) {
add("-march=native")
} else {
add("-march=compatibility")
}
// native-image rejects non-existing class path entries -> filter
add("--class-path")
val pathInput =
classpath.filter {
it.exists() && !exclusions.any { exclude -> it.name.contains(exclude) }
}
add(pathInput.asPath)
// 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
add("-J-XX:ActiveProcessorCount=${processors}")
// Pass through all `HOMEBREW_` prefixed environment variables to allow build with shimmed
// tools.
addAll(environment.keys.filter { it.startsWith("HOMEBREW_") }.map { "-E$it" })
addAll(extraNativeImageArgs.get())
addAll(extraArgsFromProperties)
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024 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.
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 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.
@@ -15,19 +15,19 @@
*/
import com.diffplug.gradle.spotless.KotlinGradleExtension
import org.gradle.accessors.dm.LibrariesForLibs
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
plugins { id("com.diffplug.spotless") }
val buildInfo = extensions.create<BuildInfo>("buildInfo", project)
dependencyLocking { lockAllConfigurations() }
configurations {
val rejectedVersionSuffix = Regex("-alpha|-beta|-eap|-m|-rc|-snapshot", RegexOption.IGNORE_CASE)
configureEach {
resolutionStrategy {
// forbid dependencies whose pom.xml's include version ranges, because this will lead to
// unreproducible builds.
failOnDynamicVersions()
componentSelection {
all {
if (rejectedVersionSuffix.containsMatchIn(candidate.version)) {
@@ -42,10 +42,25 @@ configurations {
}
}
tasks.withType<JavaCompile>().configureEach {
javaCompiler = buildInfo.javaCompiler
options.release = buildInfo.jvmTarget
options.compilerArgs.add("-Xlint:unchecked")
configurations.all {
resolutionStrategy.eachDependency {
if (requested.group == "org.jetbrains.kotlin") {
// prevent transitive deps from bumping Koltin version
useVersion(libs.versions.kotlin.get())
}
}
}
plugins.withType(JavaPlugin::class).configureEach {
tasks.withType<JavaCompile>().configureEach { options.release = 17 }
}
tasks.withType<KotlinJvmCompile>().configureEach {
compilerOptions {
jvmTarget = JvmTarget.JVM_17
freeCompilerArgs.addAll("-Xjsr305=strict", "-Xjvm-default=all")
freeCompilerArgs.add("-Xjdk-release=17")
}
}
plugins.withType(IdeaPlugin::class).configureEach {
@@ -66,7 +81,7 @@ plugins.withType(MavenPublishPlugin::class).configureEach {
repositories {
maven {
name = "projectLocal" // affects task names
url = rootDir.resolve("build/m2").toURI()
url = uri("file:///$rootDir/build/m2")
}
}
// use resolved/locked (e.g., `1.15`)
@@ -80,7 +95,14 @@ plugins.withType(MavenPublishPlugin::class).configureEach {
}
}
val allDependencies = tasks.register<DependencyReportTask>("allDependencies")
// settings.gradle.kts sets `--write-locks`
// if Gradle command line contains this task name
val updateDependencyLocks by
tasks.registering {
doLast { configurations.filter { it.isCanBeResolved }.forEach { it.resolve() } }
}
val allDependencies by tasks.registering(DependencyReportTask::class)
tasks.withType(Test::class).configureEach {
System.getProperty("testReportsDir")?.let { reportsDir ->
@@ -88,6 +110,7 @@ tasks.withType(Test::class).configureEach {
}
debugOptions {
enabled = System.getProperty("jvmdebug")?.toBoolean() ?: false
@Suppress("UnstableApiUsage")
host = "*"
port = 5005
suspend = true
@@ -98,6 +121,7 @@ tasks.withType(Test::class).configureEach {
tasks.withType(JavaExec::class).configureEach {
debugOptions {
enabled = System.getProperty("jvmdebug")?.toBoolean() ?: false
@Suppress("UnstableApiUsage")
host = "*"
port = 5005
suspend = true
@@ -109,7 +133,7 @@ tasks.withType(JavaExec::class).configureEach {
private val libs = the<LibrariesForLibs>()
private val licenseHeaderFile by lazy {
rootProject.file("build-logic/src/main/resources/license-header.star-block.txt")
rootProject.file("buildSrc/src/main/resources/license-header.star-block.txt")
}
private fun KotlinGradleExtension.configureFormatter() {
@@ -118,40 +142,26 @@ private fun KotlinGradleExtension.configureFormatter() {
}
val originalRemoteName = System.getenv("PKL_ORIGINAL_REMOTE_NAME") ?: "origin"
// if we're running against a release branch (or a PR targeted at one), use that branch for
// ratcheting
// these env vars are set by GitHub actions:
// https://docs.github.com/en/actions/reference/workflows-and-actions/variables#default-environment-variables
val ratchetBranchName =
(System.getenv("GITHUB_BASE_REF") ?: System.getenv("GITHUB_REF_NAME"))?.let {
if (it.startsWith("release/")) it else null
} ?: "main"
spotless {
ratchetFrom = "$originalRemoteName/$ratchetBranchName"
ratchetFrom = "$originalRemoteName/main"
val revertYearOnlyChangesStep =
RevertYearOnlyChangesStep(rootProject.rootDir, ratchetFrom!!).create()
// When building root project, format build-logic files too.
// We need this because build-logic is not a subproject of the root project, so a top-level
// `spotlessApply` will not trigger `build-logic:spotlessApply`.
if (project.path == rootProject.path) {
// When building root project, format buildSrc files too.
// We need this because buildSrc is not a subproject of the root project, so a top-level
// `spotlessApply` will not trigger `buildSrc:spotlessApply`.
if (project === rootProject) {
kotlinGradle {
configureFormatter()
addStep(revertYearOnlyChangesStep)
target("*.kts", "build-logic/*.kts", "build-logic/src/*/kotlin/**/*.kts")
target("*.kts", "buildSrc/*.kts", "buildSrc/src/*/kotlin/**/*.kts")
}
kotlin {
ktfmt(libs.versions.ktfmt.get()).googleStyle()
target("build-logic/src/*/kotlin/**/*.kt")
target("buildSrc/src/*/kotlin/**/*.kt")
licenseHeaderFile(licenseHeaderFile)
addStep(revertYearOnlyChangesStep)
}
} else {
kotlinGradle {
configureFormatter()
addStep(revertYearOnlyChangesStep)
target("*.kts")
}
}
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 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.
@@ -15,6 +15,7 @@
*/
import org.gradle.api.GradleException
import org.gradle.api.artifacts.Configuration
import org.gradle.api.component.AdhocComponentWithVariants
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.testing.Test
@@ -23,8 +24,7 @@ import org.gradle.kotlin.dsl.*
plugins {
`java-library`
`maven-publish`
id("com.gradleup.shadow")
id("pklJvmSourceBundle")
id("com.github.johnrengelman.shadow")
}
// make fat Jar available to other subprojects
@@ -32,6 +32,10 @@ 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
@@ -56,7 +60,6 @@ val relocations =
// pkl-doc dependencies
"org.commonmark." to "org.pkl.thirdparty.commonmark.",
"org.jetbrains." to "org.pkl.thirdparty.jetbrains.",
"_COROUTINE." to "org.pkl.thirdparty.kotlinx._COROUTINE.",
// pkl-config-java dependencies
"io.leangen.geantyref." to "org.pkl.thirdparty.geantyref.",
@@ -66,11 +69,6 @@ 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) {
@@ -86,7 +84,7 @@ for ((key, value) in relocations) {
}
}
val nonRelocations = listOf("com/oracle/truffle/", "org/graalvm/", "org/jspecify/")
val nonRelocations = listOf("com/oracle/truffle/", "org/graalvm/")
tasks.shadowJar {
inputs.property("relocations", relocations)
@@ -95,16 +93,12 @@ tasks.shadowJar {
configurations = listOf(project.configurations.runtimeClasspath.get())
addMultiReleaseAttribute = true
// not required at runtime / fat JARs can't be used in native-image builds anyway
exclude("org/pkl/cli/svm/**")
exclude("META-INF/maven/**")
exclude("META-INF/upgrade/**")
exclude("DebugProbesKt.bin")
val info = project.extensions.getByType<BuildInfo>()
val minimumJvmTarget = JavaVersion.toVersion(info.jvmTarget)
@@ -132,10 +126,13 @@ tasks.shadowJar {
mergeServiceFiles()
}
shadow { addShadowVariantIntoJavaComponent = false }
// workaround for https://github.com/johnrengelman/shadow/issues/651
components.withType(AdhocComponentWithVariants::class.java).forEach { c ->
c.withVariantsFromConfiguration(project.configurations.shadowRuntimeElements.get()) { skip() }
}
val testFatJar =
tasks.register<Test>("testFatJar") {
val testFatJar by
tasks.registering(Test::class) {
testClassesDirs = files(tasks.test.get().testClassesDirs)
classpath =
// compiled test classes
@@ -150,8 +147,8 @@ val testFatJar =
tasks.check { dependsOn(testFatJar) }
val validateFatJar =
tasks.register("validateFatJar") {
val validateFatJar by
tasks.registering {
val outputFile = layout.buildDirectory.file("validateFatJar/result.txt")
inputs.files(tasks.shadowJar)
inputs.property("nonRelocations", nonRelocations)
@@ -184,13 +181,17 @@ val validateFatJar =
tasks.check { dependsOn(validateFatJar) }
val fatSourcesJar =
tasks.register<MergeSourcesJars>("fatSourcesJar") {
val resolveSourcesJars by
tasks.registering(ResolveSourcesJars::class) {
configuration.set(configurations.runtimeClasspath)
outputDir.set(layout.buildDirectory.dir("resolveSourcesJars"))
}
val fatSourcesJar by
tasks.registering(MergeSourcesJars::class) {
plugins.withId("pklJavaLibrary") { inputJars.from(tasks.named("sourcesJar")) }
inputJars.from(configurations.named("firstPartySourcesJars"))
inputJars.from(
tasks.named<ResolveSourcesJars>("resolveSourcesJars").map { fileTree(it.outputDir) }
)
inputJars.from(firstPartySourcesJarsConfiguration)
inputJars.from(resolveSourcesJars.map { fileTree(it.outputDir) })
mergedBinaryJars.from(tasks.shadowJar)
relocatedPackages.set(relocations)
@@ -206,13 +207,13 @@ artifacts { add("fatJar", tasks.shadowJar) }
publishing {
publications {
named<MavenPublication>("fatJar") {
from(components["shadow"])
project.shadow.component(this)
// sources Jar is fat
artifact(fatSourcesJar.flatMap { it.outputJar.asFile }) { classifier = "sources" }
plugins.withId("pklJavaLibrary") {
val javadocJar = tasks.named<Jar>("javadocJar")
val javadocJar by tasks.existing(Jar::class)
// Javadoc Jar is not fat (didn't invest effort)
artifact(javadocJar.flatMap { it.archiveFile }) { classifier = "javadoc" }
}
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024 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.
@@ -21,15 +21,11 @@ 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 =
tasks.register<Download>("downloadGraalVmAarch64") {
configureDownloadGraalVm(buildInfo.graalVmAarch64)
}
val downloadGraalVmAarch64 by
tasks.registering(Download::class) { configureDownloadGraalVm(buildInfo.graalVmAarch64) }
val downloadGraalVmAmd64 =
tasks.register<Download>("downloadGraalVmAmd64") {
configureDownloadGraalVm(buildInfo.graalVmAmd64)
}
val downloadGraalVmAmd64 by
tasks.registering(Download::class) { configureDownloadGraalVm(buildInfo.graalVmAmd64) }
fun Download.configureDownloadGraalVm(graalvm: BuildInfo.GraalVm) {
onlyIf { !graalvm.installDir.exists() }
@@ -41,14 +37,14 @@ fun Download.configureDownloadGraalVm(graalvm: BuildInfo.GraalVm) {
tempAndMove(true)
}
val verifyGraalVmAarch64 =
tasks.register<Verify>("verifyGraalVmAarch64") {
val verifyGraalVmAarch64 by
tasks.registering(Verify::class) {
configureVerifyGraalVm(buildInfo.graalVmAarch64)
dependsOn(downloadGraalVmAarch64)
}
val verifyGraalVmAmd64 =
tasks.register<Verify>("verifyGraalVmAmd64") {
val verifyGraalVmAmd64 by
tasks.registering(Verify::class) {
configureVerifyGraalVm(buildInfo.graalVmAmd64)
dependsOn(downloadGraalVmAmd64)
}
@@ -64,23 +60,15 @@ fun Verify.configureVerifyGraalVm(graalvm: BuildInfo.GraalVm) {
}
@Suppress("unused")
val installGraalVmAarch64 =
tasks.register<InstallGraalVm>("installGraalVmAarch64") {
val installGraalVmAarch64 by
tasks.registering(InstallGraalVm::class) {
dependsOn(verifyGraalVmAarch64)
graalVm = buildInfo.graalVmAarch64
}
@Suppress("unused")
val installGraalVmAmd64 =
tasks.register<InstallGraalVm>("installGraalVmAmd64") {
val installGraalVmAmd64 by
tasks.registering(InstallGraalVm::class) {
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"
)
}
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 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.
@@ -21,8 +21,6 @@ tasks.addRule("Pattern: compatibilityTest[All|Releases|Latest|Candidate|Nightly|
val taskName = this
val matchResult = Regex("compatibilityTest(.+)").matchEntire(taskName) ?: return@addRule
// https://github.com/gradle/gradle/issues/32599
@Suppress("DEPRECATION")
when (val taskNameSuffix = matchResult.groupValues[1]) {
"All" ->
task("compatibilityTestAll") {
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024 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.
@@ -23,32 +23,30 @@ val validatorConfiguration: Configuration =
configurations.create("validator") {
resolutionStrategy.eachDependency {
if (requested.group == "log4j" && requested.name == "log4j") {
useTarget(buildInfo.libs.findLibrary("log4j12Api").get())
@Suppress("UnstableApiUsage") useTarget(buildInfo.libs.findLibrary("log4j12Api").get())
because("mitigate critical security vulnerabilities")
}
}
}
dependencies {
@Suppress("UnstableApiUsage")
validatorConfiguration(buildInfo.libs.findLibrary("nuValidator").get()) {
// remove unnecessary dependencies
// (some of the requested versions don't even exist on Maven Central)
exclude(group = "org.eclipse.jetty", module = "jetty-alpn-client")
// we only want jetty-util and jetty-util-ajax (with the right version)
// couldn't find a more robust way to express this
exclude(group = "org.eclipse.jetty", module = "jetty-continuation")
exclude(group = "org.eclipse.jetty", module = "jetty-http")
exclude(group = "org.eclipse.jetty", module = "jetty-io")
exclude(group = "org.eclipse.jetty", module = "jetty-security")
exclude(group = "org.eclipse.jetty", module = "jetty-server")
exclude(group = "org.eclipse.jetty", module = "jetty-servlets")
exclude(group = "org.eclipse.jetty", module = "jetty-jakarta-servlet-api")
exclude(group = "org.eclipse.jetty.toolchain")
exclude(group = "javax.servlet")
exclude(group = "org.apache.commons", module = "commons-fileupload2-core")
exclude(group = "org.apache.commons", module = "commons-fileupload2-jakarta-servlet5")
exclude(group = "commons-fileupload")
}
}
val validateHtml =
tasks.register<JavaExec>("validateHtml") {
val validateHtml by
tasks.registering(JavaExec::class) {
val resultFile = layout.buildDirectory.file("validateHtml/result.txt")
inputs.files(htmlValidator.sources)
outputs.file(resultFile)
@@ -1,5 +1,5 @@
/*
* Copyright © 2025-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2025 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.
@@ -20,15 +20,14 @@ import org.gradle.kotlin.dsl.support.serviceOf
plugins {
id("pklJavaLibrary")
// id("pklPublishLibrary")
id("com.gradleup.shadow")
id("pklJvmSourceBundle")
id("com.github.johnrengelman.shadow")
}
val executableSpec = project.extensions.create("executable", ExecutableSpec::class.java)
val buildInfo = project.extensions.getByType<BuildInfo>()
val javaExecutable =
tasks.register<ExecutableJar>("javaExecutable") {
val javaExecutable by
tasks.registering(ExecutableJar::class) {
group = "build"
dependsOn(tasks.jar)
inJar = tasks.shadowJar.flatMap { it.archiveFile }
@@ -48,19 +47,20 @@ fun Task.setupTestStartJavaExecutable(launcher: Provider<JavaLauncher>? = null)
val outputFile = layout.buildDirectory.file("testStartJavaExecutable/$name")
outputs.file(outputFile)
val execOutput = providers.exec {
val executablePath = javaExecutable.get().outputs.files.singleFile
if (launcher?.isPresent == true) {
commandLine(
launcher.get().executablePath.asFile.absolutePath,
"-jar",
executablePath.absolutePath,
"--version",
)
} else {
commandLine(executablePath.absolutePath, "--version")
val execOutput =
providers.exec {
val executablePath = javaExecutable.get().outputs.files.singleFile
if (launcher?.isPresent == true) {
commandLine(
launcher.get().executablePath.asFile.absolutePath,
"-jar",
executablePath.absolutePath,
"--version",
)
} else {
commandLine(executablePath.absolutePath, "--version")
}
}
}
doLast {
val outputText = execOutput.standardOutput.asText.get()
@@ -72,14 +72,13 @@ fun Task.setupTestStartJavaExecutable(launcher: Provider<JavaLauncher>? = null)
outputFile.get().asFile.toPath().apply {
try {
parent.createDirectories()
} catch (_: java.nio.file.FileAlreadyExistsException) {}
} catch (ignored: java.nio.file.FileAlreadyExistsException) {}
writeText("OK")
}
}
}
val testStartJavaExecutable =
tasks.register("testStartJavaExecutable") { setupTestStartJavaExecutable() }
val testStartJavaExecutable by tasks.registering { setupTestStartJavaExecutable() }
// Setup `testStartJavaExecutable` tasks for multi-JDK testing.
val testStartJavaExecutableOnOtherJdks =
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 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.
@@ -50,18 +50,20 @@ artifacts {
}
spotless {
val revertYearOnlyChanges = RevertYearOnlyChangesStep(rootProject.rootDir, ratchetFrom!!).create()
java {
addStep(revertYearOnlyChanges)
googleJavaFormat(libs.versions.googleJavaFormat.get())
target("src/*/java/**/*.java")
licenseHeaderFile(
rootProject.file("build-logic/src/main/resources/license-header.star-block.txt")
)
licenseHeaderFile(rootProject.file("buildSrc/src/main/resources/license-header.star-block.txt"))
}
kotlin {
ktfmt(libs.versions.ktfmt.get()).googleStyle()
target("src/*/kotlin/**/*.kt")
licenseHeaderFile(rootProject.file("buildSrc/src/main/resources/license-header.star-block.txt"))
}
}
tasks.compileKotlin { enabled = false }
tasks.jar {
manifest {
attributes +=
@@ -79,6 +81,19 @@ tasks.javadoc {
(options as StandardJavadocDocletOptions).addStringOption("Xdoclint:none", "-quiet")
}
val workAroundKotlinGradlePluginBug by
tasks.registering {
doLast {
// Works around this problem, which sporadically appears and disappears in different
// subprojects:
// A problem was found with the configuration of task ':pkl-executor:compileJava' (type
// 'JavaCompile').
// > Directory '[...]/pkl/pkl-executor/build/classes/kotlin/main'
// specified for property 'compileKotlinOutputClasses' does not exist.
layout.buildDirectory.dir("classes/kotlin/main").get().asFile.mkdirs()
}
}
val truffleJavacArgs =
listOf(
// TODO: determine correct limits for Truffle specializations
@@ -88,6 +103,7 @@ val truffleJavacArgs =
tasks.compileJava {
javaCompiler = info.javaCompiler
dependsOn(workAroundKotlinGradlePluginBug)
options.compilerArgs.addAll(truffleJavacArgs + info.jpmsAddModulesFlags)
}
@@ -0,0 +1,49 @@
/*
* Copyright © 2024-2025 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.accessors.dm.LibrariesForLibs
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
plugins {
id("pklJavaLibrary")
kotlin("jvm")
}
// Build configuration.
val buildInfo = project.extensions.getByType<BuildInfo>()
// Version Catalog library symbols.
val libs = the<LibrariesForLibs>()
dependencies {
// At least some of our kotlin APIs contain Kotlin stdlib types
// that aren't compiled away by kotlinc (e.g., `kotlin.Function`).
// So let's be conservative and default to `api` for now.
// For Kotlin APIs that only target Kotlin users (e.g., pkl-config-kotlin),
// it won't make a difference.
api(buildInfo.libs.findLibrary("kotlinStdLib").get())
}
tasks.compileKotlin {
enabled = true // disabled by pklJavaLibrary
}
tasks.withType<KotlinJvmCompile>().configureEach {
compilerOptions {
jvmTarget = JvmTarget.fromTarget(buildInfo.jvmTarget.toString())
freeCompilerArgs.addAll("-Xjdk-release=${buildInfo.jvmTarget}")
}
}
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 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.
@@ -19,7 +19,7 @@ import org.gradle.api.tasks.testing.logging.TestExceptionFormat
plugins {
`jvm-test-suite`
id("pklKotlinBase")
kotlin("jvm")
}
val buildInfo = project.extensions.getByType<BuildInfo>()
@@ -27,11 +27,10 @@ val buildInfo = project.extensions.getByType<BuildInfo>()
val libs = the<LibrariesForLibs>()
dependencies {
testImplementation(libs.kotlinStdLib)
testImplementation(libs.assertj)
testImplementation(libs.junitApi)
testImplementation(libs.junitParams)
testImplementation(libs.kotlinStdLib)
testRuntimeOnly(libs.junitEngine)
testRuntimeOnly(libs.junitLauncher)
@@ -45,7 +44,7 @@ tasks.withType<Test>().configureEach {
// enable checking of stdlib return types
systemProperty("org.pkl.testMode", "true")
reports.named("html") { required = true }
reports.named("html") { enabled = true }
testLogging { exceptionFormat = TestExceptionFormat.FULL }
@@ -0,0 +1,291 @@
/*
* Copyright © 2025 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 kotlin.io.path.createDirectories
import kotlin.io.path.writeText
plugins {
id("pklGraalVm")
id("pklJavaLibrary")
id("pklNativeLifecycle")
id("pklPublishLibrary")
id("com.github.johnrengelman.shadow")
}
// assumes that `pklJavaExecutable` is also applied
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
dependencies {
fun executableFile(suffix: String) =
files(
layout.buildDirectory.dir("executable").map { dir ->
dir.file(executableSpec.name.map { "$it-$suffix" })
}
)
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"))
}
private fun NativeImageBuild.amd64() {
arch = Architecture.AMD64
dependsOn(":installGraalVmAmd64")
}
private fun NativeImageBuild.aarch64() {
arch = Architecture.AARCH64
dependsOn(":installGraalVmAarch64")
}
private fun NativeImageBuild.setClasspath() {
classpath.from(sourceSets.main.map { it.output })
classpath.from(
project(":pkl-commons-cli").extensions.getByType(SourceSetContainer::class)["svm"].output
)
classpath.from(configurations.runtimeClasspath)
}
val macExecutableAmd64 by
tasks.registering(NativeImageBuild::class) {
imageName = executableSpec.name.map { "$it-macos-amd64" }
mainClass = executableSpec.mainClass
amd64()
setClasspath()
}
val macExecutableAarch64 by
tasks.registering(NativeImageBuild::class) {
imageName = executableSpec.name.map { "$it-macos-aarch64" }
mainClass = executableSpec.mainClass
aarch64()
setClasspath()
}
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()
// 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 windowsExecutableAmd64 by
tasks.registering(NativeImageBuild::class) {
imageName = executableSpec.name.map { "$it-windows-amd64" }
mainClass = executableSpec.mainClass
amd64()
setClasspath()
extraNativeImageArgs.add("-Dfile.encoding=UTF-8")
}
val assembleNative by tasks.existing
val testStartNativeExecutable by
tasks.registering {
dependsOn(assembleNative)
// 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'"
)
}
outputFile.get().asFile.toPath().apply {
try {
parent.createDirectories()
} catch (ignored: java.nio.file.FileAlreadyExistsException) {}
writeText("OK")
}
}
}
// Expose underlying task's outputs
private fun <T : Task> Task.wraps(other: TaskProvider<T>) {
dependsOn(other)
outputs.files(other)
}
val testNative by tasks.existing { dependsOn(testStartNativeExecutable) }
val assembleNativeMacOsAarch64 by tasks.existing { wraps(macExecutableAarch64) }
val assembleNativeMacOsAmd64 by tasks.existing { wraps(macExecutableAmd64) }
val assembleNativeLinuxAarch64 by tasks.existing { wraps(linuxExecutableAarch64) }
val assembleNativeLinuxAmd64 by tasks.existing { wraps(linuxExecutableAmd64) }
val assembleNativeAlpineLinuxAmd64 by tasks.existing { wraps(alpineExecutableAmd64) }
val assembleNativeWindowsAmd64 by tasks.existing { wraps(windowsExecutableAmd64) }
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."
}
}
}
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."
}
}
}
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."
}
}
}
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."
}
}
}
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."
}
}
}
}
}
}
signing {
project.afterEvaluate {
sign(publishing.publications["linuxExecutableAarch64"])
sign(publishing.publications["linuxExecutableAmd64"])
sign(publishing.publications["macExecutableAarch64"])
sign(publishing.publications["macExecutableAmd64"])
sign(publishing.publications["alpineLinuxExecutableAmd64"])
sign(publishing.publications["windowsExecutableAmd64"])
}
}
@@ -0,0 +1,129 @@
/*
* Copyright © 2024-2025 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.
*/
val assembleNativeMacOsAarch64 by tasks.registering { group = "build" }
val assembleNativeMacOsAmd64 by tasks.registering { group = "build" }
val assembleNativeLinuxAarch64 by tasks.registering { group = "build" }
val assembleNativeLinuxAmd64 by tasks.registering { group = "build" }
val assembleNativeAlpineLinuxAmd64 by tasks.registering { group = "build" }
val assembleNativeWindowsAmd64 by tasks.registering { group = "build" }
val testNativeMacOsAarch64 by tasks.registering { group = "verification" }
val testNativeMacOsAmd64 by tasks.registering { group = "verification" }
val testNativeLinuxAarch64 by tasks.registering { group = "verification" }
val testNativeLinuxAmd64 by tasks.registering { group = "verification" }
val testNativeAlpineLinuxAmd64 by tasks.registering { group = "verification" }
val testNativeWindowsAmd64 by tasks.registering { group = "verification" }
val buildInfo = project.extensions.getByType<BuildInfo>()
private fun <T : Task> Task.wraps(other: TaskProvider<T>) {
dependsOn(other)
outputs.files(other)
}
val assembleNative by
tasks.registering {
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)
}
buildInfo.musl -> {
throw GradleException("Building musl on ${buildInfo.os} is not supported")
}
else -> {
throw GradleException(
"Unsupported os/arch pair: ${buildInfo.os.name}/${buildInfo.targetArch}"
)
}
}
}
val testNative by
tasks.registering {
group = "verification"
if (!buildInfo.isCrossArchSupported && buildInfo.isCrossArch) {
throw GradleException("Cross-arch builds are not supported on ${buildInfo.os.name}")
}
when {
buildInfo.os.isMacOsX && buildInfo.targetArch == "aarch64" -> {
dependsOn(testNativeMacOsAarch64)
}
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)
}
buildInfo.musl -> {
throw GradleException("Building musl on ${buildInfo.os} is not supported")
}
else -> {
throw GradleException(
"Unsupported os/arch pair: ${buildInfo.os.name}/${buildInfo.targetArch}"
)
}
}
}
val checkNative by
tasks.registering {
group = "verification"
dependsOn(testNative)
}
val buildNative by
tasks.registering {
group = "build"
dependsOn(assembleNative, checkNative)
}
@@ -0,0 +1,147 @@
/*
* Copyright © 2024 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.nio.charset.StandardCharsets
import java.util.Base64
import org.gradle.api.publish.maven.tasks.GenerateMavenPom
plugins {
`maven-publish`
signing
}
publishing {
publications {
components.findByName("java")?.let { javaComponent ->
create<MavenPublication>("library") { from(javaComponent) }
}
withType<MavenPublication>().configureEach {
pom {
name.set(artifactId)
licenses {
license {
name.set("The Apache Software License, Version 2.0")
url.set("https://github.com/apple/pkl/blob/main/LICENSE.txt")
}
}
developers {
developer {
id.set("pkl-authors")
name.set("The Pkl Authors")
email.set("pkl-oss@group.apple.com")
}
}
scm {
connection.set("scm:git:git://github.com/apple/pkl.git")
developerConnection.set("scm:git:ssh://github.com/apple/pkl.git")
val buildInfo = project.extensions.getByType<BuildInfo>()
url.set("https://github.com/apple/pkl/tree/${buildInfo.commitish}")
}
issueManagement {
system.set("GitHub Issues")
url.set("https://github.com/apple/pkl/issues")
}
ciManagement {
system.set("Circle CI")
url.set("https://app.circleci.com/pipelines/github/apple/pkl")
}
}
}
}
}
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
dependsOn(generatePomFileForLibraryPublication)
inputs.file(generatePomFileForLibraryPublication.get().destination)
outputs.file(outputFile)
doLast {
outputFile.get().asFile.delete()
val pomFile = generatePomFileForLibraryPublication.get().destination
assert(pomFile.exists())
val text = pomFile.readText()
run {
val unresolvedVersion = Regex("<version>.*[+,()\\[\\]].*</version>")
val matches = unresolvedVersion.findAll(text).toList()
if (matches.isNotEmpty()) {
throw GradleException(
"""
Found unresolved version selector(s) in generated POM:
${matches.joinToString("\n") { it.groupValues[0] }}
"""
.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 GradleException(
"""
Found snapshot version(s) in generated POM of Pkl release version:
${matches.joinToString("\n") { it.groupValues[0] }}
"""
.trimIndent()
)
}
}
outputFile.get().asFile.writeText("OK")
}
}
tasks.publish { dependsOn(validatePom) }
// Workaround for maven publish plugin not setting up dependencies correctly.
// Taken from https://github.com/gradle/gradle/issues/26091#issuecomment-1798137734
val dependsOnTasks = mutableListOf<String>()
tasks.withType<AbstractPublishToMaven>().configureEach {
dependsOnTasks.add(name.replace("publish", "sign").replaceAfter("Publication", ""))
dependsOn(dependsOnTasks)
}
signing {
// provided as env vars `ORG_GRADLE_PROJECT_signingKey` and `ORG_GRADLE_PROJECT_signingPassword`
// in CI.
val signingKey =
(findProperty("signingKey") as String?)?.let {
Base64.getDecoder().decode(it).toString(StandardCharsets.US_ASCII)
}
val signingPassword = findProperty("signingPassword") as String?
if (signingKey != null && signingPassword != null) {
useInMemoryPgpKeys(signingKey, signingPassword)
}
publishing.publications.findByName("library")?.let { sign(it) }
}
artifacts {
project.tasks.findByName("javadocJar")?.let { archives(it) }
project.tasks.findByName("sourcesJar")?.let { archives(it) }
}
+1 -1
View File
@@ -1,6 +1,6 @@
name: main
title: Main Project
version: 0.33.0
version: 0.29.1
prerelease: false
nav:
- nav.adoc
+3 -3
View File
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 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.
@@ -16,8 +16,8 @@
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
plugins {
id("pklAllProjects")
id("pklKotlinTest")
pklAllProjects
pklKotlinTest
}
sourceSets {
Loaded 100 of 3589 files, more files were not shown because too many files have changed in this diff. Show more