mirror of
https://github.com/apple/pkl.git
synced 2026-05-25 16:19:20 +02:00
Compare commits
23 Commits
0.31.0
...
release/0.26
| Author | SHA1 | Date | |
|---|---|---|---|
| 19c292f9a2 | |||
| d720f21149 | |||
| b28cdcd631 | |||
| 825135a0f8 | |||
| 63b92ec72b | |||
| 3125fc4678 | |||
| 4bb6890621 | |||
| 8e15556201 | |||
| 2fb17fd283 | |||
| f4983c51be | |||
| 2cd2712589 | |||
| f9a3fc88fd | |||
| d097341abd | |||
| 0eab5fb552 | |||
| 7c3787396e | |||
| 57df7995fd | |||
| 6c97b09c29 | |||
| da19c3971e | |||
| efad356b7b | |||
| 261a2260a1 | |||
| 204c6b16c3 | |||
| f91f91fd30 | |||
| 309fb49fa1 |
@@ -132,8 +132,7 @@ jobs {
|
|||||||
name = "gradle compatibility"
|
name = "gradle compatibility"
|
||||||
command = #"""
|
command = #"""
|
||||||
:pkl-gradle:build \
|
:pkl-gradle:build \
|
||||||
:pkl-gradle:compatibilityTestReleases \
|
:pkl-gradle:compatibilityTestReleases
|
||||||
:pkl-gradle:compatibilityTestCandidate
|
|
||||||
"""#
|
"""#
|
||||||
}.job
|
}.job
|
||||||
["deploy-snapshot"] = new DeployJob { command = "publishToSonatype" }.job
|
["deploy-snapshot"] = new DeployJob { command = "publishToSonatype" }.job
|
||||||
|
|||||||
@@ -610,8 +610,7 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
command: |-
|
command: |-
|
||||||
./gradlew --info --stacktrace -DtestReportsDir=${HOME}/test-results :pkl-gradle:build \
|
./gradlew --info --stacktrace -DtestReportsDir=${HOME}/test-results :pkl-gradle:build \
|
||||||
:pkl-gradle:compatibilityTestReleases \
|
:pkl-gradle:compatibilityTestReleases
|
||||||
:pkl-gradle:compatibilityTestCandidate
|
|
||||||
name: gradle compatibility
|
name: gradle compatibility
|
||||||
- store_test_results:
|
- store_test_results:
|
||||||
path: ~/test-results
|
path: ~/test-results
|
||||||
|
|||||||
+1
-1
@@ -81,7 +81,7 @@ For automated build setup examples see our https://github.com/apple/pkl/blob/mai
|
|||||||
|
|
||||||
=== ANTLR
|
=== ANTLR
|
||||||
|
|
||||||
* https://github.com/antlr/antlr4/blob/main/doc/index.md[Documentation]
|
* https://github.com/antlr/antlr4/blob/master/doc/index.md[Documentation]
|
||||||
* https://groups.google.com/forum/#!forum/antlr-discussion[Forums]
|
* https://groups.google.com/forum/#!forum/antlr-discussion[Forums]
|
||||||
* https://github.com/mobileink/lab.clj.antlr/tree/main/doc[Some third-party docs]
|
* https://github.com/mobileink/lab.clj.antlr/tree/main/doc[Some third-party docs]
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
name: main
|
name: main
|
||||||
title: Main Project
|
title: Main Project
|
||||||
version: 0.26.0-dev
|
version: 0.26.3
|
||||||
prerelease: true
|
prerelease: false
|
||||||
nav:
|
nav:
|
||||||
- nav.adoc
|
- nav.adoc
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
// the following attributes must be updated immediately before a release
|
// the following attributes must be updated immediately before a release
|
||||||
|
|
||||||
// pkl version corresponding to current git commit without -dev suffix or git hash
|
// pkl version corresponding to current git commit without -dev suffix or git hash
|
||||||
:pkl-version-no-suffix: 0.26.0
|
:pkl-version-no-suffix: 0.26.3
|
||||||
// tells whether pkl version corresponding to current git commit
|
// tells whether pkl version corresponding to current git commit
|
||||||
// is a release version (:is-release-version: '') or dev version (:!is-release-version:)
|
// is a release version (:is-release-version: '') or dev version (:!is-release-version:)
|
||||||
:!is-release-version:
|
:is-release-version: ''
|
||||||
|
|
||||||
// the remaining attributes do not need to be updated regularly
|
// the remaining attributes do not need to be updated regularly
|
||||||
|
|
||||||
|
|||||||
@@ -915,6 +915,7 @@ pigeon = new Dynamic { // <1>
|
|||||||
==== Hidden Properties
|
==== Hidden Properties
|
||||||
|
|
||||||
A property with the modifier `hidden` is omitted from the rendered output and object conversions.
|
A property with the modifier `hidden` is omitted from the rendered output and object conversions.
|
||||||
|
Hidden properties are also ignored when evaluating equality or hashing (e.g. for `Mapping` or `Map` keys).
|
||||||
|
|
||||||
[source,{pkl}]
|
[source,{pkl}]
|
||||||
----
|
----
|
||||||
@@ -933,12 +934,19 @@ pigeon = new Bird { // <3>
|
|||||||
pigeonInIndex = pigeon.nameAndLifespanInIndex // <4>
|
pigeonInIndex = pigeon.nameAndLifespanInIndex // <4>
|
||||||
|
|
||||||
pigeonDynamic = pigeon.toDynamic() // <5>
|
pigeonDynamic = pigeon.toDynamic() // <5>
|
||||||
|
|
||||||
|
favoritePigeon = (pigeon) {
|
||||||
|
nameAndLifespanInIndex = "Bettie, \(lifespan)"
|
||||||
|
}
|
||||||
|
|
||||||
|
samePigeon = pigeon == favoritePigeon // <6>
|
||||||
----
|
----
|
||||||
<1> Properties defined as `hidden` are accessible on any `Bird` instance, but not output by default.
|
<1> Properties defined as `hidden` are accessible on any `Bird` instance, but not output by default.
|
||||||
<2> Non-hidden properties can refer to hidden properties as usual.
|
<2> Non-hidden properties can refer to hidden properties as usual.
|
||||||
<3> `pigeon` is an object with _four_ properties, but is rendered with _three_ properties.
|
<3> `pigeon` is an object with _four_ properties, but is rendered with _three_ properties.
|
||||||
<4> Accessing a `hidden` property from outside the class and object is like any other property.
|
<4> Accessing a `hidden` property from outside the class and object is like any other property.
|
||||||
<5> Object conversions omit hidden properties, so the resulting `Dynamic` has three properties.
|
<5> Object conversions omit hidden properties, so the resulting `Dynamic` has three properties.
|
||||||
|
<6> Objects that differ only in `hidden` property values are considered equal
|
||||||
|
|
||||||
Invoking Pkl on this file produces the following result.
|
Invoking Pkl on this file produces the following result.
|
||||||
|
|
||||||
@@ -955,6 +963,12 @@ pigeonDynamic {
|
|||||||
lifespan = 8
|
lifespan = 8
|
||||||
nameSignWidth = 9
|
nameSignWidth = 9
|
||||||
}
|
}
|
||||||
|
favoritePigeon {
|
||||||
|
name = "Pigeon"
|
||||||
|
lifespan = 8
|
||||||
|
nameSignWidth = 9
|
||||||
|
}
|
||||||
|
samePigeon = true
|
||||||
----
|
----
|
||||||
|
|
||||||
==== Local properties
|
==== Local properties
|
||||||
@@ -1992,7 +2006,8 @@ pigeon: ParentBird = new {
|
|||||||
[[methods]]
|
[[methods]]
|
||||||
== Methods
|
== Methods
|
||||||
|
|
||||||
Modules and classes can define methods.
|
Pkl methods can be defined on classes and modules using the `function` keyword.
|
||||||
|
Methods may access properties of their containing type.
|
||||||
Submodules and subclasses can override them.
|
Submodules and subclasses can override them.
|
||||||
|
|
||||||
Like Java and most other object-oriented languages, Pkl uses _single dispatch_ -- methods are dynamically dispatched based on the receiver's runtime type.
|
Like Java and most other object-oriented languages, Pkl uses _single dispatch_ -- methods are dynamically dispatched based on the receiver's runtime type.
|
||||||
@@ -2021,6 +2036,12 @@ greeting2 = greetPigeon(parrot) // <4>
|
|||||||
<3> Call instance method on `pigeon`.
|
<3> Call instance method on `pigeon`.
|
||||||
<4> Call module method (on `this`).
|
<4> Call module method (on `this`).
|
||||||
|
|
||||||
|
NOTE: Methods do not support named parameters or default parameter values.
|
||||||
|
The xref:blog:ROOT:class-as-a-function.adoc[Class-as-a-function] pattern may be a suitable replacement.
|
||||||
|
|
||||||
|
TIP: In most cases, methods without parameters should not be defined.
|
||||||
|
Instead, use <<fixed-properties,`fixed` properties>> on the module or class.
|
||||||
|
|
||||||
[[modules]]
|
[[modules]]
|
||||||
== Modules
|
== Modules
|
||||||
|
|
||||||
@@ -3156,6 +3177,7 @@ pkl: TRACE: num1 * num2 = 672 (at file:///some/module.pkl, line 42)
|
|||||||
|
|
||||||
This section discusses language features that are generally more relevant to template and library authors than template consumers.
|
This section discusses language features that are generally more relevant to template and library authors than template consumers.
|
||||||
|
|
||||||
|
<<meaning-of-new,Meaning of `new`>> +
|
||||||
<<let-expressions,Let Expressions>> +
|
<<let-expressions,Let Expressions>> +
|
||||||
<<type-tests,Type Tests>> +
|
<<type-tests,Type Tests>> +
|
||||||
<<type-casts,Type Casts>> +
|
<<type-casts,Type Casts>> +
|
||||||
@@ -3179,6 +3201,162 @@ This section discusses language features that are generally more relevant to tem
|
|||||||
<<blank-identifiers,Blank Identifiers>> +
|
<<blank-identifiers,Blank Identifiers>> +
|
||||||
<<projects,Projects>>
|
<<projects,Projects>>
|
||||||
|
|
||||||
|
[[meaning-of-new]]
|
||||||
|
=== Meaning of `new`
|
||||||
|
|
||||||
|
Objects in Pkl always <<amending-objects, amends>> _some_ value.
|
||||||
|
The `new` keyword is a special case of amending where a contextual value is amended.
|
||||||
|
In Pkl, there are two forms of `new` objects:
|
||||||
|
|
||||||
|
* `new` with explicit type information, for example, `new Foo {}`.
|
||||||
|
* `new` without type information, for example, `new {}`.
|
||||||
|
|
||||||
|
==== Type defaults
|
||||||
|
|
||||||
|
To understand instantiation cases without explicit parent or type information, it's important to first understand implicit default values.
|
||||||
|
When a property is declared in a module or class but is not provided an explicit default value, the property's default value becomes the type's default value.
|
||||||
|
Similarly, when `Listing` and `Mapping` types are declared with explicit type arguments for their element or value, their `default` property amends that declared type.
|
||||||
|
When `Listing` and `Mapping` types are declared without type arguments, their `default` property amends an empty `Dynamic` object.
|
||||||
|
Some types, including `Pair` and primitives like `String`, `Number`, and `Boolean` have no default value; attempting to render such a property results in the error "Tried to read property `<name>` but its value is undefined".
|
||||||
|
|
||||||
|
[source,{pkl}]
|
||||||
|
----
|
||||||
|
class Bird {
|
||||||
|
name: String = "polly"
|
||||||
|
}
|
||||||
|
|
||||||
|
bird: Bird // <1>
|
||||||
|
birdListing: Listing<Bird> // <2>
|
||||||
|
birdMapping: Mapping<String, Bird> // <3>
|
||||||
|
----
|
||||||
|
<1> Without an explicit default value, this property has default value `new Bird { name = "polly" }`
|
||||||
|
<2> With an explicit element type argument, this property's default value is equivalent to `new Listing<Bird> { default = (_) -> new Bird { name = "polly" } }`
|
||||||
|
<3> With an explicit value type argument, this property's default value is equivalent to `new Mapping<String, Bird> { default = (_) -> new Bird { name = "polly" } }`
|
||||||
|
|
||||||
|
==== Explicitly Typed `new`
|
||||||
|
|
||||||
|
Instantiating an object with `new <type>` results in a value that amends the specified type's default value.
|
||||||
|
Notably, creating a `Listing` element or assigning a `Mapping` entry value with an explicitly typed `new` ignores the object's `default` value.
|
||||||
|
|
||||||
|
[source,{pkl}]
|
||||||
|
----
|
||||||
|
class Bird {
|
||||||
|
/// The name of the bird
|
||||||
|
name: String
|
||||||
|
|
||||||
|
/// Whether this is a bird of prey or not.
|
||||||
|
isPredatory: Boolean?
|
||||||
|
}
|
||||||
|
|
||||||
|
newProperty = new Bird { // <1>
|
||||||
|
name = "Warbler"
|
||||||
|
}
|
||||||
|
|
||||||
|
someListing = new Listing<Bird> {
|
||||||
|
default {
|
||||||
|
isPredatory = true
|
||||||
|
}
|
||||||
|
new Bird { // <2>
|
||||||
|
name = "Sand Piper"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
someMapping = new Mapping<String, Bird> {
|
||||||
|
default {
|
||||||
|
isPredatory = true
|
||||||
|
}
|
||||||
|
["Penguin"] = new Bird { // <3>
|
||||||
|
name = "Penguin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
<1> Assigning a `new` explicitly-typed value to a property.
|
||||||
|
<2> Adding an `new` explicitly-typed `Listing` element.
|
||||||
|
The value will not have property `isPredatory = true` as the `default` property of the `Listing` is not used.
|
||||||
|
<3> Assigning a `new` explicitly-typed value to a `Mapping` entry.
|
||||||
|
The value will not have property `isPredatory = true` as the `default` property of the `Mapping` is not used.
|
||||||
|
|
||||||
|
==== Implicitly Typed `new`
|
||||||
|
|
||||||
|
When using the implicitly typed `new` invocation, there is no explicit parent value to amend.
|
||||||
|
In these cases, Pkl infers the amend operation's parent value based on context:
|
||||||
|
|
||||||
|
* When assigning to a declared property, the property's default value is amended (<<amend-null, including `null`>>).
|
||||||
|
If there is no type associated with the property, an empty `Dynamic` object is amended.
|
||||||
|
* When assigning to an entry (e.g. a `Mapping` member) or element (e.g. a `Listing` member), the enclosing object's `default` property is applied to the corresponding index or key, respectively, to produce the value to be amended.
|
||||||
|
* In other cases, evaluation fails with the error message "Cannot tell which parent to amend".
|
||||||
|
|
||||||
|
The type annotation of a <<methods,method>> parameter is not used for inference.
|
||||||
|
In this case, the argument's type should be specified explicitly.
|
||||||
|
|
||||||
|
[source,{pkl}]
|
||||||
|
----
|
||||||
|
class Bird {
|
||||||
|
name: String
|
||||||
|
|
||||||
|
function listHatchlings(items: Listing<String>): Listing<String> = new {
|
||||||
|
for (item in items) {
|
||||||
|
"\(name):\(item)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedProperty: Bird = new { // <1>
|
||||||
|
name = "Swift"
|
||||||
|
}
|
||||||
|
|
||||||
|
untypedProperty = new { // <2>
|
||||||
|
hello = "world"
|
||||||
|
}
|
||||||
|
|
||||||
|
typedListing: Listing<Bird> = new {
|
||||||
|
new { // <3>
|
||||||
|
name = "Kite"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
untypedListing: Listing = new {
|
||||||
|
new { // <4>
|
||||||
|
hello = "there"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedMapping: Mapping<String, Bird> = new {
|
||||||
|
default { entryKey ->
|
||||||
|
name = entryKey
|
||||||
|
}
|
||||||
|
["Saltmarsh Sparrow"] = new { // <5>
|
||||||
|
name = "Sharp-tailed Sparrow"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
amendedMapping = (typedMapping) {
|
||||||
|
["Saltmarsh Sparrow"] = new {} // <6>
|
||||||
|
}
|
||||||
|
|
||||||
|
class Aviary {
|
||||||
|
birds: Listing<Bird> = new {
|
||||||
|
new { name = "Osprey" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aviary: Aviary = new {
|
||||||
|
birds = new { // <7>
|
||||||
|
new { name = "Kiwi" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
swiftHatchlings = typedProperty.listHatchlings(new { "Poppy"; "Chirpy" }) // <8>
|
||||||
|
----
|
||||||
|
<1> Assignment to a property with an explicitly declared type, amending `new Bird {}`.
|
||||||
|
<2> Assignment to an undeclared property in module context, amending `new Dynamic {}`.
|
||||||
|
<3> `Listing` element creation, amending implicit `default`, `new Bird {}`.
|
||||||
|
<4> `Listing` element creation, amending implicit `default`, `new Dynamic {}`.
|
||||||
|
<5> `Mapping` value assignment, amdending the result of applying `default` to `"Saltmarsh Sparrow"`, `new Bird { name = "Saltmarsh Sparrow" }`.
|
||||||
|
<6> `Mapping` value assignment _replacing_ the parent's entry, amending the result of applying `default` to `"Saltmarsh Sparrow"`, `new Bird { name = "Saltmarsh Sparrow" }`.
|
||||||
|
<7> Admending the property default value `new Listing { new Bird { name = "Osprey" } }`; the result contains both birds.
|
||||||
|
<8> Error: Cannot tell which parent to amend.
|
||||||
|
|
||||||
[[let-expressions]]
|
[[let-expressions]]
|
||||||
=== Let Expressions
|
=== Let Expressions
|
||||||
|
|
||||||
@@ -3200,10 +3378,11 @@ Here is an example:
|
|||||||
|
|
||||||
[source%tested,{pkl}]
|
[source%tested,{pkl}]
|
||||||
----
|
----
|
||||||
birdDiets = let (diets = List("Seeds", "Berries", "Mice"))
|
birdDiets =
|
||||||
List(diets[2], diets[0]) // <1>
|
let (diets = List("Seeds", "Berries", "Mice"))
|
||||||
|
List(diets[2], diets[0]) // <1>
|
||||||
----
|
----
|
||||||
<1> result: `List("Mice", "Seeds")`
|
<1> result: `birdDiets = List("Mice", "Seeds")`
|
||||||
|
|
||||||
`let` expressions serve two purposes:
|
`let` expressions serve two purposes:
|
||||||
|
|
||||||
@@ -3214,20 +3393,22 @@ List(diets[2], diets[0]) // <1>
|
|||||||
|
|
||||||
[source%tested,{pkl}]
|
[source%tested,{pkl}]
|
||||||
----
|
----
|
||||||
birdDiets = let (diets: List<String> = List("Seeds", "Berries", "Mice"))
|
birdDiets =
|
||||||
diets[2] + diets[0] // <1>
|
let (diets: List<String> = List("Seeds", "Berries", "Mice"))
|
||||||
|
diets[2] + diets[0] // <1>
|
||||||
----
|
----
|
||||||
<1> result: `List("Mice", "Seeds")`
|
<1> result: `birdDiets = List("Mice", "Seeds")`
|
||||||
|
|
||||||
`let` expressions can be stacked:
|
`let` expressions can be stacked:
|
||||||
|
|
||||||
[source%tested,{pkl}]
|
[source%tested,{pkl}]
|
||||||
----
|
----
|
||||||
birdDiets = let (birds = List("Pigeon", "Barn owl", "Parrot"))
|
birdDiets =
|
||||||
let (diet = List("Seeds", "Mice", "Berries"))
|
let (birds = List("Pigeon", "Barn owl", "Parrot"))
|
||||||
birds.zip(diet) // <1>
|
let (diet = List("Seeds", "Mice", "Berries"))
|
||||||
|
birds.zip(diet) // <1>
|
||||||
----
|
----
|
||||||
<1> result: `List(Pair("Pigeon", "Seeds"), Pair("Barn owl", "Mice"), Pair("Parrot", "Berries"))`
|
<1> result: `birdDiets = List(Pair("Pigeon", "Seeds"), Pair("Barn owl", "Mice"), Pair("Parrot", "Berries"))`
|
||||||
|
|
||||||
[[type-tests]]
|
[[type-tests]]
|
||||||
=== Type Tests
|
=== Type Tests
|
||||||
@@ -3721,6 +3902,7 @@ bird2: Bird? = null // <2>
|
|||||||
The only class types that admit `null` values despite not ending in `?` are `Any` and `Null`.
|
The only class types that admit `null` values despite not ending in `?` are `Any` and `Null`.
|
||||||
(`Null` is not very useful as a type because it _only_ admits `null` values.)
|
(`Null` is not very useful as a type because it _only_ admits `null` values.)
|
||||||
`Any?` and `Null?` are equivalent to `Any` and `Null`, respectively.
|
`Any?` and `Null?` are equivalent to `Any` and `Null`, respectively.
|
||||||
|
In some languages, nullable types are also known as _optional types_.
|
||||||
|
|
||||||
[[generic-types]]
|
[[generic-types]]
|
||||||
==== Generic Types
|
==== Generic Types
|
||||||
@@ -5146,8 +5328,8 @@ It is defined by the presence of a `PklProject` file that amends the standard li
|
|||||||
Defining a project serves the following purposes:
|
Defining a project serves the following purposes:
|
||||||
|
|
||||||
1. It allows defining common evaluator settings for Pkl modules within a logical project.
|
1. It allows defining common evaluator settings for Pkl modules within a logical project.
|
||||||
2. It helps with managing <<packages,package>> dependencies for Pkl modules within a logical project.
|
2. It helps with managing <<package-asset-uri,package>> dependencies for Pkl modules within a logical project.
|
||||||
3. It enables packaging and sharing the contents of the project as a <<packages,package>>.
|
3. It enables packaging and sharing the contents of the project as a <<package-asset-uri,package>>.
|
||||||
4. It allows importing packages via dependency notation.
|
4. It allows importing packages via dependency notation.
|
||||||
|
|
||||||
[[project-dependencies]]
|
[[project-dependencies]]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
= Pkl 0.26 Release Notes
|
= Pkl 0.26 Release Notes
|
||||||
:version: 0.26
|
:version: 0.26
|
||||||
:version-minor: 0.26.0
|
:version-minor: 0.26.3
|
||||||
:release-date: June 17th, 2024
|
:release-date: June 17th, 2024
|
||||||
|
|
||||||
include::ROOT:partial$component-attributes.adoc[]
|
include::ROOT:partial$component-attributes.adoc[]
|
||||||
@@ -508,6 +508,7 @@ We would like to thank the contributors to this release (in alphabetical order):
|
|||||||
* https://github.com/MarkSRobinson[@MarkSRobinson]
|
* https://github.com/MarkSRobinson[@MarkSRobinson]
|
||||||
* https://github.com/mitchcapper[@mitchcapper]
|
* https://github.com/mitchcapper[@mitchcapper]
|
||||||
* https://github.com/mrs1669[@mrs1669]
|
* https://github.com/mrs1669[@mrs1669]
|
||||||
|
* https://github.com/netvl[@netvl]
|
||||||
* https://github.com/nirinchev[@nirinchev]
|
* https://github.com/nirinchev[@nirinchev]
|
||||||
* https://github.com/raj-j-shah[@raj-j-shah]
|
* https://github.com/raj-j-shah[@raj-j-shah]
|
||||||
* https://github.com/sgammon[@sgammon]
|
* https://github.com/sgammon[@sgammon]
|
||||||
|
|||||||
@@ -1,6 +1,43 @@
|
|||||||
= Changelog
|
= Changelog
|
||||||
include::ROOT:partial$component-attributes.adoc[]
|
include::ROOT:partial$component-attributes.adoc[]
|
||||||
|
|
||||||
|
[[release-0.26.3]]
|
||||||
|
== 0.26.3 (2024-08-06)
|
||||||
|
|
||||||
|
=== Fixes
|
||||||
|
|
||||||
|
* Fixes an issue where CLI argument `--property foo=""` is effectively parsed as `--property foo="true"`. This is now parsed as an empty string (https://github.com/apple/pkl/pull/596[#596]).
|
||||||
|
* Fixes a regression where amending a globbed import or globbed read results in a PklBugException (https://github.com/apple/pkl/pull/607[#607]).
|
||||||
|
* Fixes an issue around using `file()` notation when using the pkl-gradle plugin on Windows (https://github.com/apple/pkl/pull/611[#611]).
|
||||||
|
|
||||||
|
[[release-0.26.2]]
|
||||||
|
== 0.26.2 (2024-07-18)
|
||||||
|
|
||||||
|
=== Fixes
|
||||||
|
|
||||||
|
* Fixes a possible race condition where multiple concurrent Pkl evaluations results in a thrown exception when downloading packages (https://github.com/apple/pkl/pull/584[#584]).
|
||||||
|
|
||||||
|
[[release-0.26.1]]
|
||||||
|
== 0.26.1 (2024-06-28)
|
||||||
|
|
||||||
|
=== Fixes
|
||||||
|
|
||||||
|
* Fixes a regression where native executables fail to run on some environments that don't support newer CPU features (https://github.com/apple/pkl/pull/551[#551]).
|
||||||
|
* Fixes a `PklBugException` when passing `.` as a project directory to `pkl project resolve` and `pkl project package` (https://github.com/apple/pkl/pull/544[#544]).
|
||||||
|
|
||||||
|
=== Changes
|
||||||
|
|
||||||
|
* Disable revocation checking of TLS certificates (https://github.com/apple/pkl/pull/553[#553]).
|
||||||
|
+
|
||||||
|
As part of HTTP improvements in 0.26, we unwittingly fixed a bug where Pkl does not actually perform cert revocation checks when making HTTPS requests.
|
||||||
|
This fix, unfortunately, caused a regression in some cases.
|
||||||
|
For example, this happens when connecting to a server that bears a public trust certificate, while in an environment with no internet access.
|
||||||
|
This is because the HTTP client needs to check the revocation status of all certificates in the chain.
|
||||||
|
+
|
||||||
|
Revocation checks are a nuanced topic with some benefits, and also with its own problem areas.
|
||||||
|
For this reason, revocation checking is disabled for Pkl's native CLIs.
|
||||||
|
Users of Pkl's Java APIs will respect the revocation settings set in the JVM.
|
||||||
|
|
||||||
[[release-0.26.0]]
|
[[release-0.26.0]]
|
||||||
== 0.26.0 (2024-06-17)
|
== 0.26.0 (2024-06-17)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
# suppress inspection "UnusedProperty" for whole file
|
# suppress inspection "UnusedProperty" for whole file
|
||||||
|
|
||||||
group=org.pkl-lang
|
group=org.pkl-lang
|
||||||
version=0.26.0
|
version=0.26.3
|
||||||
|
|
||||||
# google-java-format requires jdk.compiler exports
|
# google-java-format requires jdk.compiler exports
|
||||||
org.gradle.jvmargs= \
|
org.gradle.jvmargs= \
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
import java.security.KeyStore
|
|
||||||
import java.security.cert.CertificateFactory
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
pklAllProjects
|
pklAllProjects
|
||||||
pklKotlinLibrary
|
pklKotlinLibrary
|
||||||
@@ -38,8 +35,6 @@ val stagedLinuxAarch64Executable: Configuration by configurations.creating
|
|||||||
val stagedAlpineLinuxAmd64Executable: Configuration by configurations.creating
|
val stagedAlpineLinuxAmd64Executable: Configuration by configurations.creating
|
||||||
val stagedWindowsAmd64Executable: Configuration by configurations.creating
|
val stagedWindowsAmd64Executable: Configuration by configurations.creating
|
||||||
|
|
||||||
val certs: SourceSet by sourceSets.creating
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly(libs.svm)
|
compileOnly(libs.svm)
|
||||||
|
|
||||||
@@ -148,38 +143,11 @@ tasks.check {
|
|||||||
dependsOn(testStartJavaExecutable)
|
dependsOn(testStartJavaExecutable)
|
||||||
}
|
}
|
||||||
|
|
||||||
val trustStore = layout.buildDirectory.dir("generateTrustStore/PklCARoots.p12")
|
|
||||||
val trustStorePassword = "password" // no sensitive data to protect
|
|
||||||
|
|
||||||
// generate a trust store for Pkl's built-in CA certificates
|
|
||||||
val generateTrustStore by tasks.registering {
|
|
||||||
inputs.file(certs.resources.singleFile)
|
|
||||||
outputs.file(trustStore)
|
|
||||||
doLast {
|
|
||||||
val certificates = certs.resources.singleFile.inputStream().use { stream ->
|
|
||||||
CertificateFactory.getInstance("X.509").generateCertificates(stream)
|
|
||||||
}
|
|
||||||
KeyStore.getInstance("PKCS12").apply {
|
|
||||||
load(null, trustStorePassword.toCharArray()) // initialize empty trust store
|
|
||||||
for ((index, certificate) in certificates.withIndex()) {
|
|
||||||
setCertificateEntry("cert-$index", certificate)
|
|
||||||
}
|
|
||||||
val trustStoreFile = trustStore.get().asFile
|
|
||||||
trustStoreFile.parentFile.mkdirs()
|
|
||||||
trustStoreFile.outputStream().use { stream ->
|
|
||||||
store(stream, trustStorePassword.toCharArray())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Exec.configureExecutable(
|
fun Exec.configureExecutable(
|
||||||
graalVm: BuildInfo.GraalVm,
|
graalVm: BuildInfo.GraalVm,
|
||||||
outputFile: Provider<RegularFile>,
|
outputFile: Provider<RegularFile>,
|
||||||
extraArgs: List<String> = listOf()
|
extraArgs: List<String> = listOf()
|
||||||
) {
|
) {
|
||||||
dependsOn(generateTrustStore)
|
|
||||||
|
|
||||||
inputs.files(sourceSets.main.map { it.output })
|
inputs.files(sourceSets.main.map { it.output })
|
||||||
.withPropertyName("mainSourceSets")
|
.withPropertyName("mainSourceSets")
|
||||||
.withPathSensitivity(PathSensitivity.RELATIVE)
|
.withPathSensitivity(PathSensitivity.RELATIVE)
|
||||||
@@ -210,14 +178,10 @@ fun Exec.configureExecutable(
|
|||||||
// needed for messagepack-java (see https://github.com/msgpack/msgpack-java/issues/600)
|
// needed for messagepack-java (see https://github.com/msgpack/msgpack-java/issues/600)
|
||||||
add("--initialize-at-run-time=org.msgpack.core.buffer.DirectBufferAccess")
|
add("--initialize-at-run-time=org.msgpack.core.buffer.DirectBufferAccess")
|
||||||
add("--no-fallback")
|
add("--no-fallback")
|
||||||
add("-Djavax.net.ssl.trustStore=${trustStore.get().asFile}")
|
|
||||||
add("-Djavax.net.ssl.trustStorePassword=$trustStorePassword")
|
|
||||||
add("-Djavax.net.ssl.trustStoreType=PKCS12")
|
|
||||||
// security property "ocsp.enable=true" is set in Main.kt
|
|
||||||
add("-Dcom.sun.net.ssl.checkRevocation=true")
|
|
||||||
add("-H:IncludeResources=org/pkl/core/stdlib/.*\\.pkl")
|
add("-H:IncludeResources=org/pkl/core/stdlib/.*\\.pkl")
|
||||||
add("-H:IncludeResources=org/jline/utils/.*")
|
add("-H:IncludeResources=org/jline/utils/.*")
|
||||||
add("-H:IncludeResourceBundles=org.pkl.core.errorMessages")
|
add("-H:IncludeResourceBundles=org.pkl.core.errorMessages")
|
||||||
|
add("-H:IncludeResources=org/pkl/commons/cli/PklCARoots.pem")
|
||||||
add("--macro:truffle")
|
add("--macro:truffle")
|
||||||
add("-H:Class=org.pkl.cli.Main")
|
add("-H:Class=org.pkl.cli.Main")
|
||||||
add("-H:Name=${outputFile.get().asFile.name}")
|
add("-H:Name=${outputFile.get().asFile.name}")
|
||||||
@@ -232,6 +196,7 @@ fun Exec.configureExecutable(
|
|||||||
if (!buildInfo.isReleaseBuild) {
|
if (!buildInfo.isReleaseBuild) {
|
||||||
add("-Ob")
|
add("-Ob")
|
||||||
}
|
}
|
||||||
|
add("-march=compatibility")
|
||||||
// native-image rejects non-existing class path entries -> filter
|
// native-image rejects non-existing class path entries -> filter
|
||||||
add("--class-path")
|
add("--class-path")
|
||||||
val pathInput = sourceSets.main.get().output + configurations.runtimeClasspath.get()
|
val pathInput = sourceSets.main.get().output + configurations.runtimeClasspath.get()
|
||||||
@@ -316,7 +281,7 @@ val windowsExecutableAmd64: TaskProvider<Exec> by tasks.registering(Exec::class)
|
|||||||
configureExecutable(
|
configureExecutable(
|
||||||
buildInfo.graalVmAmd64,
|
buildInfo.graalVmAmd64,
|
||||||
layout.buildDirectory.file("executable/pkl-windows-amd64"),
|
layout.buildDirectory.file("executable/pkl-windows-amd64"),
|
||||||
listOf("-Dfile.encoding=UTF-8", "-march=compatibility")
|
listOf("-Dfile.encoding=UTF-8")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ abstract class CliProjectCommand(cliOptions: CliBaseOptions, private val project
|
|||||||
)
|
)
|
||||||
return@lazy listOf(projectFile.normalize())
|
return@lazy listOf(projectFile.normalize())
|
||||||
}
|
}
|
||||||
projectDirs.map { dir ->
|
projectDirs.map(cliOptions.normalizedWorkingDir::resolve).map { dir ->
|
||||||
val projectFile = dir.resolve(PKL_PROJECT_FILENAME)
|
val projectFile = dir.resolve(PKL_PROJECT_FILENAME)
|
||||||
if (!Files.exists(projectFile)) {
|
if (!Files.exists(projectFile)) {
|
||||||
throw CliException("Directory $dir does not contain a PklProject file.")
|
throw CliException("Directory $dir does not contain a PklProject file.")
|
||||||
|
|||||||
@@ -171,8 +171,20 @@ abstract class CliCommand(protected val cliOptions: CliBaseOptions) {
|
|||||||
|
|
||||||
private fun HttpClient.Builder.addDefaultCliCertificates() {
|
private fun HttpClient.Builder.addDefaultCliCertificates() {
|
||||||
val caCertsDir = IoUtils.getPklHomeDir().resolve("cacerts")
|
val caCertsDir = IoUtils.getPklHomeDir().resolve("cacerts")
|
||||||
|
var certsAdded = false
|
||||||
if (Files.isDirectory(caCertsDir)) {
|
if (Files.isDirectory(caCertsDir)) {
|
||||||
Files.list(caCertsDir).filter { it.isRegularFile() }.forEach { addCertificates(it) }
|
Files.list(caCertsDir)
|
||||||
|
.filter { it.isRegularFile() }
|
||||||
|
.forEach { cert ->
|
||||||
|
certsAdded = true
|
||||||
|
addCertificates(cert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!certsAdded) {
|
||||||
|
val defaultCerts =
|
||||||
|
javaClass.classLoader.getResourceAsStream("org/pkl/commons/cli/PklCARoots.pem")
|
||||||
|
?: throw CliException("Could not find bundled certificates")
|
||||||
|
addCertificates(defaultCerts.readAllBytes())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
package org.pkl.commons.cli
|
package org.pkl.commons.cli
|
||||||
|
|
||||||
import java.io.PrintStream
|
import java.io.PrintStream
|
||||||
import java.security.Security
|
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
/** Building block for CLIs. Intended to be called from a `main` method. */
|
/** Building block for CLIs. Intended to be called from a `main` method. */
|
||||||
@@ -30,9 +29,6 @@ fun cliMain(block: () -> Unit) {
|
|||||||
|
|
||||||
// Force `native-image` to use system proxies (which does not happen with `-D`).
|
// Force `native-image` to use system proxies (which does not happen with `-D`).
|
||||||
System.setProperty("java.net.useSystemProxies", "true")
|
System.setProperty("java.net.useSystemProxies", "true")
|
||||||
// enable OCSP for default SSL context
|
|
||||||
Security.setProperty("ocsp.enable", "true")
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
block()
|
block()
|
||||||
} catch (e: CliTestException) {
|
} catch (e: CliTestException) {
|
||||||
|
|||||||
@@ -64,6 +64,16 @@ class BaseOptions : OptionGroup() {
|
|||||||
throw CliException(message)
|
throw CliException(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun RawOption.associateProps():
|
||||||
|
OptionWithValues<Map<String, String>, Pair<String, String>, Pair<String, String>> {
|
||||||
|
return convert {
|
||||||
|
val parts = it.split("=")
|
||||||
|
if (parts.size <= 1) parts[0] to "true" else parts[0] to parts[1]
|
||||||
|
}
|
||||||
|
.multiple()
|
||||||
|
.toMap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val defaults = CliBaseOptions()
|
private val defaults = CliBaseOptions()
|
||||||
@@ -116,7 +126,7 @@ class BaseOptions : OptionGroup() {
|
|||||||
metavar = "<name=value>",
|
metavar = "<name=value>",
|
||||||
help = "External property to set (repeatable)."
|
help = "External property to set (repeatable)."
|
||||||
)
|
)
|
||||||
.associate()
|
.associateProps()
|
||||||
|
|
||||||
val noCache: Boolean by
|
val noCache: Boolean by
|
||||||
option(names = arrayOf("--no-cache"), help = "Disable caching of packages")
|
option(names = arrayOf("--no-cache"), help = "Disable caching of packages")
|
||||||
@@ -215,7 +225,7 @@ class BaseOptions : OptionGroup() {
|
|||||||
allowedModules = allowedModules.ifEmpty { null },
|
allowedModules = allowedModules.ifEmpty { null },
|
||||||
allowedResources = allowedResources.ifEmpty { null },
|
allowedResources = allowedResources.ifEmpty { null },
|
||||||
environmentVariables = envVars.ifEmpty { null },
|
environmentVariables = envVars.ifEmpty { null },
|
||||||
externalProperties = properties.mapValues { it.value.ifBlank { "true" } }.ifEmpty { null },
|
externalProperties = properties.ifEmpty { null },
|
||||||
modulePath = modulePath.ifEmpty { null },
|
modulePath = modulePath.ifEmpty { null },
|
||||||
workingDir = workingDir,
|
workingDir = workingDir,
|
||||||
settings = settings,
|
settings = settings,
|
||||||
|
|||||||
@@ -45,10 +45,10 @@ class BaseCommandTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `external properties without value default to 'true'`() {
|
fun `external properties without value default to 'true'`() {
|
||||||
cmd.parse(arrayOf("-p", "flag1", "-p", "flag2", "-p", "FOO=bar"))
|
cmd.parse(arrayOf("-p", "flag1", "-p", "flag2=", "-p", "FOO=bar"))
|
||||||
val props = cmd.baseOptions.baseOptions(emptyList()).externalProperties
|
val props = cmd.baseOptions.baseOptions(emptyList()).externalProperties
|
||||||
|
|
||||||
assertThat(props).isEqualTo(mapOf("flag1" to "true", "flag2" to "true", "FOO" to "bar"))
|
assertThat(props).isEqualTo(mapOf("flag1" to "true", "flag2" to "", "FOO" to "bar"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
+1
-1
@@ -45,7 +45,7 @@ public final class ImportGlobMemberBodyNode extends ExpressionNode {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object executeGeneric(VirtualFrame frame) {
|
public Object executeGeneric(VirtualFrame frame) {
|
||||||
var mapping = VmUtils.getObjectReceiver(frame);
|
var mapping = VmUtils.getOwner(frame);
|
||||||
var path = (String) VmUtils.getMemberKey(frame);
|
var path = (String) VmUtils.getMemberKey(frame);
|
||||||
return importModule(mapping, path);
|
return importModule(mapping, path);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -33,7 +33,7 @@ public class ReadGlobMemberBodyNode extends ExpressionNode {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object executeGeneric(VirtualFrame frame) {
|
public Object executeGeneric(VirtualFrame frame) {
|
||||||
var mapping = VmUtils.getObjectReceiver(frame);
|
var mapping = VmUtils.getOwner(frame);
|
||||||
var path = (String) VmUtils.getMemberKey(frame);
|
var path = (String) VmUtils.getMemberKey(frame);
|
||||||
return readResource(mapping, path);
|
return readResource(mapping, path);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,22 +31,17 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.NoSuchFileException;
|
import java.nio.file.NoSuchFileException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.KeyStore;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.cert.CertPathBuilder;
|
import java.security.cert.Certificate;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.security.cert.PKIXBuilderParameters;
|
|
||||||
import java.security.cert.PKIXRevocationChecker;
|
|
||||||
import java.security.cert.TrustAnchor;
|
|
||||||
import java.security.cert.X509CertSelector;
|
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
import javax.net.ssl.CertPathTrustManagerParameters;
|
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
import javax.net.ssl.SSLException;
|
import javax.net.ssl.SSLException;
|
||||||
import javax.net.ssl.SSLHandshakeException;
|
import javax.net.ssl.SSLHandshakeException;
|
||||||
@@ -130,43 +125,36 @@ final class JdkHttpClient implements HttpClient {
|
|||||||
List<Path> certificateFiles, List<ByteBuffer> certificateBytes) {
|
List<Path> certificateFiles, List<ByteBuffer> certificateBytes) {
|
||||||
try {
|
try {
|
||||||
if (certificateFiles.isEmpty() && certificateBytes.isEmpty()) {
|
if (certificateFiles.isEmpty() && certificateBytes.isEmpty()) {
|
||||||
// use Pkl native executable's or JVM's built-in CA certificates
|
// use JVM's built-in CA certificates
|
||||||
return SSLContext.getDefault();
|
return SSLContext.getDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
var certPathBuilder = CertPathBuilder.getInstance("PKIX");
|
|
||||||
// create a non-legacy revocation checker that is configured via setOptions() instead of
|
|
||||||
// security property "ocsp.enabled"
|
|
||||||
var revocationChecker = (PKIXRevocationChecker) certPathBuilder.getRevocationChecker();
|
|
||||||
revocationChecker.setOptions(Set.of()); // prefer OCSP, fall back to CRLs
|
|
||||||
|
|
||||||
var certFactory = CertificateFactory.getInstance("X.509");
|
var certFactory = CertificateFactory.getInstance("X.509");
|
||||||
Set<TrustAnchor> trustAnchors =
|
List<Certificate> certs = gatherCertificates(certFactory, certificateFiles, certificateBytes);
|
||||||
createTrustAnchors(certFactory, certificateFiles, certificateBytes);
|
var keystore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
var pkixParameters = new PKIXBuilderParameters(trustAnchors, new X509CertSelector());
|
keystore.load(null);
|
||||||
// equivalent of "com.sun.net.ssl.checkRevocation=true"
|
for (var i = 0; i < certs.size(); i++) {
|
||||||
pkixParameters.setRevocationEnabled(true);
|
keystore.setCertificateEntry("Certificate" + i, certs.get(i));
|
||||||
pkixParameters.addCertPathChecker(revocationChecker);
|
}
|
||||||
|
|
||||||
var trustManagerFactory = TrustManagerFactory.getInstance("PKIX");
|
var trustManagerFactory = TrustManagerFactory.getInstance("PKIX");
|
||||||
trustManagerFactory.init(new CertPathTrustManagerParameters(pkixParameters));
|
trustManagerFactory.init(keystore);
|
||||||
|
|
||||||
var sslContext = SSLContext.getInstance("TLS");
|
var sslContext = SSLContext.getInstance("TLS");
|
||||||
sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
|
sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
|
||||||
|
|
||||||
return sslContext;
|
return sslContext;
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (GeneralSecurityException | IOException e) {
|
||||||
throw new HttpClientInitException(
|
throw new HttpClientInitException(
|
||||||
ErrorMessages.create("cannotInitHttpClient", Exceptions.getRootReason(e)), e);
|
ErrorMessages.create("cannotInitHttpClient", Exceptions.getRootReason(e)), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<TrustAnchor> createTrustAnchors(
|
private static List<Certificate> gatherCertificates(
|
||||||
CertificateFactory factory, List<Path> certificateFiles, List<ByteBuffer> certificateBytes) {
|
CertificateFactory factory, List<Path> certificateFiles, List<ByteBuffer> certificateBytes) {
|
||||||
var anchors = new HashSet<TrustAnchor>();
|
var certificates = new ArrayList<Certificate>();
|
||||||
for (var file : certificateFiles) {
|
for (var file : certificateFiles) {
|
||||||
try (var stream = Files.newInputStream(file)) {
|
try (var stream = Files.newInputStream(file)) {
|
||||||
collectTrustAnchors(anchors, factory, stream, file);
|
collectCertificates(certificates, factory, stream, file);
|
||||||
} catch (NoSuchFileException e) {
|
} catch (NoSuchFileException e) {
|
||||||
throw new HttpClientInitException(ErrorMessages.create("cannotFindCertFile", file));
|
throw new HttpClientInitException(ErrorMessages.create("cannotFindCertFile", file));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -176,13 +164,13 @@ final class JdkHttpClient implements HttpClient {
|
|||||||
}
|
}
|
||||||
for (var byteBuffer : certificateBytes) {
|
for (var byteBuffer : certificateBytes) {
|
||||||
var stream = new ByteArrayInputStream(byteBuffer.array());
|
var stream = new ByteArrayInputStream(byteBuffer.array());
|
||||||
collectTrustAnchors(anchors, factory, stream, "<unavailable>");
|
collectCertificates(certificates, factory, stream, "<unavailable>");
|
||||||
}
|
}
|
||||||
return anchors;
|
return certificates;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void collectTrustAnchors(
|
private static void collectCertificates(
|
||||||
Collection<TrustAnchor> anchors,
|
ArrayList<Certificate> anchors,
|
||||||
CertificateFactory factory,
|
CertificateFactory factory,
|
||||||
InputStream stream,
|
InputStream stream,
|
||||||
Object source) {
|
Object source) {
|
||||||
@@ -197,8 +185,6 @@ final class JdkHttpClient implements HttpClient {
|
|||||||
if (certificates.isEmpty()) {
|
if (certificates.isEmpty()) {
|
||||||
throw new HttpClientInitException(ErrorMessages.create("emptyCertFile", source));
|
throw new HttpClientInitException(ErrorMessages.create("emptyCertFile", source));
|
||||||
}
|
}
|
||||||
for (var certificate : certificates) {
|
anchors.addAll(certificates);
|
||||||
anchors.add(new TrustAnchor(certificate, null));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -455,10 +455,9 @@ final class PackageResolvers {
|
|||||||
|
|
||||||
private byte[] downloadUriToPathAndComputeChecksum(URI downloadUri, Path path)
|
private byte[] downloadUriToPathAndComputeChecksum(URI downloadUri, Path path)
|
||||||
throws IOException, SecurityManagerException {
|
throws IOException, SecurityManagerException {
|
||||||
Files.createDirectories(path.getParent());
|
|
||||||
var inputStream = openExternalUri(downloadUri);
|
var inputStream = openExternalUri(downloadUri);
|
||||||
try (var digestInputStream = newDigestInputStream(inputStream)) {
|
try (var digestInputStream = newDigestInputStream(inputStream)) {
|
||||||
Files.copy(digestInputStream, path);
|
Files.copy(digestInputStream, path, StandardCopyOption.REPLACE_EXISTING);
|
||||||
return digestInputStream.getMessageDigest().digest();
|
return digestInputStream.getMessageDigest().digest();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -472,7 +471,7 @@ final class PackageResolvers {
|
|||||||
}
|
}
|
||||||
try (var in = inputStream) {
|
try (var in = inputStream) {
|
||||||
Files.createDirectories(path.getParent());
|
Files.createDirectories(path.getParent());
|
||||||
Files.copy(in, path);
|
Files.copy(in, path, StandardCopyOption.REPLACE_EXISTING);
|
||||||
if (checksums != null) {
|
if (checksums != null) {
|
||||||
var digestInputStream = (DigestInputStream) inputStream;
|
var digestInputStream = (DigestInputStream) inputStream;
|
||||||
var checksumBytes = digestInputStream.getMessageDigest().digest();
|
var checksumBytes = digestInputStream.getMessageDigest().digest();
|
||||||
@@ -490,7 +489,10 @@ final class PackageResolvers {
|
|||||||
if (Files.exists(cachePath)) {
|
if (Files.exists(cachePath)) {
|
||||||
return cachePath;
|
return cachePath;
|
||||||
}
|
}
|
||||||
var tmpPath = tmpDir.resolve(metadataRelativePath);
|
Files.createDirectories(tmpDir);
|
||||||
|
var tmpPath =
|
||||||
|
Files.createTempFile(
|
||||||
|
tmpDir, IoUtils.encodePath(packageUri.toString().replaceAll("/", "-")), ".json");
|
||||||
try {
|
try {
|
||||||
downloadMetadata(packageUri, requestUri, tmpPath, checksums);
|
downloadMetadata(packageUri, requestUri, tmpPath, checksums);
|
||||||
Files.createDirectories(cachePath.getParent());
|
Files.createDirectories(cachePath.getParent());
|
||||||
@@ -539,7 +541,10 @@ final class PackageResolvers {
|
|||||||
if (Files.exists(cachePath)) {
|
if (Files.exists(cachePath)) {
|
||||||
return cachePath;
|
return cachePath;
|
||||||
}
|
}
|
||||||
var tmpPath = tmpDir.resolve(relativePath);
|
Files.createDirectories(tmpDir);
|
||||||
|
var tmpPath =
|
||||||
|
Files.createTempFile(
|
||||||
|
tmpDir, IoUtils.encodePath(packageUri.toString().replaceAll("/", "-")), ".zip");
|
||||||
try {
|
try {
|
||||||
var checksumBytes =
|
var checksumBytes =
|
||||||
downloadUriToPathAndComputeChecksum(dependencyMetadata.getPackageZipUrl(), tmpPath);
|
downloadUriToPathAndComputeChecksum(dependencyMetadata.getPackageZipUrl(), tmpPath);
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import java.util.stream.Collectors;
|
|||||||
import org.pkl.core.Release;
|
import org.pkl.core.Release;
|
||||||
import org.pkl.core.SecurityManager;
|
import org.pkl.core.SecurityManager;
|
||||||
import org.pkl.core.SecurityManagerException;
|
import org.pkl.core.SecurityManagerException;
|
||||||
|
import org.pkl.core.http.HttpClientInitException;
|
||||||
import org.pkl.core.module.ModuleKey;
|
import org.pkl.core.module.ModuleKey;
|
||||||
import org.pkl.core.module.ModuleKeys;
|
import org.pkl.core.module.ModuleKeys;
|
||||||
import org.pkl.core.module.ResolvedModuleKey;
|
import org.pkl.core.module.ResolvedModuleKey;
|
||||||
@@ -191,7 +192,7 @@ public final class ModuleCache {
|
|||||||
ModuleKey module, SecurityManager securityManager, @Nullable Node importNode) {
|
ModuleKey module, SecurityManager securityManager, @Nullable Node importNode) {
|
||||||
try {
|
try {
|
||||||
return module.resolve(securityManager);
|
return module.resolve(securityManager);
|
||||||
} catch (SecurityManagerException | PackageLoadError e) {
|
} catch (SecurityManagerException | PackageLoadError | HttpClientInitException e) {
|
||||||
throw new VmExceptionBuilder().withOptionalLocation(importNode).withCause(e).build();
|
throw new VmExceptionBuilder().withOptionalLocation(importNode).withCause(e).build();
|
||||||
} catch (FileNotFoundException | NoSuchFileException e) {
|
} catch (FileNotFoundException | NoSuchFileException e) {
|
||||||
var exceptionBuilder =
|
var exceptionBuilder =
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import org.pkl.core.util.Nullable;
|
|||||||
@TruffleLanguage.Registration(
|
@TruffleLanguage.Registration(
|
||||||
id = "pkl",
|
id = "pkl",
|
||||||
name = "Pkl",
|
name = "Pkl",
|
||||||
version = "0.26.0-dev",
|
version = "0.26.3",
|
||||||
characterMimeTypes = VmLanguage.MIME_TYPE,
|
characterMimeTypes = VmLanguage.MIME_TYPE,
|
||||||
contextPolicy = ContextPolicy.SHARED)
|
contextPolicy = ContextPolicy.SHARED)
|
||||||
public final class VmLanguage extends TruffleLanguage<VmContext> {
|
public final class VmLanguage extends TruffleLanguage<VmContext> {
|
||||||
|
|||||||
@@ -16,6 +16,16 @@ examples {
|
|||||||
import*("../../input-helper/globtest/**.pkl").keys.toListing()
|
import*("../../input-helper/globtest/**.pkl").keys.toListing()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
["amended"] {
|
||||||
|
(import*("../../input-helper/globtest/**.pkl")) {
|
||||||
|
[[true]] {
|
||||||
|
output {
|
||||||
|
renderer = new YamlRenderer {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.toMap().values.map((it) -> it.output.text).join("\n---\n")
|
||||||
|
}
|
||||||
|
|
||||||
["globstar then up one level"] {
|
["globstar then up one level"] {
|
||||||
import*("../../input-helper/globtest/**/../*.pkl").keys.toListing()
|
import*("../../input-helper/globtest/**/../*.pkl").keys.toListing()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,14 @@ examples {
|
|||||||
read*("globtest/*.txt")
|
read*("globtest/*.txt")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
["amended"] {
|
||||||
|
(read*("../../input-helper/globtest/**.pkl")) {
|
||||||
|
[[true]] {
|
||||||
|
text = "hi"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
["env:"] {
|
["env:"] {
|
||||||
// doesn't match names that include slashes
|
// doesn't match names that include slashes
|
||||||
read*("env:*")
|
read*("env:*")
|
||||||
|
|||||||
@@ -21,6 +21,21 @@ examples {
|
|||||||
"../../input-helper/globtest/child/moduleC.pkl"
|
"../../input-helper/globtest/child/moduleC.pkl"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
["amended"] {
|
||||||
|
"""
|
||||||
|
{}
|
||||||
|
|
||||||
|
---
|
||||||
|
name: moduleA
|
||||||
|
|
||||||
|
---
|
||||||
|
name: moduleB
|
||||||
|
|
||||||
|
---
|
||||||
|
name: child/moduleC
|
||||||
|
|
||||||
|
"""
|
||||||
|
}
|
||||||
["globstar then up one level"] {
|
["globstar then up one level"] {
|
||||||
new {
|
new {
|
||||||
"../../input-helper/globtest/child/../module with [weird] ~!characters.pkl"
|
"../../input-helper/globtest/child/../module with [weird] ~!characters.pkl"
|
||||||
|
|||||||
@@ -50,6 +50,30 @@ examples {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
["amended"] {
|
||||||
|
new {
|
||||||
|
["../../input-helper/globtest/module with [weird] ~!characters.pkl"] {
|
||||||
|
uri = "file:///$snippetsDir/input-helper/globtest/module%20with%20%5Bweird%5D%20~!characters.pkl"
|
||||||
|
text = "hi"
|
||||||
|
base64 = ""
|
||||||
|
}
|
||||||
|
["../../input-helper/globtest/moduleA.pkl"] {
|
||||||
|
uri = "file:///$snippetsDir/input-helper/globtest/moduleA.pkl"
|
||||||
|
text = "hi"
|
||||||
|
base64 = "bmFtZSA9ICJtb2R1bGVBIgo="
|
||||||
|
}
|
||||||
|
["../../input-helper/globtest/moduleB.pkl"] {
|
||||||
|
uri = "file:///$snippetsDir/input-helper/globtest/moduleB.pkl"
|
||||||
|
text = "hi"
|
||||||
|
base64 = "bmFtZSA9ICJtb2R1bGVCIgo="
|
||||||
|
}
|
||||||
|
["../../input-helper/globtest/child/moduleC.pkl"] {
|
||||||
|
uri = "file:///$snippetsDir/input-helper/globtest/child/moduleC.pkl"
|
||||||
|
text = "hi"
|
||||||
|
base64 = "bmFtZSA9ICJjaGlsZC9tb2R1bGVDIgo="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
["env:"] {
|
["env:"] {
|
||||||
new {
|
new {
|
||||||
["env:NAME1"] = "value1"
|
["env:NAME1"] = "value1"
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ package org.pkl.core.packages
|
|||||||
|
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.assertj.core.api.Assertions.assertThatCode
|
import org.assertj.core.api.Assertions.assertThatCode
|
||||||
import org.junit.jupiter.api.AfterAll
|
import org.junit.jupiter.api.*
|
||||||
import org.junit.jupiter.api.BeforeAll
|
import org.junit.jupiter.api.parallel.Execution
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.parallel.ExecutionMode
|
||||||
import org.junit.jupiter.api.assertThrows
|
|
||||||
import org.pkl.commons.deleteRecursively
|
import org.pkl.commons.deleteRecursively
|
||||||
import org.pkl.commons.readString
|
import org.pkl.commons.readString
|
||||||
import org.pkl.commons.test.FileTestUtils
|
import org.pkl.commons.test.FileTestUtils
|
||||||
@@ -44,7 +43,9 @@ class PackageResolversTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
// execute test 3 times to check concurrent writes
|
||||||
|
@RepeatedTest(3)
|
||||||
|
@Execution(ExecutionMode.CONCURRENT)
|
||||||
fun `get module bytes`() {
|
fun `get module bytes`() {
|
||||||
val expectedBirdModule = packageRoot.resolve("birds@0.5.0/package/Bird.pkl").readString(StandardCharsets.UTF_8)
|
val expectedBirdModule = packageRoot.resolve("birds@0.5.0/package/Bird.pkl").readString(StandardCharsets.UTF_8)
|
||||||
val assetUri = PackageAssetUri("package://localhost:0/birds@0.5.0#/Bird.pkl")
|
val assetUri = PackageAssetUri("package://localhost:0/birds@0.5.0#/Bird.pkl")
|
||||||
|
|||||||
@@ -134,6 +134,9 @@ public abstract class ModulesTask extends BasePklTask {
|
|||||||
*/
|
*/
|
||||||
private URI parsedModuleNotationToUri(Object notation) {
|
private URI parsedModuleNotationToUri(Object notation) {
|
||||||
if (notation instanceof File file) {
|
if (notation instanceof File file) {
|
||||||
|
if (file.isAbsolute()) {
|
||||||
|
return file.toPath().toUri();
|
||||||
|
}
|
||||||
return IoUtils.createUri(IoUtils.toNormalizedPathString(file.toPath()));
|
return IoUtils.createUri(IoUtils.toNormalizedPathString(file.toPath()));
|
||||||
} else if (notation instanceof URI uri) {
|
} else if (notation instanceof URI uri) {
|
||||||
return uri;
|
return uri;
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
///
|
///
|
||||||
/// Warning: Although this module is ready for initial use,
|
/// Warning: Although this module is ready for initial use,
|
||||||
/// benchmark results may be inaccurate or inconsistent.
|
/// benchmark results may be inaccurate or inconsistent.
|
||||||
@ModuleInfo { minPklVersion = "0.26.0" }
|
@ModuleInfo { minPklVersion = "0.26.1" }
|
||||||
module pkl.Benchmark
|
module pkl.Benchmark
|
||||||
|
|
||||||
import "pkl:platform" as _platform
|
import "pkl:platform" as _platform
|
||||||
|
|||||||
@@ -63,7 +63,7 @@
|
|||||||
/// @Deprecated { message = "Use `com.example.Birds.Parrot` instead" }
|
/// @Deprecated { message = "Use `com.example.Birds.Parrot` instead" }
|
||||||
/// amends "pkl:PackageInfo"
|
/// amends "pkl:PackageInfo"
|
||||||
/// ```
|
/// ```
|
||||||
@ModuleInfo { minPklVersion = "0.26.0" }
|
@ModuleInfo { minPklVersion = "0.26.1" }
|
||||||
module pkl.DocPackageInfo
|
module pkl.DocPackageInfo
|
||||||
|
|
||||||
import "pkl:reflect"
|
import "pkl:reflect"
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
///
|
///
|
||||||
/// title = "Title displayed in the header of each page"
|
/// title = "Title displayed in the header of each page"
|
||||||
/// ```
|
/// ```
|
||||||
@ModuleInfo { minPklVersion = "0.26.0" }
|
@ModuleInfo { minPklVersion = "0.26.1" }
|
||||||
module pkl.DocsiteInfo
|
module pkl.DocsiteInfo
|
||||||
|
|
||||||
import "pkl:reflect"
|
import "pkl:reflect"
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
/// Common settings for Pkl's own evaluator.
|
/// Common settings for Pkl's own evaluator.
|
||||||
@ModuleInfo { minPklVersion = "0.26.0" }
|
@ModuleInfo { minPklVersion = "0.26.1" }
|
||||||
@Since { version = "0.26.0" }
|
@Since { version = "0.26.0" }
|
||||||
module pkl.EvaluatorSettings
|
module pkl.EvaluatorSettings
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -64,7 +64,7 @@
|
|||||||
/// value = project
|
/// value = project
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
@ModuleInfo { minPklVersion = "0.26.0" }
|
@ModuleInfo { minPklVersion = "0.26.1" }
|
||||||
module pkl.Project
|
module pkl.Project
|
||||||
|
|
||||||
import "pkl:EvaluatorSettings" as EvaluatorSettingsModule
|
import "pkl:EvaluatorSettings" as EvaluatorSettingsModule
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@
|
|||||||
/// Fundamental properties, methods, and classes for writing Pkl programs.
|
/// Fundamental properties, methods, and classes for writing Pkl programs.
|
||||||
///
|
///
|
||||||
/// Members of this module are automatically available in every Pkl module.
|
/// Members of this module are automatically available in every Pkl module.
|
||||||
@ModuleInfo { minPklVersion = "0.26.0" }
|
@ModuleInfo { minPklVersion = "0.26.1" }
|
||||||
module pkl.base
|
module pkl.base
|
||||||
|
|
||||||
import "pkl:jsonnet"
|
import "pkl:jsonnet"
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
/// A JSON parser.
|
/// A JSON parser.
|
||||||
@ModuleInfo { minPklVersion = "0.26.0" }
|
@ModuleInfo { minPklVersion = "0.26.1" }
|
||||||
module pkl.json
|
module pkl.json
|
||||||
|
|
||||||
/// A JSON parser.
|
/// A JSON parser.
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
/// A [Jsonnet](https://jsonnet.org) renderer.
|
/// A [Jsonnet](https://jsonnet.org) renderer.
|
||||||
@ModuleInfo { minPklVersion = "0.26.0" }
|
@ModuleInfo { minPklVersion = "0.26.1" }
|
||||||
module pkl.jsonnet
|
module pkl.jsonnet
|
||||||
|
|
||||||
/// Constructs an [ImportStr].
|
/// Constructs an [ImportStr].
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
///
|
///
|
||||||
/// Note that some mathematical functions, such as `sign()`, `abs()`, and `round()`,
|
/// Note that some mathematical functions, such as `sign()`, `abs()`, and `round()`,
|
||||||
/// are directly defined in classes [Number], [Int], and [Float].
|
/// are directly defined in classes [Number], [Int], and [Float].
|
||||||
@ModuleInfo { minPklVersion = "0.26.0" }
|
@ModuleInfo { minPklVersion = "0.26.1" }
|
||||||
module pkl.math
|
module pkl.math
|
||||||
|
|
||||||
/// The minimum [Int] value: `-9223372036854775808`.
|
/// The minimum [Int] value: `-9223372036854775808`.
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
/// Information about the platform that the current program runs on.
|
/// Information about the platform that the current program runs on.
|
||||||
@ModuleInfo { minPklVersion = "0.26.0" }
|
@ModuleInfo { minPklVersion = "0.26.1" }
|
||||||
module pkl.platform
|
module pkl.platform
|
||||||
|
|
||||||
/// The platform that the current program runs on.
|
/// The platform that the current program runs on.
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
/// A renderer for [Protocol Buffers](https://developers.google.com/protocol-buffers).
|
/// A renderer for [Protocol Buffers](https://developers.google.com/protocol-buffers).
|
||||||
/// Note: This module is _experimental_ and not ready for production use.
|
/// Note: This module is _experimental_ and not ready for production use.
|
||||||
@ModuleInfo { minPklVersion = "0.26.0" }
|
@ModuleInfo { minPklVersion = "0.26.1" }
|
||||||
module pkl.protobuf
|
module pkl.protobuf
|
||||||
|
|
||||||
import "pkl:reflect"
|
import "pkl:reflect"
|
||||||
|
|||||||
+1
-1
@@ -26,7 +26,7 @@
|
|||||||
/// - Documentation generators (such as *Pkldoc*)
|
/// - Documentation generators (such as *Pkldoc*)
|
||||||
/// - Code generators (such as *pkl-codegen-java* and *pkl-codegen-kotlin*)
|
/// - Code generators (such as *pkl-codegen-java* and *pkl-codegen-kotlin*)
|
||||||
/// - Domain-specific schema validators
|
/// - Domain-specific schema validators
|
||||||
@ModuleInfo { minPklVersion = "0.26.0" }
|
@ModuleInfo { minPklVersion = "0.26.1" }
|
||||||
module pkl.reflect
|
module pkl.reflect
|
||||||
|
|
||||||
import "pkl:base"
|
import "pkl:base"
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
/// Information about the Pkl release that the current program runs on.
|
/// Information about the Pkl release that the current program runs on.
|
||||||
@ModuleInfo { minPklVersion = "0.26.0" }
|
@ModuleInfo { minPklVersion = "0.26.1" }
|
||||||
module pkl.release
|
module pkl.release
|
||||||
|
|
||||||
import "pkl:semver"
|
import "pkl:semver"
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
/// Parsing, comparison, and manipulation of [semantic version](https://semver.org/spec/v2.0.0.html) numbers.
|
/// Parsing, comparison, and manipulation of [semantic version](https://semver.org/spec/v2.0.0.html) numbers.
|
||||||
@ModuleInfo { minPklVersion = "0.26.0" }
|
@ModuleInfo { minPklVersion = "0.26.1" }
|
||||||
module pkl.semver
|
module pkl.semver
|
||||||
|
|
||||||
/// Tells whether [version] is a valid semantic version number.
|
/// Tells whether [version] is a valid semantic version number.
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@
|
|||||||
/// Every settings file must amend this module.
|
/// Every settings file must amend this module.
|
||||||
/// Unless CLI commands and build tool plugins are explicitly configured with a settings file,
|
/// Unless CLI commands and build tool plugins are explicitly configured with a settings file,
|
||||||
/// they will use `~/.pkl/settings.pkl` or the defaults specified in this module.
|
/// they will use `~/.pkl/settings.pkl` or the defaults specified in this module.
|
||||||
@ModuleInfo { minPklVersion = "0.26.0" }
|
@ModuleInfo { minPklVersion = "0.26.1" }
|
||||||
module pkl.settings
|
module pkl.settings
|
||||||
|
|
||||||
import "pkl:EvaluatorSettings"
|
import "pkl:EvaluatorSettings"
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
/// Utilities for generating shell scripts.
|
/// Utilities for generating shell scripts.
|
||||||
@ModuleInfo { minPklVersion = "0.26.0" }
|
@ModuleInfo { minPklVersion = "0.26.1" }
|
||||||
module pkl.shell
|
module pkl.shell
|
||||||
|
|
||||||
/// Escapes [str] by enclosing it in single quotes.
|
/// Escapes [str] by enclosing it in single quotes.
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
///
|
///
|
||||||
/// To write tests, amend this module and define [facts] or [examples] (or both).
|
/// To write tests, amend this module and define [facts] or [examples] (or both).
|
||||||
/// To run tests, evaluate the amended module.
|
/// To run tests, evaluate the amended module.
|
||||||
@ModuleInfo { minPklVersion = "0.26.0" }
|
@ModuleInfo { minPklVersion = "0.26.1" }
|
||||||
open module pkl.test
|
open module pkl.test
|
||||||
|
|
||||||
/// Named groups of boolean expressions that are expected to evaluate to [true].
|
/// Named groups of boolean expressions that are expected to evaluate to [true].
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
/// An XML renderer.
|
/// An XML renderer.
|
||||||
@ModuleInfo { minPklVersion = "0.26.0" }
|
@ModuleInfo { minPklVersion = "0.26.1" }
|
||||||
module pkl.xml
|
module pkl.xml
|
||||||
|
|
||||||
/// Renders values as XML.
|
/// Renders values as XML.
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
/// A YAML 1.2 compliant YAML parser.
|
/// A YAML 1.2 compliant YAML parser.
|
||||||
@ModuleInfo { minPklVersion = "0.26.0" }
|
@ModuleInfo { minPklVersion = "0.26.1" }
|
||||||
module pkl.yaml
|
module pkl.yaml
|
||||||
|
|
||||||
/// A YAML parser.
|
/// A YAML parser.
|
||||||
|
|||||||
Reference in New Issue
Block a user