Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion docs/modules/release-notes/pages/0.31.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,14 @@ The following APIs have been added:

Things to watch out for when upgrading.

=== XXX
=== Rule changes when loading external readers

The following changes have been made when loading external readers:

1. Executable paths declared inside a PklProject file are resolved relative to the enclosing directory, instead of the current working directory.
2. The `--external-module-reader` and `--external-resource-reader` CLI flags will _replace_ any external readers otherwise configured within a PklProject, instead of add to it. +
+
This makes this behavior consistent with how other settings work.

== Miscellaneous [small]#🐸#

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
* 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.
Expand Down Expand Up @@ -145,10 +145,10 @@ data class CliBaseOptions(
val httpRewrites: Map<URI, URI>? = null,

/** External module reader process specs */
val externalModuleReaders: Map<String, ExternalReader> = mapOf(),
val externalModuleReaders: Map<String, ExternalReader>? = null,

/** External resource reader process specs */
val externalResourceReaders: Map<String, ExternalReader> = mapOf(),
val externalResourceReaders: Map<String, ExternalReader>? = null,

/** Defines options for the formatting of calls to the trace() method. */
val traceMode: TraceMode? = null,
Expand Down
26 changes: 10 additions & 16 deletions pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/CliCommand.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
* 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.
Expand Down Expand Up @@ -106,7 +106,7 @@ abstract class CliCommand(protected val cliOptions: CliBaseOptions) {
}

private val evaluatorSettings: PklEvaluatorSettings? by lazy {
if (cliOptions.omitProjectSettings) null else project?.evaluatorSettings
if (cliOptions.omitProjectSettings) null else project?.resolvedEvaluatorSettings
}

protected val allowedModules: List<Pattern> by lazy {
Expand Down Expand Up @@ -169,31 +169,25 @@ abstract class CliCommand(protected val cliOptions: CliBaseOptions) {
protected val useColor: Boolean by lazy { cliOptions.color?.hasColor() ?: false }

private val proxyAddress: URI? by lazy {
cliOptions.httpProxy
?: project?.evaluatorSettings?.http?.proxy?.address
?: settings.http?.proxy?.address
cliOptions.httpProxy ?: evaluatorSettings?.http?.proxy?.address ?: settings.http?.proxy?.address
}

private val noProxy: List<String>? by lazy {
cliOptions.httpNoProxy
?: project?.evaluatorSettings?.http?.proxy?.noProxy
?: evaluatorSettings?.http?.proxy?.noProxy
?: settings.http?.proxy?.noProxy
}

private val httpRewrites: Map<URI, URI>? by lazy {
cliOptions.httpRewrites
?: project?.evaluatorSettings?.http?.rewrites
?: settings.http?.rewrites()
cliOptions.httpRewrites ?: evaluatorSettings?.http?.rewrites ?: settings.http?.rewrites()
}

private val externalModuleReaders: Map<String, PklEvaluatorSettings.ExternalReader> by lazy {
(project?.evaluatorSettings?.externalModuleReaders ?: emptyMap()) +
cliOptions.externalModuleReaders
protected val externalModuleReaders: Map<String, PklEvaluatorSettings.ExternalReader> by lazy {
cliOptions.externalModuleReaders ?: evaluatorSettings?.externalModuleReaders ?: mapOf()
}

private val externalResourceReaders: Map<String, PklEvaluatorSettings.ExternalReader> by lazy {
(project?.evaluatorSettings?.externalResourceReaders ?: emptyMap()) +
cliOptions.externalResourceReaders
protected val externalResourceReaders: Map<String, PklEvaluatorSettings.ExternalReader> by lazy {
cliOptions.externalResourceReaders ?: evaluatorSettings?.externalResourceReaders ?: mapOf()
}

private val externalProcesses:
Expand All @@ -207,7 +201,7 @@ abstract class CliCommand(protected val cliOptions: CliBaseOptions) {
}

private val traceMode: TraceMode by lazy {
cliOptions.traceMode ?: project?.evaluatorSettings?.traceMode ?: TraceMode.COMPACT
cliOptions.traceMode ?: evaluatorSettings?.traceMode ?: TraceMode.COMPACT
}

private fun HttpClient.Builder.addDefaultCliCertificates() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
* 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.
Expand Down Expand Up @@ -340,8 +340,8 @@ class BaseOptions : OptionGroup() {
httpProxy = proxy,
httpNoProxy = noProxy,
httpRewrites = httpRewrites.ifEmpty { null },
externalModuleReaders = externalModuleReaders,
externalResourceReaders = externalResourceReaders,
externalModuleReaders = externalModuleReaders.ifEmpty { null },
externalResourceReaders = externalResourceReaders.ifEmpty { null },
traceMode = traceMode,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,15 @@
package org.pkl.commons.cli

import com.github.ajalt.clikt.core.parse
import java.nio.file.Path
import kotlin.collections.mapOf
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import org.pkl.commons.cli.commands.BaseCommand
import org.pkl.commons.writeString
import org.pkl.core.SecurityManagers
import org.pkl.core.evaluatorSettings.PklEvaluatorSettings

class CliCommandTest {

Expand All @@ -28,6 +33,8 @@ class CliCommandTest {

val myAllowedResources = allowedResources
val myAllowedModules = allowedModules
val myExternalModuleReaders = externalModuleReaders
val myExternalResourceReaders = externalResourceReaders
}

private val cmd =
Expand Down Expand Up @@ -68,4 +75,32 @@ class CliCommandTest {
listOf("\\Qscheme1:\\E", "\\Qscheme2:\\E", "\\Qscheme+ext:\\E")
)
}

@Test
fun `--external-module-reader blows away PklProject externalModuleReaders`(
@TempDir tempDir: Path
) {
tempDir
.resolve("PklProject")
.writeString(
// language=pkl
"""
amends "pkl:Project"

evaluatorSettings {
externalModuleReaders {
["foo"] {
executable = "foo"
}
}
}
"""
.trimIndent()
)
cmd.parse(arrayOf("--external-module-reader", "bar=bar"))
val opts = cmd.baseOptions.baseOptions(emptyList(), null, true)
val cliTest = CliTest(opts)
assertThat(cliTest.myExternalModuleReaders)
.isEqualTo(mapOf("bar" to PklEvaluatorSettings.ExternalReader("bar", listOf())))
}
}
57 changes: 55 additions & 2 deletions pkl-core/pkl-core.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
* 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.
Expand All @@ -24,7 +24,16 @@ plugins {
idea
}

val generatorSourceSet = sourceSets.register("generator")
val generatorSourceSet: NamedDomainObjectProvider<SourceSet> = sourceSets.register("generator")

val externalReaderFixtureSourceSet: NamedDomainObjectProvider<SourceSet> =
sourceSets.register("externalReaderFixture") {
compileClasspath += sourceSets.test.get().output + sourceSets.test.get().compileClasspath
runtimeClasspath += sourceSets.test.get().output + sourceSets.test.get().runtimeClasspath
}

val externalReaderFixtureImplementation: Configuration by
configurations.getting { extendsFrom(configurations.testImplementation.get()) }

idea {
module {
Expand Down Expand Up @@ -110,8 +119,46 @@ tasks.compileJava { options.generatedSourceOutputDirectory.set(file("generated/t

tasks.compileKotlin { enabled = false }

val externalReaderFixture by
tasks.registering {
group = "build"
dependsOn(tasks.named("compileExternalReaderFixtureJava"))
inputs.files(externalReaderFixtureSourceSet.map { it.output })
val fileName = if (buildInfo.os.isWindows) "externalreader.bat" else "externalreader"
val outputFile = layout.buildDirectory.file("fixtures/$fileName")
outputs.file(outputFile)
doLast {
val classpath = externalReaderFixtureSourceSet.get().runtimeClasspath.asPath
val scriptContent =
if (buildInfo.os.isWindows) {
"""
@echo off
java -cp $classpath org.pkl.core.externalreaderfixture.Main
"""
.trimIndent()
} else {
"""
#!/usr/bin/env bash

java -cp $classpath org.pkl.core.externalreaderfixture.Main
"""
.trimIndent()
}

outputFile.get().asFile.writeText(scriptContent)
outputFile.get().asFile.setExecutable(true)
println("Created external reader ${outputFile.get().asFile.absolutePath}")
}
}

tasks.test {
configureTest()
dependsOn(externalReaderFixture)
environment(
"PATH",
listOf(System.getenv("PATH"), layout.buildDirectory.dir("fixtures/").get())
.joinToString(File.pathSeparator),
)
useJUnitPlatform {
excludeEngines("MacAmd64LanguageSnippetTestsEngine")
excludeEngines("MacAarch64LanguageSnippetTestsEngine")
Expand All @@ -123,6 +170,12 @@ tasks.test {

// testing very large lists requires more memory than the default 512m!
maxHeapSize = "1g"

dependsOn(externalReaderFixture)
systemProperty(
"org.pkl.core.testExternalReaderPath",
externalReaderFixture.map { it.outputs.files.singleFile.absolutePath },
)
}

val testJavaExecutable by
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* 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:JvmName("Main")

package org.pkl.core.externalreaderfixture

import java.net.URI
import org.pkl.core.externalreader.ExternalModuleReader
import org.pkl.core.externalreader.ExternalReaderClient
import org.pkl.core.externalreader.ExternalReaderMessagePackDecoder
import org.pkl.core.externalreader.ExternalReaderMessagePackEncoder
import org.pkl.core.externalreader.ExternalResourceReader
import org.pkl.core.messaging.MessageTransports
import org.pkl.core.module.PathElement

object ModuleReader : ExternalModuleReader {
override val isLocal: Boolean = true

override fun read(uri: URI): String = "hello"

override val scheme: String = "foo"

override val hasHierarchicalUris: Boolean = false

override val isGlobbable: Boolean = false

override fun listElements(uri: URI): List<PathElement> {
throw NotImplementedError()
}
}

object ResourceReader : ExternalResourceReader {
override fun read(uri: URI): ByteArray = "hello".toByteArray()

override val scheme: String = "foo"

override val hasHierarchicalUris: Boolean = false

override val isGlobbable: Boolean = false

override fun listElements(uri: URI): List<PathElement> {
throw NotImplementedError()
}
}

fun main() {
val transport =
MessageTransports.stream(
ExternalReaderMessagePackDecoder(System.`in`),
ExternalReaderMessagePackEncoder(System.out),
) {}
val client = ExternalReaderClient(listOf(ModuleReader), listOf(ResourceReader), transport)
client.run()
}
4 changes: 2 additions & 2 deletions pkl-core/src/main/java/org/pkl/core/EvaluatorBuilder.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
* 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.
Expand Down Expand Up @@ -475,7 +475,7 @@ public TraceMode getTraceMode() {
*/
public EvaluatorBuilder applyFromProject(Project project) {
this.dependencies = project.getDependencies();
var settings = project.getEvaluatorSettings();
var settings = project.getResolvedEvaluatorSettings();
if (securityManager != null) {
throw new IllegalStateException(
"Cannot call both `setSecurityManager` and `setProject`, because both define security manager settings. Call `setProjectOnly` if the security manager is desired.");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
* 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.
Expand All @@ -24,7 +24,6 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.pkl.core.Duration;
Expand Down Expand Up @@ -55,17 +54,13 @@ public record PklEvaluatorSettings(

/** Initializes a {@link PklEvaluatorSettings} from a raw object representation. */
@SuppressWarnings("unchecked")
public static PklEvaluatorSettings parse(
Value input, BiFunction<? super String, ? super String, Path> pathNormalizer) {
public static PklEvaluatorSettings parse(Value input) {
if (!(input instanceof PObject pSettings)) {
throw PklBugException.unreachableCode();
}

var moduleCacheDirStr = (String) pSettings.get("moduleCacheDir");
var moduleCacheDir =
moduleCacheDirStr == null
? null
: pathNormalizer.apply(moduleCacheDirStr, "moduleCacheDir");
var moduleCacheDir = moduleCacheDirStr == null ? null : Path.of(moduleCacheDirStr).normalize();

var allowedModulesStrs = (List<String>) pSettings.get("allowedModules");
var allowedModules =
Expand All @@ -80,13 +75,10 @@ public static PklEvaluatorSettings parse(
: allowedResourcesStrs.stream().map(Pattern::compile).toList();

var modulePathStrs = (List<String>) pSettings.get("modulePath");
var modulePath =
modulePathStrs == null
? null
: modulePathStrs.stream().map(it -> pathNormalizer.apply(it, "modulePath")).toList();
var modulePath = modulePathStrs == null ? null : modulePathStrs.stream().map(Path::of).toList();

var rootDirStr = (String) pSettings.get("rootDir");
var rootDir = rootDirStr == null ? null : pathNormalizer.apply(rootDirStr, "rootDir");
var rootDir = rootDirStr == null ? null : Path.of(rootDirStr).normalize();

var externalModuleReadersRaw = (Map<String, Value>) pSettings.get("externalModuleReaders");
var externalModuleReaders =
Expand Down
Loading
Loading