Pkl 0.27 Release Notes
Pkl 0.27 was released on November 5th, 2024.
The latest bugfix release is 0.27.0. (All Versions)
This release brings improvements in typechecking of Listing
and Mapping
, the ability to use readers from external processes, as well as a new import graph analyzer API.
The next release (0.28) is scheduled for February 2025.
Please send feedback and questions to GitHub Discussions, or submit an issue on Github.
Pkl is hosted on GitHub. To get started, follow Installation.
Highlights 💖
News you don’t want to miss.
Improved typechecking of Listing
and Mapping
types
Today, the typecheck listing: Listing<E>
immediately evaluates all listing elements to check that they have type E
.
Likewise, the typecheck mapping: Mapping<K, V>
immediately evaluates all mapping values to check that they have type V
.
For example, the typecheck listing: Listing<Bird>
proceeds as follows:
-
Check that
listing
has typeListing
-
Evaluate each listing element and check that it has type
Bird
This behavior is different from how the rest of Pkl works. Generally, Pkl only evaluates code that affects program output.
For example, consider the following program:
class Bird {
name: String
canFly: Boolean
}
local bird: Bird = new {
name = "Pidgy"
canFly = throw("uh oh")
}
birdName = bird.name
Even though bird.canFly
throws an error, the above program succeeds because bird.canFly
is not part of the program’s output and hence is never accessed (Note that bird
is a local property).
Typechecks of Mapping
and Listing
types have been changed to match this behavior.
Mapping and listing values are now only typechecked if and when they are accessed.
Mapping keys are still eagerly checked. |
This change causes some previously failing programs to evaluate successfully:
local myNumbers: Listing<Int> = new { 1; 2; "uh oh" }
result = myNumbers[0]
In Pkl 0.26 and below, the above program fails with a type mismatch error because element "uh oh"
is typechecked when myNumbers
is accessed.
In Pkl 0.27, the same program succeeds, because only element myNumbers[0]
is part of the program’s output and its typecheck succeeds.
As another consequence of this change, some Pkl programs now complete more quickly:
local allBirds: Mapping<String, Bird> = import*("**/bird.pkl")
environment: "prod"|"qa"
cluster: String
myBird = allBirds["\(environment)/\(cluster)/bird.pkl"] (1)
In Pkl 0.26 and below, all modules matching **/bird.pkl
are imported when allBirds
is accessed and typechecked.
This can take a long time.
In Pkl 0.27, only module "\(environment)/\(cluster)/bird.pkl"
is imported because only this module is part of the program’s output.
To learn more about this change, consult SPICE-0010.
External readers
A new feature has been added to allow Pkl to spawn external processes to read resources and modules (#660, #762, #766, #770).
Today, users who use Pkl as a library can define custom module and resource readers. This allows authors to extend how Pkl performs I/O.
For example, users can implement a reader that reads the secret:
scheme, where they define exactly how the bytes are fetched in the host runtime.
result = read("secret:mypassword") (1)
1 | secret: is a custom scheme defined by the host runtime. |
However, CLI users have been left out.
In Pkl 0.27, a new extension point is added to allow users to implement module and resource readers as external processes. When run, Pkl will spawn the external process, and talk to the process via message passing.
To learn more about this feature, consult SPICE-0009.
Thanks to @HT154 for contributing the feature!
Import analysis API
A new API has been added to analyze the import graph of Pkl modules (#695).
This API comes in four forms:
-
A standard library module:
pkl:analyze
-
A CLI command:
pkl anaylze imports
-
A Java API:
org.pkl.core.Analyzer
-
A Gradle API:
org.pkl.gradle.task.AnalyzeImportsTask
Some use-cases for this API are:
-
For build tools to perform out-of-date checks. Build tools can invalidate a cached result if any of the transitive modules have changed.
-
Static code analysis, to determine how Pkl modules depend on each other.
With this API, Gradle Plugin now by default computes the transitive modules for many of its tasks.
Here is an example of the CLI in use:
pkl analyze imports -f json myModule.pkl (1)
1 | -f means: produce output in JSON. |
Produces:
{
"imports": {
"file:///my/proj/myModule.pkl": [
{
"uri": "projectpackage://example.com/birds@1.0.0#/Bird.pkl"
}
],
"projectpackage://example.com/birds@1.0.0#/Bird.pkl": []
},
"resolvedImports": {
"file:///my/proj/myModule.pkl": "file:///my/proj/myModule.pkl",
"projectpackage://example.com/birds@1.0.0#/Bird.pkl": "file:///my/birds/Bird.pkl"
}
}
From this output, we can see that myModule.pkl
imports Bird.pkl
from the birds
package.
We can also see that the module projectpackage://example.com/birds@1.0.0#/Bird.pkl
resolves to disk location file:///my/birds/Bird.pkl
(we can deduce that birds
is a local dependency).
To learn more about this feature, consult SPICE-0001.
Noteworthy 🎶
Ready when you need them.
Colored output
Here is a sneak peek of colored error messages in action.
Thanks to @thomaspurchas for contributing to this feature!
const local
object members
The const
modifier can be applied to object members, provided that they are also local
(#678).
Currently, regular object members are not allowed to have the const
modifier.
This introduces an artificial pain point.
For example, given the following module:
amends "Bird.pkl"
import "Bird.pkl"
local northAmerica = "North America"
local typealias NorthAmericanBird = Bird(this.origin == northAmerica) (1)
1 | Error: cannot reference northAmerica from here because it is not const . |
This is invalid code, because this typealias is referencing a non-const value on the enclosing module.
However, is not possible fix this by adding the const
modifier to northAmerica
.
This is because this module amends "Bird.pkl"
, which means that this module is considered a regular object and not a class.
This means that any members declared here are object members, and not class members.
In Pkl 0.27, a new rule is introduced to allow the const
modifier to be applied to object members, provided that they are also local
.
This change affects object properties, as well as object methods.
To read more about this design, consult SPICE-0011.
Pkl CLI changes
New CLI Flags
Flag | Description |
---|---|
|
Format messages with ANSI color codes |
|
Shell out to a process to read certain modules. |
|
Shell out to a process to read certain resources. |
New command: pkl analyze imports
As part of the set of APIs added for import analysis, a new subcommand has been added called analyze imports
(#695).
For details, consult the CLI documentation.
Kotlin/Java code generator improvements
Various improvements have been made to the Kotlin and Java code generators (#705, #710, #714, #721, #729).
Java codegen improvements
-
Only generate
hashCode()
,equals()
, andtoString()
methods for Java classes that are instantiable. -
Add support for Spring Boot 3.
-
Make module classes also implement serializable.
-
Make empty Java classes instantiable.
Kotlin codegen improvements
-
Skip generation of
copy()
,equals()
,hashCode()
,toString()
methods for abstract Kotlin classes. -
Don’t implement
Serializable
for abstract classes -
Add support for Spring Boot 3.
Thanks to @translatenix for contributing these improvements!
Gradle Plugin changes
New AnalyzeImportsTask
A new task called org.pkl.gradle.task.AnalyzeImportsTask
is introduced (#695).
This task is the Gradle analogy to pkl analyze imports
.
Example:
-
build.gradle
-
build.gradle.kts
pkl {
analyzers {
imports {
appConfig {
sourceModules.add(file("src/main/resources/appConfig.pkl"))
}
}
}
}
pkl {
analyzers {
imports {
register("appConfig") {
sourceModules.add(file("src/main/resources/appConfig.pkl"))
}
}
}
}
For more details, consult the documentation.
Tracked file outputs
Tasks created by the Pkl plugin now declare tracked output files (#403).
This means that downstream tasks do not need to declare an explicit dependency on the Pkl task.
For example, assuming that evalPkl
is an EvalTask
:
val myGradleTask by tasks.registering {
inputs.files(evalPkl)
- dependsOn(evalPkl) (1)
}
1 | No longer necessary to declare this dependency. |
transitiveModules
computed by default
The transitiveModules
property of a task is now computed by building the import graph of the source modules (#695).
This means that Pkl-related tasks no longer need to declare their set of transitive modules, because the pkl-gradle plugin will compute this automatically.
This adds latency to each task. To opt out of this behavior, set transitiveModules explicitly.
|
Standard library changes
Additions to pkl:base
One of the goals of this change is to improve the experience of authoring constraints. This eliminates the need to convert to collection types as often.
The added properties and methods are:
With this, the following change can be made to existing constraints:
-ipAddresses: Listing<String>(toList().contains("127.0.0.1"))
+ipAddresses: Listing<String>(contains("127.0.0.1"))
Additionally, a new method is added to String
, called String.splitLimit()
.
Additions to pkl:EvaluatorSettings
These are:
String
to Number
conversion improvements.
The String
to Number
converter methods, for example, String.toInt()
, can now handle underscore separators (#578, #580).
This better aligns with the source code representation of number literals.
myNum = "1_000".toInt() (1)
1 | Result: 1000 |
New module: pkl:analyze
As part of Import analysis API, a new standard library module is added called pkl:analyze
(#695).
This module provides an API for computing the total import graph given a set of input modules. This API treats the inputs as entrypoints, and produces a graph representing the entire dependency tree, including transitive imports.
Example:
import "pkl:analyze"
importGraph: analyze.ImportGraph = analyze.importGraph(Set("file:///path/to/my/module.pkl"))
For details, see the documentation.
Annotations on PklProject
added to packages
When creating packages, some annotations on a PklProject
will affect how pkldoc generates documentation.
-
@Unlisted
: Omit the package from publishing documentation. -
@Deprecated
: Add information on the package to show that it is deprecated.
Example:
@Deprecated
amends "pkl:Project"
Test report improvements
-
Tests are grouped into either the
facts
orexamples
section. -
A summary line is added, describing how many tests have passed and failed, and how many assertions have passed and failed.
-
Test results are colored.
-
The ✅ and ❌ emojis are replaced with ✔ and ✘.
-
Thrown errors are reported within the test.
Here is a sneak peek of the new test result output.
Thanks to @jjmaestro for contributing to this improvement!
Java API additions
New API: org.pkl.core.Analyzer
As part of Import analysis API, a new Java API called org.pkl.core.Analyzer
is introduced (#695).
New methods
The following methods are added:
pkl-executor
-
org.pkl.executor.ExecutorException.getPklVersion
-
org.pkl.executor.ExecutorException.ExecutorException(java.lang.String, java.lang.Throwable, java.lang.String)
pkl-core
-
org.pkl.core.module.ModuleKeyFactories.externalProcess(java.lang.String, org.pkl.core.externalreader.ExternalReaderProcess)
-
org.pkl.core.module.ModuleKeyFactories.externalProcess(java.lang.String, org.pkl.core.externalreader.ExternalReaderProcess, long)
Breaking Changes 💔
Things to watch out for when upgrading.
Java API breaking changes
The following Java APIs have breaking changes:
Class/method | Breaking change |
---|---|
|
Moved to |
|
Deprecated for removal in favor of |
Spring Boot 2 support dropped
The Java code generator no longer supports Spring Boot 2.x (#729).
Spring Boot 2 users can continue to use the Pkl 0.26 code generator.
pkl test
considered failing when writing examples
If running pkl test
results in pkl-expected.pcf
files being written, the test now exits with exit code 10
(#738).
Additionally, the junit reports will consider the test as failing.
Listing
and Mapping
typecheck changes
Due to changes to Listing and Mapping typechecking, some previously failing programs now succeed. This can happen when erroneous code is never evaluated because it does not affect program output:
local myNumbers: Listing<Int> = new { 1; 2; "uh oh" }
result = myNumbers[0] (1)
1 | In Pkl 0.26 and below, throws a type mismatch error.
In Pkl 0.27, yields |
Miscellaneous 🐸
-
Make pkl-doc top box include a preview of the rest of module-level documentation (#570).
-
Module
pkl-core
now has a dependency onorg.msgpack:msgpack-core
(#660). Users concerned about potential version conflicts can use one of Pkl’s shaded fat JARs (pkl-config-java-all
,pkl-tools
). -
Make PklProject.deps.json files end in a newline (#644).
-
Fix invalid syntax of code examples in stdlib doc comments (#703).
-
Add jbang catalog support (#655).
-
Documentation improvements (#623, #680, #682, #685, #687, #703, #704, #715, #730, #753).
Bug Fixes 🐜
The following bugs have been fixed.
-
CLI
--property
flags containing=
in property values are not parsed correctly (#630). -
Thrown
PklBugException
when reading assets from a local project dependency (#641). -
Thrown
PklBugException
when PklProjects have a cyclical local dependency (#731). -
Thrown exception when sending
ReadResourceResponse
orReadModuleResponse
where bothcontents
anderror
arenull
(#657). -
Double unary minus is evaluated as single unary minus (#697).
-
Kotlin compiler fails to compiler generated classes with "copy overrides nothing" for subclasses of abstract classes (#569).
-
"Unexpected error" thrown when
Module.output
has its type overridden (#709). -
"Unexpected error" thrown when loading files with non-ASCII characters (#653).
-
Type parameters in
new Mapping<TypeA, TypeB>
/new Listing <Type>
are not checked (#405). -
Comparison methods of
pkl:semver
are incorrect (#772).
Contributors 🙏
We would like to thank the contributors to this release (in alphabetical order):