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
5 changes: 5 additions & 0 deletions build-logic/src/main/kotlin/NativeImageBuild.kt
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,11 @@ abstract class NativeImageBuild : DefaultTask() {
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")
add("--no-fallback")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ at pkl.base#Module.output.text (https://github.com/apple/pkl/blob/e4d8c882d/stdl
<6> What Pkl evaluated to discover the error.

When Pkl prints source locations, it also prints clickable links for easy access.
For local files, it generates a link for your development environment (https://pkl-lang.org/main/current/pkl-cli/index.html#settings-file[configurable in `+~/.pkl/settings.pkl+`]).
For local files, it generates a link for your development environment (https://pkl-lang.org/main/current/pkl-cli/index.html#settings-file[configurable in `+~/.config/pkl/settings.pkl+`]).
For packages imported from elsewhere, if available, Pkl produces `https://` links to their repository.

Pkl complains about a _type constraint_.
Expand Down
27 changes: 23 additions & 4 deletions docs/modules/pkl-cli/pages/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1377,14 +1377,24 @@ it works as follows:
The Pkl settings file allows to customize the CLI experience.

A settings file is a Pkl module amending the `pkl.settings` standard library module.
Its default location is `~/.pkl/settings.pkl`.

Unless configured explicitly, Pkl will look in the following locations:

. `$XDG_CONFIG_HOME/pkl/settings.pkl`
. `%APPDATA%/pkl/settings.pkl` (on Windows only)
. `~/.config/pkl/settings.pkl`
. Subdirectory `pkl/settings.pkl` within one of the paths described by `$XDG_CONFIG_DIRS`
. `/etc/xdg/pkl/settings.pkl`
. `~/.pkl/settings.pkl` (legacy location used by Pkl 0.32 and lower)

To use a different settings file, set the `--settings` command line option, for example `--settings mysettings.pkl`.
To enforce default settings, use `--settings pkl:settings`.

The settings file is also honored by (and configurable through) the Gradle plugin and `CliEvaluator` API.

Here is a typical settings file:

.~/.pkl/settings.pkl
.~/.config/pkl/settings.pkl
[source%parsed,{pkl}]
----
amends "pkl:settings" // <1>
Expand All @@ -1406,10 +1416,19 @@ When making TLS requests, Pkl comes with its own set of {uri-certificates}[CA ce
These certificates can be overridden via either of the two options:

- Set them directly via the CLI option `--ca-certificates <path>`.
- Add them to a directory at path `~/.pkl/cacerts/`.
- Add them to a user directory.

If CA certificates are not explicitly configured, Pkl will look in the following locations:

. `$XDG_CONFIG_HOME/pkl/cacerts`
. `%APPDATA%/pkl/cacerts` (on Windows only)
. `~/.config/pkl/cacerts`
. Subdirectory `pkl/cacerts` within one of the paths described by `$XDG_CONFIG_DIRS`
. `/etc/xdg/pkl/cacerts`
. `~/.pkl/cacerts` (legacy location used by Pkl 0.32 and lower)

Both these options will *replace* the default CA certificates bundled with Pkl. +
The CLI option takes precedence over the certificates in `~/.pkl/cacerts/`. +
The CLI option takes precedence over the certificates in the cacerts directory. +
Certificates need to be X.509 certificates in PEM format.

[[http-proxy]]
Expand Down
10 changes: 8 additions & 2 deletions docs/modules/pkl-cli/partials/cli-common-options.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,15 @@ Possible values:
.--cache-dir
[%collapsible]
====
Default: `~/.pkl/cache` +
Example: `/path/to/module/cache/` +

The cache directory for storing packages.

If unset, defaults to the following locations:

. `$XDG_CACHE_HOME/pkl`
. `$LOCALAPPDATA/pkl/Cache` (on Windows only)
. `~/.cache/pkl` (if `$XDG_CACHE_HOME` and `$LOCALAPPDATA` are both unset)
====

.--no-cache
Expand Down Expand Up @@ -97,7 +103,7 @@ Any symlinks are resolved before this check is performed.
Default: (none) +
Example: `mySettings.pkl` +
File path of the Pkl settings file to use.
If not set, `~/.pkl/settings.pkl` or defaults specified in the `pkl.settings` standard library module are used.
If not set, `~/.config/pkl/settings.pkl` on Unix or `%APPDATA%/pkl/settings.pkl` on Windows (or the legacy `~/.pkl/settings.pkl`), or defaults specified in the `pkl.settings` standard library module are used.
====

.-t, --timeout
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ Default: `null` +
Example 1: `moduleCacheDir = layout.buildDirectory.dir("pkl-module-cache")` +
Example 2: `moduleCacheDir.fileValue file("/absolute/path/to/cache")` +
The cache directory for storing packages.
If `null`, defaults to `~/.pkl/cache`.
If `null`, defaults to `~/.cache/pkl` on Unix or `%LOCALAPPDATA%/pkl/Cache` on Windows.

This setting can also be configured using the `$XDG_CACHE_HOME` environment variable.
====

.color: Property<Boolean>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ Example: `settingsModule = layout.projectDirectory.file("mySettings.pkl")` +
The Pkl settings module to use.
This property accepts the same input types as the `sourceModules` property.

If `null`, `~/.pkl/settings.pkl` or defaults specified in the `pkl.settings` standard library module are used.
If `null`, `~/.config/pkl/settings.pkl` on Unix or `%APPDATA%/pkl/settings.pkl` on Windows (or the legacy `~/.pkl/settings.pkl`), or defaults specified in the `pkl.settings` standard library module are used.
====

include::../partials/gradle-common-properties.adoc[]
38 changes: 37 additions & 1 deletion docs/modules/release-notes/pages/0.33.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,43 @@ include::partial$intro.adoc[]

== Noteworthy [small]#🎶#

=== XXX
=== CLI Changes

==== Default file locations

For new setups, the CLI no longer stores anything under `~/.pkl` (https://github.com/apple/pkl/pull/1809[#1809]).

It uses XDG-style locations on Unix and Known Folder locations on Windows:

[cols="1,2,2,2",options="header"]
|===
| Concern | Unix (Linux/macOS) | Windows | Legacy fallback

| Package cache
| `~/.cache/pkl`
| `$LOCALAPPDATA/pkl/Cache`
| none
Comment on lines +28 to +31

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it might make sense to have a one-time "new cache not found, but old cache is present" copy/link migration to make some attempt to reuse existing cache dirs. Cost to re-build is low, but still non-zero, especially on slow connections.


| Settings file
| `~/.config/pkl/settings.pkl`
| `$APPDATA/pkl/settings.pkl`
| `~/.pkl/settings.pkl`

| CA certificates
| `~/.config/pkl/cacerts`
| `$APPDATA/pkl/cacerts`
| `~/.pkl/cacerts`

| REPL history
| `~/.local/state/pkl/repl-history`
| `$LOCALAPPDATA/pkl/repl-history`
| none
|===

On every OS, these locations can be overridden with XDG-style env vars.
For example, setting `XDG_CACHE_HOME` will configure the cache directory.

Note that the existing `~/.pkl/cache` directory is ignored, so Pkl will download packages to populate its cache if configured to do so.

== Breaking Changes [small]#💔#
Comment thread
bioball marked this conversation as resolved.

Expand Down
8 changes: 6 additions & 2 deletions pkl-cli/src/main/kotlin/org/pkl/cli/repl/Repl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@ internal class Repl(workingDir: Path, private val server: ReplServer, private va
}
completer(AggregateCompleter(CommandCompleter, FileCompleter(workingDir)))
option(Option.DISABLE_EVENT_EXPANSION, true)
variable(LineReader.HISTORY_FILE, (IoUtils.getPklHomeDir().resolve("repl-history")))
// Will be null if `user.home` property is not set.
// If so, don't bother writing repl history.
IoUtils.getReplHistoryFile()?.let { historyFile ->
variable(LineReader.HISTORY_FILE, historyFile)
}
}
.build()

Expand All @@ -80,7 +84,7 @@ internal class Repl(workingDir: Path, private val server: ReplServer, private va

fun run() {
// JLine 2 history file is incompatible with JLine 3
IoUtils.getPklHomeDir().resolve("repl-history.bin").deleteIfExists()
IoUtils.getLegacyPklHomeDir().resolve("repl-history.bin").deleteIfExists()

println(ReplMessages.welcome)
println()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,9 @@ data class CliBaseOptions(

/**
* The Pkl settings file to use. A settings file is a Pkl module amending the `pkl.settings`
* standard library module. If `null`, `~/.pkl/settings.pkl` (if present) or the defaults
* specified in the `pkl:settings` standard library module are used.
* standard library module. If `null`, `~/.config/pkl/settings.pkl` (falling back to the legacy
* `~/.pkl/settings.pkl`), or the defaults specified in the `pkl:settings` standard library
* module, are used.
*/
private val settings: URI? = null,

Expand Down Expand Up @@ -130,8 +131,9 @@ data class CliBaseOptions(
* The given files must contain [X.509](https://en.wikipedia.org/wiki/X.509) certificates in PEM
* format.
*
* If [caCertificates] is the empty list, the certificate files in `~/.pkl/cacerts/` are used. If
* `~/.pkl/cacerts/` does not exist or is empty, Pkl's built-in CA certificates are used.
* If [caCertificates] is the empty list, the certificate files in `~/.config/pkl/cacerts/` (or
* the legacy `~/.pkl/cacerts/`) are used. If that directory does not exist or is empty, Pkl's
* built-in CA certificates are used.
*/
val caCertificates: List<Path> = listOf(),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import java.net.URI
import java.nio.file.Files
import java.nio.file.Path
import java.util.regex.Pattern
import kotlin.io.path.absolutePathString
import kotlin.io.path.isRegularFile
import org.pkl.core.*
import org.pkl.core.evaluatorSettings.PklEvaluatorSettings
Expand All @@ -33,6 +34,7 @@ import org.pkl.core.project.Project
import org.pkl.core.resource.ResourceReader
import org.pkl.core.resource.ResourceReaders
import org.pkl.core.settings.PklSettings
import org.pkl.core.util.DebugLogger
import org.pkl.core.util.IoUtils

/** Building block for CLI commands. Configured programmatically to allow for embedding. */
Expand Down Expand Up @@ -69,7 +71,7 @@ abstract class CliCommand(protected val cliOptions: CliBaseOptions) {
if (cliOptions.normalizedSettingsModule != null) {
PklSettings.load(ModuleSource.uri(cliOptions.normalizedSettingsModule))
} else {
PklSettings.loadFromPklHomeDir()
PklSettings.loadFromSystem()
}
} catch (e: PklException) {
// do not use `errorRenderer` because it depends on `settings`
Expand Down Expand Up @@ -146,7 +148,7 @@ abstract class CliCommand(protected val cliOptions: CliBaseOptions) {
?: evaluatorSettings?.let { settings ->
if (settings.noCache == true) null else settings.moduleCacheDir
}
?: IoUtils.getDefaultModuleCacheDir()
?: IoUtils.getSystemModuleCacheDir()
}

protected val modulePath: List<Path> by lazy {
Expand Down Expand Up @@ -215,7 +217,7 @@ abstract class CliCommand(protected val cliOptions: CliBaseOptions) {
}

private fun HttpClient.Builder.addDefaultCliCertificates() {
val caCertsDir = IoUtils.getPklHomeDir().resolve("cacerts")
val caCertsDir = IoUtils.getSystemCaCertsDir()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change to cacerts means that the certs can now be silently diverted via an env var. I'm not sure this is a good call security-wise.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this any different from diverting the path of the settings.pkl via an env var? Both have security implications.

var certsAdded = false
if (Files.isDirectory(caCertsDir)) {
Files.list(caCertsDir)
Expand All @@ -225,7 +227,10 @@ abstract class CliCommand(protected val cliOptions: CliBaseOptions) {
addCertificates(cert)
}
}
if (!certsAdded) {
if (certsAdded) {
DebugLogger.log("Loading CA certificates from ${caCertsDir.normalize().absolutePathString()}")
} else {
DebugLogger.log("Using built-in CA certificates")
val defaultCerts =
this@CliCommand.javaClass.classLoader.getResourceAsStream(
"org/pkl/commons/cli/PklCARoots.pem"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ class CliCommandTest {
assertThat(cliTest.myRootDir).isNull()
assertThat(builder.environmentVariables).isEqualTo(System.getenv())
assertThat(builder.externalProperties).isEmpty()
assertThat(builder.moduleCacheDir).isEqualTo(IoUtils.getDefaultModuleCacheDir())
assertThat(builder.moduleCacheDir).isEqualTo(IoUtils.getSystemModuleCacheDir())
assertThat(cliTest.myModulePath).isEmpty()
assertThat(builder.color).isFalse
assertThat(cliTest.myProxyAddress).isNull()
Expand Down
2 changes: 1 addition & 1 deletion pkl-core/src/main/java/org/pkl/core/EvaluatorBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public final class EvaluatorBuilder {

private java.time.@Nullable Duration timeout;

private @Nullable Path moduleCacheDir = IoUtils.getDefaultModuleCacheDir();
private @Nullable Path moduleCacheDir = IoUtils.getSystemModuleCacheDir();

private @Nullable String outputFormat;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public synchronized VmTyped getOrLoad(
case "semver":
return SemVerModule.getModule();
case "settings":
// always needed if ~/.pkl/settings.pkl is present
// always needed if ~/.config/pkl/settings.pkl is present
return SettingsModule.getModule();
case "test":
return TestModule.getModule();
Expand Down
42 changes: 36 additions & 6 deletions pkl-core/src/main/java/org/pkl/core/settings/PklSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,32 +26,62 @@
import org.pkl.core.resource.ResourceReaders;
import org.pkl.core.runtime.VmEvalException;
import org.pkl.core.runtime.VmExceptionBuilder;
import org.pkl.core.util.DebugLogger;
import org.pkl.core.util.IoUtils;

/**
* Java representation of a Pkl settings file. A Pkl settings file is a Pkl module amending the
* {@literal pkl.settings} standard library module. To load a settings file, use one of the static
* {@code load} methods.
*/
// keep in sync with stdlib/settings.pkl
public record PklSettings(Editor editor, PklEvaluatorSettings.@Nullable Http http) {
// keep in sync with stdlib/settings.pkl
public static final PklSettings defaultInstance = new PklSettings(Editor.SYSTEM, null);

private static final List<Pattern> ALLOWED_MODULES =
List.of(Pattern.compile("pkl:"), Pattern.compile("file:"));

private static final List<Pattern> ALLOWED_RESOURCES =
List.of(Pattern.compile("env:"), Pattern.compile("file:"));

/**
* Loads the user settings file ({@literal ~/.pkl/settings.pkl}). If this file does not exist,
* returns default settings defined by module {@literal pkl.settings}.
* Loads the user settings file.
*
* <p>Prefers XDG_CONFIG_HOME (e.g. {@code ~/.config/pkl/settings.pkl}), falling back to the
* legacy {@code ~/.pkl/settings.pkl}.
*
* <p>If neither file exists, returns default settings defined by module {@code pkl.settings}.
*/
public static PklSettings loadFromSystem() 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 defaultInstance;
}

/**
* Loads the user settings file.
*
* @deprecated As of 0.33.0, use {@link #loadFromSystem()}, which now prefers {@code
* ~/.config/pkl/settings.pkl} over the legacy {@code ~/.pkl/settings.pkl}.
*/
@Deprecated(since = "0.33.0", forRemoval = true)
public static PklSettings loadFromPklHomeDir() throws VmEvalException {
return loadFromPklHomeDir(IoUtils.getPklHomeDir());
var candidate = IoUtils.getLegacyPklHomeDir().resolve("settings.pkl");
if (Files.exists(candidate)) {
return load(ModuleSource.path(candidate));
}
return defaultInstance;
}

/** For testing only. */
static PklSettings loadFromPklHomeDir(Path pklHomeDir) throws VmEvalException {
var path = pklHomeDir.resolve("settings.pkl");
static PklSettings loadFromSettingsDir(Path settingsDir) throws VmEvalException {
return loadFromSettingsFile(settingsDir.resolve("settings.pkl"));
}

private static PklSettings loadFromSettingsFile(Path path) throws VmEvalException {
return Files.exists(path)
? load(ModuleSource.path(path))
: new PklSettings(Editor.SYSTEM, null);
Expand Down
33 changes: 33 additions & 0 deletions pkl-core/src/main/java/org/pkl/core/util/BaseDirectories.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.core.util;

final class BaseDirectories {
public static final BaseDirectory config =
new BaseDirectory(
"XDG_CONFIG_HOME",
"XDG_CONFIG_DIRS",
"APPDATA",
null,
".config",
new String[] {"/etc/xdg"});

public static final BaseDirectory cache =
new BaseDirectory("XDG_CACHE_HOME", null, "LOCALAPPDATA", "Cache", ".cache", null);

public static final BaseDirectory state =
new BaseDirectory("XDG_STATE_HOME", null, "LOCALAPPDATA", null, ".local/state", null);
}
Loading
Loading