Skip to content

Commit 2ada6e5

Browse files
committed
Resolve relative paths against project dir, simple name against PWD
1 parent 2ba0d5b commit 2ada6e5

File tree

10 files changed

+314
-99
lines changed

10 files changed

+314
-99
lines changed

docs/modules/release-notes/pages/0.31.adoc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@ Things to watch out for when upgrading.
3737
The following changes have been made when loading external readers:
3838

3939
1. Executable paths declared inside a PklProject file are resolved relative to the enclosing directory, instead of the current working directory.
40-
2. In a PklProject, the `externalModuleReaders` and `externalResourceReaders` properties of `evaluatorSettings` cannot be set in a non-file project.
41-
3. 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. +
40+
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. +
4241
+
4342
This makes this behavior consistent with how other settings work.
4443

pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/CliCommand.kt

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ abstract class CliCommand(protected val cliOptions: CliBaseOptions) {
106106
}
107107

108108
private val evaluatorSettings: PklEvaluatorSettings? by lazy {
109-
if (cliOptions.omitProjectSettings) null else project?.evaluatorSettings
109+
if (cliOptions.omitProjectSettings) null else project?.resolvedEvaluatorSettings
110110
}
111111

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

171171
private val proxyAddress: URI? by lazy {
172-
cliOptions.httpProxy
173-
?: project?.evaluatorSettings?.http?.proxy?.address
174-
?: settings.http?.proxy?.address
172+
cliOptions.httpProxy ?: evaluatorSettings?.http?.proxy?.address ?: settings.http?.proxy?.address
175173
}
176174

177175
private val noProxy: List<String>? by lazy {
178176
cliOptions.httpNoProxy
179-
?: project?.evaluatorSettings?.http?.proxy?.noProxy
177+
?: evaluatorSettings?.http?.proxy?.noProxy
180178
?: settings.http?.proxy?.noProxy
181179
}
182180

183181
private val httpRewrites: Map<URI, URI>? by lazy {
184-
cliOptions.httpRewrites
185-
?: project?.evaluatorSettings?.http?.rewrites
186-
?: settings.http?.rewrites()
182+
cliOptions.httpRewrites ?: evaluatorSettings?.http?.rewrites ?: settings.http?.rewrites()
187183
}
188184

189185
protected val externalModuleReaders: Map<String, PklEvaluatorSettings.ExternalReader> by lazy {
190-
cliOptions.externalModuleReaders ?: project?.evaluatorSettings?.externalModuleReaders ?: mapOf()
186+
cliOptions.externalModuleReaders ?: evaluatorSettings?.externalModuleReaders ?: mapOf()
191187
}
192188

193189
protected val externalResourceReaders: Map<String, PklEvaluatorSettings.ExternalReader> by lazy {
194-
cliOptions.externalResourceReaders
195-
?: project?.evaluatorSettings?.externalResourceReaders
196-
?: mapOf()
190+
cliOptions.externalResourceReaders ?: evaluatorSettings?.externalResourceReaders ?: mapOf()
197191
}
198192

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

209203
private val traceMode: TraceMode by lazy {
210-
cliOptions.traceMode ?: project?.evaluatorSettings?.traceMode ?: TraceMode.COMPACT
204+
cliOptions.traceMode ?: evaluatorSettings?.traceMode ?: TraceMode.COMPACT
211205
}
212206

