Skip to content
Merged
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
14 changes: 12 additions & 2 deletions pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/CliCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ abstract class CliCommand(protected val cliOptions: CliBaseOptions) {
protected val settings: PklSettings by lazy {
try {
if (cliOptions.normalizedSettingsModule != null) {
PklSettings.load(ModuleSource.uri(cliOptions.normalizedSettingsModule))
PklSettings.load(ModuleSource.uri(cliOptions.normalizedSettingsModule), cliModuleCacheDir)
} else {
PklSettings.loadFromSystem()
PklSettings.loadFromSystem(cliModuleCacheDir)
}
} catch (e: PklException) {
// do not use `errorRenderer` because it depends on `settings`
Expand Down Expand Up @@ -107,9 +107,19 @@ abstract class CliCommand(protected val cliOptions: CliBaseOptions) {
stackFrameTransformer,
envVars,
cliOptions.powerAssertionsEnabled,
cliModuleCacheDir,
)
}

/**
* The module cache dir as determined by CLI options alone. Used to evaluate the settings and
* project files, which are loaded before project evaluator settings are known.
*/
private val cliModuleCacheDir: Path? by lazy {
if (cliOptions.noCache) null
else cliOptions.normalizedModuleCacheDir ?: IoUtils.getSystemModuleCacheDir()
}

private val evaluatorSettings: PklEvaluatorSettings? by lazy {
@Suppress("PklCliDirectProjectEvaluatorSettingsAccess")
if (cliOptions.omitProjectSettings) null else project?.resolvedEvaluatorSettings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import org.junit.jupiter.api.condition.JRE
import org.junit.jupiter.api.io.TempDir
import org.pkl.commons.cli.commands.BaseCommand
import org.pkl.commons.cli.commands.ProjectOptions
import org.pkl.commons.test.PackageServer
import org.pkl.commons.writeString
import org.pkl.core.SecurityManagers
import org.pkl.core.evaluatorSettings.PklEvaluatorSettings
Expand Down Expand Up @@ -134,7 +135,9 @@ class CliCommandTest {
@DisabledOnJre(JRE.JAVA_22, JRE.JAVA_23, JRE.JAVA_24)
fun `test that --omit-project-settings actually omits project settings`(@TempDir tempDir: Path) {
val project = tempDir.resolve("PklProject").writeString(projectWithAllEvaluatorSettings)
cmd.parse(arrayOf("--working-dir=$tempDir", "--omit-project-settings"))
cmd.parse(
arrayOf("--working-dir=$tempDir", "--omit-project-settings", "--settings=pkl:settings")
)
val opts =
cmd.baseOptions.baseOptions(listOf(project.toUri()), cmd.projectOptions, testMode = true)
val cliTest = CliTest(opts)
Expand Down Expand Up @@ -200,4 +203,31 @@ class CliCommandTest {
assertThat(cliTest.myExternalModuleReaders)
.isEqualTo(mapOf("bar" to PklEvaluatorSettings.ExternalReader("bar", listOf(), null)))
}

@Test
fun `--cache-dir is used when loading the project`(@TempDir tempDir: Path) {
val cacheDir = tempDir.resolve("cache").also(PackageServer::populateCacheDir)
tempDir
.resolve("PklProject")
.writeString(
// language=pkl
"""
amends "pkl:Project"

import "package://localhost:0/fruit@1.0.5#/catalog/apple.pkl"

evaluatorSettings {
externalProperties {
["fruit"] = apple.name
}
}
"""
.trimIndent()
)
cmd.parse(arrayOf("--working-dir=$tempDir", "--cache-dir=$cacheDir"))
val opts = cmd.baseOptions.baseOptions(emptyList(), cmd.projectOptions, testMode = true)
val cliTest = CliTest(opts)
assertThat(cliTest.myProjectEvaluatorSettings!!.externalProperties)
.containsEntry("fruit", "Apple")
}
}
29 changes: 29 additions & 0 deletions pkl-core/src/main/java/org/pkl/core/project/Project.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,34 @@ public static Project loadFromPath(
StackFrameTransformer stackFrameTransformer,
Map<String, String> envVars,
boolean powerAssertionsEnabled) {
return loadFromPath(
path,
securityManager,
timeout,
stackFrameTransformer,
envVars,
powerAssertionsEnabled,
IoUtils.getSystemModuleCacheDir());
}

/**
* Loads Project data from the given {@link Path}.
*
* <p>Evaluates a module's {@code output.value} to allow for embedding a project within a
* template.
*
* @param moduleCacheDir the directory where packages imported by the project file are cached;
* {@code null} disables caching
* @throws PklException if an error occurred while evaluating the project file.
*/
public static Project loadFromPath(
Path path,
SecurityManager securityManager,
java.time.@Nullable Duration timeout,
StackFrameTransformer stackFrameTransformer,
Map<String, String> envVars,
boolean powerAssertionsEnabled,
@Nullable Path moduleCacheDir) {
try (var evaluator =
EvaluatorBuilder.unconfigured()
.setSecurityManager(securityManager)
Expand All @@ -97,6 +125,7 @@ public static Project loadFromPath(
.addEnvironmentVariables(envVars)
.setTimeout(timeout)
.setPowerAssertionsEnabled(powerAssertionsEnabled)
.setModuleCacheDir(moduleCacheDir)
.build()) {
return load(evaluator, ModuleSource.path(path));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@ public String evaluatePath(Path modulePath, ExecutorSpiOptions options) {
securityManager,
null,
transformer,
options.getEnvironmentVariables());
options.getEnvironmentVariables(),
false,
options.getModuleCacheDir());
builder.setProjectDependencies(project.getDependencies());
}

Expand Down
23 changes: 22 additions & 1 deletion pkl-core/src/main/java/org/pkl/core/settings/PklSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,20 @@ public record PklSettings(Editor editor, PklEvaluatorSettings.@Nullable Http htt
* <p>If neither file exists, returns default settings defined by module {@code pkl.settings}.
*/
public static PklSettings loadFromSystem() throws VmEvalException {
return loadFromSystem(IoUtils.getSystemModuleCacheDir());
}

/**
* Loads the user settings file, using the given module cache directory.
*
* @param moduleCacheDir the directory where packages are cached; {@code null} disables caching
* @see #loadFromSystem()
*/
public static PklSettings loadFromSystem(@Nullable Path moduleCacheDir) throws VmEvalException {
var file = IoUtils.getSystemSettingsFile();
if (Files.exists(file)) {
DebugLogger.log("Loading settings file from " + file.normalize().toAbsolutePath());
return load(ModuleSource.path(file));
return load(ModuleSource.path(file), moduleCacheDir);
}
return defaultInstance;
}
Expand Down Expand Up @@ -89,6 +99,16 @@ private static PklSettings loadFromSettingsFile(Path path) throws VmEvalExceptio

/** Loads a settings file from the given path. */
public static PklSettings load(ModuleSource moduleSource) throws VmEvalException {
return load(moduleSource, IoUtils.getSystemModuleCacheDir());
}

/**
* Loads a settings file from the given path, using the given module cache directory.
*
* @param moduleCacheDir the directory where packages are cached; {@code null} disables caching
*/
public static PklSettings load(ModuleSource moduleSource, @Nullable Path moduleCacheDir)
throws VmEvalException {
try (var evaluator =
EvaluatorBuilder.unconfigured()
.setSecurityManager(
Expand All @@ -100,6 +120,7 @@ public static PklSettings load(ModuleSource moduleSource) throws VmEvalException
.addResourceReader(ResourceReaders.environmentVariable())
.addResourceReader(ResourceReaders.file())
.addEnvironmentVariables(System.getenv())
.setModuleCacheDir(moduleCacheDir)
.build()) {
var module = evaluator.evaluateOutputValueAs(moduleSource, PClassInfo.Settings);
return parseSettings(module, moduleSource);
Expand Down
52 changes: 52 additions & 0 deletions pkl-core/src/test/kotlin/org/pkl/core/project/ProjectTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -362,4 +362,56 @@ class ProjectTest {
assertThat(project.evaluatorSettings.externalModuleReaders?.get("foo")!!.executable())
.isEqualTo("my-command")
}

@Test
fun `loadFromPath uses the given module cache dir`(@TempDir tempDir: Path) {
val cacheDir = tempDir.resolve("cache").also(PackageServer::populateCacheDir)
val pklProject = tempDir.resolve("PklProject").writeString(PROJECT_IMPORTING_PACKAGE)
val project =
Project.loadFromPath(
pklProject,
SecurityManagers.defaultManager,
null,
StackFrameTransformers.empty,
mapOf(),
false,
cacheDir,
)
assertThat(project.evaluatorSettings.externalProperties).containsEntry("fruit", "Apple")
}

@Test
fun `loadFromPath does not use a cache if module cache dir is null`(@TempDir tempDir: Path) {
val pklProject = tempDir.resolve("PklProject").writeString(PROJECT_IMPORTING_PACKAGE)
assertThrows<PklException> {
Project.loadFromPath(
pklProject,
SecurityManagers.defaultManager,
null,
StackFrameTransformers.empty,
mapOf(),
false,
null,
)
}
}

companion object {
// `localhost:0` is never reachable without a test HTTP client, so this package can only be
// resolved from a pre-populated cache dir.
private val PROJECT_IMPORTING_PACKAGE =
// language=pkl
"""
amends "pkl:Project"

import "package://localhost:0/fruit@1.0.5#/catalog/apple.pkl"

evaluatorSettings {
externalProperties {
["fruit"] = apple.name
}
}
"""
.trimIndent()
}
}
Loading