213207
private fun HttpClient.Builder.addDefaultCliCertificates() {

pkl-core/src/main/java/org/pkl/core/EvaluatorBuilder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
2+
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -475,7 +475,7 @@ public TraceMode getTraceMode() {
475475
*/
476476
public EvaluatorBuilder applyFromProject(Project project) {
477477
this.dependencies = project.getDependencies();
478-
var settings = project.getEvaluatorSettings();
478+
var settings = project.getResolvedEvaluatorSettings();
479479
if (securityManager != null) {
480480
throw new IllegalStateException(
481481
"Cannot call both `setSecurityManager` and `setProject`, because both define security manager settings. Call `setProjectOnly` if the security manager is desired.");

pkl-core/src/main/java/org/pkl/core/evaluatorSettings/PklEvaluatorSettings.java

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,13 @@
1717

1818
import java.net.URI;
1919
import java.net.URISyntaxException;
20-
import java.nio.file.Files;
2120
import java.nio.file.Path;
2221
import java.util.Collections;
2322
import java.util.HashMap;
2423
import java.util.List;
2524
import java.util.Map;
2625
import java.util.Map.Entry;
2726
import java.util.Objects;
28-
import java.util.function.BiFunction;
2927
import java.util.regex.Pattern;
3028
import java.util.stream.Collectors;
3129
import org.pkl.core.Duration;
@@ -56,17 +54,13 @@ public record PklEvaluatorSettings(
5654

5755
/** Initializes a {@link PklEvaluatorSettings} from a raw object representation. */
5856
@SuppressWarnings("unchecked")
59-
public static PklEvaluatorSettings parse(
60-
Value input, BiFunction<String, String, Path> pathNormalizer) {
57+
public static PklEvaluatorSettings parse(Value input) {
6158
if (!(input instanceof PObject pSettings)) {
6259
throw PklBugException.unreachableCode();
6360
}
6461

6562
var moduleCacheDirStr = (String) pSettings.get("moduleCacheDir");
66-
var moduleCacheDir =
67-
moduleCacheDirStr == null
68-
? null
69-
: pathNormalizer.apply(moduleCacheDirStr, "moduleCacheDir");
63+
var moduleCacheDir = moduleCacheDirStr == null ? null : Path.of(moduleCacheDirStr).normalize();
7064

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

8377
var modulePathStrs = (List<String>) pSettings.get("modulePath");
84-
var modulePath =
85-
modulePathStrs == null
86-
? null
87-
: modulePathStrs.stream().map(it -> pathNormalizer.apply(it, "modulePath")).toList();
78+
var modulePath = modulePathStrs == null ? null : modulePathStrs.stream().map(Path::of).toList();
8879

8980
var rootDirStr = (String) pSettings.get("rootDir");
90-
var rootDir = rootDirStr == null ? null : pathNormalizer.apply(rootDirStr, "rootDir");
81+
var rootDir = rootDirStr == null ? null : Path.of(rootDirStr).normalize();
9182

9283
var externalModuleReadersRaw = (Map<String, Value>) pSettings.get("externalModuleReaders");
9384
var externalModuleReaders =
@@ -96,8 +87,7 @@ public static PklEvaluatorSettings parse(
9687
: externalModuleReadersRaw.entrySet().stream()
9788
.collect(
9889
Collectors.toMap(
99-
Entry::getKey,
100-
entry -> ExternalReader.parse(entry.getValue(), pathNormalizer)));
90+
Entry::getKey, entry -> ExternalReader.parse(entry.getValue())));
10191

10292
var externalResourceReadersRaw = (Map<String, Value>) pSettings.get("externalResourceReaders");
10393
var externalResourceReaders =
@@ -106,8 +96,7 @@ public static PklEvaluatorSettings parse(
10696
: externalResourceReadersRaw.entrySet().stream()
10797
.collect(
10898
Collectors.toMap(
109-
Entry::getKey,
110-
entry -> ExternalReader.parse(entry.getValue(), pathNormalizer)));
99+
Entry::getKey, entry -> ExternalReader.parse(entry.getValue())));
111100

112101
var color = (String) pSettings.get("color");
113102
var traceMode = (String) pSettings.get("traceMode");
@@ -193,15 +182,10 @@ public static Proxy create(@Nullable String address, @Nullable List<String> noPr
193182

194183
public record ExternalReader(String executable, @Nullable List<String> arguments) {
195184
@SuppressWarnings("unchecked")
196-
public static ExternalReader parse(
197-
Value input, BiFunction<String, String, Path> pathNormalizer) {
185+
public static ExternalReader parse(Value input) {
198186
if (input instanceof PObject externalReader) {
199187
var executable = (String) externalReader.getProperty("executable");
200-
var executablePath = pathNormalizer.apply(executable, "executable");
201188
var arguments = (List<String>) externalReader.get("arguments");
202-
if (Files.exists(executablePath)) {
203-
return new ExternalReader(executablePath.toString(), arguments);
204-
}
205189
return new ExternalReader(executable, arguments);
206190
}
207191
throw PklBugException.unreachableCode();

pkl-core/src/main/java/org/pkl/core/externalreader/ExternalReaderProcessImpl.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
2+
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -33,6 +33,7 @@
3333
import org.pkl.core.messaging.MessageTransports;
3434
import org.pkl.core.messaging.ProtocolException;
3535
import org.pkl.core.util.ErrorMessages;
36+
import org.pkl.core.util.IoUtils;
3637
import org.pkl.core.util.LateInit;
3738
import org.pkl.core.util.Nullable;
3839

@@ -85,6 +86,17 @@ public ExternalResourceResolver getResourceResolver(long evaluatorId)
8586
return ExternalResourceResolver.of(getTransport(), evaluatorId);
8687
}
8788

89+
private @Nullable String getExecutablePath(String executable) {
90+
if (executable.contains("/") || executable.contains("\"")) {
91+
return executable;
92+
}
93+
var resolved = IoUtils.findExecutableOnPath(executable);
94+
if (resolved != null) {
95+
return resolved.toAbsolutePath().toString();
96+
}
97+
return null;
98+
}
99+
88100
private MessageTransport getTransport() throws ExternalReaderProcessException {
89101
synchronized (lock) {
90102
if (closed) {
@@ -100,9 +112,13 @@ private MessageTransport getTransport() throws ExternalReaderProcessException {
100112
}
101113
}
102114

103-
// This relies on Java/OS behavior around PATH resolution, absolute/relative paths, etc.
104115
var command = new ArrayList<String>();
105-
command.add(spec.executable());
116+
var executable = getExecutablePath(spec.executable());
117+
if (executable == null) {
118+
throw new ExternalReaderProcessException(
119+
ErrorMessages.create("cannotFindCommand", spec.executable()));
120+
}
121+
command.add(executable);
106122
if (spec.arguments() != null) {
107123
command.addAll(spec.arguments());
108124
}

pkl-core/src/main/java/org/pkl/core/project/Project.java

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
2+
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -62,6 +62,7 @@ public final class Project {
6262
private final @Nullable Package pkg;
6363
private final DeclaredDependencies dependencies;
6464
private final PklEvaluatorSettings evaluatorSettings;
65+
private final PklEvaluatorSettings resolvedEvaluatorSettings;
6566
private final URI projectFileUri;
6667
private final URI projectBaseUri;
6768
private final List<URI> tests;
@@ -278,9 +279,14 @@ public static Project parseProject(PObject module) throws URISyntaxException {
278279
getProperty(
279280
module,
280281
"evaluatorSettings",
281-
(settings) ->
282-
PklEvaluatorSettings.parse(
283-
(Value) settings, (it, name) -> resolveNullablePath(it, projectBaseUri, name)));
282+
(settings) -> PklEvaluatorSettings.parse((Value) settings));
283+
284+
var resolvedEvaluatorSettings =
285+
getProperty(
286+
module,
287+
"resolvedEvaluatorSettings",
288+
(settings) -> PklEvaluatorSettings.parse((Value) settings));
289+
284290
@SuppressWarnings("unchecked")
285291
var testPathStrs = (List<String>) getProperty(module, "tests");
286292
var tests =
@@ -293,6 +299,7 @@ public static Project parseProject(PObject module) throws URISyntaxException {
293299
pkg,
294300
dependencies,
295301
evaluatorSettings,
302+
resolvedEvaluatorSettings,
296303
projectFileUri,
297304
projectBaseUri,
298305
tests,
@@ -348,24 +355,6 @@ private static <T> T getProperty(PObject settings, String propertyName, Function
348355
return new URI((String) value);
349356
}
350357

351-
/**
352-
* Resolve a path string against projectBaseUri.
353-
*
354-
* @throws PackageLoadError if projectBaseUri is not a {@code file:} URI.
355-
*/
356-
private static @Nullable Path resolveNullablePath(
357-
@Nullable String path, URI projectBaseUri, String propertyName) {
358-
if (path == null) {
359-
return null;
360-
}
361-
try {
362-
return Path.of(projectBaseUri).resolve(path).normalize();
363-
} catch (FileSystemNotFoundException e) {
364-
throw new PackageLoadError(
365-
"relativePathPropertyDefinedByProjectFromNonFileUri", projectBaseUri, propertyName);
366-
}
367-
}
368-
369358
@SuppressWarnings("unchecked")
370359
private static Package parsePackage(PObject pObj) throws URISyntaxException {
371360
var name = (String) pObj.getProperty("name");
@@ -407,6 +396,7 @@ private Project(
407396
@Nullable Package pkg,
408397
DeclaredDependencies dependencies,
409398
PklEvaluatorSettings evaluatorSettings,
399+
PklEvaluatorSettings resolvedEvaluatorSettings,
410400
URI projectFileUri,
411401
URI projectBaseUri,
412402
List<URI> tests,
@@ -415,6 +405,7 @@ private Project(
415405
this.pkg = pkg;
416406
this.dependencies = dependencies;
417407
this.evaluatorSettings = evaluatorSettings;
408+
this.resolvedEvaluatorSettings = resolvedEvaluatorSettings;
418409
this.projectFileUri = projectFileUri;
419410
this.projectBaseUri = projectBaseUri;
420411
this.tests = tests;
@@ -436,6 +427,15 @@ public PklEvaluatorSettings getEvaluatorSettings() {
436427
return evaluatorSettings;
437428
}
438429

430+
/**
431+
* The evaluator settings whose paths have been resolved against the project dir.
432+
*
433+
* @since 0.31.0
434+
*/
435+
public PklEvaluatorSettings getResolvedEvaluatorSettings() {
436+
return resolvedEvaluatorSettings;
437+
}
438+
439439
public URI getProjectFileUri() {
440440
return projectFileUri;
441441
}
@@ -483,6 +483,7 @@ public Map<String, Project> getLocalProjectDependencies() {
483483
return localProjectDependencies;
484484
}
485485

486+
@SuppressWarnings("unused")
486487
public URI getProjectBaseUri() {
487488
return projectBaseUri;
488489
}

pkl-core/src/main/java/org/pkl/core/util/IoUtils.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
2+
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616
package org.pkl.core.util;
1717

1818
import com.oracle.truffle.api.TruffleOptions;
19+
import java.io.File;
1920
import java.io.FileNotFoundException;
2021
import java.io.IOException;
2122
import java.io.InputStream;
@@ -854,4 +855,22 @@ public static void validateRewriteRule(URI rewrite) {
854855
"Rewrite rule must end with '/', but was '%s'".formatted(rewrite));
855856
}
856857
}
858+
859+
public static @Nullable Path findExecutableOnPath(String executable) {
860+
var pathEnvVar = System.getenv("PATH");
861+
if (pathEnvVar == null) {
862+
return null;
863+
}
864+
var extensions = isWindows() ? List.of(".cmd", ".bat", ".exe", ".dll") : List.of("");
865+
var pathDirs = pathEnvVar.split(File.pathSeparator);
866+
for (var dir : pathDirs) {
867+
for (var extension : extensions) {
868+
var candidate = Path.of(dir, executable + extension);
869+
if (Files.exists(candidate) && Files.isExecutable(candidate)) {
870+
return candidate;
871+
}
872+
}
873+
}
874+
return null;
875+
}
857876
}

0 commit comments

Comments
 (0)