Repository: apple/pkl Branch: main Commit: 3f3271d3b156 Files: 4780 Total size: 19.9 MB Directory structure: gitextract_j18pm444/ ├── .editorconfig ├── .git-blame-ignore-revs ├── .gitattributes ├── .githooks/ │ └── pre-commit ├── .github/ │ ├── PklProject │ ├── PklProject.deps.json │ ├── dependabot.yml │ ├── index.pkl │ ├── jobs/ │ │ ├── BuildJavaExecutableJob.pkl │ │ ├── BuildNativeJob.pkl │ │ ├── DeployJob.pkl │ │ ├── GithubRelease.pkl │ │ ├── GradleJob.pkl │ │ ├── PklJob.pkl │ │ └── SimpleGradleJob.pkl │ ├── scripts/ │ │ ├── cc_macos_amd64.sh │ │ └── install_musl.sh │ └── workflows/ │ ├── __lockfile__.yml │ ├── build.yml │ ├── main.yml │ ├── prb.yml │ ├── release-branch.yml │ ├── release.yml │ └── test_report.yml ├── .gitignore ├── .idea/ │ └── vcs.xml ├── .java-version ├── .mailmap ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.adoc ├── DEVELOPMENT.adoc ├── LICENSE.txt ├── MAINTAINERS.adoc ├── NOTICE.txt ├── README.adoc ├── SECURITY.md ├── THIRD-PARTY-NOTICES.txt ├── bench/ │ ├── bench.gradle.kts │ ├── gradle.lockfile │ └── src/ │ └── jmh/ │ ├── java/ │ │ └── org/ │ │ └── pkl/ │ │ └── core/ │ │ ├── Fibonacci.java │ │ ├── ListSort.java │ │ └── parser/ │ │ └── ParserBenchmark.java │ └── resources/ │ └── org/ │ └── pkl/ │ └── core/ │ ├── fib_class.pkl │ ├── fib_class_constrained1.pkl │ ├── fib_class_constrained2.pkl │ ├── fib_class_explicitThis.pkl │ ├── fib_class_typed.pkl │ ├── fib_lambda.pkl │ ├── fib_module.pkl │ └── fib_module_explicitThis.pkl ├── build.gradle.kts ├── buildSrc/ │ ├── build.gradle.kts │ ├── settings.gradle.kts │ └── src/ │ └── main/ │ ├── kotlin/ │ │ ├── BuildInfo.kt │ │ ├── ExecutableJar.kt │ │ ├── ExecutableSpec.kt │ │ ├── GradlePluginTests.kt │ │ ├── GradleVersionInfo.kt │ │ ├── HtmlValidator.kt │ │ ├── InstallGraalVm.kt │ │ ├── JavaVersionRange.kt │ │ ├── MergeSourcesJars.kt │ │ ├── NativeImageBuild.kt │ │ ├── PklFormatterSpotless.kt │ │ ├── PklPublishing.kt │ │ ├── ResolveSourcesJars.kt │ │ ├── pklAllProjects.gradle.kts │ │ ├── pklFatJar.gradle.kts │ │ ├── pklGraalVm.gradle.kts │ │ ├── pklGradlePluginTest.gradle.kts │ │ ├── pklHtmlValidator.gradle.kts │ │ ├── pklJavaExecutable.gradle.kts │ │ ├── pklJavaLibrary.gradle.kts │ │ ├── pklKotlinLibrary.gradle.kts │ │ ├── pklKotlinTest.gradle.kts │ │ ├── pklNativeExecutable.gradle.kts │ │ ├── pklNativeLifecycle.gradle.kts │ │ ├── pklPublishLibrary.gradle.kts │ │ └── pklSpotlessFormat.gradle.kts │ └── resources/ │ ├── license-header.line-comment.txt │ └── license-header.star-block.txt ├── docs/ │ ├── antora.yml │ ├── docs.gradle.kts │ ├── gradle.lockfile │ ├── modules/ │ │ ├── ROOT/ │ │ │ ├── pages/ │ │ │ │ ├── community.adoc │ │ │ │ ├── evolution-and-roadmap.adoc │ │ │ │ ├── examples.adoc │ │ │ │ ├── index.adoc │ │ │ │ ├── integrations.adoc │ │ │ │ ├── language-bindings.adoc │ │ │ │ ├── language.adoc │ │ │ │ ├── resources.adoc │ │ │ │ ├── standard-library.adoc │ │ │ │ └── tools.adoc │ │ │ └── partials/ │ │ │ └── component-attributes.adoc │ │ ├── bindings-specification/ │ │ │ ├── pages/ │ │ │ │ ├── binary-encoding.adoc │ │ │ │ ├── index.adoc │ │ │ │ └── message-passing-api.adoc │ │ │ └── partials/ │ │ │ └── component-attributes.adoc │ │ ├── introduction/ │ │ │ └── pages/ │ │ │ ├── comparison.adoc │ │ │ ├── concepts.adoc │ │ │ ├── index.adoc │ │ │ └── use-cases.adoc │ │ ├── java-binding/ │ │ │ ├── examples/ │ │ │ │ └── JavaConfigExample.java │ │ │ ├── pages/ │ │ │ │ ├── codegen.adoc │ │ │ │ ├── index.adoc │ │ │ │ └── pkl-config-java.adoc │ │ │ └── partials/ │ │ │ └── cli-codegen-options.adoc │ │ ├── kotlin-binding/ │ │ │ ├── examples/ │ │ │ │ └── KotlinConfigExample.kt │ │ │ └── pages/ │ │ │ ├── codegen.adoc │ │ │ ├── index.adoc │ │ │ └── pkl-config-kotlin.adoc │ │ ├── language-reference/ │ │ │ └── pages/ │ │ │ └── index.adoc │ │ ├── language-tutorial/ │ │ │ └── pages/ │ │ │ ├── 01_basic_config.adoc │ │ │ ├── 02_filling_out_a_template.adoc │ │ │ ├── 03_writing_a_template.adoc │ │ │ └── index.adoc │ │ ├── pkl-cli/ │ │ │ ├── pages/ │ │ │ │ └── index.adoc │ │ │ └── partials/ │ │ │ ├── cli-common-options.adoc │ │ │ └── cli-project-options.adoc │ │ ├── pkl-core/ │ │ │ ├── examples/ │ │ │ │ └── CoreEvaluatorExample.java │ │ │ └── pages/ │ │ │ └── index.adoc │ │ ├── pkl-doc/ │ │ │ └── pages/ │ │ │ └── index.adoc │ │ ├── pkl-gradle/ │ │ │ ├── pages/ │ │ │ │ └── index.adoc │ │ │ └── partials/ │ │ │ ├── gradle-codegen-properties.adoc │ │ │ ├── gradle-common-properties.adoc │ │ │ └── gradle-modules-properties.adoc │ │ ├── release-notes/ │ │ │ ├── pages/ │ │ │ │ ├── 0.25.adoc │ │ │ │ ├── 0.26.adoc │ │ │ │ ├── 0.27.adoc │ │ │ │ ├── 0.28.adoc │ │ │ │ ├── 0.29.adoc │ │ │ │ ├── 0.30.adoc │ │ │ │ ├── 0.31.adoc │ │ │ │ ├── 0.32.adoc │ │ │ │ ├── changelog.adoc │ │ │ │ └── index.adoc │ │ │ ├── partials/ │ │ │ │ └── intro.adoc │ │ │ └── template.adoc │ │ └── style-guide/ │ │ └── pages/ │ │ └── index.adoc │ ├── nav.adoc │ └── src/ │ └── test/ │ ├── kotlin/ │ │ └── DocSnippetTests.kt │ └── resources/ │ └── META-INF/ │ └── services/ │ └── org.junit.platform.engine.TestEngine ├── gradle/ │ ├── libs.versions.toml │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── jbang-catalog.json ├── pkl-bom/ │ └── pkl-bom.gradle.kts ├── pkl-cli/ │ ├── README.adoc │ ├── gradle.lockfile │ ├── pkl-cli.gradle.kts │ └── src/ │ ├── main/ │ │ └── kotlin/ │ │ └── org/ │ │ └── pkl/ │ │ └── cli/ │ │ ├── CliCommandRunner.kt │ │ ├── CliEvaluator.kt │ │ ├── CliEvaluatorOptions.kt │ │ ├── CliFormatterCommand.kt │ │ ├── CliImportAnalyzer.kt │ │ ├── CliImportAnalyzerOptions.kt │ │ ├── CliPackageDownloader.kt │ │ ├── CliProjectCommand.kt │ │ ├── CliProjectPackager.kt │ │ ├── CliProjectResolver.kt │ │ ├── CliRepl.kt │ │ ├── CliServer.kt │ │ ├── CliTestRunner.kt │ │ ├── Main.kt │ │ ├── OutputUtils.kt │ │ ├── commands/ │ │ │ ├── AnalyzeCommand.kt │ │ │ ├── DownloadPackageCommand.kt │ │ │ ├── EvalCommand.kt │ │ │ ├── FormatterCommand.kt │ │ │ ├── ProjectCommand.kt │ │ │ ├── ReplCommand.kt │ │ │ ├── RootCommand.kt │ │ │ ├── RunCommand.kt │ │ │ ├── ServerCommand.kt │ │ │ ├── TestCommand.kt │ │ │ └── Utils.kt │ │ └── repl/ │ │ ├── Repl.kt │ │ ├── ReplCommands.kt │ │ ├── ReplCompleters.kt │ │ ├── ReplMessages.kt │ │ └── package-info.java │ └── test/ │ ├── files/ │ │ └── projects/ │ │ └── project1/ │ │ └── PklProject │ └── kotlin/ │ └── org/ │ └── pkl/ │ └── cli/ │ ├── CliCommandRunnerTest.kt │ ├── CliEvaluatorTest.kt │ ├── CliFormatterTest.kt │ ├── CliImportAnalyzerTest.kt │ ├── CliMainTest.kt │ ├── CliPackageDownloaderTest.kt │ ├── CliProjectPackagerTest.kt │ ├── CliProjectResolverTest.kt │ ├── CliShellCompletionTest.kt │ ├── CliTestRunnerTest.kt │ ├── repl/ │ │ └── ReplMessagesTest.kt │ └── testExtensions.kt ├── pkl-codegen-java/ │ ├── gradle.lockfile │ ├── pkl-codegen-java.gradle.kts │ └── src/ │ ├── main/ │ │ └── kotlin/ │ │ └── org/ │ │ └── pkl/ │ │ └── codegen/ │ │ └── java/ │ │ ├── CliJavaCodeGenerator.kt │ │ ├── CliJavaCodeGeneratorOptions.kt │ │ ├── JavaCodeGenerator.kt │ │ └── Main.kt │ └── test/ │ ├── kotlin/ │ │ └── org/ │ │ └── pkl/ │ │ └── codegen/ │ │ └── java/ │ │ ├── CliJavaCodeGeneratorTest.kt │ │ ├── InMemoryJavaCompiler.kt │ │ ├── JavaCodeGeneratorTest.kt │ │ └── PklModule.kt │ └── resources/ │ └── org/ │ └── pkl/ │ └── codegen/ │ └── java/ │ ├── GenerateGetters.jva │ ├── GeneratedAnnotation.jva │ ├── Inheritance.jva │ ├── Javadoc.jva │ └── PropertyTypes.jva ├── pkl-codegen-kotlin/ │ ├── gradle.lockfile │ ├── pkl-codegen-kotlin.gradle.kts │ └── src/ │ ├── main/ │ │ └── kotlin/ │ │ └── org/ │ │ └── pkl/ │ │ └── codegen/ │ │ └── kotlin/ │ │ ├── CliKotlinCodeGenerator.kt │ │ ├── CliKotlinCodeGeneratorOptions.kt │ │ ├── KotlinCodeGenerator.kt │ │ └── Main.kt │ └── test/ │ ├── kotlin/ │ │ └── org/ │ │ └── pkl/ │ │ └── codegen/ │ │ └── kotlin/ │ │ ├── CliKotlinCodeGeneratorTest.kt │ │ ├── InMemoryKotlinCompiler.kt │ │ ├── KotlinCodeGeneratorTest.kt │ │ └── PklModule.kt │ └── resources/ │ └── org/ │ └── pkl/ │ └── codegen/ │ └── kotlin/ │ ├── Inheritance.kotlin │ ├── Kdoc.kotlin │ └── PropertyTypes.kotlin ├── pkl-commons/ │ ├── gradle.lockfile │ ├── pkl-commons.gradle.kts │ └── src/ │ ├── main/ │ │ └── kotlin/ │ │ └── org/ │ │ └── pkl/ │ │ └── commons/ │ │ ├── Control.kt │ │ ├── NameMapper.kt │ │ ├── Paths.kt │ │ ├── Strings.kt │ │ ├── Throwables.kt │ │ └── Uris.kt │ └── test/ │ └── kotlin/ │ └── org/ │ └── pkl/ │ └── commons/ │ ├── NameMapperTest.kt │ └── ShlexTest.kt ├── pkl-commons-cli/ │ ├── gradle.lockfile │ ├── pkl-commons-cli.gradle.kts │ └── src/ │ ├── main/ │ │ ├── kotlin/ │ │ │ └── org/ │ │ │ └── pkl/ │ │ │ └── commons/ │ │ │ └── cli/ │ │ │ ├── CliBaseOptions.kt │ │ │ ├── CliCommand.kt │ │ │ ├── CliException.kt │ │ │ ├── CliMain.kt │ │ │ ├── CliTestException.kt │ │ │ ├── CliTestOptions.kt │ │ │ └── commands/ │ │ │ ├── BaseCommand.kt │ │ │ ├── BaseOptions.kt │ │ │ ├── ModulesCommand.kt │ │ │ ├── NoOpCommand.kt │ │ │ ├── OptionExtensions.kt │ │ │ ├── ProjectOptions.kt │ │ │ ├── TestOptions.kt │ │ │ └── extensions.kt │ │ └── resources/ │ │ └── org/ │ │ └── pkl/ │ │ └── commons/ │ │ └── cli/ │ │ └── PklCARoots.pem │ ├── svm/ │ │ └── java/ │ │ └── org/ │ │ └── pkl/ │ │ └── commons/ │ │ └── cli/ │ │ └── svm/ │ │ ├── InitFeature.java │ │ ├── MessagePackRecomputations.java │ │ ├── PolyglotContextImplTarget.java │ │ ├── PolyglotThreadInfoTarget.java │ │ ├── ThreadLocalHandshakeTarget.java │ │ └── WeakAssumedValueTarget.java │ └── test/ │ └── kotlin/ │ └── org/ │ └── pkl/ │ └── commons/ │ └── cli/ │ ├── BaseCommandTest.kt │ └── CliCommandTest.kt ├── pkl-commons-test/ │ ├── gradle.lockfile │ ├── pkl-commons-test.gradle.kts │ └── src/ │ └── main/ │ ├── files/ │ │ └── packages/ │ │ ├── badChecksum@1.0.0/ │ │ │ ├── badChecksum@1.0.0.json │ │ │ └── package/ │ │ │ └── Bug.pkl │ │ ├── badImportsWithinPackage@1.0.0/ │ │ │ ├── badImportsWithinPackage@1.0.0.json │ │ │ └── package/ │ │ │ ├── invalidPath.pkl │ │ │ ├── invalidPathRead.pkl │ │ │ ├── unknownDependency.pkl │ │ │ └── unknownDependencyRead.pkl │ │ ├── badMetadataJson@1.0.0/ │ │ │ ├── badMetadataJson@1.0.0.json │ │ │ └── package/ │ │ │ └── Bug.pkl │ │ ├── badPackageZipUrl@1.0.0/ │ │ │ ├── badPackageZipUrl@1.0.0.json │ │ │ └── package/ │ │ │ └── Bug.pkl │ │ ├── birds@0.5.0/ │ │ │ ├── birds@0.5.0.json │ │ │ └── package/ │ │ │ ├── Bird.pkl │ │ │ ├── allFruit.pkl │ │ │ ├── catalog/ │ │ │ │ ├── Ostrich.pkl │ │ │ │ └── Swallow.pkl │ │ │ ├── catalog.pkl │ │ │ └── some/ │ │ │ └── dir/ │ │ │ └── Bird.pkl │ │ ├── birds@0.6.0/ │ │ │ ├── birds@0.6.0.json │ │ │ └── package/ │ │ │ ├── Bird.pkl │ │ │ ├── allFruit.pkl │ │ │ ├── catalog/ │ │ │ │ ├── Ostrich.pkl │ │ │ │ └── Swallow.pkl │ │ │ ├── catalog.pkl │ │ │ └── some/ │ │ │ └── dir/ │ │ │ └── Bird.pkl │ │ ├── birds@0.7.0/ │ │ │ ├── birds@0.7.0.json │ │ │ └── package/ │ │ │ ├── Bird.pkl │ │ │ ├── allFruit.pkl │ │ │ ├── catalog/ │ │ │ │ ├── Ostrich.pkl │ │ │ │ └── Swallow.pkl │ │ │ ├── catalog.pkl │ │ │ └── some/ │ │ │ └── dir/ │ │ │ └── Bird.pkl │ │ ├── deprecated@1.0.0/ │ │ │ ├── deprecated@1.0.0.json │ │ │ └── package/ │ │ │ └── deprecated.pkl │ │ ├── fruit@1.0.5/ │ │ │ ├── fruit@1.0.5.json │ │ │ └── package/ │ │ │ ├── Fruit.pkl │ │ │ └── catalog/ │ │ │ └── apple.pkl │ │ ├── fruit@1.1.0/ │ │ │ ├── fruit@1.1.0.json │ │ │ └── package/ │ │ │ ├── Fruit.pkl │ │ │ └── catalog/ │ │ │ ├── apple.pkl │ │ │ └── pineapple.pkl │ │ ├── packageContainingWildcardCharacters@1.0.0/ │ │ │ ├── package/ │ │ │ │ └── name with [wildcard]! characters~~.pkl │ │ │ └── packageContainingWildcardCharacters@1.0.0.json │ │ └── unlisted@1.0.0/ │ │ ├── package/ │ │ │ └── unlisted.pkl │ │ └── unlisted@1.0.0.json │ └── kotlin/ │ └── org/ │ └── pkl/ │ └── commons/ │ └── test/ │ ├── Executables.kt │ ├── FakeHttpResponse.kt │ ├── FileTestUtils.kt │ ├── FilteringClassLoader.kt │ ├── InputOutputTestEngine.kt │ ├── MessagePackDebugRenderer.kt │ ├── PackageServer.kt │ ├── PklAssertionFailedError.kt │ └── ReflectionUtils.kt ├── pkl-config-java/ │ ├── gradle.lockfile │ ├── pkl-config-java.gradle.kts │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── pkl/ │ │ └── config/ │ │ └── java/ │ │ ├── AbstractConfig.java │ │ ├── CompositeConfig.java │ │ ├── Config.java │ │ ├── ConfigEvaluator.java │ │ ├── ConfigEvaluatorBuilder.java │ │ ├── ConfigEvaluatorImpl.java │ │ ├── Generated.java │ │ ├── InvalidMappingException.java │ │ ├── JavaType.java │ │ ├── LeafConfig.java │ │ ├── MapConfig.java │ │ ├── NoSuchChildException.java │ │ ├── mapper/ │ │ │ ├── ClassRegistry.java │ │ │ ├── Conversion.java │ │ │ ├── ConversionException.java │ │ │ ├── Conversions.java │ │ │ ├── Converter.java │ │ │ ├── ConverterFactories.java │ │ │ ├── ConverterFactory.java │ │ │ ├── Named.java │ │ │ ├── NonNull.java │ │ │ ├── PAnyToOptional.java │ │ │ ├── PCollectionToArray.java │ │ │ ├── PCollectionToCollection.java │ │ │ ├── PMapToMap.java │ │ │ ├── PNullToAny.java │ │ │ ├── PObjectToDataObject.java │ │ │ ├── PObjectToMap.java │ │ │ ├── PObjectToPObject.java │ │ │ ├── PPairToPair.java │ │ │ ├── PStringToEnum.java │ │ │ ├── Reflection.java │ │ │ ├── Tuple2.java │ │ │ ├── TypeMapping.java │ │ │ ├── TypeMappings.java │ │ │ ├── Types.java │ │ │ ├── ValueMapper.java │ │ │ ├── ValueMapperBuilder.java │ │ │ ├── ValueMapperImpl.java │ │ │ └── package-info.java │ │ └── package-info.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── pkl/ │ │ └── config/ │ │ └── java/ │ │ ├── AbstractConfigTest.java │ │ ├── ConfigEvaluatorBuilderTest.java │ │ ├── ConfigPklBinaryDecoderTest.java │ │ ├── ConfigTest.java │ │ ├── JavaTypeTest.java │ │ └── mapper/ │ │ ├── BytesToByteArrayTest.java │ │ ├── ConversionsTest.java │ │ ├── PAnyToOptionalTest.java │ │ ├── PCollectionToArrayTest.java │ │ ├── PCollectionToCollectionTest.java │ │ ├── PMapToMapTest.java │ │ ├── PModuleToDataObjectTest.java │ │ ├── PNullToAnyTest.java │ │ ├── PObjectToDataObjectJavaxInjectTest.java │ │ ├── PObjectToDataObjectOverriddenPropertyTest.java │ │ ├── PObjectToDataObjectTest.java │ │ ├── PObjectToInnerClassTest.java │ │ ├── PObjectToPObjectTest.java │ │ ├── PPairToPairTest.java │ │ ├── PStringToEnumTest.java │ │ ├── PStringToVersionTest.java │ │ ├── PVersionToStringTest.java │ │ ├── PVersionToVersionTest.java │ │ ├── Person.java │ │ ├── PolymorphicTest.kt │ │ ├── ReflectionTest.java │ │ └── TypesTest.java │ └── resources/ │ ├── codegenPkl/ │ │ ├── OverriddenProperty.pkl │ │ ├── PolymorphicLib.pkl │ │ └── PolymorphicModuleTest.pkl │ └── org/ │ └── pkl/ │ └── config/ │ └── java/ │ └── mapper/ │ ├── BytesToByteArrayTest.pkl │ ├── PAnyToOptionalTest.pkl │ ├── PCollectionToArrayTest.pkl │ ├── PCollectionToCollectionTest.pkl │ ├── PMapToMapTest.pkl │ ├── PModuleToDataObjectTest.pkl │ ├── PObjectToDataObjectPolymorphismTest.pkl │ ├── PObjectToDataObjectTest.pkl │ ├── PObjectToPObjectTest.pkl │ ├── PPairToPairTest.pkl │ ├── PStringToEnumTest.pkl │ ├── PStringToVersionTest.pkl │ └── PVersionToVersionTest.pkl ├── pkl-config-kotlin/ │ ├── gradle.lockfile │ ├── pkl-config-kotlin.gradle.kts │ └── src/ │ ├── main/ │ │ └── kotlin/ │ │ └── org/ │ │ └── pkl/ │ │ └── config/ │ │ └── kotlin/ │ │ ├── ConfigExtensions.kt │ │ └── mapper/ │ │ ├── KotlinConversions.kt │ │ ├── KotlinConverterFactories.kt │ │ └── PPairToKotlinPair.kt │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── pkl/ │ │ └── config/ │ │ └── kotlin/ │ │ └── JavaPerson.java │ ├── kotlin/ │ │ └── org/ │ │ └── pkl/ │ │ └── config/ │ │ └── kotlin/ │ │ ├── ConfigExtensionsTest.kt │ │ ├── KotlinObjectMappingTest.kt │ │ └── mapper/ │ │ ├── KotlinConversionsTest.kt │ │ ├── OverriddenPropertyTest.kt │ │ ├── PPairToKotlinPairTest.kt │ │ └── PolymorphicTest.kt │ └── resources/ │ ├── codegenPkl/ │ │ ├── OverriddenProperty.pkl │ │ ├── PolymorphicLib.pkl │ │ └── PolymorphicModuleTest.pkl │ └── org/ │ └── pkl/ │ └── config/ │ └── kotlin/ │ └── mapper/ │ └── PPairToKotlinPairTest.pkl ├── pkl-core/ │ ├── README.adoc │ ├── gradle.lockfile │ ├── pkl-core.gradle.kts │ └── src/ │ ├── generator/ │ │ ├── kotlin/ │ │ │ └── org/ │ │ │ └── pkl/ │ │ │ └── core/ │ │ │ └── generator/ │ │ │ └── MemberRegistryGenerator.kt │ │ └── resources/ │ │ └── META-INF/ │ │ ├── gradle/ │ │ │ └── incremental.annotation.processors │ │ └── services/ │ │ └── javax.annotation.processing.Processor │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── pkl/ │ │ │ └── core/ │ │ │ ├── Analyzer.java │ │ │ ├── BufferedLogger.java │ │ │ ├── Closeables.java │ │ │ ├── CommandSpec.java │ │ │ ├── Composite.java │ │ │ ├── DataSize.java │ │ │ ├── DataSizeUnit.java │ │ │ ├── Duration.java │ │ │ ├── DurationUnit.java │ │ │ ├── Evaluator.java │ │ │ ├── EvaluatorBuilder.java │ │ │ ├── EvaluatorImpl.java │ │ │ ├── FileOutput.java │ │ │ ├── FileOutputImpl.java │ │ │ ├── ImportGraph.java │ │ │ ├── JsonRenderer.java │ │ │ ├── Logger.java │ │ │ ├── Loggers.java │ │ │ ├── Member.java │ │ │ ├── Modifier.java │ │ │ ├── ModuleSchema.java │ │ │ ├── ModuleSource.java │ │ │ ├── NoSuchPropertyException.java │ │ │ ├── OutputFormat.java │ │ │ ├── PClass.java │ │ │ ├── PClassInfo.java │ │ │ ├── PListRenderer.java │ │ │ ├── PModule.java │ │ │ ├── PNull.java │ │ │ ├── PObject.java │ │ │ ├── PType.java │ │ │ ├── Pair.java │ │ │ ├── PcfRenderer.java │ │ │ ├── PklBinaryDecoder.java │ │ │ ├── PklBugException.java │ │ │ ├── PklException.java │ │ │ ├── PklInfo.java │ │ │ ├── Platform.java │ │ │ ├── PropertiesRenderer.java │ │ │ ├── Release.java │ │ │ ├── RendererException.java │ │ │ ├── SecurityManager.java │ │ │ ├── SecurityManagerBuilder.java │ │ │ ├── SecurityManagerException.java │ │ │ ├── SecurityManagers.java │ │ │ ├── StackFrame.java │ │ │ ├── StackFrameTransformer.java │ │ │ ├── StackFrameTransformers.java │ │ │ ├── TestResults.java │ │ │ ├── TypeAlias.java │ │ │ ├── TypeParameter.java │ │ │ ├── Value.java │ │ │ ├── ValueConverter.java │ │ │ ├── ValueFormatter.java │ │ │ ├── ValueRenderer.java │ │ │ ├── ValueRenderers.java │ │ │ ├── ValueVisitor.java │ │ │ ├── Version.java │ │ │ ├── YamlRenderer.java │ │ │ ├── ast/ │ │ │ │ ├── ByteConstantValueNode.java │ │ │ │ ├── ConstantNode.java │ │ │ │ ├── ConstantValueNode.java │ │ │ │ ├── ExpressionNode.java │ │ │ │ ├── MemberLookupMode.java │ │ │ │ ├── MemberNode.java │ │ │ │ ├── PklNode.java │ │ │ │ ├── PklRootNode.java │ │ │ │ ├── SimpleRootNode.java │ │ │ │ ├── VmModifier.java │ │ │ │ ├── builder/ │ │ │ │ │ ├── AbstractAstBuilder.java │ │ │ │ │ ├── AstBuilder.java │ │ │ │ │ ├── CannotInvokeAbstractFunctionNode.java │ │ │ │ │ ├── CannotInvokeAbstractPropertyNode.java │ │ │ │ │ ├── ConstLevel.java │ │ │ │ │ ├── ImportsAndReadsParser.java │ │ │ │ │ ├── SymbolTable.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── expression/ │ │ │ │ │ ├── binary/ │ │ │ │ │ │ ├── AdditionNode.java │ │ │ │ │ │ ├── BinaryExpressionNode.java │ │ │ │ │ │ ├── ComparatorNode.java │ │ │ │ │ │ ├── DivisionNode.java │ │ │ │ │ │ ├── EqualNode.java │ │ │ │ │ │ ├── ExponentiationNode.java │ │ │ │ │ │ ├── GreaterThanNode.java │ │ │ │ │ │ ├── GreaterThanOrEqualNode.java │ │ │ │ │ │ ├── LessThanNode.java │ │ │ │ │ │ ├── LessThanOrEqualNode.java │ │ │ │ │ │ ├── LetExprNode.java │ │ │ │ │ │ ├── LogicalAndNode.java │ │ │ │ │ │ ├── LogicalOrNode.java │ │ │ │ │ │ ├── MultiplicationNode.java │ │ │ │ │ │ ├── NotEqualNode.java │ │ │ │ │ │ ├── NullCoalescingNode.java │ │ │ │ │ │ ├── PipeNode.java │ │ │ │ │ │ ├── RemainderNode.java │ │ │ │ │ │ ├── ShortCircuitingExpressionNode.java │ │ │ │ │ │ ├── SubscriptNode.java │ │ │ │ │ │ ├── SubtractionNode.java │ │ │ │ │ │ ├── TruncatingDivisionNode.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── generator/ │ │ │ │ │ │ ├── GeneratorElementNode.java │ │ │ │ │ │ ├── GeneratorEntryNode.java │ │ │ │ │ │ ├── GeneratorForNode.java │ │ │ │ │ │ ├── GeneratorMemberNode.java │ │ │ │ │ │ ├── GeneratorObjectLiteralNode.java │ │ │ │ │ │ ├── GeneratorPredicateMemberNode.java │ │ │ │ │ │ ├── GeneratorPropertyNode.java │ │ │ │ │ │ ├── GeneratorSpreadNode.java │ │ │ │ │ │ ├── GeneratorWhenNode.java │ │ │ │ │ │ ├── ObjectData.java │ │ │ │ │ │ ├── RestoreForBindingsNode.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── literal/ │ │ │ │ │ │ ├── AmendFunctionNode.java │ │ │ │ │ │ ├── AmendModuleNode.java │ │ │ │ │ │ ├── BytesLiteralNode.java │ │ │ │ │ │ ├── CheckIsAnnotationClassNode.java │ │ │ │ │ │ ├── ConstantEntriesLiteralNode.java │ │ │ │ │ │ ├── ElementsEntriesLiteralNode.java │ │ │ │ │ │ ├── ElementsLiteralNode.java │ │ │ │ │ │ ├── EmptyObjectLiteralNode.java │ │ │ │ │ │ ├── EntriesLiteralNode.java │ │ │ │ │ │ ├── FalseLiteralNode.java │ │ │ │ │ │ ├── FloatLiteralNode.java │ │ │ │ │ │ ├── FunctionLiteralNode.java │ │ │ │ │ │ ├── IntLiteralNode.java │ │ │ │ │ │ ├── InterpolatedStringLiteralNode.java │ │ │ │ │ │ ├── ListLiteralNode.java │ │ │ │ │ │ ├── MapLiteralNode.java │ │ │ │ │ │ ├── ObjectLiteralNode.java │ │ │ │ │ │ ├── PropertiesLiteralNode.java │ │ │ │ │ │ ├── SetLiteralNode.java │ │ │ │ │ │ ├── SpecializedObjectLiteralNode.java │ │ │ │ │ │ ├── TrueLiteralNode.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── member/ │ │ │ │ │ │ ├── InferParentWithinMethodNode.java │ │ │ │ │ │ ├── InferParentWithinObjectMethodNode.java │ │ │ │ │ │ ├── InferParentWithinPropertyNode.java │ │ │ │ │ │ ├── InvokeMethodDirectNode.java │ │ │ │ │ │ ├── InvokeMethodLexicalNode.java │ │ │ │ │ │ ├── InvokeMethodVirtualNode.java │ │ │ │ │ │ ├── InvokeSuperMethodNode.java │ │ │ │ │ │ ├── ReadLocalPropertyNode.java │ │ │ │ │ │ ├── ReadPropertyNode.java │ │ │ │ │ │ ├── ReadSuperEntryNode.java │ │ │ │ │ │ ├── ReadSuperPropertyNode.java │ │ │ │ │ │ ├── ResolveMethodNode.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ ├── primary/ │ │ │ │ │ │ ├── CustomThisNode.java │ │ │ │ │ │ ├── GetEnclosingOwnerNode.java │ │ │ │ │ │ ├── GetEnclosingReceiverNode.java │ │ │ │ │ │ ├── GetMemberKeyNode.java │ │ │ │ │ │ ├── GetModuleNode.java │ │ │ │ │ │ ├── GetOwnerNode.java │ │ │ │ │ │ ├── GetReceiverNode.java │ │ │ │ │ │ ├── OuterNode.java │ │ │ │ │ │ ├── ResolveVariableNode.java │ │ │ │ │ │ ├── ThisNode.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── ternary/ │ │ │ │ │ │ ├── IfElseNode.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── unary/ │ │ │ │ │ ├── AbstractImportNode.java │ │ │ │ │ ├── AbstractReadNode.java │ │ │ │ │ ├── ImportGlobMemberBodyNode.java │ │ │ │ │ ├── ImportGlobNode.java │ │ │ │ │ ├── ImportNode.java │ │ │ │ │ ├── LogicalNotNode.java │ │ │ │ │ ├── NonNullNode.java │ │ │ │ │ ├── NullPropagatingOperationNode.java │ │ │ │ │ ├── PropagateNullReceiverNode.java │ │ │ │ │ ├── ReadGlobMemberBodyNode.java │ │ │ │ │ ├── ReadGlobNode.java │ │ │ │ │ ├── ReadNode.java │ │ │ │ │ ├── ReadOrNullNode.java │ │ │ │ │ ├── ReadOrNullStdLibNode.java │ │ │ │ │ ├── ThrowNode.java │ │ │ │ │ ├── TraceNode.java │ │ │ │ │ ├── UnaryExpressionNode.java │ │ │ │ │ ├── UnaryMinusNode.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── frame/ │ │ │ │ │ ├── ReadEnclosingFrameSlotNode.java │ │ │ │ │ ├── ReadFrameSlotNode.java │ │ │ │ │ ├── WriteFrameSlotNode.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── internal/ │ │ │ │ │ ├── BlackholeNode.java │ │ │ │ │ ├── GetBaseModuleClassNode.java │ │ │ │ │ ├── GetClassNode.java │ │ │ │ │ ├── IsInstanceOfNode.java │ │ │ │ │ ├── SyntheticNode.java │ │ │ │ │ ├── ToStringNode.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── lambda/ │ │ │ │ │ ├── ApplyVmFunction0Node.java │ │ │ │ │ ├── ApplyVmFunction1Node.java │ │ │ │ │ ├── ApplyVmFunction2Node.java │ │ │ │ │ ├── ApplyVmFunction3Node.java │ │ │ │ │ ├── ApplyVmFunction4Node.java │ │ │ │ │ ├── ApplyVmFunction5Node.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── member/ │ │ │ │ │ ├── ClassMember.java │ │ │ │ │ ├── ClassMethod.java │ │ │ │ │ ├── ClassNode.java │ │ │ │ │ ├── ClassProperty.java │ │ │ │ │ ├── DefaultPropertyBodyNode.java │ │ │ │ │ ├── DelegateToExtraStorageMapOrParentNode.java │ │ │ │ │ ├── DelegateToExtraStorageObjNode.java │ │ │ │ │ ├── DelegateToExtraStorageObjOrParentNode.java │ │ │ │ │ ├── ElementOrEntryNode.java │ │ │ │ │ ├── FunctionNode.java │ │ │ │ │ ├── Lambda.java │ │ │ │ │ ├── ListingOrMappingTypeCastNode.java │ │ │ │ │ ├── LocalTypedPropertyNode.java │ │ │ │ │ ├── Member.java │ │ │ │ │ ├── ModuleNode.java │ │ │ │ │ ├── ObjectMember.java │ │ │ │ │ ├── ObjectMethodNode.java │ │ │ │ │ ├── PropertyTypeNode.java │ │ │ │ │ ├── RegularMemberNode.java │ │ │ │ │ ├── SharedMemberNode.java │ │ │ │ │ ├── TypeAliasNode.java │ │ │ │ │ ├── TypeCheckedPropertyNode.java │ │ │ │ │ ├── TypedPropertyNode.java │ │ │ │ │ ├── UnresolvedClassMemberNode.java │ │ │ │ │ ├── UnresolvedFunctionNode.java │ │ │ │ │ ├── UnresolvedMethodNode.java │ │ │ │ │ ├── UnresolvedPropertyNode.java │ │ │ │ │ ├── UntypedObjectMemberNode.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── repl/ │ │ │ │ │ ├── ResolveClassMemberNode.java │ │ │ │ │ └── package-info.java │ │ │ │ └── type/ │ │ │ │ ├── GetParentForTypeNode.java │ │ │ │ ├── IdentityMixinNode.java │ │ │ │ ├── ResolveDeclaredTypeNode.java │ │ │ │ ├── ResolveQualifiedDeclaredTypeNode.java │ │ │ │ ├── ResolveSimpleDeclaredTypeNode.java │ │ │ │ ├── TypeCastNode.java │ │ │ │ ├── TypeConstraintNode.java │ │ │ │ ├── TypeNode.java │ │ │ │ ├── TypeTestNode.java │ │ │ │ ├── UnresolvedTypeNode.java │ │ │ │ ├── VmTypeMismatchException.java │ │ │ │ └── package-info.java │ │ │ ├── evaluatorSettings/ │ │ │ │ ├── Color.java │ │ │ │ ├── PklEvaluatorSettings.java │ │ │ │ ├── TraceMode.java │ │ │ │ └── package-info.java │ │ │ ├── externalreader/ │ │ │ │ ├── ExternalModuleResolver.java │ │ │ │ ├── ExternalModuleResolverImpl.java │ │ │ │ ├── ExternalReaderMessagePackDecoder.java │ │ │ │ ├── ExternalReaderMessagePackEncoder.java │ │ │ │ ├── ExternalReaderMessages.java │ │ │ │ ├── ExternalReaderProcess.java │ │ │ │ ├── ExternalReaderProcessException.java │ │ │ │ ├── ExternalReaderProcessImpl.java │ │ │ │ ├── ExternalResourceResolver.java │ │ │ │ ├── ExternalResourceResolverImpl.java │ │ │ │ ├── ModuleReaderSpec.java │ │ │ │ ├── ResourceReaderSpec.java │ │ │ │ └── package-info.java │ │ │ ├── http/ │ │ │ │ ├── DummyHttpClient.java │ │ │ │ ├── HttpClient.java │ │ │ │ ├── HttpClientBuilder.java │ │ │ │ ├── HttpClientInitException.java │ │ │ │ ├── JdkHttpClient.java │ │ │ │ ├── LazyHttpClient.java │ │ │ │ ├── NoProxyRule.java │ │ │ │ ├── ProxySelector.java │ │ │ │ ├── RequestRewritingClient.java │ │ │ │ └── package-info.java │ │ │ ├── messaging/ │ │ │ │ ├── AbstractMessagePackDecoder.java │ │ │ │ ├── AbstractMessagePackEncoder.java │ │ │ │ ├── BaseMessagePackDecoder.java │ │ │ │ ├── BaseMessagePackEncoder.java │ │ │ │ ├── DecodeException.java │ │ │ │ ├── Message.java │ │ │ │ ├── MessageDecoder.java │ │ │ │ ├── MessageEncoder.java │ │ │ │ ├── MessageTransport.java │ │ │ │ ├── MessageTransports.java │ │ │ │ ├── Messages.java │ │ │ │ ├── ProtocolException.java │ │ │ │ └── package-info.java │ │ │ ├── module/ │ │ │ │ ├── FileResolver.java │ │ │ │ ├── ModuleKey.java │ │ │ │ ├── ModuleKeyFactories.java │ │ │ │ ├── ModuleKeyFactory.java │ │ │ │ ├── ModuleKeys.java │ │ │ │ ├── ModulePathResolver.java │ │ │ │ ├── PathElement.java │ │ │ │ ├── ProjectDependenciesManager.java │ │ │ │ ├── ResolvedModuleKey.java │ │ │ │ ├── ResolvedModuleKeys.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ ├── packages/ │ │ │ │ ├── Checksums.java │ │ │ │ ├── Dependency.java │ │ │ │ ├── DependencyMetadata.java │ │ │ │ ├── PackageAssetUri.java │ │ │ │ ├── PackageLoadError.java │ │ │ │ ├── PackageResolver.java │ │ │ │ ├── PackageResolvers.java │ │ │ │ ├── PackageUri.java │ │ │ │ ├── PackageUtils.java │ │ │ │ └── package-info.java │ │ │ ├── project/ │ │ │ │ ├── CanonicalPackageUri.java │ │ │ │ ├── DeclaredDependencies.java │ │ │ │ ├── Package.java │ │ │ │ ├── Project.java │ │ │ │ ├── ProjectDependenciesResolver.java │ │ │ │ ├── ProjectDeps.java │ │ │ │ ├── ProjectPackager.java │ │ │ │ └── package-info.java │ │ │ ├── repl/ │ │ │ │ ├── ReplRequest.java │ │ │ │ ├── ReplResponse.java │ │ │ │ ├── ReplServer.java │ │ │ │ └── package-info.java │ │ │ ├── resource/ │ │ │ │ ├── Resource.java │ │ │ │ ├── ResourceReader.java │ │ │ │ ├── ResourceReaders.java │ │ │ │ └── package-info.java │ │ │ ├── runtime/ │ │ │ │ ├── AnalyzeModule.java │ │ │ │ ├── BaseModule.java │ │ │ │ ├── BenchmarkModule.java │ │ │ │ ├── CommandModule.java │ │ │ │ ├── CommandSpecParser.java │ │ │ │ ├── FileSystemManager.java │ │ │ │ ├── Identifier.java │ │ │ │ ├── Iterators.java │ │ │ │ ├── JsonnetModule.java │ │ │ │ ├── KeyLookupSuggestions.java │ │ │ │ ├── LoggerImpl.java │ │ │ │ ├── MathModule.java │ │ │ │ ├── MemberLookupSuggestions.java │ │ │ │ ├── MinPklVersionChecker.java │ │ │ │ ├── MirrorFactories.java │ │ │ │ ├── ModuleCache.java │ │ │ │ ├── ModuleInfo.java │ │ │ │ ├── ModuleResolver.java │ │ │ │ ├── NullReceiverException.java │ │ │ │ ├── PklBinaryModule.java │ │ │ │ ├── PklTags.java │ │ │ │ ├── PlatformModule.java │ │ │ │ ├── PowerAssertions.java │ │ │ │ ├── ProjectModule.java │ │ │ │ ├── ReaderBase.java │ │ │ │ ├── ReflectModule.java │ │ │ │ ├── ReleaseModule.java │ │ │ │ ├── ResourceManager.java │ │ │ │ ├── SemVerModule.java │ │ │ │ ├── SettingsModule.java │ │ │ │ ├── StackTraceGenerator.java │ │ │ │ ├── StackTraceRenderer.java │ │ │ │ ├── StdLibModule.java │ │ │ │ ├── TestModule.java │ │ │ │ ├── TestRunner.java │ │ │ │ ├── VmBugException.java │ │ │ │ ├── VmBytes.java │ │ │ │ ├── VmClass.java │ │ │ │ ├── VmCollection.java │ │ │ │ ├── VmContext.java │ │ │ │ ├── VmDataSize.java │ │ │ │ ├── VmDuration.java │ │ │ │ ├── VmDynamic.java │ │ │ │ ├── VmEvalException.java │ │ │ │ ├── VmException.java │ │ │ │ ├── VmExceptionBuilder.java │ │ │ │ ├── VmExceptionRenderer.java │ │ │ │ ├── VmFileDetector.java │ │ │ │ ├── VmFunction.java │ │ │ │ ├── VmImportAnalyzer.java │ │ │ │ ├── VmIntSeq.java │ │ │ │ ├── VmLanguage.java │ │ │ │ ├── VmList.java │ │ │ │ ├── VmListing.java │ │ │ │ ├── VmListingOrMapping.java │ │ │ │ ├── VmLocalContext.java │ │ │ │ ├── VmMap.java │ │ │ │ ├── VmMapping.java │ │ │ │ ├── VmNull.java │ │ │ │ ├── VmObject.java │ │ │ │ ├── VmObjectBuilder.java │ │ │ │ ├── VmObjectLike.java │ │ │ │ ├── VmPair.java │ │ │ │ ├── VmPklBinaryEncoder.java │ │ │ │ ├── VmRegex.java │ │ │ │ ├── VmSafeMath.java │ │ │ │ ├── VmSet.java │ │ │ │ ├── VmStackOverflowException.java │ │ │ │ ├── VmTypeAlias.java │ │ │ │ ├── VmTyped.java │ │ │ │ ├── VmTypes.java │ │ │ │ ├── VmUndefinedValueException.java │ │ │ │ ├── VmUtils.java │ │ │ │ ├── VmValue.java │ │ │ │ ├── VmValueConverter.java │ │ │ │ ├── VmValueRenderer.java │ │ │ │ ├── VmValueTracker.java │ │ │ │ ├── VmValueTrackerFactory.java │ │ │ │ ├── VmValueVisitor.java │ │ │ │ ├── VmWrappedEvalException.java │ │ │ │ ├── XmlModule.java │ │ │ │ └── package-info.java │ │ │ ├── service/ │ │ │ │ └── ExecutorSpiImpl.java │ │ │ ├── settings/ │ │ │ │ ├── PklSettings.java │ │ │ │ └── package-info.java │ │ │ ├── stdlib/ │ │ │ │ ├── AbstractRenderer.java │ │ │ │ ├── AbstractStringRenderer.java │ │ │ │ ├── ExternalMemberNode.java │ │ │ │ ├── ExternalMethod0Node.java │ │ │ │ ├── ExternalMethod1Node.java │ │ │ │ ├── ExternalMethod2Node.java │ │ │ │ ├── ExternalMethod3Node.java │ │ │ │ ├── ExternalMethod4Node.java │ │ │ │ ├── ExternalMethod5Node.java │ │ │ │ ├── ExternalMethodNode.java │ │ │ │ ├── ExternalPropertyNode.java │ │ │ │ ├── LanguageAwareNode.java │ │ │ │ ├── PathConverterSupport.java │ │ │ │ ├── PathSpecParser.java │ │ │ │ ├── PklConverter.java │ │ │ │ ├── PklName.java │ │ │ │ ├── VmObjectFactories.java │ │ │ │ ├── VmObjectFactory.java │ │ │ │ ├── analyze/ │ │ │ │ │ ├── AnalyzeNodes.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── base/ │ │ │ │ │ ├── AnyNodes.java │ │ │ │ │ ├── BaseNodes.java │ │ │ │ │ ├── BooleanNodes.java │ │ │ │ │ ├── BytesNodes.java │ │ │ │ │ ├── ClassNodes.java │ │ │ │ │ ├── CollectionNodes.java │ │ │ │ │ ├── DataSizeNodes.java │ │ │ │ │ ├── DurationNodes.java │ │ │ │ │ ├── DynamicNodes.java │ │ │ │ │ ├── FloatNodes.java │ │ │ │ │ ├── Function0Nodes.java │ │ │ │ │ ├── Function1Nodes.java │ │ │ │ │ ├── Function2Nodes.java │ │ │ │ │ ├── Function3Nodes.java │ │ │ │ │ ├── Function4Nodes.java │ │ │ │ │ ├── Function5Nodes.java │ │ │ │ │ ├── FunctionNodes.java │ │ │ │ │ ├── IntNodes.java │ │ │ │ │ ├── IntSeqNodes.java │ │ │ │ │ ├── JsonRendererNodes.java │ │ │ │ │ ├── ListNodes.java │ │ │ │ │ ├── ListingNodes.java │ │ │ │ │ ├── MapNodes.java │ │ │ │ │ ├── MappingNodes.java │ │ │ │ │ ├── MergeSort.java │ │ │ │ │ ├── ModuleClassNodes.java │ │ │ │ │ ├── PListRendererNodes.java │ │ │ │ │ ├── PairNodes.java │ │ │ │ │ ├── PcfRenderer.java │ │ │ │ │ ├── PcfRendererNodes.java │ │ │ │ │ ├── PropertiesRendererNodes.java │ │ │ │ │ ├── RegexMatchFactory.java │ │ │ │ │ ├── RegexNodes.java │ │ │ │ │ ├── SetNodes.java │ │ │ │ │ ├── StringNodes.java │ │ │ │ │ ├── TypedNodes.java │ │ │ │ │ ├── YamlRendererNodes.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── benchmark/ │ │ │ │ │ ├── BenchmarkUtils.java │ │ │ │ │ ├── MicrobenchmarkNodes.java │ │ │ │ │ ├── OutputBenchmarkNodes.java │ │ │ │ │ ├── ParserBenchmarkNodes.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── json/ │ │ │ │ │ ├── ParserNodes.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── jsonnet/ │ │ │ │ │ ├── RendererNodes.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── math/ │ │ │ │ │ ├── MathNodes.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── pklbinary/ │ │ │ │ │ ├── RendererNodes.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── platform/ │ │ │ │ │ ├── PlatformNodes.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── protobuf/ │ │ │ │ │ ├── RendererNodes.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── reflect/ │ │ │ │ │ ├── ClassNodes.java │ │ │ │ │ ├── DeclaredTypeNodes.java │ │ │ │ │ ├── ReflectNodes.java │ │ │ │ │ ├── TypeNodes.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── registry/ │ │ │ │ │ ├── EmptyMemberRegistry.java │ │ │ │ │ ├── ExternalMemberRegistry.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── release/ │ │ │ │ │ ├── ReleaseNodes.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── test/ │ │ │ │ │ ├── TestNodes.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── report/ │ │ │ │ │ ├── JUnitReport.java │ │ │ │ │ ├── SimpleReport.java │ │ │ │ │ └── TestReport.java │ │ │ │ ├── xml/ │ │ │ │ │ ├── RendererNodes.java │ │ │ │ │ └── package-info.java │ │ │ │ └── yaml/ │ │ │ │ ├── ParserNodes.java │ │ │ │ └── package-info.java │ │ │ └── util/ │ │ │ ├── AbstractCharEscaper.java │ │ │ ├── AnsiStringBuilder.java │ │ │ ├── AnsiTheme.java │ │ │ ├── ArrayCharEscaper.java │ │ │ ├── ByteArrayUtils.java │ │ │ ├── CodeGeneratorUtils.java │ │ │ ├── CollectionUtils.java │ │ │ ├── DurationUtils.java │ │ │ ├── EconomicMaps.java │ │ │ ├── EconomicSets.java │ │ │ ├── ErrorMessages.java │ │ │ ├── Exceptions.java │ │ │ ├── GlobResolver.java │ │ │ ├── HttpUtils.java │ │ │ ├── ImportGraphUtils.java │ │ │ ├── IoUtils.java │ │ │ ├── LateInit.java │ │ │ ├── MathUtils.java │ │ │ ├── MutableBoolean.java │ │ │ ├── MutableLong.java │ │ │ ├── MutableReference.java │ │ │ ├── Nonnull.java │ │ │ ├── NonnullByDefault.java │ │ │ ├── Nullable.java │ │ │ ├── Pair.java │ │ │ ├── StringBuilderWriter.java │ │ │ ├── StringSimilarity.java │ │ │ ├── StringUtils.java │ │ │ ├── SyntaxHighlighter.java │ │ │ ├── json/ │ │ │ │ ├── Json.java │ │ │ │ ├── JsonEscaper.java │ │ │ │ ├── JsonHandler.java │ │ │ │ ├── JsonParser.java │ │ │ │ ├── JsonWriter.java │ │ │ │ ├── Location.java │ │ │ │ ├── ParseException.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ ├── paguro/ │ │ │ │ ├── RrbTree.java │ │ │ │ └── package-info.java │ │ │ ├── pklbinary/ │ │ │ │ ├── AbstractPklBinaryDecoder.java │ │ │ │ ├── PklBinaryCode.java │ │ │ │ └── package-info.java │ │ │ ├── properties/ │ │ │ │ └── PropertiesUtils.java │ │ │ ├── xml/ │ │ │ │ ├── Xml10Validator.java │ │ │ │ ├── Xml11Validator.java │ │ │ │ ├── XmlValidator.java │ │ │ │ └── package-info.java │ │ │ └── yaml/ │ │ │ ├── Yaml11Emitter.java │ │ │ ├── Yaml12Emitter.java │ │ │ ├── YamlCompatEmitter.java │ │ │ ├── YamlEmitter.java │ │ │ ├── YamlEscaper.java │ │ │ ├── package-info.java │ │ │ └── snake/ │ │ │ ├── Yaml11Resolver.java │ │ │ ├── Yaml12Resolver.java │ │ │ ├── YamlCompatEmitterResolver.java │ │ │ ├── YamlCompatParserResolver.java │ │ │ ├── YamlResolver.java │ │ │ ├── YamlUtils.java │ │ │ └── package-info.java │ │ └── resources/ │ │ ├── META-INF/ │ │ │ └── services/ │ │ │ ├── java.nio.file.spi.FileTypeDetector │ │ │ └── org.pkl.executor.spi.v1.ExecutorSpi │ │ └── org/ │ │ └── pkl/ │ │ └── core/ │ │ ├── Release.properties │ │ └── errorMessages.properties │ └── test/ │ ├── files/ │ │ └── LanguageSnippetTests/ │ │ ├── input/ │ │ │ ├── annotation/ │ │ │ │ ├── annotation1.pkl │ │ │ │ ├── annotation2.pkl │ │ │ │ ├── annotation3.pkl │ │ │ │ ├── annotation4.pkl │ │ │ │ ├── annotation5.pkl │ │ │ │ ├── annotation6.pkl │ │ │ │ ├── annotationIsNotExpression.pkl │ │ │ │ ├── annotationIsNotExpression2.pkl │ │ │ │ ├── deprecated1.pkl │ │ │ │ ├── deprecated2.pkl │ │ │ │ ├── deprecated3.pkl │ │ │ │ ├── deprecatedNoRepeatedWarnings.pkl │ │ │ │ ├── deprecatedWithMessage1.pkl │ │ │ │ ├── deprecatedWithMessage2.pkl │ │ │ │ ├── deprecatedWithMessage3.pkl │ │ │ │ └── deprecationSuperMethod.pkl │ │ │ ├── api/ │ │ │ │ ├── Resource.pkl │ │ │ │ ├── analyze1.pkl │ │ │ │ ├── annotationConverters.pkl │ │ │ │ ├── any.pkl │ │ │ │ ├── anyConverter.pkl │ │ │ │ ├── baseModule.pkl │ │ │ │ ├── benchmarkModule.pkl │ │ │ │ ├── bytes.pkl │ │ │ │ ├── dataSize.pkl │ │ │ │ ├── dir1/ │ │ │ │ │ └── dir2/ │ │ │ │ │ └── relativePathTo.pkl │ │ │ │ ├── duration.pkl │ │ │ │ ├── dynamic.pkl │ │ │ │ ├── empty.txt │ │ │ │ ├── float.pkl │ │ │ │ ├── fox.txt │ │ │ │ ├── int.pkl │ │ │ │ ├── intseq.pkl │ │ │ │ ├── jsonParser1.pkl │ │ │ │ ├── jsonParser2.pkl │ │ │ │ ├── jsonParser3.pkl │ │ │ │ ├── jsonParser4.pkl │ │ │ │ ├── jsonParser5.pkl │ │ │ │ ├── jsonRenderer1.json.pkl │ │ │ │ ├── jsonRenderer2.json.pkl │ │ │ │ ├── jsonRenderer2b.pkl │ │ │ │ ├── jsonRenderer3.json.pkl │ │ │ │ ├── jsonRenderer4.pkl │ │ │ │ ├── jsonRenderer5.pkl │ │ │ │ ├── jsonRenderer6.json.pkl │ │ │ │ ├── jsonRenderer7.pkl │ │ │ │ ├── jsonRenderer8.pkl │ │ │ │ ├── jsonRenderer9.json5.pkl │ │ │ │ ├── jsonRendererEmptyComposites.pkl │ │ │ │ ├── jsonnetRenderer1.jsonnet.pkl │ │ │ │ ├── jsonnetRenderer2.jsonnet.pkl │ │ │ │ ├── jsonnetRenderer3.jsonnet.pkl │ │ │ │ ├── jsonnetRenderer4.pkl │ │ │ │ ├── jsonnetRenderer5.pkl │ │ │ │ ├── jsonnetRenderer6.jsonnet.pkl │ │ │ │ ├── jsonnetRenderer7.pkl │ │ │ │ ├── jsonnetRenderer8.jsonnet.pkl │ │ │ │ ├── list.pkl │ │ │ │ ├── listNullable.pkl │ │ │ │ ├── listing.pkl │ │ │ │ ├── map.pkl │ │ │ │ ├── mapping.pkl │ │ │ │ ├── mathModule.pkl │ │ │ │ ├── module.pkl │ │ │ │ ├── moduleOutput.pkl │ │ │ │ ├── moduleOutput2.pkl │ │ │ │ ├── pListRenderer1.plist.pkl │ │ │ │ ├── pListRenderer2.plist.pkl │ │ │ │ ├── pListRenderer3.plist.pkl │ │ │ │ ├── pListRenderer4.pkl │ │ │ │ ├── pListRenderer5.pkl │ │ │ │ ├── pListRenderer6.plist.pkl │ │ │ │ ├── pListRenderer7.pkl │ │ │ │ ├── pListRenderer8.plist.pkl │ │ │ │ ├── pair.pkl │ │ │ │ ├── pcfRenderer1.pkl │ │ │ │ ├── pcfRenderer2.pkl │ │ │ │ ├── pcfRenderer2b.pkl │ │ │ │ ├── pcfRenderer3.pkl │ │ │ │ ├── pcfRenderer4.pkl │ │ │ │ ├── pcfRenderer5.pkl │ │ │ │ ├── pcfRenderer6.pkl │ │ │ │ ├── pcfRenderer7.pkl │ │ │ │ ├── pcfRenderer8.pkl │ │ │ │ ├── pcfRenderer9.pkl │ │ │ │ ├── pklbinary1.msgpack.yaml.pkl │ │ │ │ ├── platformModule.pkl │ │ │ │ ├── plistRenderer2b.pkl │ │ │ │ ├── propertiesRenderer1.properties.pkl │ │ │ │ ├── propertiesRenderer10.properties.pkl │ │ │ │ ├── propertiesRenderer11.pkl │ │ │ │ ├── propertiesRenderer12.properties.pkl │ │ │ │ ├── propertiesRenderer2.properties.pkl │ │ │ │ ├── propertiesRenderer2b.pkl │ │ │ │ ├── propertiesRenderer3.properties.pkl │ │ │ │ ├── propertiesRenderer4.pkl │ │ │ │ ├── propertiesRenderer5.pkl │ │ │ │ ├── propertiesRenderer6.properties.pkl │ │ │ │ ├── propertiesRenderer7.properties.pkl │ │ │ │ ├── propertiesRenderer8.properties.pkl │ │ │ │ ├── propertiesRenderer9.properties.pkl │ │ │ │ ├── propertiesRendererBug66849708.properties.pkl │ │ │ │ ├── protobuf.pkl │ │ │ │ ├── protobuf2.pkl │ │ │ │ ├── protobuf3.txtpb.pkl │ │ │ │ ├── reflect1.pkl │ │ │ │ ├── reflect2.pkl │ │ │ │ ├── reflect3.pkl │ │ │ │ ├── reflect4.pkl │ │ │ │ ├── reflectedDeclaration.pkl │ │ │ │ ├── regex.pkl │ │ │ │ ├── releaseModule.pkl │ │ │ │ ├── renderDirective.pkl │ │ │ │ ├── renderDirective2.pkl │ │ │ │ ├── resource.txt │ │ │ │ ├── semverModule.pkl │ │ │ │ ├── set.pkl │ │ │ │ ├── setNullable.pkl │ │ │ │ ├── shellModule.pkl │ │ │ │ ├── string.pkl │ │ │ │ ├── stringUnicode.pkl │ │ │ │ ├── typeAliases.pkl │ │ │ │ ├── typeConverters.pkl │ │ │ │ ├── typed.pkl │ │ │ │ ├── xmlRenderer1.xml.pkl │ │ │ │ ├── xmlRenderer2.xml.pkl │ │ │ │ ├── xmlRenderer2b.pkl │ │ │ │ ├── xmlRenderer3.xml.pkl │ │ │ │ ├── xmlRenderer4.pkl │ │ │ │ ├── xmlRenderer5.pkl │ │ │ │ ├── xmlRenderer6.xml.pkl │ │ │ │ ├── xmlRenderer8.pkl │ │ │ │ ├── xmlRenderer9.xml.pkl │ │ │ │ ├── xmlRendererCData.xml.pkl │ │ │ │ ├── xmlRendererElement.xml.pkl │ │ │ │ ├── xmlRendererHtml.xml.pkl │ │ │ │ ├── xmlRendererInline.xml.pkl │ │ │ │ ├── xmlRendererInline2.xml.pkl │ │ │ │ ├── xmlRendererInline3.xml.pkl │ │ │ │ ├── xmlRendererValidation10.pkl │ │ │ │ ├── xmlRendererValidation11.pkl │ │ │ │ ├── yamlParser1Compat.pkl │ │ │ │ ├── yamlParser1Yaml11.pkl │ │ │ │ ├── yamlParser1Yaml12.pkl │ │ │ │ ├── yamlParser2.pkl │ │ │ │ ├── yamlParser3.pkl │ │ │ │ ├── yamlParser4.pkl │ │ │ │ ├── yamlParser5.pkl │ │ │ │ ├── yamlParser6.pkl │ │ │ │ ├── yamlRenderer1.yml.pkl │ │ │ │ ├── yamlRenderer10.yml.pkl │ │ │ │ ├── yamlRenderer2.yml.pkl │ │ │ │ ├── yamlRenderer2b.pkl │ │ │ │ ├── yamlRenderer3.yml.pkl │ │ │ │ ├── yamlRenderer4.pkl │ │ │ │ ├── yamlRenderer5.pkl │ │ │ │ ├── yamlRenderer6.yml.pkl │ │ │ │ ├── yamlRenderer7.pkl │ │ │ │ ├── yamlRenderer8.yml.pkl │ │ │ │ ├── yamlRenderer9.yml.pkl │ │ │ │ ├── yamlRendererBug66849708.yml.pkl │ │ │ │ ├── yamlRendererEmpty.yml.pkl │ │ │ │ ├── yamlRendererIndentationWidth2.yml.pkl │ │ │ │ ├── yamlRendererIndentationWidth4.yml.pkl │ │ │ │ ├── yamlRendererIndentationWidth5.yml.pkl │ │ │ │ ├── yamlRendererKeys.yml.pkl │ │ │ │ ├── yamlRendererStream1.pkl │ │ │ │ ├── yamlRendererStream2.pkl │ │ │ │ ├── yamlRendererStrings.yml.pkl │ │ │ │ ├── yamlRendererStringsCompat.pkl │ │ │ │ ├── yamlRendererStringsYaml11.pkl │ │ │ │ └── yamlRendererStringsYaml12.pkl │ │ │ ├── basic/ │ │ │ │ ├── amendsChains.pkl │ │ │ │ ├── as.pkl │ │ │ │ ├── as2.pkl │ │ │ │ ├── as3.pkl │ │ │ │ ├── baseModule.pkl │ │ │ │ ├── boolean.pkl │ │ │ │ ├── bytes.pkl │ │ │ │ ├── comments.pkl │ │ │ │ ├── constModifier.pkl │ │ │ │ ├── constModifier2.pkl │ │ │ │ ├── constModifier3.pkl │ │ │ │ ├── constModifier4.pkl │ │ │ │ ├── constModifier5.pkl │ │ │ │ ├── dataSize.pkl │ │ │ │ ├── duration.pkl │ │ │ │ ├── exceptions.pkl │ │ │ │ ├── fixedProperty1.pkl │ │ │ │ ├── fixedProperty2.pkl │ │ │ │ ├── fixedProperty3.pkl │ │ │ │ ├── fixedProperty4.pkl │ │ │ │ ├── fixedProperty5.pkl │ │ │ │ ├── fixedProperty6.pkl │ │ │ │ ├── float.pkl │ │ │ │ ├── globtest/ │ │ │ │ │ ├── file1.txt │ │ │ │ │ └── file2.txt │ │ │ │ ├── identifier.pkl │ │ │ │ ├── if.pkl │ │ │ │ ├── import1.pkl │ │ │ │ ├── import1b.pkl │ │ │ │ ├── import2.pkl │ │ │ │ ├── import3.pkl │ │ │ │ ├── importGlob.pkl │ │ │ │ ├── imported.pkl │ │ │ │ ├── indexExpressions.pkl │ │ │ │ ├── int.pkl │ │ │ │ ├── intseq.pkl │ │ │ │ ├── is.pkl │ │ │ │ ├── is2.pkl │ │ │ │ ├── let.pkl │ │ │ │ ├── letTyped.pkl │ │ │ │ ├── list.pkl │ │ │ │ ├── localMethodDynamicBinding.pkl │ │ │ │ ├── localMethodInAmendingModule.pkl │ │ │ │ ├── localMethodOverride1.pkl │ │ │ │ ├── localMethodOverride2.pkl │ │ │ │ ├── localMethodTyped.pkl │ │ │ │ ├── localMethodTyped2.pkl │ │ │ │ ├── localMethodTyped3.pkl │ │ │ │ ├── localMethodTyped4.pkl │ │ │ │ ├── localMethodTyped5.pkl │ │ │ │ ├── localMethodTyped6.pkl │ │ │ │ ├── localMethodUntyped.pkl │ │ │ │ ├── localModuleMemberOverride1.pkl │ │ │ │ ├── localModuleMemberOverride2.pkl │ │ │ │ ├── localProperty1.pkl │ │ │ │ ├── localProperty2.pkl │ │ │ │ ├── localPropertyAmendInAmendingModule.pkl │ │ │ │ ├── localPropertyInAmendingModule.pkl │ │ │ │ ├── localPropertyOverride1.pkl │ │ │ │ ├── localPropertyOverride2.pkl │ │ │ │ ├── localPropertyOverride3.pkl │ │ │ │ ├── localTypedClassMember.pkl │ │ │ │ ├── localTypedModuleMember.pkl │ │ │ │ ├── localTypedObjectMember.pkl │ │ │ │ ├── map.pkl │ │ │ │ ├── minPklVersion.pkl │ │ │ │ ├── moduleRef1.pkl │ │ │ │ ├── moduleRef2.pkl │ │ │ │ ├── moduleRef3.pkl │ │ │ │ ├── moduleRefLibrary.pkl │ │ │ │ ├── new.pkl │ │ │ │ ├── newInAmendingModuleMethod.pkl │ │ │ │ ├── newInsideIf.pkl │ │ │ │ ├── newInsideLet.pkl │ │ │ │ ├── newType.pkl │ │ │ │ ├── nonNull.pkl │ │ │ │ ├── nullable.pkl │ │ │ │ ├── objectMember.pkl │ │ │ │ ├── objectMemberInvalid.pkl │ │ │ │ ├── objectMemberInvalid2.pkl │ │ │ │ ├── objectMemberInvalid3.pkl │ │ │ │ ├── parens.pkl │ │ │ │ ├── propertyDefaults.pkl │ │ │ │ ├── rawString.pkl │ │ │ │ ├── read.pkl │ │ │ │ ├── readGlob.pkl │ │ │ │ ├── semicolon.pkl │ │ │ │ ├── set.pkl │ │ │ │ ├── string.pkl │ │ │ │ ├── stringError1.pkl │ │ │ │ ├── stringMultiline.pkl │ │ │ │ ├── stringUnicode.pkl │ │ │ │ ├── trace.pkl │ │ │ │ ├── typeResolution1.pkl │ │ │ │ ├── typeResolution2.pkl │ │ │ │ ├── typeResolution3.pkl │ │ │ │ ├── typeResolution4.pkl │ │ │ │ └── underscore.pkl │ │ │ ├── classes/ │ │ │ │ ├── class1.pkl │ │ │ │ ├── class2.pkl │ │ │ │ ├── class2a.pkl │ │ │ │ ├── class3.pkl │ │ │ │ ├── class4.pkl │ │ │ │ ├── constraints1.pkl │ │ │ │ ├── constraints10.pkl │ │ │ │ ├── constraints11.pkl │ │ │ │ ├── constraints12.pkl │ │ │ │ ├── constraints13.pkl │ │ │ │ ├── constraints14.pkl │ │ │ │ ├── constraints2.pkl │ │ │ │ ├── constraints3.pkl │ │ │ │ ├── constraints4.pkl │ │ │ │ ├── constraints5.pkl │ │ │ │ ├── constraints6.pkl │ │ │ │ ├── constraints7.pkl │ │ │ │ ├── constraints8.pkl │ │ │ │ ├── constraints9.pkl │ │ │ │ ├── constraintsLambdaThis.pkl │ │ │ │ ├── duplicateFunction.pkl │ │ │ │ ├── duplicateProperty.pkl │ │ │ │ ├── equality.pkl │ │ │ │ ├── externalClass.pkl │ │ │ │ ├── externalFunction.pkl │ │ │ │ ├── externalProperty.pkl │ │ │ │ ├── functions1.pkl │ │ │ │ ├── functions2.pkl │ │ │ │ ├── functions3.pkl │ │ │ │ ├── functions4.pkl │ │ │ │ ├── inheritance1.pkl │ │ │ │ ├── inheritance2.pkl │ │ │ │ ├── inheritanceError1.pkl │ │ │ │ ├── invalidInstantiation1.pkl │ │ │ │ ├── invalidInstantiation2.pkl │ │ │ │ ├── lambdaConstraints1.pkl │ │ │ │ ├── listConstraints1.pkl │ │ │ │ ├── mapConstraints1.pkl │ │ │ │ ├── nullableTypes.pkl │ │ │ │ ├── setConstraints1.pkl │ │ │ │ ├── supercalls.pkl │ │ │ │ ├── supercallsInLet.pkl │ │ │ │ ├── unionTypes.pkl │ │ │ │ ├── unionTypesErrorAlias.pkl │ │ │ │ ├── unionTypesErrorDifferent1.pkl │ │ │ │ ├── unionTypesErrorDifferent2.pkl │ │ │ │ ├── unionTypesErrorMultipleAliases.pkl │ │ │ │ ├── unionTypesErrorNested.pkl │ │ │ │ ├── unionTypesErrorSimple.pkl │ │ │ │ ├── unionTypesErrorString1.pkl │ │ │ │ ├── unionTypesErrorString2.pkl │ │ │ │ ├── wrongType1.pkl │ │ │ │ ├── wrongType2.pkl │ │ │ │ ├── wrongType3.pkl │ │ │ │ ├── wrongType4.pkl │ │ │ │ ├── wrongType5.pkl │ │ │ │ └── wrongType6.pkl │ │ │ ├── errors/ │ │ │ │ ├── abstractOpenMember.pkl │ │ │ │ ├── analyzeImportsCannotFindModule.pkl │ │ │ │ ├── analyzeImportsInvalidGlob.pkl │ │ │ │ ├── analyzeInvalidModuleUri.pkl │ │ │ │ ├── analyzeRelativeModuleUri.pkl │ │ │ │ ├── anyConverterError.pkl │ │ │ │ ├── baseModule.pkl │ │ │ │ ├── binopDifferentLine.pkl │ │ │ │ ├── cannotAmendFixedProperty1.pkl │ │ │ │ ├── cannotAmendFixedProperty2.pkl │ │ │ │ ├── cannotAssignFixedProperty1.pkl │ │ │ │ ├── cannotAssignFixedProperty2.pkl │ │ │ │ ├── cannotAssignFixedProperty3.pkl │ │ │ │ ├── cannotAssignToNothing.pkl │ │ │ │ ├── cannotChangeFixed1.pkl │ │ │ │ ├── cannotChangeFixed2.pkl │ │ │ │ ├── cannotFindMapKey.pkl │ │ │ │ ├── cannotFindStdLibModule.pkl │ │ │ │ ├── cannotInferParent2.pkl │ │ │ │ ├── cannotInferParent3.pkl │ │ │ │ ├── cannotInstantiateAbstractModule.pkl │ │ │ │ ├── cannotRenderMixin.pkl │ │ │ │ ├── classExtendsSelf.pkl │ │ │ │ ├── const/ │ │ │ │ │ ├── constAmend.pkl │ │ │ │ │ ├── constAnnotation.pkl │ │ │ │ │ ├── constAnnotation2.pkl │ │ │ │ │ ├── constAnnotation3.pkl │ │ │ │ │ ├── constAssign.pkl │ │ │ │ │ ├── constClassBody.pkl │ │ │ │ │ ├── constExplicitThis.pkl │ │ │ │ │ ├── constExplicitThisMethod.pkl │ │ │ │ │ ├── constFunctionCallingNonConst.pkl │ │ │ │ │ ├── constImplicitThis.pkl │ │ │ │ │ ├── constImplicitThisMethod.pkl │ │ │ │ │ ├── constLexicalScope.pkl │ │ │ │ │ ├── constLocalAmendModule.pkl │ │ │ │ │ ├── constLocalMethod.pkl │ │ │ │ │ ├── constLocalProperty.pkl │ │ │ │ │ ├── constMethod.pkl │ │ │ │ │ ├── constMethod2.pkl │ │ │ │ │ ├── constModule.pkl │ │ │ │ │ ├── constModule2.pkl │ │ │ │ │ ├── constModule3.pkl │ │ │ │ │ ├── constObjectMember.pkl │ │ │ │ │ ├── constOuter.pkl │ │ │ │ │ ├── constOuter2.pkl │ │ │ │ │ ├── constOuter3.pkl │ │ │ │ │ ├── constQualified.pkl │ │ │ │ │ ├── constSubclass.pkl │ │ │ │ │ ├── constSubclass2.pkl │ │ │ │ │ ├── constSuper.pkl │ │ │ │ │ ├── constSuperMethod.pkl │ │ │ │ │ ├── constThis.pkl │ │ │ │ │ └── constTypeAliasConstraint.pkl │ │ │ │ ├── constraintDetails1.pkl │ │ │ │ ├── constraintDetails2.pkl │ │ │ │ ├── constraintDetails3.pkl │ │ │ │ ├── decodingException.pkl │ │ │ │ ├── delimiters/ │ │ │ │ │ ├── missingClassDelimiter.pkl │ │ │ │ │ ├── missingConstrainedTypeSeparator.pkl │ │ │ │ │ ├── missingContainerAmendDefDelimiter.pkl │ │ │ │ │ ├── missingContainerAmendExprDelimiter.pkl │ │ │ │ │ ├── missingEmptyMultiLineStringDelimiter.pkl │ │ │ │ │ ├── missingEmptyMultiLineStringDelimiterAtEof.pkl │ │ │ │ │ ├── missingEmptyStringDelimiter.pkl │ │ │ │ │ ├── missingEmptyStringDelimiterAtEof.pkl │ │ │ │ │ ├── missingFunction0ParameterListDelimiter.pkl │ │ │ │ │ ├── missingFunction1ParameterListDelimiter.pkl │ │ │ │ │ ├── missingFunctionAmendParameterListSeparator.pkl │ │ │ │ │ ├── missingFunctionNParameterListDelimiter.pkl │ │ │ │ │ ├── missingFunctionParameterListSeparator.pkl │ │ │ │ │ ├── missingFunctionType0ParameterListDelimiter.pkl │ │ │ │ │ ├── missingFunctionType1ParameterListDelimiter.pkl │ │ │ │ │ ├── missingFunctionTypeNParameterListDelimiter.pkl │ │ │ │ │ ├── missingFunctionTypeParameterListSeparator.pkl │ │ │ │ │ ├── missingIfExprDelimiter.pkl │ │ │ │ │ ├── missingListDelimiter.pkl │ │ │ │ │ ├── missingListSeparator.pkl │ │ │ │ │ ├── missingMapDelimiter.pkl │ │ │ │ │ ├── missingMapSeparator.pkl │ │ │ │ │ ├── missingMethodArgumentListDelimiter.pkl │ │ │ │ │ ├── missingMethodArgumentListSeparator.pkl │ │ │ │ │ ├── missingMethodParameterListDelimiter.pkl │ │ │ │ │ ├── missingMethodParameterListSeparator.pkl │ │ │ │ │ ├── missingMultiLineStringDelimiter.pkl │ │ │ │ │ ├── missingObjectAmendDefDelimiter.pkl │ │ │ │ │ ├── missingObjectAmendDefDelimiter2.pkl │ │ │ │ │ ├── missingObjectAmendDefDelimiter3.pkl │ │ │ │ │ ├── missingObjectAmendExprDelimiter.pkl │ │ │ │ │ ├── missingObjectDelimiter.pkl │ │ │ │ │ ├── missingParenthesizedExprDelimiter.pkl │ │ │ │ │ ├── missingParenthesizedTypeDelimiter.pkl │ │ │ │ │ ├── missingRawMultiLineStringDelimiter.pkl │ │ │ │ │ ├── missingRawStringDelimiter.pkl │ │ │ │ │ ├── missingSetSeparator.pkl │ │ │ │ │ ├── missingStringDelimiter.pkl │ │ │ │ │ ├── missingSubscriptDelimiter.pkl │ │ │ │ │ ├── missingTypeConstraintListDelimiter.pkl │ │ │ │ │ ├── missingTypedMethodParameterListDelimiter.pkl │ │ │ │ │ ├── missingTypedMethodParameterListSeparator.pkl │ │ │ │ │ ├── unbalancedEntryBrackets1.pkl │ │ │ │ │ ├── unbalancedEntryBrackets2.pkl │ │ │ │ │ ├── unbalancedEntryBrackets3.pkl │ │ │ │ │ └── unbalancedEntryBrackets4.pkl │ │ │ │ ├── duplicateTypeParameter.pkl │ │ │ │ ├── emptyParenthesizedTypeAnnotation.pkl │ │ │ │ ├── extendExternalClass.pkl │ │ │ │ ├── extendTypeAlias.pkl │ │ │ │ ├── forGeneratorCannotGenerateMethods.pkl │ │ │ │ ├── forGeneratorCannotGenerateProperties.pkl │ │ │ │ ├── forGeneratorCannotIterateOverThisValue.pkl │ │ │ │ ├── forGeneratorCannotIterateOverTyped.pkl │ │ │ │ ├── forGeneratorDuplicateParams1.pkl │ │ │ │ ├── forGeneratorWrongVariableName.pkl │ │ │ │ ├── fullStackTraces.pkl │ │ │ │ ├── fullStackTraces2.pkl │ │ │ │ ├── functionNotFoundInClass.pkl │ │ │ │ ├── functionNotFoundInModule.pkl │ │ │ │ ├── functionNotFoundInScope.pkl │ │ │ │ ├── functionNotFoundMaybeLambda.pkl │ │ │ │ ├── functionNotFoundMaybeProperty.pkl │ │ │ │ ├── intrinsifiedTypeAlias1.pkl │ │ │ │ ├── intrinsifiedTypeAlias2.pkl │ │ │ │ ├── intrinsifiedTypeAlias3.pkl │ │ │ │ ├── intrinsifiedTypeAlias4.pkl │ │ │ │ ├── invalidBytes1.pkl │ │ │ │ ├── invalidBytes2.pkl │ │ │ │ ├── invalidBytes3.pkl │ │ │ │ ├── invalidBytes4.pkl │ │ │ │ ├── invalidCharacterEscape.pkl │ │ │ │ ├── invalidClassMethodModifier.pkl │ │ │ │ ├── invalidClassModifier.pkl │ │ │ │ ├── invalidClassModifier2.pkl │ │ │ │ ├── invalidClassPropertyModifier.pkl │ │ │ │ ├── invalidFileUri1.pkl │ │ │ │ ├── invalidFileUri2.pkl │ │ │ │ ├── invalidFileUri3.pkl │ │ │ │ ├── invalidFileUri4.pkl │ │ │ │ ├── invalidGlobImport1.pkl │ │ │ │ ├── invalidGlobImport2.pkl │ │ │ │ ├── invalidGlobImport3.pkl │ │ │ │ ├── invalidGlobImport4.pkl │ │ │ │ ├── invalidGlobImport5.pkl │ │ │ │ ├── invalidGlobImport6.pkl │ │ │ │ ├── invalidGlobImport7.pkl │ │ │ │ ├── invalidGlobImport8.pkl │ │ │ │ ├── invalidGlobRead1.pkl │ │ │ │ ├── invalidGlobRead2.pkl │ │ │ │ ├── invalidGlobRead3.pkl │ │ │ │ ├── invalidImportBackslashSep.pkl │ │ │ │ ├── invalidImportUri.pkl │ │ │ │ ├── invalidMethodModifier.pkl │ │ │ │ ├── invalidModuleModifier.pkl │ │ │ │ ├── invalidObjectPropertyModifier.pkl │ │ │ │ ├── invalidOutput1.pkl │ │ │ │ ├── invalidOutput2.pkl │ │ │ │ ├── invalidOutput3.pkl │ │ │ │ ├── invalidPropertyModifier.pkl │ │ │ │ ├── invalidTripleDotSyntax1.pkl │ │ │ │ ├── invalidTripleDotSyntax2.pkl │ │ │ │ ├── invalidTypeAliasModifier.pkl │ │ │ │ ├── invalidTypeName1.pkl │ │ │ │ ├── invalidTypeName2.pkl │ │ │ │ ├── invalidTypeName3.pkl │ │ │ │ ├── invalidTypeName4.pkl │ │ │ │ ├── invalidUnicodeEscape.pkl │ │ │ │ ├── keywordNotAllowedHere1.pkl │ │ │ │ ├── keywordNotAllowedHere2.pkl │ │ │ │ ├── keywordNotAllowedHere3.pkl │ │ │ │ ├── keywordNotAllowedHere4.pkl │ │ │ │ ├── letExpressionError1.pkl │ │ │ │ ├── letExpressionError2.pkl │ │ │ │ ├── letExpressionErrorTyped.pkl │ │ │ │ ├── listingTypeCheckError1.pkl │ │ │ │ ├── listingTypeCheckError2.pkl │ │ │ │ ├── listingTypeCheckError3.pkl │ │ │ │ ├── listingTypeCheckError4.pkl │ │ │ │ ├── listingTypeCheckError5.pkl │ │ │ │ ├── listingTypeCheckError6.pkl │ │ │ │ ├── listingTypeCheckError7.pkl │ │ │ │ ├── listingTypeCheckError8.pkl │ │ │ │ ├── listingTypeCheckError9.pkl │ │ │ │ ├── localFixedMember.pkl │ │ │ │ ├── localFunctionWithTypeParameter.pkl │ │ │ │ ├── localHiddenMember.pkl │ │ │ │ ├── mappingTypeCheckError1.pkl │ │ │ │ ├── mappingTypeCheckError10.pkl │ │ │ │ ├── mappingTypeCheckError11.pkl │ │ │ │ ├── mappingTypeCheckError2.pkl │ │ │ │ ├── mappingTypeCheckError3.pkl │ │ │ │ ├── mappingTypeCheckError4.pkl │ │ │ │ ├── mappingTypeCheckError5.pkl │ │ │ │ ├── mappingTypeCheckError6.pkl │ │ │ │ ├── mappingTypeCheckError7.pkl │ │ │ │ ├── mappingTypeCheckError8.pkl │ │ │ │ ├── mappingTypeCheckError9.pkl │ │ │ │ ├── missingLocalPropertyValue1.pkl │ │ │ │ ├── missingLocalPropertyValue2.pkl │ │ │ │ ├── moduleAmendsSelf.pkl │ │ │ │ ├── moduleAmendsVersionCheck.pkl │ │ │ │ ├── moduleAmendsVersionCheck2.pkl │ │ │ │ ├── moduleExpected.pkl │ │ │ │ ├── moduleExtendsSelf.pkl │ │ │ │ ├── moduleExtendsVersionCheck.pkl │ │ │ │ ├── moduleExtendsVersionCheck2.pkl │ │ │ │ ├── moduleImportVersionCheck.pkl │ │ │ │ ├── moduleWithHighMinPklVersion.pkl │ │ │ │ ├── moduleWithHighMinPklVersionAndParseErrors.pkl │ │ │ │ ├── multipleDefaults.pkl │ │ │ │ ├── nested1.pkl │ │ │ │ ├── noDefault.pkl │ │ │ │ ├── noDefault2.pkl │ │ │ │ ├── notAUnionDefault.pkl │ │ │ │ ├── objectCannotHaveElement.pkl │ │ │ │ ├── objectCannotHaveElement2.pkl │ │ │ │ ├── objectCannotHavePredicateMember.pkl │ │ │ │ ├── outOfRange1.pkl │ │ │ │ ├── outOfRange2.pkl │ │ │ │ ├── outOfRange3.pkl │ │ │ │ ├── parser1.pkl │ │ │ │ ├── parser10.pkl │ │ │ │ ├── parser11.pkl │ │ │ │ ├── parser12.pkl │ │ │ │ ├── parser14.pkl │ │ │ │ ├── parser15.pkl │ │ │ │ ├── parser16.pkl │ │ │ │ ├── parser17.pkl │ │ │ │ ├── parser18.pkl │ │ │ │ ├── parser2.pkl │ │ │ │ ├── parser3.pkl │ │ │ │ ├── parser4.pkl │ │ │ │ ├── parser5.pkl │ │ │ │ ├── parser6.pkl │ │ │ │ ├── parser7.pkl │ │ │ │ ├── parser8.pkl │ │ │ │ ├── parser9.pkl │ │ │ │ ├── power/ │ │ │ │ │ ├── typeConstraints1.pkl │ │ │ │ │ ├── typeConstraints10.pkl │ │ │ │ │ ├── typeConstraints11.pkl │ │ │ │ │ ├── typeConstraints12.pkl │ │ │ │ │ ├── typeConstraints13.pkl │ │ │ │ │ ├── typeConstraints14.pkl │ │ │ │ │ ├── typeConstraints15.pkl │ │ │ │ │ ├── typeConstraints16.pkl │ │ │ │ │ ├── typeConstraints17.pkl │ │ │ │ │ ├── typeConstraints2.pkl │ │ │ │ │ ├── typeConstraints3.pkl │ │ │ │ │ ├── typeConstraints4.pkl │ │ │ │ │ ├── typeConstraints5.pkl │ │ │ │ │ ├── typeConstraints6.pkl │ │ │ │ │ ├── typeConstraints7.pkl │ │ │ │ │ ├── typeConstraints8.pkl │ │ │ │ │ ├── typeConstraints9.pkl │ │ │ │ │ └── typeConstraints9a.pkl │ │ │ │ ├── propertyNotFound1.pkl │ │ │ │ ├── propertyNotFound2.pkl │ │ │ │ ├── refusingToLoadModule.pkl │ │ │ │ ├── shebang.pkl │ │ │ │ ├── singleBacktick.pkl │ │ │ │ ├── spreadSyntaxCannotHaveElement.pkl │ │ │ │ ├── spreadSyntaxCannotHaveEntry.pkl │ │ │ │ ├── spreadSyntaxCannotHaveProperty.pkl │ │ │ │ ├── spreadSyntaxCannotIterateOverTyped.pkl │ │ │ │ ├── spreadSyntaxCannotSpreadListIntoMapping.pkl │ │ │ │ ├── spreadSyntaxDuplicateEntry1.pkl │ │ │ │ ├── spreadSyntaxDuplicateEntry2.pkl │ │ │ │ ├── spreadSyntaxDuplicateProperty1.pkl │ │ │ │ ├── spreadSyntaxDuplicateProperty2.pkl │ │ │ │ ├── spreadSyntaxNullValue.pkl │ │ │ │ ├── spreadSyntaxUnknownTypedProperty.pkl │ │ │ │ ├── stackTraceWithQuotedMemberName.pkl │ │ │ │ ├── supercalls.pkl │ │ │ │ ├── typeMismatchHelper.pkl │ │ │ │ ├── typeMismatchWithSameQualifiedClassName.pkl │ │ │ │ ├── typeMismatchWithSameQualifiedModuleName.pkl │ │ │ │ ├── undefinedOp1.pkl │ │ │ │ ├── undefinedOp2.pkl │ │ │ │ ├── undefinedOp3.pkl │ │ │ │ ├── undefinedProperty1.pkl │ │ │ │ ├── undefinedProperty2.pkl │ │ │ │ ├── undefinedProperty3.pkl │ │ │ │ ├── undefinedProperty4.pkl │ │ │ │ ├── undefinedProperty5.pkl │ │ │ │ ├── undefinedProperty6.pkl │ │ │ │ ├── undefinedProperty7.pkl │ │ │ │ ├── undefinedProperty8.pkl │ │ │ │ ├── underscore.pkl │ │ │ │ ├── underscoreLambda.pkl │ │ │ │ ├── underscoreLet.pkl │ │ │ │ ├── unterminatedUnicodeEscape.pkl │ │ │ │ ├── userDefinedTypeParameter1.pkl │ │ │ │ ├── userDefinedTypeParameter2.pkl │ │ │ │ ├── wrongForGeneratorType1.pkl │ │ │ │ ├── wrongForGeneratorType2.pkl │ │ │ │ └── wrongNumberOfMapArguments.pkl │ │ │ ├── generators/ │ │ │ │ ├── duplicateDefinition1.pkl │ │ │ │ ├── duplicateDefinition2.pkl │ │ │ │ ├── duplicateDefinition3.pkl │ │ │ │ ├── elementGenerators.pkl │ │ │ │ ├── elementGeneratorsTyped.pkl │ │ │ │ ├── entryGenerators.pkl │ │ │ │ ├── entryGeneratorsTyped.pkl │ │ │ │ ├── forGeneratorInFunctionBody.pkl │ │ │ │ ├── forGeneratorInMixins.pkl │ │ │ │ ├── forGeneratorLexicalScope.pkl │ │ │ │ ├── forGeneratorNestedReference.pkl │ │ │ │ ├── forGeneratorNestedReference2.pkl │ │ │ │ ├── forGeneratorVariableShadowing.pkl │ │ │ │ ├── generatorNoMembers.pkl │ │ │ │ ├── predicateMembersDynamicListing.pkl │ │ │ │ ├── predicateMembersDynamicMapping.pkl │ │ │ │ ├── predicateMembersListing.pkl │ │ │ │ ├── predicateMembersMapping.pkl │ │ │ │ ├── predicateMembersThis.pkl │ │ │ │ ├── propertyGenerators.pkl │ │ │ │ ├── spreadSyntaxDynamic.pkl │ │ │ │ ├── spreadSyntaxListing.pkl │ │ │ │ ├── spreadSyntaxMapping.pkl │ │ │ │ ├── spreadSyntaxNoSpace.pkl │ │ │ │ ├── spreadSyntaxNullable.pkl │ │ │ │ └── spreadSyntaxTyped.pkl │ │ │ ├── implementation/ │ │ │ │ ├── equality.pkl │ │ │ │ └── inequality.pkl │ │ │ ├── internal/ │ │ │ │ └── polymorphicCallSite.pkl │ │ │ ├── lambdas/ │ │ │ │ ├── amendLambdaDef.pkl │ │ │ │ ├── amendLambdaExpr.pkl │ │ │ │ ├── amendLambdaExternalClassError.pkl │ │ │ │ ├── amendLambdaParameters.pkl │ │ │ │ ├── amendLambdaParametersTyped.pkl │ │ │ │ ├── amendLambdaThatReturnsAnotherLambda.pkl │ │ │ │ ├── amendLambdaTooFewArgsError.pkl │ │ │ │ ├── amendLambdaTooManyArgsError.pkl │ │ │ │ ├── equality.pkl │ │ │ │ ├── inequality.pkl │ │ │ │ ├── lambda1.pkl │ │ │ │ ├── lambda2.pkl │ │ │ │ ├── lambda3.pkl │ │ │ │ ├── lambda4.pkl │ │ │ │ ├── lambda5.pkl │ │ │ │ ├── lambdaStackTrace1.pkl │ │ │ │ ├── lambdaStackTrace2.pkl │ │ │ │ ├── lambdaStackTrace3.pkl │ │ │ │ ├── pipeOperator.pkl │ │ │ │ ├── tooManyLambdaParams.pkl │ │ │ │ ├── typedLambdas.pkl │ │ │ │ └── wrongArgumentListLength.pkl │ │ │ ├── listings/ │ │ │ │ ├── cacheStealing.pkl │ │ │ │ ├── cacheStealingTypeCheck.pkl │ │ │ │ ├── default.pkl │ │ │ │ ├── equality.pkl │ │ │ │ ├── hashCode.pkl │ │ │ │ ├── inequality.pkl │ │ │ │ ├── listing1.pkl │ │ │ │ ├── listing2.pkl │ │ │ │ ├── listing3.pkl │ │ │ │ ├── listing4.pkl │ │ │ │ ├── listing5.pkl │ │ │ │ ├── listing6.pkl │ │ │ │ ├── listing7.pkl │ │ │ │ ├── numberLiterals.pkl │ │ │ │ ├── typeCheck.pkl │ │ │ │ ├── wrongIndex.pkl │ │ │ │ └── wrongParent.pkl │ │ │ ├── listings2/ │ │ │ │ ├── default.pkl │ │ │ │ ├── equality.pkl │ │ │ │ ├── inequality.pkl │ │ │ │ ├── listing1.pkl │ │ │ │ ├── listing2.pkl │ │ │ │ ├── listing3.pkl │ │ │ │ ├── numberLiterals.pkl │ │ │ │ ├── typeCheck.pkl │ │ │ │ ├── wrongIndex.pkl │ │ │ │ └── wrongParent.pkl │ │ │ ├── mappings/ │ │ │ │ ├── default.pkl │ │ │ │ ├── duplicateComputedKey.pkl │ │ │ │ ├── duplicateConstantKey.pkl │ │ │ │ ├── equality.pkl │ │ │ │ ├── hashCode.pkl │ │ │ │ ├── inequality.pkl │ │ │ │ ├── mapping1.pkl │ │ │ │ ├── mapping2.pkl │ │ │ │ ├── stringKeyNotFound.pkl │ │ │ │ ├── typeCheck.pkl │ │ │ │ └── wrongParent.pkl │ │ │ ├── mappings2/ │ │ │ │ ├── default.pkl │ │ │ │ ├── duplicateComputedKey.pkl │ │ │ │ ├── duplicateConstantKey.pkl │ │ │ │ ├── equality.pkl │ │ │ │ ├── inequality.pkl │ │ │ │ ├── mapping1.pkl │ │ │ │ ├── mapping2.pkl │ │ │ │ ├── stringKeyNotFound.pkl │ │ │ │ ├── typeCheck.pkl │ │ │ │ └── wrongParent.pkl │ │ │ ├── methods/ │ │ │ │ ├── methodParameterConstraints1.pkl │ │ │ │ ├── methodParameterConstraints2.pkl │ │ │ │ ├── methodParameterTypes1.pkl │ │ │ │ ├── methodParameterTypes2.pkl │ │ │ │ └── methodParameterTypes3.pkl │ │ │ ├── modules/ │ │ │ │ ├── amendModule1.pkl │ │ │ │ ├── amendModule2.pkl │ │ │ │ ├── amendModule3.pkl │ │ │ │ ├── amendModule4.pkl │ │ │ │ ├── amendModule5.pkl │ │ │ │ ├── amendModule6.pkl │ │ │ │ ├── duplicateFunction.pkl │ │ │ │ ├── duplicateProperty1.pkl │ │ │ │ ├── duplicateProperty2.pkl │ │ │ │ ├── duplicateProperty3.pkl │ │ │ │ ├── equality.pkl │ │ │ │ ├── extendModule1.pkl │ │ │ │ ├── externalFunction.pkl │ │ │ │ ├── externalProperty.pkl │ │ │ │ ├── filename with spaces 2.pkl │ │ │ │ ├── filename with spaces.pkl │ │ │ │ ├── functions.pkl │ │ │ │ ├── invalidAmend1.pkl │ │ │ │ ├── invalidAmend2.pkl │ │ │ │ ├── invalidAmend3.pkl │ │ │ │ ├── invalidAmend4.pkl │ │ │ │ ├── invalidAmend5.pkl │ │ │ │ ├── invalidAmend6.pkl │ │ │ │ ├── invalidExtend1.pkl │ │ │ │ ├── invalidExtend2.pkl │ │ │ │ ├── invalidExtend3.pkl │ │ │ │ ├── invalidModule1.pkl │ │ │ │ ├── library.pkl │ │ │ │ ├── lists.pkl │ │ │ │ ├── objects.pkl │ │ │ │ ├── recursiveModule1.pkl │ │ │ │ ├── recursiveModule2.pkl │ │ │ │ ├── supercalls1.pkl │ │ │ │ ├── supercalls2.pkl │ │ │ │ ├── supercalls3.pkl │ │ │ │ ├── typedModuleMethods1.pkl │ │ │ │ ├── typedModuleProperties1.pkl │ │ │ │ ├── typedModuleProperties2.pkl │ │ │ │ ├── typedModuleProperties3.pkl │ │ │ │ ├── 日本語.pkl │ │ │ │ └── 日本語_error.pkl │ │ │ ├── objects/ │ │ │ │ ├── closure.pkl │ │ │ │ ├── configureObjectAssign.pkl │ │ │ │ ├── duplicateProperty.pkl │ │ │ │ ├── equality.pkl │ │ │ │ ├── hashCode.pkl │ │ │ │ ├── implicitReceiver1.pkl │ │ │ │ ├── implicitReceiver2.pkl │ │ │ │ ├── implicitReceiver3.pkl │ │ │ │ ├── implicitReceiver4.pkl │ │ │ │ ├── inequality.pkl │ │ │ │ ├── lateBinding1.pkl │ │ │ │ ├── lateBinding2.pkl │ │ │ │ ├── lateBinding3.pkl │ │ │ │ ├── lateBinding4.pkl │ │ │ │ ├── outer.pkl │ │ │ │ ├── outer2.pkl │ │ │ │ ├── super1.pkl │ │ │ │ ├── super2.pkl │ │ │ │ ├── super3.pkl │ │ │ │ ├── super4.pkl │ │ │ │ ├── super5.pkl │ │ │ │ ├── this1.pkl │ │ │ │ └── this2.pkl │ │ │ ├── packages/ │ │ │ │ ├── badImport1.error.pkl │ │ │ │ ├── badImport10.error.pkl │ │ │ │ ├── badImport11.error.pkl │ │ │ │ ├── badImport2.error.pkl │ │ │ │ ├── badImport3.error.pkl │ │ │ │ ├── badImport4.error.pkl │ │ │ │ ├── badImport6.error.pkl │ │ │ │ ├── badImport7.error.pkl │ │ │ │ ├── badImport8.error.pkl │ │ │ │ ├── badImport9.error.pkl │ │ │ │ ├── badRead1.error.pkl │ │ │ │ ├── badRead2.error.pkl │ │ │ │ ├── badRead3.error.pkl │ │ │ │ ├── badRead4.error.pkl │ │ │ │ ├── badRead8.error.pkl │ │ │ │ ├── badRead9.error.pkl │ │ │ │ ├── packages1.pkl │ │ │ │ ├── packages2.pkl │ │ │ │ └── redirects.pkl │ │ │ ├── parser/ │ │ │ │ ├── amendsRequiresParens.pkl │ │ │ │ ├── constantStringInterpolation.pkl │ │ │ │ ├── constraintsTrailingComma.pkl │ │ │ │ ├── invalidCharacter.pkl │ │ │ │ ├── lambdaTrailingCommas.pkl │ │ │ │ ├── lineCommentBetween.pkl │ │ │ │ ├── methodTrailingCommas.pkl │ │ │ │ ├── newline.pkl │ │ │ │ ├── spacesBetweenDocComments.pkl │ │ │ │ ├── spread.pkl │ │ │ │ ├── trailingCommas.pkl │ │ │ │ ├── typeAnnotationInAmends.pkl │ │ │ │ └── wrongDocComment.pkl │ │ │ ├── pklbinary/ │ │ │ │ ├── basic.msgpack.yaml.pkl │ │ │ │ ├── classes.msgpack.yaml.pkl │ │ │ │ ├── datasize.msgpack.yaml.pkl │ │ │ │ ├── duration.msgpack.yaml.pkl │ │ │ │ ├── intseq.msgpack.yaml.pkl │ │ │ │ ├── list.msgpack.yaml.pkl │ │ │ │ ├── map.msgpack.yaml.pkl │ │ │ │ ├── pair.msgpack.yaml.pkl │ │ │ │ ├── regex.msgpack.yaml.pkl │ │ │ │ └── set.msgpack.yaml.pkl │ │ │ ├── pklbinaryTest.pkl │ │ │ ├── projects/ │ │ │ │ ├── badLocalProject/ │ │ │ │ │ ├── PklProject │ │ │ │ │ └── dog.pkl │ │ │ │ ├── badPklProject1/ │ │ │ │ │ ├── PklProject │ │ │ │ │ └── bug.pkl │ │ │ │ ├── badPklProject2/ │ │ │ │ │ ├── PklProject │ │ │ │ │ └── bug.pkl │ │ │ │ ├── badPklProject3/ │ │ │ │ │ ├── PklProject │ │ │ │ │ └── bug.pkl │ │ │ │ ├── badProjectDeps1/ │ │ │ │ │ ├── PklProject │ │ │ │ │ ├── PklProject.deps.json │ │ │ │ │ └── bug.pkl │ │ │ │ ├── badProjectDeps2/ │ │ │ │ │ ├── PklProject │ │ │ │ │ ├── PklProject.deps.json │ │ │ │ │ └── bug.pkl │ │ │ │ ├── badProjectDeps3/ │ │ │ │ │ ├── PklProject │ │ │ │ │ ├── PklProject.deps.json │ │ │ │ │ └── bug.pkl │ │ │ │ ├── badProjectDeps4/ │ │ │ │ │ ├── PklProject │ │ │ │ │ ├── PklProject.deps.json │ │ │ │ │ └── bug.pkl │ │ │ │ ├── badProjectDeps5/ │ │ │ │ │ ├── PklProject │ │ │ │ │ ├── PklProject.deps.json │ │ │ │ │ └── bug.pkl │ │ │ │ ├── badProjectDeps6/ │ │ │ │ │ ├── PklProject │ │ │ │ │ ├── PklProject.deps.json │ │ │ │ │ └── bug.pkl │ │ │ │ ├── compactTraceMode/ │ │ │ │ │ ├── PklProject │ │ │ │ │ └── compactTraceMode.pkl │ │ │ │ ├── evaluatorSettings/ │ │ │ │ │ ├── PklProject │ │ │ │ │ ├── basic.pkl │ │ │ │ │ └── nullTraceMode.pkl │ │ │ │ ├── missingProjectDeps/ │ │ │ │ │ ├── PklProject │ │ │ │ │ └── bug.pkl │ │ │ │ ├── notAProject/ │ │ │ │ │ ├── @child/ │ │ │ │ │ │ └── theChild.pkl │ │ │ │ │ ├── badImport.error.pkl │ │ │ │ │ └── goodImport.pkl │ │ │ │ ├── prettyTraceMode/ │ │ │ │ │ ├── PklProject │ │ │ │ │ └── prettyTraceMode.pkl │ │ │ │ ├── project1/ │ │ │ │ │ ├── PklProject │ │ │ │ │ ├── PklProject.deps.json │ │ │ │ │ ├── badImport1.error.pkl │ │ │ │ │ ├── badImport2.error.pkl │ │ │ │ │ ├── badImport3.error.pkl │ │ │ │ │ ├── badRead1.error.pkl │ │ │ │ │ ├── basic.pkl │ │ │ │ │ ├── directPackageImport.error.pkl │ │ │ │ │ ├── globbing.pkl │ │ │ │ │ ├── localProject.pkl │ │ │ │ │ └── localProjectRead.pkl │ │ │ │ ├── project2/ │ │ │ │ │ ├── PklProject │ │ │ │ │ ├── PklProject.deps.json │ │ │ │ │ └── penguin.pkl │ │ │ │ ├── project3/ │ │ │ │ │ ├── PklProject │ │ │ │ │ ├── PklProject.deps.json │ │ │ │ │ └── basic.pkl │ │ │ │ ├── project4/ │ │ │ │ │ ├── PklProject │ │ │ │ │ ├── PklProject.deps.json │ │ │ │ │ └── main.pkl │ │ │ │ ├── project5/ │ │ │ │ │ ├── PklProject │ │ │ │ │ ├── PklProject.deps.json │ │ │ │ │ └── main.pkl │ │ │ │ └── project6/ │ │ │ │ ├── PklProject │ │ │ │ ├── PklProject.deps.json │ │ │ │ ├── children/ │ │ │ │ │ ├── a.pkl │ │ │ │ │ ├── b.pkl │ │ │ │ │ └── c.pkl │ │ │ │ └── children.pkl │ │ │ ├── snippetTest.pkl │ │ │ ├── syntax/ │ │ │ │ └── shebang.pkl │ │ │ └── types/ │ │ │ ├── ThisInTypeConstraint.pkl │ │ │ ├── currentModuleType1.pkl │ │ │ ├── currentModuleType2.pkl │ │ │ ├── currentModuleType3.pkl │ │ │ ├── cyclicTypeAlias1.pkl │ │ │ ├── cyclicTypeAlias2.pkl │ │ │ ├── helpers/ │ │ │ │ ├── originalTypealias.pkl │ │ │ │ └── someModule.pkl │ │ │ ├── modifiersForTypes.pkl │ │ │ ├── moduleType.pkl │ │ │ ├── moduleType2.pkl │ │ │ ├── moduleType3.pkl │ │ │ ├── moduleType4.pkl │ │ │ ├── moduleType5.pkl │ │ │ ├── nothingType.pkl │ │ │ ├── nothingWithUnions.pkl │ │ │ ├── typeAlias1.pkl │ │ │ ├── typeAlias2.pkl │ │ │ ├── typeAliasConstraint1.pkl │ │ │ ├── typeAliasConstraint2.pkl │ │ │ ├── typeAliasContext.pkl │ │ │ └── typeAliasUnion.pkl │ │ ├── input-helper/ │ │ │ ├── analyze/ │ │ │ │ ├── a.pkl │ │ │ │ ├── b.pkl │ │ │ │ ├── cannotFindModule.pkl │ │ │ │ ├── cyclicalA.pkl │ │ │ │ ├── cyclicalB.pkl │ │ │ │ ├── globImport.pkl │ │ │ │ └── invalidGlob.pkl │ │ │ ├── api/ │ │ │ │ ├── annotationConverter.pkl │ │ │ │ ├── benchmark/ │ │ │ │ │ └── moduleToBenchmark.pkl │ │ │ │ └── reflect/ │ │ │ │ ├── BaseModule.pkl │ │ │ │ ├── ExtendingModule.pkl │ │ │ │ ├── amendingModule.pkl │ │ │ │ └── testHelpers.pkl │ │ │ ├── basic/ │ │ │ │ └── read/ │ │ │ │ ├── child/ │ │ │ │ │ ├── module2.pkl │ │ │ │ │ └── resource.txt │ │ │ │ ├── module1.pkl │ │ │ │ └── resource.txt │ │ │ ├── classes/ │ │ │ │ ├── MyClass.pkl │ │ │ │ └── myClass1.pkl │ │ │ ├── globtest/ │ │ │ │ ├── child/ │ │ │ │ │ └── moduleC.pkl │ │ │ │ ├── module with [weird] ~!characters.pkl │ │ │ │ ├── moduleA.pkl │ │ │ │ └── moduleB.pkl │ │ │ ├── listings/ │ │ │ │ └── cacheStealingTypeCheck.pkl │ │ │ ├── modules/ │ │ │ │ ├── Birds.pkl │ │ │ │ └── Birds2.pkl │ │ │ └── types/ │ │ │ └── typeAliasConstraint2.pkl │ │ └── output/ │ │ ├── annotation/ │ │ │ ├── annotation1.pcf │ │ │ ├── annotation2.err │ │ │ ├── annotation3.err │ │ │ ├── annotation4.err │ │ │ ├── annotation5.err │ │ │ ├── annotation6.err │ │ │ ├── annotationIsNotExpression.err │ │ │ ├── annotationIsNotExpression2.err │ │ │ ├── deprecated1.err │ │ │ ├── deprecated2.err │ │ │ ├── deprecated3.err │ │ │ ├── deprecatedNoRepeatedWarnings.err │ │ │ ├── deprecatedWithMessage1.err │ │ │ ├── deprecatedWithMessage2.err │ │ │ ├── deprecatedWithMessage3.err │ │ │ └── deprecationSuperMethod.err │ │ ├── api/ │ │ │ ├── Resource.pcf │ │ │ ├── analyze1.pcf │ │ │ ├── annotationConverters.pcf │ │ │ ├── any.pcf │ │ │ ├── anyConverter.pcf │ │ │ ├── baseModule.pcf │ │ │ ├── benchmarkModule.pcf │ │ │ ├── bytes.pcf │ │ │ ├── dataSize.pcf │ │ │ ├── dir1/ │ │ │ │ └── dir2/ │ │ │ │ └── relativePathTo.pcf │ │ │ ├── duration.pcf │ │ │ ├── dynamic.pcf │ │ │ ├── float.pcf │ │ │ ├── int.pcf │ │ │ ├── intseq.pcf │ │ │ ├── jsonParser1.pcf │ │ │ ├── jsonParser2.pcf │ │ │ ├── jsonParser3.pcf │ │ │ ├── jsonParser4.pcf │ │ │ ├── jsonParser5.pcf │ │ │ ├── jsonRenderer1.json │ │ │ ├── jsonRenderer2.json │ │ │ ├── jsonRenderer2b.pcf │ │ │ ├── jsonRenderer3.json │ │ │ ├── jsonRenderer4.pcf │ │ │ ├── jsonRenderer5.pcf │ │ │ ├── jsonRenderer6.json │ │ │ ├── jsonRenderer7.pcf │ │ │ ├── jsonRenderer8.err │ │ │ ├── jsonRenderer9.json5 │ │ │ ├── jsonRendererEmptyComposites.pcf │ │ │ ├── jsonnetRenderer1.jsonnet │ │ │ ├── jsonnetRenderer2.jsonnet │ │ │ ├── jsonnetRenderer3.jsonnet │ │ │ ├── jsonnetRenderer4.pcf │ │ │ ├── jsonnetRenderer5.pcf │ │ │ ├── jsonnetRenderer6.jsonnet │ │ │ ├── jsonnetRenderer7.err │ │ │ ├── jsonnetRenderer8.jsonnet │ │ │ ├── list.pcf │ │ │ ├── listNullable.pcf │ │ │ ├── listing.pcf │ │ │ ├── map.pcf │ │ │ ├── mapping.pcf │ │ │ ├── mathModule.pcf │ │ │ ├── module.pcf │ │ │ ├── moduleOutput.pcf │ │ │ ├── moduleOutput2.pcf │ │ │ ├── pListRenderer1.plist │ │ │ ├── pListRenderer2.plist │ │ │ ├── pListRenderer3.plist │ │ │ ├── pListRenderer4.pcf │ │ │ ├── pListRenderer5.pcf │ │ │ ├── pListRenderer6.plist │ │ │ ├── pListRenderer7.err │ │ │ ├── pListRenderer8.plist │ │ │ ├── pair.pcf │ │ │ ├── pcfRenderer1.pcf │ │ │ ├── pcfRenderer2.pcf │ │ │ ├── pcfRenderer2b.pcf │ │ │ ├── pcfRenderer3.pcf │ │ │ ├── pcfRenderer4.pcf │ │ │ ├── pcfRenderer5.pcf │ │ │ ├── pcfRenderer6.pcf │ │ │ ├── pcfRenderer7.pcf │ │ │ ├── pcfRenderer8.err │ │ │ ├── pcfRenderer9.pcf │ │ │ ├── pklbinary1.msgpack.yaml │ │ │ ├── platformModule.pcf │ │ │ ├── plistRenderer2b.pcf │ │ │ ├── propertiesRenderer1.properties │ │ │ ├── propertiesRenderer10.err │ │ │ ├── propertiesRenderer11.err │ │ │ ├── propertiesRenderer12.properties │ │ │ ├── propertiesRenderer2.properties │ │ │ ├── propertiesRenderer2b.pcf │ │ │ ├── propertiesRenderer3.properties │ │ │ ├── propertiesRenderer4.pcf │ │ │ ├── propertiesRenderer5.pcf │ │ │ ├── propertiesRenderer6.properties │ │ │ ├── propertiesRenderer7.properties │ │ │ ├── propertiesRenderer8.properties │ │ │ ├── propertiesRenderer9.properties │ │ │ ├── propertiesRendererBug66849708.properties │ │ │ ├── protobuf.pcf │ │ │ ├── protobuf2.pcf │ │ │ ├── protobuf3.txtpb │ │ │ ├── reflect1.pcf │ │ │ ├── reflect2.pcf │ │ │ ├── reflect3.pcf │ │ │ ├── reflect4.pcf │ │ │ ├── reflectedDeclaration.pcf │ │ │ ├── regex.pcf │ │ │ ├── releaseModule.pcf │ │ │ ├── renderDirective.pcf │ │ │ ├── renderDirective2.pcf │ │ │ ├── semverModule.pcf │ │ │ ├── set.pcf │ │ │ ├── setNullable.pcf │ │ │ ├── shellModule.pcf │ │ │ ├── string.pcf │ │ │ ├── stringUnicode.pcf │ │ │ ├── typeAliases.pcf │ │ │ ├── typeConverters.pcf │ │ │ ├── typed.pcf │ │ │ ├── xmlRenderer1.xml │ │ │ ├── xmlRenderer2.xml │ │ │ ├── xmlRenderer2b.pcf │ │ │ ├── xmlRenderer3.xml │ │ │ ├── xmlRenderer4.pcf │ │ │ ├── xmlRenderer5.pcf │ │ │ ├── xmlRenderer6.xml │ │ │ ├── xmlRenderer8.err │ │ │ ├── xmlRenderer9.xml │ │ │ ├── xmlRendererCData.xml │ │ │ ├── xmlRendererElement.xml │ │ │ ├── xmlRendererHtml.xml │ │ │ ├── xmlRendererInline.xml │ │ │ ├── xmlRendererInline2.xml │ │ │ ├── xmlRendererInline3.xml │ │ │ ├── xmlRendererValidation10.pcf │ │ │ ├── xmlRendererValidation11.pcf │ │ │ ├── yamlParser1Compat.pcf │ │ │ ├── yamlParser1Yaml11.pcf │ │ │ ├── yamlParser1Yaml12.pcf │ │ │ ├── yamlParser2.pcf │ │ │ ├── yamlParser3.pcf │ │ │ ├── yamlParser4.pcf │ │ │ ├── yamlParser5.pcf │ │ │ ├── yamlParser6.pcf │ │ │ ├── yamlRenderer1.yml │ │ │ ├── yamlRenderer10.yml │ │ │ ├── yamlRenderer2.yml │ │ │ ├── yamlRenderer2b.pcf │ │ │ ├── yamlRenderer3.yml │ │ │ ├── yamlRenderer4.pcf │ │ │ ├── yamlRenderer5.pcf │ │ │ ├── yamlRenderer6.yml │ │ │ ├── yamlRenderer7.err │ │ │ ├── yamlRenderer8.yml │ │ │ ├── yamlRenderer9.yml │ │ │ ├── yamlRendererBug66849708.yml │ │ │ ├── yamlRendererEmpty.yml │ │ │ ├── yamlRendererIndentationWidth2.yml │ │ │ ├── yamlRendererIndentationWidth4.yml │ │ │ ├── yamlRendererIndentationWidth5.yml │ │ │ ├── yamlRendererKeys.yml │ │ │ ├── yamlRendererStream1.pcf │ │ │ ├── yamlRendererStream2.pcf │ │ │ ├── yamlRendererStrings.yml │ │ │ ├── yamlRendererStringsCompat.pcf │ │ │ ├── yamlRendererStringsYaml11.pcf │ │ │ └── yamlRendererStringsYaml12.pcf │ │ ├── basic/ │ │ │ ├── amendsChains.pcf │ │ │ ├── as.pcf │ │ │ ├── as2.pcf │ │ │ ├── as3.pcf │ │ │ ├── baseModule.pcf │ │ │ ├── boolean.pcf │ │ │ ├── bytes.pcf │ │ │ ├── comments.pcf │ │ │ ├── constModifier.pcf │ │ │ ├── constModifier2.pcf │ │ │ ├── constModifier3.pcf │ │ │ ├── constModifier4.pcf │ │ │ ├── constModifier5.err │ │ │ ├── dataSize.pcf │ │ │ ├── duration.pcf │ │ │ ├── exceptions.err │ │ │ ├── fixedProperty1.pcf │ │ │ ├── fixedProperty2.pcf │ │ │ ├── fixedProperty3.pcf │ │ │ ├── fixedProperty4.err │ │ │ ├── fixedProperty5.err │ │ │ ├── fixedProperty6.err │ │ │ ├── float.pcf │ │ │ ├── identifier.pcf │ │ │ ├── if.pcf │ │ │ ├── import1.pcf │ │ │ ├── import1b.pcf │ │ │ ├── import2.pcf │ │ │ ├── import3.pcf │ │ │ ├── importGlob.pcf │ │ │ ├── imported.pcf │ │ │ ├── indexExpressions.pcf │ │ │ ├── int.pcf │ │ │ ├── intseq.pcf │ │ │ ├── is.pcf │ │ │ ├── is2.pcf │ │ │ ├── let.pcf │ │ │ ├── letTyped.pcf │ │ │ ├── list.pcf │ │ │ ├── localMethodDynamicBinding.pcf │ │ │ ├── localMethodInAmendingModule.pcf │ │ │ ├── localMethodOverride1.pcf │ │ │ ├── localMethodOverride2.pcf │ │ │ ├── localMethodTyped.pcf │ │ │ ├── localMethodTyped2.pcf │ │ │ ├── localMethodTyped3.pcf │ │ │ ├── localMethodTyped4.pcf │ │ │ ├── localMethodTyped5.pcf │ │ │ ├── localMethodTyped6.pcf │ │ │ ├── localMethodUntyped.pcf │ │ │ ├── localModuleMemberOverride1.pcf │ │ │ ├── localModuleMemberOverride2.pcf │ │ │ ├── localProperty1.pcf │ │ │ ├── localProperty2.pcf │ │ │ ├── localPropertyAmendInAmendingModule.err │ │ │ ├── localPropertyInAmendingModule.pcf │ │ │ ├── localPropertyOverride1.pcf │ │ │ ├── localPropertyOverride2.pcf │ │ │ ├── localPropertyOverride3.pcf │ │ │ ├── localTypedClassMember.pcf │ │ │ ├── localTypedModuleMember.pcf │ │ │ ├── localTypedObjectMember.pcf │ │ │ ├── map.pcf │ │ │ ├── minPklVersion.pcf │ │ │ ├── moduleRef1.pcf │ │ │ ├── moduleRef2.pcf │ │ │ ├── moduleRef3.pcf │ │ │ ├── moduleRefLibrary.pcf │ │ │ ├── new.pcf │ │ │ ├── newInAmendingModuleMethod.pcf │ │ │ ├── newInsideIf.pcf │ │ │ ├── newInsideLet.pcf │ │ │ ├── newType.pcf │ │ │ ├── nonNull.pcf │ │ │ ├── nullable.pcf │ │ │ ├── objectMember.pcf │ │ │ ├── objectMemberInvalid.err │ │ │ ├── objectMemberInvalid2.err │ │ │ ├── objectMemberInvalid3.err │ │ │ ├── parens.pcf │ │ │ ├── propertyDefaults.pcf │ │ │ ├── rawString.pcf │ │ │ ├── read.pcf │ │ │ ├── readGlob.pcf │ │ │ ├── semicolon.pcf │ │ │ ├── set.pcf │ │ │ ├── string.pcf │ │ │ ├── stringError1.err │ │ │ ├── stringMultiline.pcf │ │ │ ├── stringUnicode.pcf │ │ │ ├── trace.err │ │ │ ├── typeResolution1.pcf │ │ │ ├── typeResolution2.pcf │ │ │ ├── typeResolution3.pcf │ │ │ ├── typeResolution4.pcf │ │ │ └── underscore.pcf │ │ ├── classes/ │ │ │ ├── class1.pcf │ │ │ ├── class2.pcf │ │ │ ├── class2a.pcf │ │ │ ├── class3.err │ │ │ ├── class4.pcf │ │ │ ├── constraints1.pcf │ │ │ ├── constraints10.pcf │ │ │ ├── constraints11.pcf │ │ │ ├── constraints12.pcf │ │ │ ├── constraints13.pcf │ │ │ ├── constraints14.pcf │ │ │ ├── constraints2.pcf │ │ │ ├── constraints3.pcf │ │ │ ├── constraints4.pcf │ │ │ ├── constraints5.err │ │ │ ├── constraints6.pcf │ │ │ ├── constraints7.pcf │ │ │ ├── constraints8.pcf │ │ │ ├── constraints9.pcf │ │ │ ├── constraintsLambdaThis.pcf │ │ │ ├── duplicateFunction.err │ │ │ ├── duplicateProperty.err │ │ │ ├── equality.pcf │ │ │ ├── externalClass.err │ │ │ ├── externalFunction.err │ │ │ ├── externalProperty.err │ │ │ ├── functions1.pcf │ │ │ ├── functions2.pcf │ │ │ ├── functions3.pcf │ │ │ ├── functions4.pcf │ │ │ ├── inheritance1.pcf │ │ │ ├── inheritance2.pcf │ │ │ ├── inheritanceError1.err │ │ │ ├── invalidInstantiation1.err │ │ │ ├── invalidInstantiation2.err │ │ │ ├── lambdaConstraints1.pcf │ │ │ ├── listConstraints1.pcf │ │ │ ├── mapConstraints1.pcf │ │ │ ├── nullableTypes.pcf │ │ │ ├── setConstraints1.pcf │ │ │ ├── supercalls.pcf │ │ │ ├── supercallsInLet.pcf │ │ │ ├── unionTypes.pcf │ │ │ ├── unionTypesErrorAlias.err │ │ │ ├── unionTypesErrorDifferent1.err │ │ │ ├── unionTypesErrorDifferent2.err │ │ │ ├── unionTypesErrorMultipleAliases.err │ │ │ ├── unionTypesErrorNested.err │ │ │ ├── unionTypesErrorSimple.err │ │ │ ├── unionTypesErrorString1.err │ │ │ ├── unionTypesErrorString2.err │ │ │ ├── wrongType1.err │ │ │ ├── wrongType2.err │ │ │ ├── wrongType3.err │ │ │ ├── wrongType4.err │ │ │ ├── wrongType5.pcf │ │ │ └── wrongType6.err │ │ ├── errors/ │ │ │ ├── abstractOpenMember.err │ │ │ ├── analyzeImportsCannotFindModule.err │ │ │ ├── analyzeImportsInvalidGlob.err │ │ │ ├── analyzeInvalidModuleUri.err │ │ │ ├── analyzeRelativeModuleUri.err │ │ │ ├── anyConverterError.err │ │ │ ├── baseModule.pcf │ │ │ ├── binopDifferentLine.err │ │ │ ├── cannotAmendFixedProperty1.err │ │ │ ├── cannotAmendFixedProperty2.err │ │ │ ├── cannotAssignFixedProperty1.err │ │ │ ├── cannotAssignFixedProperty2.err │ │ │ ├── cannotAssignFixedProperty3.err │ │ │ ├── cannotAssignToNothing.err │ │ │ ├── cannotChangeFixed1.err │ │ │ ├── cannotChangeFixed2.err │ │ │ ├── cannotFindMapKey.err │ │ │ ├── cannotFindStdLibModule.err │ │ │ ├── cannotInferParent2.err │ │ │ ├── cannotInferParent3.err │ │ │ ├── cannotInstantiateAbstractModule.err │ │ │ ├── cannotRenderMixin.err │ │ │ ├── classExtendsSelf.err │ │ │ ├── const/ │ │ │ │ ├── constAmend.err │ │ │ │ ├── constAnnotation.err │ │ │ │ ├── constAnnotation2.err │ │ │ │ ├── constAnnotation3.err │ │ │ │ ├── constAssign.err │ │ │ │ ├── constClassBody.err │ │ │ │ ├── constExplicitThis.err │ │ │ │ ├── constExplicitThisMethod.err │ │ │ │ ├── constFunctionCallingNonConst.err │ │ │ │ ├── constImplicitThis.err │ │ │ │ ├── constImplicitThisMethod.err │ │ │ │ ├── constLexicalScope.err │ │ │ │ ├── constLocalAmendModule.pcf │ │ │ │ ├── constLocalMethod.err │ │ │ │ ├── constLocalProperty.err │ │ │ │ ├── constMethod.err │ │ │ │ ├── constMethod2.err │ │ │ │ ├── constModule.err │ │ │ │ ├── constModule2.err │ │ │ │ ├── constModule3.err │ │ │ │ ├── constObjectMember.err │ │ │ │ ├── constOuter.err │ │ │ │ ├── constOuter2.err │ │ │ │ ├── constOuter3.err │ │ │ │ ├── constQualified.err │ │ │ │ ├── constSubclass.err │ │ │ │ ├── constSubclass2.err │ │ │ │ ├── constSuper.err │ │ │ │ ├── constSuperMethod.err │ │ │ │ ├── constThis.err │ │ │ │ └── constTypeAliasConstraint.err │ │ │ ├── constraintDetails1.err │ │ │ ├── constraintDetails2.err │ │ │ ├── constraintDetails3.err │ │ │ ├── decodingException.err │ │ │ ├── delimiters/ │ │ │ │ ├── missingClassDelimiter.err │ │ │ │ ├── missingConstrainedTypeSeparator.err │ │ │ │ ├── missingContainerAmendDefDelimiter.err │ │ │ │ ├── missingContainerAmendExprDelimiter.err │ │ │ │ ├── missingEmptyMultiLineStringDelimiter.err │ │ │ │ ├── missingEmptyMultiLineStringDelimiterAtEof.err │ │ │ │ ├── missingEmptyStringDelimiter.err │ │ │ │ ├── missingEmptyStringDelimiterAtEof.err │ │ │ │ ├── missingFunction0ParameterListDelimiter.err │ │ │ │ ├── missingFunction1ParameterListDelimiter.err │ │ │ │ ├── missingFunctionAmendParameterListSeparator.err │ │ │ │ ├── missingFunctionNParameterListDelimiter.err │ │ │ │ ├── missingFunctionParameterListSeparator.err │ │ │ │ ├── missingFunctionType0ParameterListDelimiter.err │ │ │ │ ├── missingFunctionType1ParameterListDelimiter.err │ │ │ │ ├── missingFunctionTypeNParameterListDelimiter.err │ │ │ │ ├── missingFunctionTypeParameterListSeparator.err │ │ │ │ ├── missingIfExprDelimiter.err │ │ │ │ ├── missingListDelimiter.err │ │ │ │ ├── missingListSeparator.err │ │ │ │ ├── missingMapDelimiter.err │ │ │ │ ├── missingMapSeparator.err │ │ │ │ ├── missingMethodArgumentListDelimiter.err │ │ │ │ ├── missingMethodArgumentListSeparator.err │ │ │ │ ├── missingMethodParameterListDelimiter.err │ │ │ │ ├── missingMethodParameterListSeparator.err │ │ │ │ ├── missingMultiLineStringDelimiter.err │ │ │ │ ├── missingObjectAmendDefDelimiter.err │ │ │ │ ├── missingObjectAmendDefDelimiter2.err │ │ │ │ ├── missingObjectAmendDefDelimiter3.err │ │ │ │ ├── missingObjectAmendExprDelimiter.err │ │ │ │ ├── missingObjectDelimiter.err │ │ │ │ ├── missingParenthesizedExprDelimiter.err │ │ │ │ ├── missingParenthesizedTypeDelimiter.err │ │ │ │ ├── missingRawMultiLineStringDelimiter.err │ │ │ │ ├── missingRawStringDelimiter.err │ │ │ │ ├── missingSetSeparator.err │ │ │ │ ├── missingStringDelimiter.err │ │ │ │ ├── missingSubscriptDelimiter.err │ │ │ │ ├── missingTypeConstraintListDelimiter.err │ │ │ │ ├── missingTypedMethodParameterListDelimiter.err │ │ │ │ ├── missingTypedMethodParameterListSeparator.err │ │ │ │ ├── unbalancedEntryBrackets1.err │ │ │ │ ├── unbalancedEntryBrackets2.err │ │ │ │ ├── unbalancedEntryBrackets3.err │ │ │ │ └── unbalancedEntryBrackets4.err │ │ │ ├── duplicateTypeParameter.err │ │ │ ├── emptyParenthesizedTypeAnnotation.err │ │ │ ├── extendExternalClass.err │ │ │ ├── extendTypeAlias.err │ │ │ ├── forGeneratorCannotGenerateMethods.err │ │ │ ├── forGeneratorCannotGenerateProperties.err │ │ │ ├── forGeneratorCannotIterateOverThisValue.err │ │ │ ├── forGeneratorCannotIterateOverTyped.err │ │ │ ├── forGeneratorDuplicateParams1.err │ │ │ ├── forGeneratorWrongVariableName.err │ │ │ ├── fullStackTraces.err │ │ │ ├── fullStackTraces2.err │ │ │ ├── functionNotFoundInClass.err │ │ │ ├── functionNotFoundInModule.err │ │ │ ├── functionNotFoundInScope.err │ │ │ ├── functionNotFoundMaybeLambda.err │ │ │ ├── functionNotFoundMaybeProperty.err │ │ │ ├── intrinsifiedTypeAlias1.err │ │ │ ├── intrinsifiedTypeAlias2.err │ │ │ ├── intrinsifiedTypeAlias3.err │ │ │ ├── intrinsifiedTypeAlias4.err │ │ │ ├── invalidBytes1.err │ │ │ ├── invalidBytes2.err │ │ │ ├── invalidBytes3.err │ │ │ ├── invalidBytes4.err │ │ │ ├── invalidCharacterEscape.err │ │ │ ├── invalidClassMethodModifier.err │ │ │ ├── invalidClassModifier.err │ │ │ ├── invalidClassModifier2.err │ │ │ ├── invalidClassPropertyModifier.err │ │ │ ├── invalidFileUri1.err │ │ │ ├── invalidFileUri2.err │ │ │ ├── invalidFileUri3.err │ │ │ ├── invalidFileUri4.err │ │ │ ├── invalidGlobImport1.err │ │ │ ├── invalidGlobImport2.err │ │ │ ├── invalidGlobImport3.err │ │ │ ├── invalidGlobImport4.err │ │ │ ├── invalidGlobImport5.err │ │ │ ├── invalidGlobImport6.err │ │ │ ├── invalidGlobImport7.err │ │ │ ├── invalidGlobImport8.err │ │ │ ├── invalidGlobRead1.err │ │ │ ├── invalidGlobRead2.err │ │ │ ├── invalidGlobRead3.err │ │ │ ├── invalidImportBackslashSep.err │ │ │ ├── invalidImportUri.err │ │ │ ├── invalidMethodModifier.err │ │ │ ├── invalidModuleModifier.err │ │ │ ├── invalidObjectPropertyModifier.err │ │ │ ├── invalidOutput1.err │ │ │ ├── invalidOutput2.err │ │ │ ├── invalidOutput3.err │ │ │ ├── invalidPropertyModifier.err │ │ │ ├── invalidTripleDotSyntax1.err │ │ │ ├── invalidTripleDotSyntax2.err │ │ │ ├── invalidTypeAliasModifier.err │ │ │ ├── invalidTypeName1.err │ │ │ ├── invalidTypeName2.err │ │ │ ├── invalidTypeName3.err │ │ │ ├── invalidTypeName4.err │ │ │ ├── invalidUnicodeEscape.err │ │ │ ├── keywordNotAllowedHere1.err │ │ │ ├── keywordNotAllowedHere2.err │ │ │ ├── keywordNotAllowedHere3.err │ │ │ ├── keywordNotAllowedHere4.err │ │ │ ├── letExpressionError1.err │ │ │ ├── letExpressionError2.err │ │ │ ├── letExpressionErrorTyped.err │ │ │ ├── listingTypeCheckError1.err │ │ │ ├── listingTypeCheckError2.err │ │ │ ├── listingTypeCheckError3.err │ │ │ ├── listingTypeCheckError4.err │ │ │ ├── listingTypeCheckError5.err │ │ │ ├── listingTypeCheckError6.err │ │ │ ├── listingTypeCheckError7.err │ │ │ ├── listingTypeCheckError8.err │ │ │ ├── listingTypeCheckError9.err │ │ │ ├── localFixedMember.err │ │ │ ├── localFunctionWithTypeParameter.err │ │ │ ├── localHiddenMember.err │ │ │ ├── mappingTypeCheckError1.err │ │ │ ├── mappingTypeCheckError10.err │ │ │ ├── mappingTypeCheckError11.err │ │ │ ├── mappingTypeCheckError2.err │ │ │ ├── mappingTypeCheckError3.err │ │ │ ├── mappingTypeCheckError4.err │ │ │ ├── mappingTypeCheckError5.err │ │ │ ├── mappingTypeCheckError6.err │ │ │ ├── mappingTypeCheckError7.err │ │ │ ├── mappingTypeCheckError8.err │ │ │ ├── mappingTypeCheckError9.err │ │ │ ├── missingLocalPropertyValue1.err │ │ │ ├── missingLocalPropertyValue2.err │ │ │ ├── moduleAmendsSelf.err │ │ │ ├── moduleAmendsVersionCheck.err │ │ │ ├── moduleAmendsVersionCheck2.err │ │ │ ├── moduleExpected.err │ │ │ ├── moduleExtendsSelf.err │ │ │ ├── moduleExtendsVersionCheck.err │ │ │ ├── moduleExtendsVersionCheck2.err │ │ │ ├── moduleImportVersionCheck.err │ │ │ ├── moduleWithHighMinPklVersion.err │ │ │ ├── moduleWithHighMinPklVersionAndParseErrors.err │ │ │ ├── multipleDefaults.err │ │ │ ├── nested1.err │ │ │ ├── noDefault.err │ │ │ ├── noDefault2.err │ │ │ ├── notAUnionDefault.err │ │ │ ├── objectCannotHaveElement.err │ │ │ ├── objectCannotHaveElement2.err │ │ │ ├── objectCannotHavePredicateMember.err │ │ │ ├── outOfRange1.err │ │ │ ├── outOfRange2.err │ │ │ ├── outOfRange3.err │ │ │ ├── parser1.err │ │ │ ├── parser10.err │ │ │ ├── parser11.err │ │ │ ├── parser12.err │ │ │ ├── parser14.err │ │ │ ├── parser15.err │ │ │ ├── parser16.err │ │ │ ├── parser17.err │ │ │ ├── parser18.err │ │ │ ├── parser2.err │ │ │ ├── parser3.err │ │ │ ├── parser4.err │ │ │ ├── parser5.err │ │ │ ├── parser6.err │ │ │ ├── parser7.err │ │ │ ├── parser8.err │ │ │ ├── parser9.err │ │ │ ├── power/ │ │ │ │ ├── typeConstraints1.err │ │ │ │ ├── typeConstraints10.err │ │ │ │ ├── typeConstraints11.err │ │ │ │ ├── typeConstraints12.err │ │ │ │ ├── typeConstraints13.err │ │ │ │ ├── typeConstraints14.err │ │ │ │ ├── typeConstraints15.err │ │ │ │ ├── typeConstraints16.err │ │ │ │ ├── typeConstraints17.err │ │ │ │ ├── typeConstraints2.err │ │ │ │ ├── typeConstraints3.err │ │ │ │ ├── typeConstraints4.err │ │ │ │ ├── typeConstraints5.err │ │ │ │ ├── typeConstraints6.err │ │ │ │ ├── typeConstraints7.err │ │ │ │ ├── typeConstraints8.err │ │ │ │ ├── typeConstraints9.err │ │ │ │ └── typeConstraints9a.err │ │ │ ├── propertyNotFound1.err │ │ │ ├── propertyNotFound2.err │ │ │ ├── refusingToLoadModule.err │ │ │ ├── shebang.err │ │ │ ├── singleBacktick.err │ │ │ ├── spreadSyntaxCannotHaveElement.err │ │ │ ├── spreadSyntaxCannotHaveEntry.err │ │ │ ├── spreadSyntaxCannotHaveProperty.err │ │ │ ├── spreadSyntaxCannotIterateOverTyped.err │ │ │ ├── spreadSyntaxCannotSpreadListIntoMapping.err │ │ │ ├── spreadSyntaxDuplicateEntry1.err │ │ │ ├── spreadSyntaxDuplicateEntry2.err │ │ │ ├── spreadSyntaxDuplicateProperty1.err │ │ │ ├── spreadSyntaxDuplicateProperty2.err │ │ │ ├── spreadSyntaxNullValue.err │ │ │ ├── spreadSyntaxUnknownTypedProperty.err │ │ │ ├── stackTraceWithQuotedMemberName.err │ │ │ ├── supercalls.err │ │ │ ├── typeMismatchWithSameQualifiedClassName.err │ │ │ ├── typeMismatchWithSameQualifiedModuleName.err │ │ │ ├── undefinedOp1.err │ │ │ ├── undefinedOp2.err │ │ │ ├── undefinedOp3.err │ │ │ ├── undefinedProperty1.err │ │ │ ├── undefinedProperty2.err │ │ │ ├── undefinedProperty3.err │ │ │ ├── undefinedProperty4.err │ │ │ ├── undefinedProperty5.err │ │ │ ├── undefinedProperty6.err │ │ │ ├── undefinedProperty7.err │ │ │ ├── undefinedProperty8.err │ │ │ ├── underscore.err │ │ │ ├── underscoreLambda.err │ │ │ ├── underscoreLet.err │ │ │ ├── unterminatedUnicodeEscape.err │ │ │ ├── userDefinedTypeParameter1.err │ │ │ ├── userDefinedTypeParameter2.err │ │ │ ├── wrongForGeneratorType1.err │ │ │ ├── wrongForGeneratorType2.err │ │ │ └── wrongNumberOfMapArguments.err │ │ ├── generators/ │ │ │ ├── duplicateDefinition1.err │ │ │ ├── duplicateDefinition2.err │ │ │ ├── duplicateDefinition3.err │ │ │ ├── elementGenerators.pcf │ │ │ ├── elementGeneratorsTyped.pcf │ │ │ ├── entryGenerators.pcf │ │ │ ├── entryGeneratorsTyped.pcf │ │ │ ├── forGeneratorInFunctionBody.pcf │ │ │ ├── forGeneratorInMixins.pcf │ │ │ ├── forGeneratorLexicalScope.pcf │ │ │ ├── forGeneratorNestedReference.pcf │ │ │ ├── forGeneratorNestedReference2.pcf │ │ │ ├── forGeneratorVariableShadowing.pcf │ │ │ ├── generatorNoMembers.err │ │ │ ├── predicateMembersDynamicListing.pcf │ │ │ ├── predicateMembersDynamicMapping.pcf │ │ │ ├── predicateMembersListing.pcf │ │ │ ├── predicateMembersMapping.pcf │ │ │ ├── predicateMembersThis.pcf │ │ │ ├── propertyGenerators.pcf │ │ │ ├── spreadSyntaxDynamic.pcf │ │ │ ├── spreadSyntaxListing.pcf │ │ │ ├── spreadSyntaxMapping.pcf │ │ │ ├── spreadSyntaxNoSpace.err │ │ │ ├── spreadSyntaxNullable.pcf │ │ │ └── spreadSyntaxTyped.pcf │ │ ├── implementation/ │ │ │ ├── equality.pcf │ │ │ └── inequality.pcf │ │ ├── internal/ │ │ │ └── polymorphicCallSite.pcf │ │ ├── lambdas/ │ │ │ ├── amendLambdaDef.pcf │ │ │ ├── amendLambdaExpr.pcf │ │ │ ├── amendLambdaExternalClassError.err │ │ │ ├── amendLambdaParameters.pcf │ │ │ ├── amendLambdaParametersTyped.pcf │ │ │ ├── amendLambdaThatReturnsAnotherLambda.pcf │ │ │ ├── amendLambdaTooFewArgsError.err │ │ │ ├── amendLambdaTooManyArgsError.err │ │ │ ├── equality.pcf │ │ │ ├── inequality.pcf │ │ │ ├── lambda1.pcf │ │ │ ├── lambda2.pcf │ │ │ ├── lambda3.pcf │ │ │ ├── lambda4.pcf │ │ │ ├── lambda5.pcf │ │ │ ├── lambdaStackTrace1.err │ │ │ ├── lambdaStackTrace2.err │ │ │ ├── lambdaStackTrace3.err │ │ │ ├── pipeOperator.pcf │ │ │ ├── tooManyLambdaParams.err │ │ │ ├── typedLambdas.pcf │ │ │ └── wrongArgumentListLength.err │ │ ├── listings/ │ │ │ ├── cacheStealing.pcf │ │ │ ├── cacheStealingTypeCheck.pcf │ │ │ ├── default.pcf │ │ │ ├── equality.pcf │ │ │ ├── hashCode.pcf │ │ │ ├── inequality.pcf │ │ │ ├── listing1.pcf │ │ │ ├── listing2.pcf │ │ │ ├── listing3.pcf │ │ │ ├── listing4.pcf │ │ │ ├── listing5.pcf │ │ │ ├── listing6.pcf │ │ │ ├── listing7.err │ │ │ ├── numberLiterals.pcf │ │ │ ├── typeCheck.pcf │ │ │ ├── wrongIndex.pcf │ │ │ └── wrongParent.pcf │ │ ├── listings2/ │ │ │ ├── default.pcf │ │ │ ├── equality.pcf │ │ │ ├── inequality.pcf │ │ │ ├── listing1.pcf │ │ │ ├── listing2.pcf │ │ │ ├── listing3.pcf │ │ │ ├── numberLiterals.pcf │ │ │ ├── typeCheck.pcf │ │ │ ├── wrongIndex.pcf │ │ │ └── wrongParent.pcf │ │ ├── mappings/ │ │ │ ├── default.pcf │ │ │ ├── duplicateComputedKey.pcf │ │ │ ├── duplicateConstantKey.err │ │ │ ├── equality.pcf │ │ │ ├── hashCode.pcf │ │ │ ├── inequality.pcf │ │ │ ├── mapping1.pcf │ │ │ ├── mapping2.pcf │ │ │ ├── stringKeyNotFound.err │ │ │ ├── typeCheck.pcf │ │ │ └── wrongParent.pcf │ │ ├── mappings2/ │ │ │ ├── default.pcf │ │ │ ├── duplicateComputedKey.pcf │ │ │ ├── duplicateConstantKey.err │ │ │ ├── equality.pcf │ │ │ ├── inequality.pcf │ │ │ ├── mapping1.pcf │ │ │ ├── mapping2.pcf │ │ │ ├── stringKeyNotFound.err │ │ │ ├── typeCheck.pcf │ │ │ └── wrongParent.pcf │ │ ├── methods/ │ │ │ ├── methodParameterConstraints1.pcf │ │ │ ├── methodParameterConstraints2.pcf │ │ │ ├── methodParameterTypes1.pcf │ │ │ ├── methodParameterTypes2.pcf │ │ │ └── methodParameterTypes3.pcf │ │ ├── modules/ │ │ │ ├── amendModule1.pcf │ │ │ ├── amendModule2.pcf │ │ │ ├── amendModule3.pcf │ │ │ ├── amendModule4.pcf │ │ │ ├── amendModule5.pcf │ │ │ ├── amendModule6.pcf │ │ │ ├── duplicateFunction.err │ │ │ ├── duplicateProperty1.err │ │ │ ├── duplicateProperty2.err │ │ │ ├── duplicateProperty3.err │ │ │ ├── equality.pcf │ │ │ ├── extendModule1.pcf │ │ │ ├── externalFunction.err │ │ │ ├── externalProperty.err │ │ │ ├── filename with spaces 2.pcf │ │ │ ├── filename with spaces.pcf │ │ │ ├── functions.pcf │ │ │ ├── invalidAmend1.err │ │ │ ├── invalidAmend2.err │ │ │ ├── invalidAmend3.err │ │ │ ├── invalidAmend4.err │ │ │ ├── invalidAmend5.err │ │ │ ├── invalidAmend6.err │ │ │ ├── invalidExtend1.err │ │ │ ├── invalidExtend2.err │ │ │ ├── invalidExtend3.err │ │ │ ├── invalidModule1.err │ │ │ ├── library.pcf │ │ │ ├── lists.pcf │ │ │ ├── objects.pcf │ │ │ ├── recursiveModule1.pcf │ │ │ ├── supercalls1.pcf │ │ │ ├── supercalls2.pcf │ │ │ ├── supercalls3.pcf │ │ │ ├── typedModuleMethods1.pcf │ │ │ ├── typedModuleProperties1.pcf │ │ │ ├── typedModuleProperties2.err │ │ │ ├── typedModuleProperties3.err │ │ │ ├── 日本語.pcf │ │ │ └── 日本語_error.err │ │ ├── objects/ │ │ │ ├── closure.pcf │ │ │ ├── configureObjectAssign.pcf │ │ │ ├── duplicateProperty.err │ │ │ ├── equality.pcf │ │ │ ├── hashCode.pcf │ │ │ ├── implicitReceiver1.pcf │ │ │ ├── implicitReceiver2.pcf │ │ │ ├── implicitReceiver3.pcf │ │ │ ├── implicitReceiver4.err │ │ │ ├── inequality.pcf │ │ │ ├── lateBinding1.pcf │ │ │ ├── lateBinding2.pcf │ │ │ ├── lateBinding3.pcf │ │ │ ├── lateBinding4.pcf │ │ │ ├── outer.pcf │ │ │ ├── outer2.err │ │ │ ├── super1.pcf │ │ │ ├── super2.pcf │ │ │ ├── super3.pcf │ │ │ ├── super4.pcf │ │ │ ├── super5.pcf │ │ │ ├── this1.pcf │ │ │ └── this2.pcf │ │ ├── packages/ │ │ │ ├── badImport1.err │ │ │ ├── badImport10.err │ │ │ ├── badImport11.err │ │ │ ├── badImport2.err │ │ │ ├── badImport3.err │ │ │ ├── badImport4.err │ │ │ ├── badImport6.err │ │ │ ├── badImport7.err │ │ │ ├── badImport8.err │ │ │ ├── badImport9.err │ │ │ ├── badRead1.err │ │ │ ├── badRead2.err │ │ │ ├── badRead3.err │ │ │ ├── badRead4.err │ │ │ ├── badRead8.err │ │ │ ├── badRead9.err │ │ │ ├── packages1.pcf │ │ │ ├── packages2.pcf │ │ │ └── redirects.pcf │ │ ├── parser/ │ │ │ ├── amendsRequiresParens.err │ │ │ ├── constantStringInterpolation.err │ │ │ ├── constraintsTrailingComma.pcf │ │ │ ├── invalidCharacter.err │ │ │ ├── lineCommentBetween.pcf │ │ │ ├── methodTrailingCommas.pcf │ │ │ ├── newline.pcf │ │ │ ├── spacesBetweenDocComments.err │ │ │ ├── spread.pcf │ │ │ ├── trailingCommas.pcf │ │ │ ├── typeAnnotationInAmends.err │ │ │ └── wrongDocComment.err │ │ ├── pklbinary/ │ │ │ ├── basic.msgpack.yaml │ │ │ ├── classes.msgpack.yaml │ │ │ ├── datasize.msgpack.yaml │ │ │ ├── duration.msgpack.yaml │ │ │ ├── intseq.msgpack.yaml │ │ │ ├── list.msgpack.yaml │ │ │ ├── map.msgpack.yaml │ │ │ ├── pair.msgpack.yaml │ │ │ ├── regex.msgpack.yaml │ │ │ └── set.msgpack.yaml │ │ ├── projects/ │ │ │ ├── badLocalProject/ │ │ │ │ └── dog.pcf │ │ │ ├── badPklProject1/ │ │ │ │ └── bug.err │ │ │ ├── badPklProject2/ │ │ │ │ └── bug.err │ │ │ ├── badPklProject3/ │ │ │ │ └── bug.err │ │ │ ├── badProjectDeps1/ │ │ │ │ └── bug.err │ │ │ ├── badProjectDeps2/ │ │ │ │ └── bug.err │ │ │ ├── badProjectDeps3/ │ │ │ │ └── bug.err │ │ │ ├── badProjectDeps4/ │ │ │ │ └── bug.err │ │ │ ├── badProjectDeps5/ │ │ │ │ └── bug.err │ │ │ ├── badProjectDeps6/ │ │ │ │ └── bug.err │ │ │ ├── compactTraceMode/ │ │ │ │ └── compactTraceMode.err │ │ │ ├── evaluatorSettings/ │ │ │ │ ├── basic.pcf │ │ │ │ └── nullTraceMode.err │ │ │ ├── missingProjectDeps/ │ │ │ │ └── bug.err │ │ │ ├── notAProject/ │ │ │ │ ├── @child/ │ │ │ │ │ └── theChild.pcf │ │ │ │ ├── badImport.err │ │ │ │ └── goodImport.pcf │ │ │ ├── prettyTraceMode/ │ │ │ │ └── prettyTraceMode.err │ │ │ ├── project1/ │ │ │ │ ├── badImport1.err │ │ │ │ ├── badImport2.err │ │ │ │ ├── badImport3.err │ │ │ │ ├── badRead1.err │ │ │ │ ├── basic.pcf │ │ │ │ ├── directPackageImport.err │ │ │ │ ├── globbing.pcf │ │ │ │ ├── localProject.pcf │ │ │ │ └── localProjectRead.pcf │ │ │ ├── project2/ │ │ │ │ └── penguin.pcf │ │ │ ├── project3/ │ │ │ │ └── basic.pcf │ │ │ ├── project4/ │ │ │ │ └── main.pcf │ │ │ ├── project5/ │ │ │ │ └── main.pcf │ │ │ └── project6/ │ │ │ ├── children/ │ │ │ │ ├── a.pcf │ │ │ │ ├── b.pcf │ │ │ │ └── c.pcf │ │ │ └── children.pcf │ │ ├── syntax/ │ │ │ └── shebang.pcf │ │ └── types/ │ │ ├── ThisInTypeConstraint.pcf │ │ ├── currentModuleType1.pcf │ │ ├── currentModuleType2.pcf │ │ ├── currentModuleType3.pcf │ │ ├── cyclicTypeAlias1.err │ │ ├── cyclicTypeAlias2.err │ │ ├── helpers/ │ │ │ └── someModule.pcf │ │ ├── modifiersForTypes.pcf │ │ ├── moduleType.pcf │ │ ├── moduleType2.pcf │ │ ├── moduleType3.pcf │ │ ├── moduleType4.pcf │ │ ├── moduleType5.pcf │ │ ├── nothingType.err │ │ ├── nothingWithUnions.pcf │ │ ├── typeAlias1.pcf │ │ ├── typeAlias2.pcf │ │ ├── typeAliasConstraint1.pcf │ │ ├── typeAliasConstraint2.pcf │ │ ├── typeAliasContext.err │ │ └── typeAliasUnion.pcf │ ├── kotlin/ │ │ └── org/ │ │ └── pkl/ │ │ └── core/ │ │ ├── AnalyzerTest.kt │ │ ├── ClassInheritanceTest.kt │ │ ├── DataSizeTest.kt │ │ ├── DataSizeUnitTest.kt │ │ ├── DurationTest.kt │ │ ├── DurationUnitTest.kt │ │ ├── DynamicTest.kt │ │ ├── EvaluateExpressionTest.kt │ │ ├── EvaluateMultipleFileOutputTest.kt │ │ ├── EvaluateOutputTextTest.kt │ │ ├── EvaluateSchemaTest.kt │ │ ├── EvaluateTestsTest.kt │ │ ├── EvaluatorBuilderTest.kt │ │ ├── EvaluatorTest.kt │ │ ├── JsonRendererTest.kt │ │ ├── LanguageSnippetTests.kt │ │ ├── LanguageSnippetTestsEngine.kt │ │ ├── PClassInfoTest.kt │ │ ├── PListRendererTest.kt │ │ ├── PModuleTest.kt │ │ ├── PNullTest.kt │ │ ├── PObjectTest.kt │ │ ├── PairTest.kt │ │ ├── PcfRendererTest.kt │ │ ├── PklBinaryDecoderTest.kt │ │ ├── PklInfoTest.kt │ │ ├── PlatformTest.kt │ │ ├── PropertiesRendererTest.kt │ │ ├── ReleaseTest.kt │ │ ├── ReplServerTest.kt │ │ ├── RepositoryHygiene.kt │ │ ├── SecurityManagersTest.kt │ │ ├── StackFrameTransformersTest.kt │ │ ├── VersionTest.kt │ │ ├── YamlRendererTest.kt │ │ ├── ast/ │ │ │ └── builder/ │ │ │ └── ImportsAndReadsParserTest.kt │ │ ├── externalreader/ │ │ │ ├── ExternalModuleReader.kt │ │ │ ├── ExternalReaderBase.kt │ │ │ ├── ExternalReaderClient.kt │ │ │ ├── ExternalResourceReader.kt │ │ │ ├── MessagePackCodecTest.kt │ │ │ ├── TestExternalModuleReader.kt │ │ │ ├── TestExternalReaderProcess.kt │ │ │ └── TestExternalResourceReader.kt │ │ ├── http/ │ │ │ ├── DummyHttpClientTest.kt │ │ │ ├── HttpClientTest.kt │ │ │ ├── LazyHttpClientTest.kt │ │ │ ├── NoProxyRuleTest.kt │ │ │ ├── RequestCapturingClient.kt │ │ │ └── RequestRewritingClientTest.kt │ │ ├── messaging/ │ │ │ └── BaseMessagePackCodecTest.kt │ │ ├── module/ │ │ │ ├── ModuleKeyFactoriesTest.kt │ │ │ ├── ModuleKeysTest.kt │ │ │ ├── ModulePathResolverTest.kt │ │ │ ├── ResolvedModuleKeysTest.kt │ │ │ ├── ServiceProviderTest.kt │ │ │ └── TestModuleKeyFactory.kt │ │ ├── packages/ │ │ │ ├── DependencyMetadataTest.kt │ │ │ └── PackageResolversTest.kt │ │ ├── parser/ │ │ │ ├── MultiLineStringLiteralTest.kt │ │ │ ├── ShebangTest.kt │ │ │ └── TrailingCommasTest.kt │ │ ├── project/ │ │ │ ├── ProjectDependenciesResolverTest.kt │ │ │ ├── ProjectDepsTest.kt │ │ │ └── ProjectTest.kt │ │ ├── resource/ │ │ │ ├── ResourceReadersEvaluatorTest.kt │ │ │ ├── ResourceReadersTest.kt │ │ │ └── TestResourceReader.kt │ │ ├── runtime/ │ │ │ ├── CommandSpecParserTest.kt │ │ │ ├── DefaultModuleResolverTest.kt │ │ │ ├── FileSystemManagerTest.kt │ │ │ ├── IteratorsTest.kt │ │ │ ├── ModuleKeyTest.kt │ │ │ ├── StackTraceRendererTest.kt │ │ │ ├── VmClassTest.kt │ │ │ ├── VmDataSizeTest.kt │ │ │ ├── VmDurationTest.kt │ │ │ ├── VmSafeMathTest.kt │ │ │ ├── VmUtilsTest.kt │ │ │ └── VmValueRendererTest.kt │ │ ├── settings/ │ │ │ └── PklSettingsTest.kt │ │ ├── stdlib/ │ │ │ ├── PathConverterSupportTest.kt │ │ │ ├── PathSpecParserTest.kt │ │ │ ├── ReflectModuleTest.kt │ │ │ └── SimpleReportTest.kt │ │ ├── truffle/ │ │ │ └── LongVsDoubleSpecializationTest.kt │ │ └── util/ │ │ ├── AnsiStringBuilderTest.kt │ │ ├── ArrayCharEscaperTest.kt │ │ ├── ExceptionsTest.kt │ │ ├── GlobResolverTest.kt │ │ ├── HttpUtilsTest.kt │ │ ├── ImportGraphUtilsTest.kt │ │ └── IoUtilsTest.kt │ └── resources/ │ ├── META-INF/ │ │ └── services/ │ │ ├── org.junit.platform.engine.TestEngine │ │ ├── org.pkl.core.module.ModuleKeyFactory │ │ └── org.pkl.core.resource.ResourceReader │ └── org/ │ └── pkl/ │ └── core/ │ ├── EvaluateSchemaTest.pkl │ ├── EvaluateSchemaTestBaseModule.pkl │ ├── EvaluatorTest.pkl │ ├── PropertyList-1.0.dtd │ ├── brokenModule1.pkl │ ├── brokenModule2.pkl │ ├── module/ │ │ ├── NamedModuleResolversTest.pkl │ │ ├── test.jar │ │ └── testFactoryTest.pkl │ ├── pklBinaryDecoderTest.msgpack │ ├── project/ │ │ ├── badProjectChecksum/ │ │ │ └── PklProject │ │ ├── badProjectChecksum2/ │ │ │ ├── PklProject │ │ │ ├── PklProject.deps.json │ │ │ └── bug.pkl │ │ ├── project1/ │ │ │ └── PklProject │ │ ├── project2/ │ │ │ └── PklProject │ │ ├── project3/ │ │ │ └── PklProject │ │ ├── project4/ │ │ │ ├── PklProject │ │ │ ├── main.txt │ │ │ ├── module1.pkl │ │ │ └── module2.pkl │ │ ├── project5/ │ │ │ ├── PklProject │ │ │ ├── PklProject.deps.json │ │ │ └── main.pkl │ │ ├── project6/ │ │ │ ├── PklProject │ │ │ ├── PklProject.deps.json │ │ │ ├── globIntoDependency.pkl │ │ │ └── globWithinDependency.pkl │ │ ├── project7/ │ │ │ ├── PklProject │ │ │ ├── main.pkl │ │ │ ├── moduleA.pkl │ │ │ └── moduleB.pkl │ │ ├── projectCycle1/ │ │ │ └── PklProject │ │ ├── projectCycle2/ │ │ │ └── PklProject │ │ ├── projectCycle3/ │ │ │ └── PklProject │ │ └── projectCycle4/ │ │ └── PklProject │ ├── propertiesRendererTest.pkl │ ├── propertiesRendererTest.properties │ ├── rendererTest.json │ ├── rendererTest.pcf │ ├── rendererTest.pkl │ ├── rendererTest.plist │ ├── rendererTest.yaml │ ├── resource/ │ │ ├── resource.txt │ │ └── resource1.jar │ ├── snippets/ │ │ ├── imported.pkl │ │ └── name with [wierd]! chars~~.pkl │ └── stdlib/ │ └── protobufRendererTest.textproto ├── pkl-doc/ │ ├── gradle.lockfile │ ├── pkl-doc.gradle.kts │ └── src/ │ ├── main/ │ │ ├── kotlin/ │ │ │ └── org/ │ │ │ └── pkl/ │ │ │ └── doc/ │ │ │ ├── AbstractGenerator.kt │ │ │ ├── ClassPageGenerator.kt │ │ │ ├── CliDocGenerator.kt │ │ │ ├── CliDocGeneratorOptions.kt │ │ │ ├── Constants.kt │ │ │ ├── DocGenerator.kt │ │ │ ├── DocGeneratorException.kt │ │ │ ├── DocMigrator.kt │ │ │ ├── DocPackageInfo.kt │ │ │ ├── DocScope.kt │ │ │ ├── DocsiteInfo.kt │ │ │ ├── HtmlGenerator.kt │ │ │ ├── Main.kt │ │ │ ├── MainOrPackagePageGenerator.kt │ │ │ ├── MainPageGenerator.kt │ │ │ ├── Markdown.kt │ │ │ ├── ModuleOrClassPageGenerator.kt │ │ │ ├── ModulePageGenerator.kt │ │ │ ├── PackageDataGenerator.kt │ │ │ ├── PackagePageGenerator.kt │ │ │ ├── PageGenerator.kt │ │ │ ├── RuntimeData.kt │ │ │ ├── RuntimeDataGenerator.kt │ │ │ ├── SearchIndexGenerator.kt │ │ │ ├── Serializers.kt │ │ │ └── Util.kt │ │ └── resources/ │ │ └── org/ │ │ └── pkl/ │ │ └── doc/ │ │ ├── scripts/ │ │ │ ├── pkldoc.js │ │ │ └── search-worker.js │ │ └── styles/ │ │ └── pkldoc.css │ └── test/ │ ├── files/ │ │ ├── DocGeneratorTest/ │ │ │ ├── input/ │ │ │ │ ├── com.externalpackage/ │ │ │ │ │ ├── doc-package-info.pkl │ │ │ │ │ ├── external1.pkl │ │ │ │ │ └── external2.pkl │ │ │ │ ├── com.package1/ │ │ │ │ │ ├── Module Containing Spaces.pkl │ │ │ │ │ ├── baseModule.pkl │ │ │ │ │ ├── classAnnotations.pkl │ │ │ │ │ ├── classComments.pkl │ │ │ │ │ ├── classInheritance.pkl │ │ │ │ │ ├── classMethodComments.pkl │ │ │ │ │ ├── classMethodModifiers.pkl │ │ │ │ │ ├── classMethodTypeAnnotations.pkl │ │ │ │ │ ├── classMethodTypeReferences.pkl │ │ │ │ │ ├── classPropertyAnnotations.pkl │ │ │ │ │ ├── classPropertyComments.pkl │ │ │ │ │ ├── classPropertyModifiers.pkl │ │ │ │ │ ├── classPropertyTypeAnnotations.pkl │ │ │ │ │ ├── classPropertyTypeReferences.pkl │ │ │ │ │ ├── classTypeConstraints.pkl │ │ │ │ │ ├── doc-package-info.pkl │ │ │ │ │ ├── docExample.pkl │ │ │ │ │ ├── docExample2.pkl │ │ │ │ │ ├── docExampleSubject1.pkl │ │ │ │ │ ├── docExampleSubject2.pkl │ │ │ │ │ ├── docLinks.pkl │ │ │ │ │ ├── methodAnnotations.pkl │ │ │ │ │ ├── moduleComments.pkl │ │ │ │ │ ├── moduleExtend.pkl │ │ │ │ │ ├── moduleInfoAnnotation.pkl │ │ │ │ │ ├── moduleMethodCommentInheritance.pkl │ │ │ │ │ ├── moduleMethodComments.pkl │ │ │ │ │ ├── moduleMethodModifiers.pkl │ │ │ │ │ ├── moduleMethodTypeAnnotations.pkl │ │ │ │ │ ├── moduleMethodTypeReferences.pkl │ │ │ │ │ ├── modulePropertyAnnotations.pkl │ │ │ │ │ ├── modulePropertyCommentInheritance.pkl │ │ │ │ │ ├── modulePropertyComments.pkl │ │ │ │ │ ├── modulePropertyModifiers.pkl │ │ │ │ │ ├── modulePropertyTypeAnnotations.pkl │ │ │ │ │ ├── modulePropertyTypeReferences.pkl │ │ │ │ │ ├── moduleTypes1.pkl │ │ │ │ │ ├── moduleTypes2.pkl │ │ │ │ │ ├── nested/ │ │ │ │ │ │ └── nested2/ │ │ │ │ │ │ └── nestedModule.pkl │ │ │ │ │ ├── referenceToExternalPackage.pkl │ │ │ │ │ ├── shared.pkl │ │ │ │ │ ├── typeAliases.pkl │ │ │ │ │ ├── typeAliases2.pkl │ │ │ │ │ ├── typeAliasesInheritance.pkl │ │ │ │ │ ├── unionTypes.pkl │ │ │ │ │ ├── unlistedClass.pkl │ │ │ │ │ ├── unlistedMethod.pkl │ │ │ │ │ ├── unlistedModule.pkl │ │ │ │ │ └── unlistedProperty.pkl │ │ │ │ ├── com.package2/ │ │ │ │ │ ├── Module3.pkl │ │ │ │ │ └── doc-package-info.pkl │ │ │ │ └── docsite-info.pkl │ │ │ └── output/ │ │ │ ├── run-1/ │ │ │ │ ├── .pkldoc/ │ │ │ │ │ └── VERSION │ │ │ │ ├── com.package1/ │ │ │ │ │ └── 1.2.3/ │ │ │ │ │ ├── Module Containing Spaces/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── baseModule/ │ │ │ │ │ │ ├── BaseClass.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classAnnotations/ │ │ │ │ │ │ ├── AnnotatedClass.html │ │ │ │ │ │ ├── AnnotatedClss.html │ │ │ │ │ │ ├── AnnotatedClssWithExpandableComment.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classComments/ │ │ │ │ │ │ ├── Comments1.html │ │ │ │ │ │ ├── Comments2.html │ │ │ │ │ │ ├── Comments3.html │ │ │ │ │ │ ├── Comments4.html │ │ │ │ │ │ ├── Comments5.html │ │ │ │ │ │ ├── Comments6.html │ │ │ │ │ │ ├── Comments7.html │ │ │ │ │ │ ├── Comments8.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classInheritance/ │ │ │ │ │ │ ├── MyClass1.html │ │ │ │ │ │ ├── MyClass2.html │ │ │ │ │ │ ├── MyClass3.html │ │ │ │ │ │ ├── MyClass4.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classMethodComments/ │ │ │ │ │ │ ├── Comments.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classMethodModifiers/ │ │ │ │ │ │ ├── Modifiers.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classMethodTypeAnnotations/ │ │ │ │ │ │ ├── TypeAnnotations.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classMethodTypeReferences/ │ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ │ ├── TypeReferences.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classPropertyAnnotations/ │ │ │ │ │ │ ├── ClassWithAnnotatedProperty.html │ │ │ │ │ │ ├── UserDefinedAnnotation.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classPropertyComments/ │ │ │ │ │ │ ├── Comments.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classPropertyModifiers/ │ │ │ │ │ │ ├── Modifiers.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classPropertyTypeAnnotations/ │ │ │ │ │ │ ├── TypeAnnotations.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classPropertyTypeReferences/ │ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ │ ├── TypeReferences.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classTypeConstraints/ │ │ │ │ │ │ ├── Address.html │ │ │ │ │ │ ├── Person1.html │ │ │ │ │ │ ├── Person2.html │ │ │ │ │ │ ├── Project.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── docExampleSubject1/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── docExampleSubject2/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── docLinks/ │ │ │ │ │ │ ├── Person.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── methodAnnotations/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── moduleComments/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── moduleExtend/ │ │ │ │ │ │ ├── ExtendClass.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── moduleInfoAnnotation/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── moduleMethodCommentInheritance/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── moduleMethodComments/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── moduleMethodModifiers/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── moduleMethodTypeAnnotations/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── moduleMethodTypeReferences/ │ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── modulePropertyAnnotations/ │ │ │ │ │ │ ├── UserDefinedAnnotation.html │ │ │ │ │ │ ├── UserDefinedAnnotation1.html │ │ │ │ │ │ ├── UserDefinedAnnotation2.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── modulePropertyCommentInheritance/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── modulePropertyComments/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── modulePropertyModifiers/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── modulePropertyTypeAnnotations/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── modulePropertyTypeReferences/ │ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── moduleTypes1/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── moduleTypes2/ │ │ │ │ │ │ ├── Foo.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── nested/ │ │ │ │ │ │ └── nested2/ │ │ │ │ │ │ └── nestedModule/ │ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── package-data.json │ │ │ │ │ ├── search-index.js │ │ │ │ │ ├── shared/ │ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── ternalPackage/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── typeAliasInheritance/ │ │ │ │ │ │ ├── Person2.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── typealiases/ │ │ │ │ │ │ ├── Person.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── typealiases2/ │ │ │ │ │ │ ├── Foo.html │ │ │ │ │ │ ├── Person.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── unionTypes/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── unlistedClass/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── unlistedMethod/ │ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ │ └── index.html │ │ │ │ │ └── unlistedProperty/ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ └── index.html │ │ │ │ ├── com.package2/ │ │ │ │ │ └── 4.5.6/ │ │ │ │ │ ├── Module3/ │ │ │ │ │ │ ├── Class Two {}.html │ │ │ │ │ │ ├── Class3.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package-data.json │ │ │ │ │ └── search-index.js │ │ │ │ ├── data/ │ │ │ │ │ ├── com.package1/ │ │ │ │ │ │ ├── 1.2.3/ │ │ │ │ │ │ │ ├── Module Containing Spaces/ │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── baseModule/ │ │ │ │ │ │ │ │ ├── BaseClass.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── classAnnotations/ │ │ │ │ │ │ │ │ ├── AnnotatedClass.json │ │ │ │ │ │ │ │ ├── AnnotatedClss.json │ │ │ │ │ │ │ │ ├── AnnotatedClssWithExpandableComment.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── classComments/ │ │ │ │ │ │ │ │ ├── Comments1.json │ │ │ │ │ │ │ │ ├── Comments2.json │ │ │ │ │ │ │ │ ├── Comments3.json │ │ │ │ │ │ │ │ ├── Comments4.json │ │ │ │ │ │ │ │ ├── Comments5.json │ │ │ │ │ │ │ │ ├── Comments6.json │ │ │ │ │ │ │ │ ├── Comments7.json │ │ │ │ │ │ │ │ ├── Comments8.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── classInheritance/ │ │ │ │ │ │ │ │ ├── MyClass1.json │ │ │ │ │ │ │ │ ├── MyClass2.json │ │ │ │ │ │ │ │ ├── MyClass3.json │ │ │ │ │ │ │ │ ├── MyClass4.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── classMethodComments/ │ │ │ │ │ │ │ │ ├── Comments.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── classMethodModifiers/ │ │ │ │ │ │ │ │ ├── Modifiers.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── classMethodTypeAnnotations/ │ │ │ │ │ │ │ │ ├── TypeAnnotations.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── classMethodTypeReferences/ │ │ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ │ │ ├── TypeReferences.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── classPropertyAnnotations/ │ │ │ │ │ │ │ │ ├── ClassWithAnnotatedProperty.json │ │ │ │ │ │ │ │ ├── UserDefinedAnnotation.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── classPropertyComments/ │ │ │ │ │ │ │ │ ├── Comments.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── classPropertyModifiers/ │ │ │ │ │ │ │ │ ├── Modifiers.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── classPropertyTypeAnnotations/ │ │ │ │ │ │ │ │ ├── TypeAnnotations.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── classPropertyTypeReferences/ │ │ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ │ │ ├── TypeReferences.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── classTypeConstraints/ │ │ │ │ │ │ │ │ ├── Address.json │ │ │ │ │ │ │ │ ├── Person1.json │ │ │ │ │ │ │ │ ├── Person2.json │ │ │ │ │ │ │ │ ├── Project.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── docExampleSubject1/ │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── docExampleSubject2/ │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── docLinks/ │ │ │ │ │ │ │ │ ├── Person.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── index.json │ │ │ │ │ │ │ ├── methodAnnotations/ │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── moduleComments/ │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── moduleExtend/ │ │ │ │ │ │ │ │ ├── ExtendClass.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── moduleInfoAnnotation/ │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── moduleMethodCommentInheritance/ │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── moduleMethodComments/ │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── moduleMethodModifiers/ │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── moduleMethodTypeAnnotations/ │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── moduleMethodTypeReferences/ │ │ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── modulePropertyAnnotations/ │ │ │ │ │ │ │ │ ├── UserDefinedAnnotation.json │ │ │ │ │ │ │ │ ├── UserDefinedAnnotation1.json │ │ │ │ │ │ │ │ ├── UserDefinedAnnotation2.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── modulePropertyCommentInheritance/ │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── modulePropertyComments/ │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── modulePropertyModifiers/ │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── modulePropertyTypeAnnotations/ │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── modulePropertyTypeReferences/ │ │ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── moduleTypes1/ │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── moduleTypes2/ │ │ │ │ │ │ │ │ ├── Foo.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── nested/ │ │ │ │ │ │ │ │ └── nested2/ │ │ │ │ │ │ │ │ └── nestedModule/ │ │ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── shared/ │ │ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── ternalPackage/ │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── typeAliasInheritance/ │ │ │ │ │ │ │ │ ├── Person2.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── typealiases/ │ │ │ │ │ │ │ │ ├── Person.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── typealiases2/ │ │ │ │ │ │ │ │ ├── Foo.json │ │ │ │ │ │ │ │ ├── Person.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── unionTypes/ │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── unlistedClass/ │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── unlistedMethod/ │ │ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ └── unlistedProperty/ │ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ └── _/ │ │ │ │ │ │ ├── Module Containing Spaces/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── baseModule/ │ │ │ │ │ │ │ ├── BaseClass.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classAnnotations/ │ │ │ │ │ │ │ ├── AnnotatedClass.json │ │ │ │ │ │ │ ├── AnnotatedClss.json │ │ │ │ │ │ │ ├── AnnotatedClssWithExpandableComment.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classComments/ │ │ │ │ │ │ │ ├── Comments1.json │ │ │ │ │ │ │ ├── Comments2.json │ │ │ │ │ │ │ ├── Comments3.json │ │ │ │ │ │ │ ├── Comments4.json │ │ │ │ │ │ │ ├── Comments5.json │ │ │ │ │ │ │ ├── Comments6.json │ │ │ │ │ │ │ ├── Comments7.json │ │ │ │ │ │ │ ├── Comments8.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classInheritance/ │ │ │ │ │ │ │ ├── MyClass1.json │ │ │ │ │ │ │ ├── MyClass2.json │ │ │ │ │ │ │ ├── MyClass3.json │ │ │ │ │ │ │ ├── MyClass4.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classMethodComments/ │ │ │ │ │ │ │ ├── Comments.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classMethodModifiers/ │ │ │ │ │ │ │ ├── Modifiers.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classMethodTypeAnnotations/ │ │ │ │ │ │ │ ├── TypeAnnotations.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classMethodTypeReferences/ │ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ │ ├── TypeReferences.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classPropertyAnnotations/ │ │ │ │ │ │ │ ├── ClassWithAnnotatedProperty.json │ │ │ │ │ │ │ ├── UserDefinedAnnotation.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classPropertyComments/ │ │ │ │ │ │ │ ├── Comments.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classPropertyModifiers/ │ │ │ │ │ │ │ ├── Modifiers.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classPropertyTypeAnnotations/ │ │ │ │ │ │ │ ├── TypeAnnotations.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classPropertyTypeReferences/ │ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ │ ├── TypeReferences.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classTypeConstraints/ │ │ │ │ │ │ │ ├── Address.json │ │ │ │ │ │ │ ├── Person1.json │ │ │ │ │ │ │ ├── Person2.json │ │ │ │ │ │ │ ├── Project.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── docExampleSubject1/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── docExampleSubject2/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── docLinks/ │ │ │ │ │ │ │ ├── Person.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── index.json │ │ │ │ │ │ ├── methodAnnotations/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── moduleComments/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── moduleExtend/ │ │ │ │ │ │ │ ├── ExtendClass.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── moduleInfoAnnotation/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── moduleMethodCommentInheritance/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── moduleMethodComments/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── moduleMethodModifiers/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── moduleMethodTypeAnnotations/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── moduleMethodTypeReferences/ │ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── modulePropertyAnnotations/ │ │ │ │ │ │ │ ├── UserDefinedAnnotation.json │ │ │ │ │ │ │ ├── UserDefinedAnnotation1.json │ │ │ │ │ │ │ ├── UserDefinedAnnotation2.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── modulePropertyCommentInheritance/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── modulePropertyComments/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── modulePropertyModifiers/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── modulePropertyTypeAnnotations/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── modulePropertyTypeReferences/ │ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── moduleTypes1/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── moduleTypes2/ │ │ │ │ │ │ │ ├── Foo.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── nested/ │ │ │ │ │ │ │ └── nested2/ │ │ │ │ │ │ │ └── nestedModule/ │ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── shared/ │ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── ternalPackage/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── typeAliasInheritance/ │ │ │ │ │ │ │ ├── Person2.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── typealiases/ │ │ │ │ │ │ │ ├── Person.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── typealiases2/ │ │ │ │ │ │ │ ├── Foo.json │ │ │ │ │ │ │ ├── Person.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── unionTypes/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── unlistedClass/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── unlistedMethod/ │ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ └── unlistedProperty/ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── com.package2/ │ │ │ │ │ │ ├── 4.5.6/ │ │ │ │ │ │ │ ├── Module3/ │ │ │ │ │ │ │ │ ├── Class Two {}.json │ │ │ │ │ │ │ │ ├── Class3.json │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ └── _/ │ │ │ │ │ │ ├── Module3/ │ │ │ │ │ │ │ ├── Class Two {}.json │ │ │ │ │ │ │ ├── Class3.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ └── index.json │ │ │ │ │ └── localhost(3a)0/ │ │ │ │ │ ├── birds/ │ │ │ │ │ │ ├── 0.5.0/ │ │ │ │ │ │ │ ├── Bird/ │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── allFruit/ │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ ├── catalog/ │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ └── _/ │ │ │ │ │ │ ├── Bird/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── allFruit/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── catalog/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── deprecated/ │ │ │ │ │ │ ├── 1.0.0/ │ │ │ │ │ │ │ ├── deprecated/ │ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ └── _/ │ │ │ │ │ │ ├── deprecated/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ └── index.json │ │ │ │ │ └── fruit/ │ │ │ │ │ ├── 1.1.0/ │ │ │ │ │ │ ├── Fruit/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ └── index.json │ │ │ │ │ └── _/ │ │ │ │ │ ├── Fruit/ │ │ │ │ │ │ └── index.json │ │ │ │ │ └── index.json │ │ │ │ ├── index.html │ │ │ │ ├── localhost(3a)0/ │ │ │ │ │ ├── birds/ │ │ │ │ │ │ └── 0.5.0/ │ │ │ │ │ │ ├── Bird/ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ ├── allFruit/ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ ├── catalog/ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── package-data.json │ │ │ │ │ │ └── search-index.js │ │ │ │ │ ├── deprecated/ │ │ │ │ │ │ └── 1.0.0/ │ │ │ │ │ │ ├── deprecated/ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── package-data.json │ │ │ │ │ │ └── search-index.js │ │ │ │ │ └── fruit/ │ │ │ │ │ └── 1.1.0/ │ │ │ │ │ ├── Fruit/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package-data.json │ │ │ │ │ └── search-index.js │ │ │ │ ├── scripts/ │ │ │ │ │ ├── pkldoc.js │ │ │ │ │ └── search-worker.js │ │ │ │ ├── search-index.js │ │ │ │ └── styles/ │ │ │ │ └── pkldoc.css │ │ │ └── run-2/ │ │ │ ├── .pkldoc/ │ │ │ │ └── VERSION │ │ │ ├── com.package1/ │ │ │ │ └── 1.2.3/ │ │ │ │ ├── Module Containing Spaces/ │ │ │ │ │ └── index.html │ │ │ │ ├── baseModule/ │ │ │ │ │ ├── BaseClass.html │ │ │ │ │ └── index.html │ │ │ │ ├── classAnnotations/ │ │ │ │ │ ├── AnnotatedClass.html │ │ │ │ │ ├── AnnotatedClss.html │ │ │ │ │ ├── AnnotatedClssWithExpandableComment.html │ │ │ │ │ └── index.html │ │ │ │ ├── classComments/ │ │ │ │ │ ├── Comments1.html │ │ │ │ │ ├── Comments2.html │ │ │ │ │ ├── Comments3.html │ │ │ │ │ ├── Comments4.html │ │ │ │ │ ├── Comments5.html │ │ │ │ │ ├── Comments6.html │ │ │ │ │ ├── Comments7.html │ │ │ │ │ ├── Comments8.html │ │ │ │ │ └── index.html │ │ │ │ ├── classInheritance/ │ │ │ │ │ ├── MyClass1.html │ │ │ │ │ ├── MyClass2.html │ │ │ │ │ ├── MyClass3.html │ │ │ │ │ ├── MyClass4.html │ │ │ │ │ └── index.html │ │ │ │ ├── classMethodComments/ │ │ │ │ │ ├── Comments.html │ │ │ │ │ └── index.html │ │ │ │ ├── classMethodModifiers/ │ │ │ │ │ ├── Modifiers.html │ │ │ │ │ └── index.html │ │ │ │ ├── classMethodTypeAnnotations/ │ │ │ │ │ ├── TypeAnnotations.html │ │ │ │ │ └── index.html │ │ │ │ ├── classMethodTypeReferences/ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ ├── TypeReferences.html │ │ │ │ │ └── index.html │ │ │ │ ├── classPropertyAnnotations/ │ │ │ │ │ ├── ClassWithAnnotatedProperty.html │ │ │ │ │ ├── UserDefinedAnnotation.html │ │ │ │ │ └── index.html │ │ │ │ ├── classPropertyComments/ │ │ │ │ │ ├── Comments.html │ │ │ │ │ └── index.html │ │ │ │ ├── classPropertyModifiers/ │ │ │ │ │ ├── Modifiers.html │ │ │ │ │ └── index.html │ │ │ │ ├── classPropertyTypeAnnotations/ │ │ │ │ │ ├── TypeAnnotations.html │ │ │ │ │ └── index.html │ │ │ │ ├── classPropertyTypeReferences/ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ ├── TypeReferences.html │ │ │ │ │ └── index.html │ │ │ │ ├── classTypeConstraints/ │ │ │ │ │ ├── Address.html │ │ │ │ │ ├── Person1.html │ │ │ │ │ ├── Person2.html │ │ │ │ │ ├── Project.html │ │ │ │ │ └── index.html │ │ │ │ ├── docExampleSubject1/ │ │ │ │ │ └── index.html │ │ │ │ ├── docExampleSubject2/ │ │ │ │ │ └── index.html │ │ │ │ ├── docLinks/ │ │ │ │ │ ├── Person.html │ │ │ │ │ └── index.html │ │ │ │ ├── index.html │ │ │ │ ├── methodAnnotations/ │ │ │ │ │ └── index.html │ │ │ │ ├── moduleComments/ │ │ │ │ │ └── index.html │ │ │ │ ├── moduleExtend/ │ │ │ │ │ ├── ExtendClass.html │ │ │ │ │ └── index.html │ │ │ │ ├── moduleInfoAnnotation/ │ │ │ │ │ └── index.html │ │ │ │ ├── moduleMethodCommentInheritance/ │ │ │ │ │ └── index.html │ │ │ │ ├── moduleMethodComments/ │ │ │ │ │ └── index.html │ │ │ │ ├── moduleMethodModifiers/ │ │ │ │ │ └── index.html │ │ │ │ ├── moduleMethodTypeAnnotations/ │ │ │ │ │ └── index.html │ │ │ │ ├── moduleMethodTypeReferences/ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ └── index.html │ │ │ │ ├── modulePropertyAnnotations/ │ │ │ │ │ ├── UserDefinedAnnotation.html │ │ │ │ │ ├── UserDefinedAnnotation1.html │ │ │ │ │ ├── UserDefinedAnnotation2.html │ │ │ │ │ └── index.html │ │ │ │ ├── modulePropertyCommentInheritance/ │ │ │ │ │ └── index.html │ │ │ │ ├── modulePropertyComments/ │ │ │ │ │ └── index.html │ │ │ │ ├── modulePropertyModifiers/ │ │ │ │ │ └── index.html │ │ │ │ ├── modulePropertyTypeAnnotations/ │ │ │ │ │ └── index.html │ │ │ │ ├── modulePropertyTypeReferences/ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ └── index.html │ │ │ │ ├── moduleTypes1/ │ │ │ │ │ └── index.html │ │ │ │ ├── moduleTypes2/ │ │ │ │ │ ├── Foo.html │ │ │ │ │ └── index.html │ │ │ │ ├── nested/ │ │ │ │ │ └── nested2/ │ │ │ │ │ └── nestedModule/ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ └── index.html │ │ │ │ ├── package-data.json │ │ │ │ ├── search-index.js │ │ │ │ ├── shared/ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ └── index.html │ │ │ │ ├── ternalPackage/ │ │ │ │ │ └── index.html │ │ │ │ ├── typeAliasInheritance/ │ │ │ │ │ ├── Person2.html │ │ │ │ │ └── index.html │ │ │ │ ├── typealiases/ │ │ │ │ │ ├── Person.html │ │ │ │ │ └── index.html │ │ │ │ ├── typealiases2/ │ │ │ │ │ ├── Foo.html │ │ │ │ │ ├── Person.html │ │ │ │ │ └── index.html │ │ │ │ ├── unionTypes/ │ │ │ │ │ └── index.html │ │ │ │ ├── unlistedClass/ │ │ │ │ │ └── index.html │ │ │ │ ├── unlistedMethod/ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ └── index.html │ │ │ │ └── unlistedProperty/ │ │ │ │ ├── MyClass.html │ │ │ │ └── index.html │ │ │ ├── com.package2/ │ │ │ │ └── 4.5.6/ │ │ │ │ ├── Module3/ │ │ │ │ │ ├── Class Two {}.html │ │ │ │ │ ├── Class3.html │ │ │ │ │ └── index.html │ │ │ │ ├── index.html │ │ │ │ ├── package-data.json │ │ │ │ └── search-index.js │ │ │ ├── data/ │ │ │ │ ├── com.package1/ │ │ │ │ │ ├── 1.2.3/ │ │ │ │ │ │ ├── Module Containing Spaces/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── baseModule/ │ │ │ │ │ │ │ ├── BaseClass.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classAnnotations/ │ │ │ │ │ │ │ ├── AnnotatedClass.json │ │ │ │ │ │ │ ├── AnnotatedClss.json │ │ │ │ │ │ │ ├── AnnotatedClssWithExpandableComment.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classComments/ │ │ │ │ │ │ │ ├── Comments1.json │ │ │ │ │ │ │ ├── Comments2.json │ │ │ │ │ │ │ ├── Comments3.json │ │ │ │ │ │ │ ├── Comments4.json │ │ │ │ │ │ │ ├── Comments5.json │ │ │ │ │ │ │ ├── Comments6.json │ │ │ │ │ │ │ ├── Comments7.json │ │ │ │ │ │ │ ├── Comments8.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classInheritance/ │ │ │ │ │ │ │ ├── MyClass1.json │ │ │ │ │ │ │ ├── MyClass2.json │ │ │ │ │ │ │ ├── MyClass3.json │ │ │ │ │ │ │ ├── MyClass4.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classMethodComments/ │ │ │ │ │ │ │ ├── Comments.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classMethodModifiers/ │ │ │ │ │ │ │ ├── Modifiers.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classMethodTypeAnnotations/ │ │ │ │ │ │ │ ├── TypeAnnotations.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classMethodTypeReferences/ │ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ │ ├── TypeReferences.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classPropertyAnnotations/ │ │ │ │ │ │ │ ├── ClassWithAnnotatedProperty.json │ │ │ │ │ │ │ ├── UserDefinedAnnotation.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classPropertyComments/ │ │ │ │ │ │ │ ├── Comments.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classPropertyModifiers/ │ │ │ │ │ │ │ ├── Modifiers.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classPropertyTypeAnnotations/ │ │ │ │ │ │ │ ├── TypeAnnotations.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classPropertyTypeReferences/ │ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ │ ├── TypeReferences.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── classTypeConstraints/ │ │ │ │ │ │ │ ├── Address.json │ │ │ │ │ │ │ ├── Person1.json │ │ │ │ │ │ │ ├── Person2.json │ │ │ │ │ │ │ ├── Project.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── docExampleSubject1/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── docExampleSubject2/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── docLinks/ │ │ │ │ │ │ │ ├── Person.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── index.json │ │ │ │ │ │ ├── methodAnnotations/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── moduleComments/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── moduleExtend/ │ │ │ │ │ │ │ ├── ExtendClass.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── moduleInfoAnnotation/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── moduleMethodCommentInheritance/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── moduleMethodComments/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── moduleMethodModifiers/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── moduleMethodTypeAnnotations/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── moduleMethodTypeReferences/ │ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── modulePropertyAnnotations/ │ │ │ │ │ │ │ ├── UserDefinedAnnotation.json │ │ │ │ │ │ │ ├── UserDefinedAnnotation1.json │ │ │ │ │ │ │ ├── UserDefinedAnnotation2.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── modulePropertyCommentInheritance/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── modulePropertyComments/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── modulePropertyModifiers/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── modulePropertyTypeAnnotations/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── modulePropertyTypeReferences/ │ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── moduleTypes1/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── moduleTypes2/ │ │ │ │ │ │ │ ├── Foo.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── nested/ │ │ │ │ │ │ │ └── nested2/ │ │ │ │ │ │ │ └── nestedModule/ │ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── shared/ │ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── ternalPackage/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── typeAliasInheritance/ │ │ │ │ │ │ │ ├── Person2.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── typealiases/ │ │ │ │ │ │ │ ├── Person.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── typealiases2/ │ │ │ │ │ │ │ ├── Foo.json │ │ │ │ │ │ │ ├── Person.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── unionTypes/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── unlistedClass/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── unlistedMethod/ │ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ └── unlistedProperty/ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ └── index.json │ │ │ │ │ └── _/ │ │ │ │ │ ├── Module Containing Spaces/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── baseModule/ │ │ │ │ │ │ ├── BaseClass.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classAnnotations/ │ │ │ │ │ │ ├── AnnotatedClass.json │ │ │ │ │ │ ├── AnnotatedClss.json │ │ │ │ │ │ ├── AnnotatedClssWithExpandableComment.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classComments/ │ │ │ │ │ │ ├── Comments1.json │ │ │ │ │ │ ├── Comments2.json │ │ │ │ │ │ ├── Comments3.json │ │ │ │ │ │ ├── Comments4.json │ │ │ │ │ │ ├── Comments5.json │ │ │ │ │ │ ├── Comments6.json │ │ │ │ │ │ ├── Comments7.json │ │ │ │ │ │ ├── Comments8.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classInheritance/ │ │ │ │ │ │ ├── MyClass1.json │ │ │ │ │ │ ├── MyClass2.json │ │ │ │ │ │ ├── MyClass3.json │ │ │ │ │ │ ├── MyClass4.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classMethodComments/ │ │ │ │ │ │ ├── Comments.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classMethodModifiers/ │ │ │ │ │ │ ├── Modifiers.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classMethodTypeAnnotations/ │ │ │ │ │ │ ├── TypeAnnotations.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classMethodTypeReferences/ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ ├── TypeReferences.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classPropertyAnnotations/ │ │ │ │ │ │ ├── ClassWithAnnotatedProperty.json │ │ │ │ │ │ ├── UserDefinedAnnotation.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classPropertyComments/ │ │ │ │ │ │ ├── Comments.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classPropertyModifiers/ │ │ │ │ │ │ ├── Modifiers.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classPropertyTypeAnnotations/ │ │ │ │ │ │ ├── TypeAnnotations.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classPropertyTypeReferences/ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ ├── TypeReferences.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classTypeConstraints/ │ │ │ │ │ │ ├── Address.json │ │ │ │ │ │ ├── Person1.json │ │ │ │ │ │ ├── Person2.json │ │ │ │ │ │ ├── Project.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── docExampleSubject1/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── docExampleSubject2/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── docLinks/ │ │ │ │ │ │ ├── Person.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── index.json │ │ │ │ │ ├── methodAnnotations/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── moduleComments/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── moduleExtend/ │ │ │ │ │ │ ├── ExtendClass.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── moduleInfoAnnotation/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── moduleMethodCommentInheritance/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── moduleMethodComments/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── moduleMethodModifiers/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── moduleMethodTypeAnnotations/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── moduleMethodTypeReferences/ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── modulePropertyAnnotations/ │ │ │ │ │ │ ├── UserDefinedAnnotation.json │ │ │ │ │ │ ├── UserDefinedAnnotation1.json │ │ │ │ │ │ ├── UserDefinedAnnotation2.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── modulePropertyCommentInheritance/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── modulePropertyComments/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── modulePropertyModifiers/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── modulePropertyTypeAnnotations/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── modulePropertyTypeReferences/ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── moduleTypes1/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── moduleTypes2/ │ │ │ │ │ │ ├── Foo.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── nested/ │ │ │ │ │ │ └── nested2/ │ │ │ │ │ │ └── nestedModule/ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── shared/ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── ternalPackage/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── typeAliasInheritance/ │ │ │ │ │ │ ├── Person2.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── typealiases/ │ │ │ │ │ │ ├── Person.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── typealiases2/ │ │ │ │ │ │ ├── Foo.json │ │ │ │ │ │ ├── Person.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── unionTypes/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── unlistedClass/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── unlistedMethod/ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ └── index.json │ │ │ │ │ └── unlistedProperty/ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ └── index.json │ │ │ │ ├── com.package2/ │ │ │ │ │ ├── 4.5.6/ │ │ │ │ │ │ ├── Module3/ │ │ │ │ │ │ │ ├── Class Two {}.json │ │ │ │ │ │ │ ├── Class3.json │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ └── index.json │ │ │ │ │ └── _/ │ │ │ │ │ ├── Module3/ │ │ │ │ │ │ ├── Class Two {}.json │ │ │ │ │ │ ├── Class3.json │ │ │ │ │ │ └── index.json │ │ │ │ │ └── index.json │ │ │ │ └── localhost(3a)0/ │ │ │ │ ├── birds/ │ │ │ │ │ ├── 0.5.0/ │ │ │ │ │ │ ├── Bird/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── allFruit/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── catalog/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── 0.6.0/ │ │ │ │ │ │ ├── Bird/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── allFruit/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── catalog/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── 0.7.0/ │ │ │ │ │ │ ├── Bird/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── allFruit/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ ├── catalog/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ └── index.json │ │ │ │ │ └── _/ │ │ │ │ │ ├── Bird/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── allFruit/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── catalog/ │ │ │ │ │ │ └── index.json │ │ │ │ │ └── index.json │ │ │ │ ├── deprecated/ │ │ │ │ │ ├── 1.0.0/ │ │ │ │ │ │ ├── deprecated/ │ │ │ │ │ │ │ └── index.json │ │ │ │ │ │ └── index.json │ │ │ │ │ └── _/ │ │ │ │ │ ├── deprecated/ │ │ │ │ │ │ └── index.json │ │ │ │ │ └── index.json │ │ │ │ └── fruit/ │ │ │ │ ├── 1.1.0/ │ │ │ │ │ ├── Fruit/ │ │ │ │ │ │ └── index.json │ │ │ │ │ └── index.json │ │ │ │ └── _/ │ │ │ │ ├── Fruit/ │ │ │ │ │ └── index.json │ │ │ │ └── index.json │ │ │ ├── index.html │ │ │ ├── localhost(3a)0/ │ │ │ │ ├── birds/ │ │ │ │ │ ├── 0.5.0/ │ │ │ │ │ │ ├── Bird/ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ ├── allFruit/ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ ├── catalog/ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── package-data.json │ │ │ │ │ │ └── search-index.js │ │ │ │ │ ├── 0.6.0/ │ │ │ │ │ │ ├── Bird/ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ ├── allFruit/ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ ├── catalog/ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── package-data.json │ │ │ │ │ │ └── search-index.js │ │ │ │ │ └── 0.7.0/ │ │ │ │ │ ├── Bird/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── allFruit/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── catalog/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package-data.json │ │ │ │ │ └── search-index.js │ │ │ │ ├── deprecated/ │ │ │ │ │ └── 1.0.0/ │ │ │ │ │ ├── deprecated/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package-data.json │ │ │ │ │ └── search-index.js │ │ │ │ └── fruit/ │ │ │ │ └── 1.1.0/ │ │ │ │ ├── Fruit/ │ │ │ │ │ └── index.html │ │ │ │ ├── index.html │ │ │ │ ├── package-data.json │ │ │ │ └── search-index.js │ │ │ ├── scripts/ │ │ │ │ ├── pkldoc.js │ │ │ │ └── search-worker.js │ │ │ ├── search-index.js │ │ │ └── styles/ │ │ │ └── pkldoc.css │ │ └── DocMigratorTest/ │ │ ├── input/ │ │ │ └── version-1/ │ │ │ ├── com.package1/ │ │ │ │ ├── 1.2.3/ │ │ │ │ │ ├── Module Containing Spaces/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── baseModule/ │ │ │ │ │ │ ├── BaseClass.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classAnnotations/ │ │ │ │ │ │ ├── AnnotatedClass.html │ │ │ │ │ │ ├── AnnotatedClss.html │ │ │ │ │ │ ├── AnnotatedClssWithExpandableComment.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classComments/ │ │ │ │ │ │ ├── Comments1.html │ │ │ │ │ │ ├── Comments2.html │ │ │ │ │ │ ├── Comments3.html │ │ │ │ │ │ ├── Comments4.html │ │ │ │ │ │ ├── Comments5.html │ │ │ │ │ │ ├── Comments6.html │ │ │ │ │ │ ├── Comments7.html │ │ │ │ │ │ ├── Comments8.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classInheritance/ │ │ │ │ │ │ ├── MyClass1.html │ │ │ │ │ │ ├── MyClass2.html │ │ │ │ │ │ ├── MyClass3.html │ │ │ │ │ │ ├── MyClass4.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classMethodComments/ │ │ │ │ │ │ ├── Comments.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classMethodModifiers/ │ │ │ │ │ │ ├── Modifiers.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classMethodTypeAnnotations/ │ │ │ │ │ │ ├── TypeAnnotations.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classMethodTypeReferences/ │ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ │ ├── TypeReferences.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classPropertyAnnotations/ │ │ │ │ │ │ ├── ClassWithAnnotatedProperty.html │ │ │ │ │ │ ├── UserDefinedAnnotation.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classPropertyComments/ │ │ │ │ │ │ ├── Comments.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classPropertyModifiers/ │ │ │ │ │ │ ├── Modifiers.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classPropertyTypeAnnotations/ │ │ │ │ │ │ ├── TypeAnnotations.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classPropertyTypeReferences/ │ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ │ ├── TypeReferences.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── classTypeConstraints/ │ │ │ │ │ │ ├── Address.html │ │ │ │ │ │ ├── Person1.html │ │ │ │ │ │ ├── Person2.html │ │ │ │ │ │ ├── Project.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── docExampleSubject1/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── docExampleSubject2/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── docLinks/ │ │ │ │ │ │ ├── Person.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── methodAnnotations/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── moduleComments/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── moduleExtend/ │ │ │ │ │ │ ├── ExtendClass.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── moduleInfoAnnotation/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── moduleMethodCommentInheritance/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── moduleMethodComments/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── moduleMethodModifiers/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── moduleMethodTypeAnnotations/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── moduleMethodTypeReferences/ │ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── modulePropertyAnnotations/ │ │ │ │ │ │ ├── UserDefinedAnnotation.html │ │ │ │ │ │ ├── UserDefinedAnnotation1.html │ │ │ │ │ │ ├── UserDefinedAnnotation2.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── modulePropertyCommentInheritance/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── modulePropertyComments/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── modulePropertyModifiers/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── modulePropertyTypeAnnotations/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── modulePropertyTypeReferences/ │ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── moduleTypes1/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── moduleTypes2/ │ │ │ │ │ │ ├── Foo.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── nested/ │ │ │ │ │ │ └── nested2/ │ │ │ │ │ │ └── nestedModule/ │ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── package-data.json │ │ │ │ │ ├── search-index.js │ │ │ │ │ ├── shared/ │ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── ternalPackage/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── typeAliasInheritance/ │ │ │ │ │ │ ├── Person2.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── typealiases/ │ │ │ │ │ │ ├── Person.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── typealiases2/ │ │ │ │ │ │ ├── Foo.html │ │ │ │ │ │ ├── Person.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── unionTypes/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── unlistedClass/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── unlistedMethod/ │ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ │ └── index.html │ │ │ │ │ └── unlistedProperty/ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ └── index.html │ │ │ │ └── current/ │ │ │ │ ├── Module Containing Spaces/ │ │ │ │ │ └── index.html │ │ │ │ ├── baseModule/ │ │ │ │ │ ├── BaseClass.html │ │ │ │ │ └── index.html │ │ │ │ ├── classAnnotations/ │ │ │ │ │ ├── AnnotatedClass.html │ │ │ │ │ ├── AnnotatedClss.html │ │ │ │ │ ├── AnnotatedClssWithExpandableComment.html │ │ │ │ │ └── index.html │ │ │ │ ├── classComments/ │ │ │ │ │ ├── Comments1.html │ │ │ │ │ ├── Comments2.html │ │ │ │ │ ├── Comments3.html │ │ │ │ │ ├── Comments4.html │ │ │ │ │ ├── Comments5.html │ │ │ │ │ ├── Comments6.html │ │ │ │ │ ├── Comments7.html │ │ │ │ │ ├── Comments8.html │ │ │ │ │ └── index.html │ │ │ │ ├── classInheritance/ │ │ │ │ │ ├── MyClass1.html │ │ │ │ │ ├── MyClass2.html │ │ │ │ │ ├── MyClass3.html │ │ │ │ │ ├── MyClass4.html │ │ │ │ │ └── index.html │ │ │ │ ├── classMethodComments/ │ │ │ │ │ ├── Comments.html │ │ │ │ │ └── index.html │ │ │ │ ├── classMethodModifiers/ │ │ │ │ │ ├── Modifiers.html │ │ │ │ │ └── index.html │ │ │ │ ├── classMethodTypeAnnotations/ │ │ │ │ │ ├── TypeAnnotations.html │ │ │ │ │ └── index.html │ │ │ │ ├── classMethodTypeReferences/ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ ├── TypeReferences.html │ │ │ │ │ └── index.html │ │ │ │ ├── classPropertyAnnotations/ │ │ │ │ │ ├── ClassWithAnnotatedProperty.html │ │ │ │ │ ├── UserDefinedAnnotation.html │ │ │ │ │ └── index.html │ │ │ │ ├── classPropertyComments/ │ │ │ │ │ ├── Comments.html │ │ │ │ │ └── index.html │ │ │ │ ├── classPropertyModifiers/ │ │ │ │ │ ├── Modifiers.html │ │ │ │ │ └── index.html │ │ │ │ ├── classPropertyTypeAnnotations/ │ │ │ │ │ ├── TypeAnnotations.html │ │ │ │ │ └── index.html │ │ │ │ ├── classPropertyTypeReferences/ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ ├── TypeReferences.html │ │ │ │ │ └── index.html │ │ │ │ ├── classTypeConstraints/ │ │ │ │ │ ├── Address.html │ │ │ │ │ ├── Person1.html │ │ │ │ │ ├── Person2.html │ │ │ │ │ ├── Project.html │ │ │ │ │ └── index.html │ │ │ │ ├── docExampleSubject1/ │ │ │ │ │ └── index.html │ │ │ │ ├── docExampleSubject2/ │ │ │ │ │ └── index.html │ │ │ │ ├── docLinks/ │ │ │ │ │ ├── Person.html │ │ │ │ │ └── index.html │ │ │ │ ├── index.html │ │ │ │ ├── methodAnnotations/ │ │ │ │ │ └── index.html │ │ │ │ ├── moduleComments/ │ │ │ │ │ └── index.html │ │ │ │ ├── moduleExtend/ │ │ │ │ │ ├── ExtendClass.html │ │ │ │ │ └── index.html │ │ │ │ ├── moduleInfoAnnotation/ │ │ │ │ │ └── index.html │ │ │ │ ├── moduleMethodCommentInheritance/ │ │ │ │ │ └── index.html │ │ │ │ ├── moduleMethodComments/ │ │ │ │ │ └── index.html │ │ │ │ ├── moduleMethodModifiers/ │ │ │ │ │ └── index.html │ │ │ │ ├── moduleMethodTypeAnnotations/ │ │ │ │ │ └── index.html │ │ │ │ ├── moduleMethodTypeReferences/ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ └── index.html │ │ │ │ ├── modulePropertyAnnotations/ │ │ │ │ │ ├── UserDefinedAnnotation.html │ │ │ │ │ ├── UserDefinedAnnotation1.html │ │ │ │ │ ├── UserDefinedAnnotation2.html │ │ │ │ │ └── index.html │ │ │ │ ├── modulePropertyCommentInheritance/ │ │ │ │ │ └── index.html │ │ │ │ ├── modulePropertyComments/ │ │ │ │ │ └── index.html │ │ │ │ ├── modulePropertyModifiers/ │ │ │ │ │ └── index.html │ │ │ │ ├── modulePropertyTypeAnnotations/ │ │ │ │ │ └── index.html │ │ │ │ ├── modulePropertyTypeReferences/ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ └── index.html │ │ │ │ ├── moduleTypes1/ │ │ │ │ │ └── index.html │ │ │ │ ├── moduleTypes2/ │ │ │ │ │ ├── Foo.html │ │ │ │ │ └── index.html │ │ │ │ ├── nested/ │ │ │ │ │ └── nested2/ │ │ │ │ │ └── nestedModule/ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ └── index.html │ │ │ │ ├── package-data.json │ │ │ │ ├── search-index.js │ │ │ │ ├── shared/ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ └── index.html │ │ │ │ ├── ternalPackage/ │ │ │ │ │ └── index.html │ │ │ │ ├── typeAliasInheritance/ │ │ │ │ │ ├── Person2.html │ │ │ │ │ └── index.html │ │ │ │ ├── typealiases/ │ │ │ │ │ ├── Person.html │ │ │ │ │ └── index.html │ │ │ │ ├── typealiases2/ │ │ │ │ │ ├── Foo.html │ │ │ │ │ ├── Person.html │ │ │ │ │ └── index.html │ │ │ │ ├── unionTypes/ │ │ │ │ │ └── index.html │ │ │ │ ├── unlistedClass/ │ │ │ │ │ └── index.html │ │ │ │ ├── unlistedMethod/ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ └── index.html │ │ │ │ └── unlistedProperty/ │ │ │ │ ├── MyClass.html │ │ │ │ └── index.html │ │ │ ├── com.package2/ │ │ │ │ ├── 4.5.6/ │ │ │ │ │ ├── Module3/ │ │ │ │ │ │ ├── Class Two {}.html │ │ │ │ │ │ ├── Class3.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package-data.json │ │ │ │ │ └── search-index.js │ │ │ │ └── current/ │ │ │ │ ├── Module3/ │ │ │ │ │ ├── Class Two {}.html │ │ │ │ │ ├── Class3.html │ │ │ │ │ └── index.html │ │ │ │ ├── index.html │ │ │ │ ├── package-data.json │ │ │ │ └── search-index.js │ │ │ ├── data/ │ │ │ │ ├── com.package1/ │ │ │ │ │ └── 1.2.3/ │ │ │ │ │ ├── Module Containing Spaces/ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── baseModule/ │ │ │ │ │ │ ├── BaseClass.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── classAnnotations/ │ │ │ │ │ │ ├── AnnotatedClass.js │ │ │ │ │ │ ├── AnnotatedClss.js │ │ │ │ │ │ ├── AnnotatedClssWithExpandableComment.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── classComments/ │ │ │ │ │ │ ├── Comments1.js │ │ │ │ │ │ ├── Comments2.js │ │ │ │ │ │ ├── Comments3.js │ │ │ │ │ │ ├── Comments4.js │ │ │ │ │ │ ├── Comments5.js │ │ │ │ │ │ ├── Comments6.js │ │ │ │ │ │ ├── Comments7.js │ │ │ │ │ │ ├── Comments8.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── classInheritance/ │ │ │ │ │ │ ├── MyClass1.js │ │ │ │ │ │ ├── MyClass2.js │ │ │ │ │ │ ├── MyClass3.js │ │ │ │ │ │ ├── MyClass4.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── classMethodComments/ │ │ │ │ │ │ ├── Comments.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── classMethodModifiers/ │ │ │ │ │ │ ├── Modifiers.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── classMethodTypeAnnotations/ │ │ │ │ │ │ ├── TypeAnnotations.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── classMethodTypeReferences/ │ │ │ │ │ │ ├── MyClass.js │ │ │ │ │ │ ├── TypeReferences.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── classPropertyAnnotations/ │ │ │ │ │ │ ├── ClassWithAnnotatedProperty.js │ │ │ │ │ │ ├── UserDefinedAnnotation.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── classPropertyComments/ │ │ │ │ │ │ ├── Comments.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── classPropertyModifiers/ │ │ │ │ │ │ ├── Modifiers.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── classPropertyTypeAnnotations/ │ │ │ │ │ │ ├── TypeAnnotations.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── classPropertyTypeReferences/ │ │ │ │ │ │ ├── MyClass.js │ │ │ │ │ │ ├── TypeReferences.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── classTypeConstraints/ │ │ │ │ │ │ ├── Address.js │ │ │ │ │ │ ├── Person1.js │ │ │ │ │ │ ├── Person2.js │ │ │ │ │ │ ├── Project.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── docExampleSubject1/ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── docExampleSubject2/ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── docLinks/ │ │ │ │ │ │ ├── Person.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── index.js │ │ │ │ │ ├── methodAnnotations/ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── moduleComments/ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── moduleExtend/ │ │ │ │ │ │ ├── ExtendClass.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── moduleInfoAnnotation/ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── moduleMethodCommentInheritance/ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── moduleMethodComments/ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── moduleMethodModifiers/ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── moduleMethodTypeAnnotations/ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── moduleMethodTypeReferences/ │ │ │ │ │ │ ├── MyClass.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── modulePropertyAnnotations/ │ │ │ │ │ │ ├── UserDefinedAnnotation.js │ │ │ │ │ │ ├── UserDefinedAnnotation1.js │ │ │ │ │ │ ├── UserDefinedAnnotation2.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── modulePropertyCommentInheritance/ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── modulePropertyComments/ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── modulePropertyModifiers/ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── modulePropertyTypeAnnotations/ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── modulePropertyTypeReferences/ │ │ │ │ │ │ ├── MyClass.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── moduleTypes1/ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── moduleTypes2/ │ │ │ │ │ │ ├── Foo.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── nested/ │ │ │ │ │ │ └── nested2/ │ │ │ │ │ │ └── nestedModule/ │ │ │ │ │ │ ├── MyClass.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── shared/ │ │ │ │ │ │ ├── MyClass.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── ternalPackage/ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── typeAliasInheritance/ │ │ │ │ │ │ ├── Person2.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── typealiases/ │ │ │ │ │ │ ├── Person.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── typealiases2/ │ │ │ │ │ │ ├── Foo.js │ │ │ │ │ │ ├── Person.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── unionTypes/ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── unlistedClass/ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── unlistedMethod/ │ │ │ │ │ │ ├── MyClass.js │ │ │ │ │ │ └── index.js │ │ │ │ │ └── unlistedProperty/ │ │ │ │ │ ├── MyClass.js │ │ │ │ │ └── index.js │ │ │ │ ├── com.package2/ │ │ │ │ │ └── 4.5.6/ │ │ │ │ │ ├── Module3/ │ │ │ │ │ │ ├── Class Two {}.js │ │ │ │ │ │ ├── Class3.js │ │ │ │ │ │ └── index.js │ │ │ │ │ └── index.js │ │ │ │ └── localhost(3a)0/ │ │ │ │ ├── birds/ │ │ │ │ │ └── 0.5.0/ │ │ │ │ │ ├── Bird/ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── allFruit/ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── catalog/ │ │ │ │ │ │ └── index.js │ │ │ │ │ └── index.js │ │ │ │ ├── deprecated/ │ │ │ │ │ └── 1.0.0/ │ │ │ │ │ ├── deprecated/ │ │ │ │ │ │ └── index.js │ │ │ │ │ └── index.js │ │ │ │ └── fruit/ │ │ │ │ └── 1.1.0/ │ │ │ │ ├── Fruit/ │ │ │ │ │ └── index.js │ │ │ │ └── index.js │ │ │ ├── index.html │ │ │ ├── localhost(3a)0/ │ │ │ │ ├── birds/ │ │ │ │ │ ├── 0.5.0/ │ │ │ │ │ │ ├── Bird/ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ ├── allFruit/ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ ├── catalog/ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── package-data.json │ │ │ │ │ │ └── search-index.js │ │ │ │ │ └── current/ │ │ │ │ │ ├── Bird/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── allFruit/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── catalog/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package-data.json │ │ │ │ │ └── search-index.js │ │ │ │ ├── deprecated/ │ │ │ │ │ ├── 1.0.0/ │ │ │ │ │ │ ├── deprecated/ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── package-data.json │ │ │ │ │ │ └── search-index.js │ │ │ │ │ └── current/ │ │ │ │ │ ├── deprecated/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package-data.json │ │ │ │ │ └── search-index.js │ │ │ │ └── fruit/ │ │ │ │ ├── 1.1.0/ │ │ │ │ │ ├── Fruit/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package-data.json │ │ │ │ │ └── search-index.js │ │ │ │ └── current/ │ │ │ │ ├── Fruit/ │ │ │ │ │ └── index.html │ │ │ │ ├── index.html │ │ │ │ ├── package-data.json │ │ │ │ └── search-index.js │ │ │ ├── scripts/ │ │ │ │ ├── pkldoc.js │ │ │ │ └── search-worker.js │ │ │ ├── search-index.js │ │ │ └── styles/ │ │ │ └── pkldoc.css │ │ └── output/ │ │ ├── .pkldoc/ │ │ │ └── VERSION │ │ ├── com.package1/ │ │ │ ├── 1.2.3/ │ │ │ │ ├── Module Containing Spaces/ │ │ │ │ │ └── index.html │ │ │ │ ├── baseModule/ │ │ │ │ │ ├── BaseClass.html │ │ │ │ │ └── index.html │ │ │ │ ├── classAnnotations/ │ │ │ │ │ ├── AnnotatedClass.html │ │ │ │ │ ├── AnnotatedClss.html │ │ │ │ │ ├── AnnotatedClssWithExpandableComment.html │ │ │ │ │ └── index.html │ │ │ │ ├── classComments/ │ │ │ │ │ ├── Comments1.html │ │ │ │ │ ├── Comments2.html │ │ │ │ │ ├── Comments3.html │ │ │ │ │ ├── Comments4.html │ │ │ │ │ ├── Comments5.html │ │ │ │ │ ├── Comments6.html │ │ │ │ │ ├── Comments7.html │ │ │ │ │ ├── Comments8.html │ │ │ │ │ └── index.html │ │ │ │ ├── classInheritance/ │ │ │ │ │ ├── MyClass1.html │ │ │ │ │ ├── MyClass2.html │ │ │ │ │ ├── MyClass3.html │ │ │ │ │ ├── MyClass4.html │ │ │ │ │ └── index.html │ │ │ │ ├── classMethodComments/ │ │ │ │ │ ├── Comments.html │ │ │ │ │ └── index.html │ │ │ │ ├── classMethodModifiers/ │ │ │ │ │ ├── Modifiers.html │ │ │ │ │ └── index.html │ │ │ │ ├── classMethodTypeAnnotations/ │ │ │ │ │ ├── TypeAnnotations.html │ │ │ │ │ └── index.html │ │ │ │ ├── classMethodTypeReferences/ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ ├── TypeReferences.html │ │ │ │ │ └── index.html │ │ │ │ ├── classPropertyAnnotations/ │ │ │ │ │ ├── ClassWithAnnotatedProperty.html │ │ │ │ │ ├── UserDefinedAnnotation.html │ │ │ │ │ └── index.html │ │ │ │ ├── classPropertyComments/ │ │ │ │ │ ├── Comments.html │ │ │ │ │ └── index.html │ │ │ │ ├── classPropertyModifiers/ │ │ │ │ │ ├── Modifiers.html │ │ │ │ │ └── index.html │ │ │ │ ├── classPropertyTypeAnnotations/ │ │ │ │ │ ├── TypeAnnotations.html │ │ │ │ │ └── index.html │ │ │ │ ├── classPropertyTypeReferences/ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ ├── TypeReferences.html │ │ │ │ │ └── index.html │ │ │ │ ├── classTypeConstraints/ │ │ │ │ │ ├── Address.html │ │ │ │ │ ├── Person1.html │ │ │ │ │ ├── Person2.html │ │ │ │ │ ├── Project.html │ │ │ │ │ └── index.html │ │ │ │ ├── docExampleSubject1/ │ │ │ │ │ └── index.html │ │ │ │ ├── docExampleSubject2/ │ │ │ │ │ └── index.html │ │ │ │ ├── docLinks/ │ │ │ │ │ ├── Person.html │ │ │ │ │ └── index.html │ │ │ │ ├── index.html │ │ │ │ ├── methodAnnotations/ │ │ │ │ │ └── index.html │ │ │ │ ├── moduleComments/ │ │ │ │ │ └── index.html │ │ │ │ ├── moduleExtend/ │ │ │ │ │ ├── ExtendClass.html │ │ │ │ │ └── index.html │ │ │ │ ├── moduleInfoAnnotation/ │ │ │ │ │ └── index.html │ │ │ │ ├── moduleMethodCommentInheritance/ │ │ │ │ │ └── index.html │ │ │ │ ├── moduleMethodComments/ │ │ │ │ │ └── index.html │ │ │ │ ├── moduleMethodModifiers/ │ │ │ │ │ └── index.html │ │ │ │ ├── moduleMethodTypeAnnotations/ │ │ │ │ │ └── index.html │ │ │ │ ├── moduleMethodTypeReferences/ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ └── index.html │ │ │ │ ├── modulePropertyAnnotations/ │ │ │ │ │ ├── UserDefinedAnnotation.html │ │ │ │ │ ├── UserDefinedAnnotation1.html │ │ │ │ │ ├── UserDefinedAnnotation2.html │ │ │ │ │ └── index.html │ │ │ │ ├── modulePropertyCommentInheritance/ │ │ │ │ │ └── index.html │ │ │ │ ├── modulePropertyComments/ │ │ │ │ │ └── index.html │ │ │ │ ├── modulePropertyModifiers/ │ │ │ │ │ └── index.html │ │ │ │ ├── modulePropertyTypeAnnotations/ │ │ │ │ │ └── index.html │ │ │ │ ├── modulePropertyTypeReferences/ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ └── index.html │ │ │ │ ├── moduleTypes1/ │ │ │ │ │ └── index.html │ │ │ │ ├── moduleTypes2/ │ │ │ │ │ ├── Foo.html │ │ │ │ │ └── index.html │ │ │ │ ├── nested/ │ │ │ │ │ └── nested2/ │ │ │ │ │ └── nestedModule/ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ └── index.html │ │ │ │ ├── package-data.json │ │ │ │ ├── search-index.js │ │ │ │ ├── shared/ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ └── index.html │ │ │ │ ├── ternalPackage/ │ │ │ │ │ └── index.html │ │ │ │ ├── typeAliasInheritance/ │ │ │ │ │ ├── Person2.html │ │ │ │ │ └── index.html │ │ │ │ ├── typealiases/ │ │ │ │ │ ├── Person.html │ │ │ │ │ └── index.html │ │ │ │ ├── typealiases2/ │ │ │ │ │ ├── Foo.html │ │ │ │ │ ├── Person.html │ │ │ │ │ └── index.html │ │ │ │ ├── unionTypes/ │ │ │ │ │ └── index.html │ │ │ │ ├── unlistedClass/ │ │ │ │ │ └── index.html │ │ │ │ ├── unlistedMethod/ │ │ │ │ │ ├── MyClass.html │ │ │ │ │ └── index.html │ │ │ │ └── unlistedProperty/ │ │ │ │ ├── MyClass.html │ │ │ │ └── index.html │ │ │ └── current/ │ │ │ ├── Module Containing Spaces/ │ │ │ │ └── index.html │ │ │ ├── baseModule/ │ │ │ │ ├── BaseClass.html │ │ │ │ └── index.html │ │ │ ├── classAnnotations/ │ │ │ │ ├── AnnotatedClass.html │ │ │ │ ├── AnnotatedClss.html │ │ │ │ ├── AnnotatedClssWithExpandableComment.html │ │ │ │ └── index.html │ │ │ ├── classComments/ │ │ │ │ ├── Comments1.html │ │ │ │ ├── Comments2.html │ │ │ │ ├── Comments3.html │ │ │ │ ├── Comments4.html │ │ │ │ ├── Comments5.html │ │ │ │ ├── Comments6.html │ │ │ │ ├── Comments7.html │ │ │ │ ├── Comments8.html │ │ │ │ └── index.html │ │ │ ├── classInheritance/ │ │ │ │ ├── MyClass1.html │ │ │ │ ├── MyClass2.html │ │ │ │ ├── MyClass3.html │ │ │ │ ├── MyClass4.html │ │ │ │ └── index.html │ │ │ ├── classMethodComments/ │ │ │ │ ├── Comments.html │ │ │ │ └── index.html │ │ │ ├── classMethodModifiers/ │ │ │ │ ├── Modifiers.html │ │ │ │ └── index.html │ │ │ ├── classMethodTypeAnnotations/ │ │ │ │ ├── TypeAnnotations.html │ │ │ │ └── index.html │ │ │ ├── classMethodTypeReferences/ │ │ │ │ ├── MyClass.html │ │ │ │ ├── TypeReferences.html │ │ │ │ └── index.html │ │ │ ├── classPropertyAnnotations/ │ │ │ │ ├── ClassWithAnnotatedProperty.html │ │ │ │ ├── UserDefinedAnnotation.html │ │ │ │ └── index.html │ │ │ ├── classPropertyComments/ │ │ │ │ ├── Comments.html │ │ │ │ └── index.html │ │ │ ├── classPropertyModifiers/ │ │ │ │ ├── Modifiers.html │ │ │ │ └── index.html │ │ │ ├── classPropertyTypeAnnotations/ │ │ │ │ ├── TypeAnnotations.html │ │ │ │ └── index.html │ │ │ ├── classPropertyTypeReferences/ │ │ │ │ ├── MyClass.html │ │ │ │ ├── TypeReferences.html │ │ │ │ └── index.html │ │ │ ├── classTypeConstraints/ │ │ │ │ ├── Address.html │ │ │ │ ├── Person1.html │ │ │ │ ├── Person2.html │ │ │ │ ├── Project.html │ │ │ │ └── index.html │ │ │ ├── docExampleSubject1/ │ │ │ │ └── index.html │ │ │ ├── docExampleSubject2/ │ │ │ │ └── index.html │ │ │ ├── docLinks/ │ │ │ │ ├── Person.html │ │ │ │ └── index.html │ │ │ ├── index.html │ │ │ ├── methodAnnotations/ │ │ │ │ └── index.html │ │ │ ├── moduleComments/ │ │ │ │ └── index.html │ │ │ ├── moduleExtend/ │ │ │ │ ├── ExtendClass.html │ │ │ │ └── index.html │ │ │ ├── moduleInfoAnnotation/ │ │ │ │ └── index.html │ │ │ ├── moduleMethodCommentInheritance/ │ │ │ │ └── index.html │ │ │ ├── moduleMethodComments/ │ │ │ │ └── index.html │ │ │ ├── moduleMethodModifiers/ │ │ │ │ └── index.html │ │ │ ├── moduleMethodTypeAnnotations/ │ │ │ │ └── index.html │ │ │ ├── moduleMethodTypeReferences/ │ │ │ │ ├── MyClass.html │ │ │ │ └── index.html │ │ │ ├── modulePropertyAnnotations/ │ │ │ │ ├── UserDefinedAnnotation.html │ │ │ │ ├── UserDefinedAnnotation1.html │ │ │ │ ├── UserDefinedAnnotation2.html │ │ │ │ └── index.html │ │ │ ├── modulePropertyCommentInheritance/ │ │ │ │ └── index.html │ │ │ ├── modulePropertyComments/ │ │ │ │ └── index.html │ │ │ ├── modulePropertyModifiers/ │ │ │ │ └── index.html │ │ │ ├── modulePropertyTypeAnnotations/ │ │ │ │ └── index.html │ │ │ ├── modulePropertyTypeReferences/ │ │ │ │ ├── MyClass.html │ │ │ │ └── index.html │ │ │ ├── moduleTypes1/ │ │ │ │ └── index.html │ │ │ ├── moduleTypes2/ │ │ │ │ ├── Foo.html │ │ │ │ └── index.html │ │ │ ├── nested/ │ │ │ │ └── nested2/ │ │ │ │ └── nestedModule/ │ │ │ │ ├── MyClass.html │ │ │ │ └── index.html │ │ │ ├── package-data.json │ │ │ ├── search-index.js │ │ │ ├── shared/ │ │ │ │ ├── MyClass.html │ │ │ │ └── index.html │ │ │ ├── ternalPackage/ │ │ │ │ └── index.html │ │ │ ├── typeAliasInheritance/ │ │ │ │ ├── Person2.html │ │ │ │ └── index.html │ │ │ ├── typealiases/ │ │ │ │ ├── Person.html │ │ │ │ └── index.html │ │ │ ├── typealiases2/ │ │ │ │ ├── Foo.html │ │ │ │ ├── Person.html │ │ │ │ └── index.html │ │ │ ├── unionTypes/ │ │ │ │ └── index.html │ │ │ ├── unlistedClass/ │ │ │ │ └── index.html │ │ │ ├── unlistedMethod/ │ │ │ │ ├── MyClass.html │ │ │ │ └── index.html │ │ │ └── unlistedProperty/ │ │ │ ├── MyClass.html │ │ │ └── index.html │ │ ├── com.package2/ │ │ │ ├── 4.5.6/ │ │ │ │ ├── Module3/ │ │ │ │ │ ├── Class Two {}.html │ │ │ │ │ ├── Class3.html │ │ │ │ │ └── index.html │ │ │ │ ├── index.html │ │ │ │ ├── package-data.json │ │ │ │ └── search-index.js │ │ │ └── current/ │ │ │ ├── Module3/ │ │ │ │ ├── Class Two {}.html │ │ │ │ ├── Class3.html │ │ │ │ └── index.html │ │ │ ├── index.html │ │ │ ├── package-data.json │ │ │ └── search-index.js │ │ ├── data/ │ │ │ ├── com.package1/ │ │ │ │ ├── 1.2.3/ │ │ │ │ │ ├── Module Containing Spaces/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── baseModule/ │ │ │ │ │ │ ├── BaseClass.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classAnnotations/ │ │ │ │ │ │ ├── AnnotatedClass.json │ │ │ │ │ │ ├── AnnotatedClss.json │ │ │ │ │ │ ├── AnnotatedClssWithExpandableComment.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classComments/ │ │ │ │ │ │ ├── Comments1.json │ │ │ │ │ │ ├── Comments2.json │ │ │ │ │ │ ├── Comments3.json │ │ │ │ │ │ ├── Comments4.json │ │ │ │ │ │ ├── Comments5.json │ │ │ │ │ │ ├── Comments6.json │ │ │ │ │ │ ├── Comments7.json │ │ │ │ │ │ ├── Comments8.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classInheritance/ │ │ │ │ │ │ ├── MyClass1.json │ │ │ │ │ │ ├── MyClass2.json │ │ │ │ │ │ ├── MyClass3.json │ │ │ │ │ │ ├── MyClass4.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classMethodComments/ │ │ │ │ │ │ ├── Comments.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classMethodModifiers/ │ │ │ │ │ │ ├── Modifiers.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classMethodTypeAnnotations/ │ │ │ │ │ │ ├── TypeAnnotations.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classMethodTypeReferences/ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ ├── TypeReferences.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classPropertyAnnotations/ │ │ │ │ │ │ ├── ClassWithAnnotatedProperty.json │ │ │ │ │ │ ├── UserDefinedAnnotation.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classPropertyComments/ │ │ │ │ │ │ ├── Comments.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classPropertyModifiers/ │ │ │ │ │ │ ├── Modifiers.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classPropertyTypeAnnotations/ │ │ │ │ │ │ ├── TypeAnnotations.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classPropertyTypeReferences/ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ ├── TypeReferences.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── classTypeConstraints/ │ │ │ │ │ │ ├── Address.json │ │ │ │ │ │ ├── Person1.json │ │ │ │ │ │ ├── Person2.json │ │ │ │ │ │ ├── Project.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── docExampleSubject1/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── docExampleSubject2/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── docLinks/ │ │ │ │ │ │ ├── Person.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── index.json │ │ │ │ │ ├── methodAnnotations/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── moduleComments/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── moduleExtend/ │ │ │ │ │ │ ├── ExtendClass.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── moduleInfoAnnotation/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── moduleMethodCommentInheritance/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── moduleMethodComments/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── moduleMethodModifiers/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── moduleMethodTypeAnnotations/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── moduleMethodTypeReferences/ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── modulePropertyAnnotations/ │ │ │ │ │ │ ├── UserDefinedAnnotation.json │ │ │ │ │ │ ├── UserDefinedAnnotation1.json │ │ │ │ │ │ ├── UserDefinedAnnotation2.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── modulePropertyCommentInheritance/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── modulePropertyComments/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── modulePropertyModifiers/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── modulePropertyTypeAnnotations/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── modulePropertyTypeReferences/ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── moduleTypes1/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── moduleTypes2/ │ │ │ │ │ │ ├── Foo.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── nested/ │ │ │ │ │ │ └── nested2/ │ │ │ │ │ │ └── nestedModule/ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── shared/ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── ternalPackage/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── typeAliasInheritance/ │ │ │ │ │ │ ├── Person2.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── typealiases/ │ │ │ │ │ │ ├── Person.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── typealiases2/ │ │ │ │ │ │ ├── Foo.json │ │ │ │ │ │ ├── Person.json │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── unionTypes/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── unlistedClass/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── unlistedMethod/ │ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ │ └── index.json │ │ │ │ │ └── unlistedProperty/ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ └── index.json │ │ │ │ └── _/ │ │ │ │ ├── Module Containing Spaces/ │ │ │ │ │ └── index.json │ │ │ │ ├── baseModule/ │ │ │ │ │ ├── BaseClass.json │ │ │ │ │ └── index.json │ │ │ │ ├── classAnnotations/ │ │ │ │ │ ├── AnnotatedClass.json │ │ │ │ │ ├── AnnotatedClss.json │ │ │ │ │ ├── AnnotatedClssWithExpandableComment.json │ │ │ │ │ └── index.json │ │ │ │ ├── classComments/ │ │ │ │ │ ├── Comments1.json │ │ │ │ │ ├── Comments2.json │ │ │ │ │ ├── Comments3.json │ │ │ │ │ ├── Comments4.json │ │ │ │ │ ├── Comments5.json │ │ │ │ │ ├── Comments6.json │ │ │ │ │ ├── Comments7.json │ │ │ │ │ ├── Comments8.json │ │ │ │ │ └── index.json │ │ │ │ ├── classInheritance/ │ │ │ │ │ ├── MyClass1.json │ │ │ │ │ ├── MyClass2.json │ │ │ │ │ ├── MyClass3.json │ │ │ │ │ ├── MyClass4.json │ │ │ │ │ └── index.json │ │ │ │ ├── classMethodComments/ │ │ │ │ │ ├── Comments.json │ │ │ │ │ └── index.json │ │ │ │ ├── classMethodModifiers/ │ │ │ │ │ ├── Modifiers.json │ │ │ │ │ └── index.json │ │ │ │ ├── classMethodTypeAnnotations/ │ │ │ │ │ ├── TypeAnnotations.json │ │ │ │ │ └── index.json │ │ │ │ ├── classMethodTypeReferences/ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ ├── TypeReferences.json │ │ │ │ │ └── index.json │ │ │ │ ├── classPropertyAnnotations/ │ │ │ │ │ ├── ClassWithAnnotatedProperty.json │ │ │ │ │ ├── UserDefinedAnnotation.json │ │ │ │ │ └── index.json │ │ │ │ ├── classPropertyComments/ │ │ │ │ │ ├── Comments.json │ │ │ │ │ └── index.json │ │ │ │ ├── classPropertyModifiers/ │ │ │ │ │ ├── Modifiers.json │ │ │ │ │ └── index.json │ │ │ │ ├── classPropertyTypeAnnotations/ │ │ │ │ │ ├── TypeAnnotations.json │ │ │ │ │ └── index.json │ │ │ │ ├── classPropertyTypeReferences/ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ ├── TypeReferences.json │ │ │ │ │ └── index.json │ │ │ │ ├── classTypeConstraints/ │ │ │ │ │ ├── Address.json │ │ │ │ │ ├── Person1.json │ │ │ │ │ ├── Person2.json │ │ │ │ │ ├── Project.json │ │ │ │ │ └── index.json │ │ │ │ ├── docExampleSubject1/ │ │ │ │ │ └── index.json │ │ │ │ ├── docExampleSubject2/ │ │ │ │ │ └── index.json │ │ │ │ ├── docLinks/ │ │ │ │ │ ├── Person.json │ │ │ │ │ └── index.json │ │ │ │ ├── index.json │ │ │ │ ├── methodAnnotations/ │ │ │ │ │ └── index.json │ │ │ │ ├── moduleComments/ │ │ │ │ │ └── index.json │ │ │ │ ├── moduleExtend/ │ │ │ │ │ ├── ExtendClass.json │ │ │ │ │ └── index.json │ │ │ │ ├── moduleInfoAnnotation/ │ │ │ │ │ └── index.json │ │ │ │ ├── moduleMethodCommentInheritance/ │ │ │ │ │ └── index.json │ │ │ │ ├── moduleMethodComments/ │ │ │ │ │ └── index.json │ │ │ │ ├── moduleMethodModifiers/ │ │ │ │ │ └── index.json │ │ │ │ ├── moduleMethodTypeAnnotations/ │ │ │ │ │ └── index.json │ │ │ │ ├── moduleMethodTypeReferences/ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ └── index.json │ │ │ │ ├── modulePropertyAnnotations/ │ │ │ │ │ ├── UserDefinedAnnotation.json │ │ │ │ │ ├── UserDefinedAnnotation1.json │ │ │ │ │ ├── UserDefinedAnnotation2.json │ │ │ │ │ └── index.json │ │ │ │ ├── modulePropertyCommentInheritance/ │ │ │ │ │ └── index.json │ │ │ │ ├── modulePropertyComments/ │ │ │ │ │ └── index.json │ │ │ │ ├── modulePropertyModifiers/ │ │ │ │ │ └── index.json │ │ │ │ ├── modulePropertyTypeAnnotations/ │ │ │ │ │ └── index.json │ │ │ │ ├── modulePropertyTypeReferences/ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ └── index.json │ │ │ │ ├── moduleTypes1/ │ │ │ │ │ └── index.json │ │ │ │ ├── moduleTypes2/ │ │ │ │ │ ├── Foo.json │ │ │ │ │ └── index.json │ │ │ │ ├── nested/ │ │ │ │ │ └── nested2/ │ │ │ │ │ └── nestedModule/ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ └── index.json │ │ │ │ ├── shared/ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ └── index.json │ │ │ │ ├── ternalPackage/ │ │ │ │ │ └── index.json │ │ │ │ ├── typeAliasInheritance/ │ │ │ │ │ ├── Person2.json │ │ │ │ │ └── index.json │ │ │ │ ├── typealiases/ │ │ │ │ │ ├── Person.json │ │ │ │ │ └── index.json │ │ │ │ ├── typealiases2/ │ │ │ │ │ ├── Foo.json │ │ │ │ │ ├── Person.json │ │ │ │ │ └── index.json │ │ │ │ ├── unionTypes/ │ │ │ │ │ └── index.json │ │ │ │ ├── unlistedClass/ │ │ │ │ │ └── index.json │ │ │ │ ├── unlistedMethod/ │ │ │ │ │ ├── MyClass.json │ │ │ │ │ └── index.json │ │ │ │ └── unlistedProperty/ │ │ │ │ ├── MyClass.json │ │ │ │ └── index.json │ │ │ ├── com.package2/ │ │ │ │ ├── 4.5.6/ │ │ │ │ │ ├── Module3/ │ │ │ │ │ │ ├── Class Two {}.json │ │ │ │ │ │ ├── Class3.json │ │ │ │ │ │ └── index.json │ │ │ │ │ └── index.json │ │ │ │ └── _/ │ │ │ │ ├── Module3/ │ │ │ │ │ ├── Class Two {}.json │ │ │ │ │ ├── Class3.json │ │ │ │ │ └── index.json │ │ │ │ └── index.json │ │ │ └── localhost(3a)0/ │ │ │ ├── birds/ │ │ │ │ ├── 0.5.0/ │ │ │ │ │ ├── Bird/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── allFruit/ │ │ │ │ │ │ └── index.json │ │ │ │ │ ├── catalog/ │ │ │ │ │ │ └── index.json │ │ │ │ │ └── index.json │ │ │ │ └── _/ │ │ │ │ ├── Bird/ │ │ │ │ │ └── index.json │ │ │ │ ├── allFruit/ │ │ │ │ │ └── index.json │ │ │ │ ├── catalog/ │ │ │ │ │ └── index.json │ │ │ │ └── index.json │ │ │ ├── deprecated/ │ │ │ │ ├── 1.0.0/ │ │ │ │ │ ├── deprecated/ │ │ │ │ │ │ └── index.json │ │ │ │ │ └── index.json │ │ │ │ └── _/ │ │ │ │ ├── deprecated/ │ │ │ │ │ └── index.json │ │ │ │ └── index.json │ │ │ └── fruit/ │ │ │ ├── 1.1.0/ │ │ │ │ ├── Fruit/ │ │ │ │ │ └── index.json │ │ │ │ └── index.json │ │ │ └── _/ │ │ │ ├── Fruit/ │ │ │ │ └── index.json │ │ │ └── index.json │ │ ├── index.html │ │ ├── localhost(3a)0/ │ │ │ ├── birds/ │ │ │ │ ├── 0.5.0/ │ │ │ │ │ ├── Bird/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── allFruit/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── catalog/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package-data.json │ │ │ │ │ └── search-index.js │ │ │ │ └── current/ │ │ │ │ ├── Bird/ │ │ │ │ │ └── index.html │ │ │ │ ├── allFruit/ │ │ │ │ │ └── index.html │ │ │ │ ├── catalog/ │ │ │ │ │ └── index.html │ │ │ │ ├── index.html │ │ │ │ ├── package-data.json │ │ │ │ └── search-index.js │ │ │ ├── deprecated/ │ │ │ │ ├── 1.0.0/ │ │ │ │ │ ├── deprecated/ │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package-data.json │ │ │ │ │ └── search-index.js │ │ │ │ └── current/ │ │ │ │ ├── deprecated/ │ │ │ │ │ └── index.html │ │ │ │ ├── index.html │ │ │ │ ├── package-data.json │ │ │ │ └── search-index.js │ │ │ └── fruit/ │ │ │ ├── 1.1.0/ │ │ │ │ ├── Fruit/ │ │ │ │ │ └── index.html │ │ │ │ ├── index.html │ │ │ │ ├── package-data.json │ │ │ │ └── search-index.js │ │ │ └── current/ │ │ │ ├── Fruit/ │ │ │ │ └── index.html │ │ │ ├── index.html │ │ │ ├── package-data.json │ │ │ └── search-index.js │ │ ├── scripts/ │ │ │ ├── pkldoc.js │ │ │ └── search-worker.js │ │ ├── search-index.js │ │ └── styles/ │ │ └── pkldoc.css │ └── kotlin/ │ └── org/ │ └── pkl/ │ └── doc/ │ ├── CliDocGeneratorTest.kt │ ├── CliMainTest.kt │ ├── DocGeneratorTest.kt │ ├── DocGeneratorTestHelper.kt │ ├── DocMigratorTest.kt │ ├── DocScopeTest.kt │ ├── DocTestUtils.kt │ ├── JavaExecutableTest.kt │ ├── NativeExecutableTest.kt │ ├── RuntimeDataTest.kt │ └── SearchTest.kt ├── pkl-executor/ │ ├── gradle.lockfile │ ├── pkl-executor.gradle.kts │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── pkl/ │ │ └── executor/ │ │ ├── EmbeddedExecutor.java │ │ ├── Executor.java │ │ ├── ExecutorException.java │ │ ├── ExecutorOptions.java │ │ ├── Executors.java │ │ ├── Version.java │ │ └── spi/ │ │ ├── package-info.java │ │ └── v1/ │ │ ├── ExecutorSpi.java │ │ ├── ExecutorSpiException.java │ │ ├── ExecutorSpiOptions.java │ │ ├── ExecutorSpiOptions2.java │ │ └── ExecutorSpiOptions3.java │ └── test/ │ └── kotlin/ │ └── org/ │ └── pkl/ │ └── executor/ │ ├── EmbeddedExecutorTest.kt │ └── VersionTest.kt ├── pkl-formatter/ │ ├── gradle.lockfile │ ├── pkl-formatter.gradle.kts │ └── src/ │ ├── main/ │ │ └── kotlin/ │ │ └── org/ │ │ └── pkl/ │ │ └── formatter/ │ │ ├── Builder.kt │ │ ├── Formatter.kt │ │ ├── Generator.kt │ │ ├── NaturalOrderComparator.kt │ │ └── ast/ │ │ └── FormatNode.kt │ └── test/ │ ├── files/ │ │ └── FormatterSnippetTests/ │ │ ├── input/ │ │ │ ├── class-bodies.pkl │ │ │ ├── comma-termination.pkl │ │ │ ├── comment-interleaved.pkl │ │ │ ├── dangling-doc-comment.pkl │ │ │ ├── doc-comments.pkl │ │ │ ├── expr-binary.pkl │ │ │ ├── expr-chain-grouping.pkl │ │ │ ├── expr-chain.pkl │ │ │ ├── expr-if.pkl │ │ │ ├── expr-let.pkl │ │ │ ├── imports.pkl │ │ │ ├── indentation.pkl │ │ │ ├── line-breaks.pkl │ │ │ ├── line-breaks2.pkl │ │ │ ├── line-breaks3.pkl │ │ │ ├── line-width.pkl │ │ │ ├── map-function.pkl │ │ │ ├── method-call-trailing-lambdas.pkl │ │ │ ├── modifiers.pkl │ │ │ ├── module-definitions.pkl │ │ │ ├── multi-line-strings.pkl │ │ │ ├── object-members.pkl │ │ │ ├── prefixes.pkl │ │ │ ├── single-line-strings.pkl │ │ │ ├── spaces.pkl │ │ │ ├── string-interpolation.pkl │ │ │ ├── type-aliases.pkl │ │ │ └── when.pkl │ │ └── output/ │ │ ├── class-bodies.pkl │ │ ├── comma-termination.pkl │ │ ├── comment-interleaved.pkl │ │ ├── dangling-doc-comment.pkl │ │ ├── doc-comments.pkl │ │ ├── expr-binary.pkl │ │ ├── expr-chain-grouping.pkl │ │ ├── expr-chain.pkl │ │ ├── expr-if.pkl │ │ ├── expr-let.pkl │ │ ├── imports.pkl │ │ ├── indentation.pkl │ │ ├── line-breaks.pkl │ │ ├── line-breaks2.pkl │ │ ├── line-breaks3.pkl │ │ ├── line-width.pkl │ │ ├── map-function.pkl │ │ ├── method-call-trailing-lambdas.pkl │ │ ├── modifiers.pkl │ │ ├── module-definitions.pkl │ │ ├── multi-line-strings.pkl │ │ ├── object-members.pkl │ │ ├── prefixes.pkl │ │ ├── single-line-strings.pkl │ │ ├── spaces.pkl │ │ ├── string-interpolation.pkl │ │ ├── type-aliases.pkl │ │ └── when.pkl │ ├── kotlin/ │ │ └── org/ │ │ └── pkl/ │ │ └── formatter/ │ │ ├── FormatterSnippetTests.kt │ │ ├── FormatterSnippetTestsEngine.kt │ │ └── FormatterTest.kt │ └── resources/ │ └── META-INF/ │ └── services/ │ └── org.junit.platform.engine.TestEngine ├── pkl-gradle/ │ ├── gradle.lockfile │ ├── pkl-gradle.gradle.kts │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── pkl/ │ │ └── gradle/ │ │ ├── PklAnalyzerCommands.java │ │ ├── PklExtension.java │ │ ├── PklPlugin.java │ │ ├── PklProjectCommands.java │ │ ├── package-info.java │ │ ├── spec/ │ │ │ ├── AnalyzeImportsSpec.java │ │ │ ├── BasePklSpec.java │ │ │ ├── CodeGenSpec.java │ │ │ ├── EvalSpec.java │ │ │ ├── JavaCodeGenSpec.java │ │ │ ├── KotlinCodeGenSpec.java │ │ │ ├── ModulesSpec.java │ │ │ ├── PkldocSpec.java │ │ │ ├── ProjectPackageSpec.java │ │ │ ├── ProjectResolveSpec.java │ │ │ ├── TestSpec.java │ │ │ └── package-info.java │ │ ├── task/ │ │ │ ├── AnalyzeImportsTask.java │ │ │ ├── BasePklTask.java │ │ │ ├── CodeGenTask.java │ │ │ ├── EvalTask.java │ │ │ ├── JavaCodeGenTask.java │ │ │ ├── KotlinCodeGenTask.java │ │ │ ├── ModulesTask.java │ │ │ ├── PkldocTask.java │ │ │ ├── ProjectPackageTask.java │ │ │ ├── ProjectResolveTask.java │ │ │ ├── TestTask.java │ │ │ └── package-info.java │ │ └── utils/ │ │ ├── PluginUtils.java │ │ └── package-info.java │ └── test/ │ └── kotlin/ │ └── org/ │ └── pkl/ │ └── gradle/ │ ├── AbstractTest.kt │ ├── AnalyzeImportsTest.kt │ ├── EvaluatorsTest.kt │ ├── JavaCodeGeneratorsTest.kt │ ├── KotlinCodeGeneratorsTest.kt │ ├── PkldocGeneratorsTest.kt │ ├── ProjectPackageTest.kt │ ├── ProjectResolveTest.kt │ └── TestsTest.kt ├── pkl-internal-intellij-plugin/ │ ├── README.adoc │ ├── pkl-internal-intellij-plugin.gradle.kts │ └── src/ │ └── main/ │ ├── kotlin/ │ │ └── org/ │ │ └── pkl/ │ │ └── internal/ │ │ └── intellij/ │ │ ├── SnippetTestEditorProvider.kt │ │ ├── SnippetTestSplitEditor.kt │ │ └── util.kt │ └── resources/ │ └── META-INF/ │ └── plugin.xml ├── pkl-parser/ │ ├── gradle.lockfile │ ├── pkl-parser.gradle.kts │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── pkl/ │ │ │ └── parser/ │ │ │ ├── BaseParserVisitor.java │ │ │ ├── GenericParser.java │ │ │ ├── GenericParserError.java │ │ │ ├── Lexer.java │ │ │ ├── Parser.java │ │ │ ├── ParserError.java │ │ │ ├── ParserVisitor.java │ │ │ ├── Span.java │ │ │ ├── Token.java │ │ │ ├── package-info.java │ │ │ ├── syntax/ │ │ │ │ ├── AbstractNode.java │ │ │ │ ├── Annotation.java │ │ │ │ ├── ArgumentList.java │ │ │ │ ├── Class.java │ │ │ │ ├── ClassBody.java │ │ │ │ ├── ClassMethod.java │ │ │ │ ├── ClassProperty.java │ │ │ │ ├── DocComment.java │ │ │ │ ├── Expr.java │ │ │ │ ├── ExtendsOrAmendsClause.java │ │ │ │ ├── Identifier.java │ │ │ │ ├── ImportClause.java │ │ │ │ ├── Keyword.java │ │ │ │ ├── Modifier.java │ │ │ │ ├── Module.java │ │ │ │ ├── ModuleDecl.java │ │ │ │ ├── Node.java │ │ │ │ ├── ObjectBody.java │ │ │ │ ├── ObjectMember.java │ │ │ │ ├── Operator.java │ │ │ │ ├── Parameter.java │ │ │ │ ├── ParameterList.java │ │ │ │ ├── QualifiedIdentifier.java │ │ │ │ ├── ReplInput.java │ │ │ │ ├── StringConstant.java │ │ │ │ ├── StringPart.java │ │ │ │ ├── Type.java │ │ │ │ ├── TypeAlias.java │ │ │ │ ├── TypeAnnotation.java │ │ │ │ ├── TypeArgumentList.java │ │ │ │ ├── TypeParameter.java │ │ │ │ ├── TypeParameterList.java │ │ │ │ ├── generic/ │ │ │ │ │ ├── FullSpan.java │ │ │ │ │ ├── Node.java │ │ │ │ │ ├── NodeType.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── util/ │ │ │ ├── ErrorMessages.java │ │ │ ├── NonnullByDefault.java │ │ │ └── Nullable.java │ │ └── resources/ │ │ └── org/ │ │ └── pkl/ │ │ └── parser/ │ │ └── errorMessages.properties │ └── test/ │ └── kotlin/ │ └── org/ │ └── pkl/ │ └── parser/ │ ├── GenericSexpRenderer.kt │ ├── LexerTest.kt │ ├── ParserComparisonTest.kt │ ├── SexpRenderer.kt │ └── SpanTest.kt ├── pkl-server/ │ ├── gradle.lockfile │ ├── pkl-server.gradle.kts │ └── src/ │ ├── main/ │ │ └── kotlin/ │ │ └── org/ │ │ └── pkl/ │ │ └── server/ │ │ ├── ClientLogger.kt │ │ ├── ClientModuleKeyFactory.kt │ │ ├── Server.kt │ │ ├── ServerMessagePackDecoder.kt │ │ ├── ServerMessagePackEncoder.kt │ │ ├── ServerMessages.kt │ │ └── Utils.kt │ └── test/ │ ├── kotlin/ │ │ └── org/ │ │ └── pkl/ │ │ └── server/ │ │ ├── AbstractServerTest.kt │ │ ├── JvmServerTest.kt │ │ ├── NativeServerTest.kt │ │ ├── ServerMessagePackCodecTest.kt │ │ ├── TestTransport.kt │ │ └── TestUtils.kt │ └── resources/ │ └── org/ │ └── pkl/ │ └── server/ │ └── resource1.jar ├── pkl-tools/ │ ├── gradle.lockfile │ ├── pkl-tools.gradle.kts │ └── src/ │ └── dummy/ │ └── java/ │ └── org/ │ └── pkl/ │ └── tools/ │ └── Empty.java ├── settings-gradle.lockfile ├── settings.gradle.kts └── stdlib/ ├── Benchmark.pkl ├── Command.pkl ├── DocPackageInfo.pkl ├── DocsiteInfo.pkl ├── EvaluatorSettings.pkl ├── Project.pkl ├── analyze.pkl ├── base.pkl ├── doc-package-info.pkl ├── gradle.lockfile ├── json.pkl ├── jsonnet.pkl ├── math.pkl ├── pklbinary.pkl ├── platform.pkl ├── protobuf.pkl ├── reflect.pkl ├── release.pkl ├── semver.pkl ├── settings.pkl ├── shell.pkl ├── stdlib.gradle.kts ├── test.pkl ├── xml.pkl └── yaml.pkl ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 indent_style = space indent_size = 2 # don't trim whitespace within (say) a Pkl multiline string trim_trailing_whitespace = false insert_final_newline = true max_line_length = 100 ================================================ FILE: .git-blame-ignore-revs ================================================ # Auto-format Kotlin code 816cd483c8adf4c04e14236c173a1dc6bd2579ea ================================================ FILE: .gitattributes ================================================ # linguist-generated would suppress files in diffs **/src/test/files/** linguist-vendored .github/workflows/* linguist-generated /docs/** linguist-documentation *.pcf linguist-language=Pkl PklProject linguist-language=Pkl * text eol=lf *.bat text eol=crlf *.gif binary *.jar binary *.woff2 binary *.pem binary *.png binary ================================================ FILE: .githooks/pre-commit ================================================ #!/bin/sh files=`git diff --cached --name-status` if [[ $files =~ .github/* ]]; then pkl eval --project-dir .github/ -m .github .github/index.pkl git add .github/workflows/ fi ================================================ FILE: .github/PklProject ================================================ amends "pkl:Project" dependencies { ["pkl.impl.ghactions"] { uri = "package://pkg.pkl-lang.org/pkl-project-commons/pkl.impl.ghactions@1.5.0" } ["gha"] { uri = "package://pkg.pkl-lang.org/pkl-pantry/com.github.actions@1.2.0" } } ================================================ FILE: .github/PklProject.deps.json ================================================ { "schemaVersion": 1, "resolvedDependencies": { "package://pkg.pkl-lang.org/pkl-pantry/com.github.actions@1": { "type": "remote", "uri": "projectpackage://pkg.pkl-lang.org/pkl-pantry/com.github.actions@1.3.1", "checksums": { "sha256": "fd515da685ea126678c3ec684e84a4f992d43481cc1d75cb866cd55775f675f9" } }, "package://pkg.pkl-lang.org/pkl-project-commons/pkl.impl.ghactions@1": { "type": "remote", "uri": "projectpackage://pkg.pkl-lang.org/pkl-project-commons/pkl.impl.ghactions@1.5.0", "checksums": { "sha256": "2c1e0d9efcd65b3c3207bf535c325ebc0ec2ab169187b324c4bb70821cac0e51" } }, "package://pkg.pkl-lang.org/pkl-pantry/pkl.experimental.deepToTyped@1": { "type": "remote", "uri": "projectpackage://pkg.pkl-lang.org/pkl-pantry/pkl.experimental.deepToTyped@1.2.0", "checksums": { "sha256": "84c7feb391f4ac273a99dc89b8fd51dbcd21dbda4ce640f6908527f83acdd4d6" } }, "package://pkg.pkl-lang.org/pkl-pantry/pkl.github.dependabotManagedActions@1": { "type": "remote", "uri": "projectpackage://pkg.pkl-lang.org/pkl-pantry/pkl.github.dependabotManagedActions@1.0.3", "checksums": { "sha256": "d368900942efb88ed51a98f9614748b06c74ba43423f045fcd6dedb5dbdc0bea" } }, "package://pkg.pkl-lang.org/pkl-pantry/com.github.dependabot@1": { "type": "remote", "uri": "projectpackage://pkg.pkl-lang.org/pkl-pantry/com.github.dependabot@1.0.0", "checksums": { "sha256": "02ef6f25bfca5b1d095db73ea15de79d2d2c6832ebcab61e6aba90554382abcb" } } } } ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: github-actions directory: / ignore: - dependency-name: '*' update-types: - version-update:semver-major schedule: interval: weekly ================================================ FILE: .github/index.pkl ================================================ amends "@pkl.impl.ghactions/PklCI.pkl" import "@gha/Workflow.pkl" import "jobs/BuildJavaExecutableJob.pkl" import "jobs/BuildNativeJob.pkl" import "jobs/DeployJob.pkl" import "jobs/GithubRelease.pkl" import "jobs/GradleJob.pkl" import "jobs/PklJob.pkl" import "jobs/SimpleGradleJob.pkl" triggerDocsBuild = "both" testReports { junit { "**/build/test-results/**/*.xml" } html { "**/build/reports/tests/**/*" } excludeJobs { "bench" "github-release" Regex("deploy-.*") } } local baseGradleCheck: SimpleGradleJob = new { isRelease = false command = "check" fetchDepth = 0 } local gradleCheck = (baseGradleCheck) { os = "linux" } local gradleCheckWindows = (baseGradleCheck) { os = "windows" } local typealias PklJobs = Mapping local toWorkflowJobs: (PklJobs) -> Workflow.Jobs = (it) -> new Workflow.Jobs { for (k, v in it) { [k] = v.job } } local gradleCheckJobs: PklJobs = new { ["gradle-check"] = gradleCheck ["gradle-check-windows"] = gradleCheckWindows } local buildNativeJobs: Mapping = new { for (_dist in List("release", "snapshot")) { for (_project in List("pkl-cli", "pkl-doc")) { for (_arch in List("amd64", "aarch64")) { for (_os in List("macOS", "linux")) { ["\(_project)-\(_os)-\(_arch)-\(_dist)"] { arch = _arch os = _os isRelease = _dist == "release" project = _project } } } ["\(_project)-alpine-linux-amd64-\(_dist)"] { arch = "amd64" os = "linux" musl = true isRelease = _dist == "release" project = _project } ["\(_project)-windows-amd64-\(_dist)"] { arch = "amd64" os = "windows" isRelease = _dist == "release" project = _project } } } } local buildNativeSnapshots = buildNativeJobs.toMap().filter((key, _) -> key.endsWith("snapshot")) local buildNativeReleases = buildNativeJobs.toMap().filter((key, _) -> key.endsWith("release")) local benchmarkJob: SimpleGradleJob = new { command = "bench:jmh" } local gradleCompatibilityJob: SimpleGradleJob = new { command = ":pkl-gradle:build :pkl-gradle:compatibilityTestReleases" fetchDepth = 0 } local buildJavaExecutableJob: BuildJavaExecutableJob = new { fetchDepth = 0 } local buildAndTestJobs: PklJobs = new { ...gradleCheckJobs ["bench"] = benchmarkJob ["gradle-compatibility"] = gradleCompatibilityJob ["java-executables-snapshot"] = (buildJavaExecutableJob) { isRelease = false } ...buildNativeSnapshots } local releaseJobs: PklJobs = new { ...gradleCheckJobs ["bench"] = benchmarkJob ["gradle-compatibility"] = gradleCompatibilityJob ["java-executables-release"] = (buildJavaExecutableJob) { isRelease = true } ...buildNativeReleases } // By default, just run ./gradlew check on linux. // Trigger other checks based on GitHub PR description. Examples: // // * [windows] -- Test on Windows // * [native] -- Test all native builds // * [native-pkl-cli] -- Test all pkl-cli os/arch pairs // * [native-pkl-cli-macos] -- Test pkl-cli on macOS prb { local prbJobs: Mapping = new { ["gradle-check"] = gradleCheck ["gradle-check-windows"] = (gradleCheckWindows) { `if` = "contains(github.event.pull_request.body, '[windows]')" } for (jobName, job in buildNativeSnapshots) { [jobName] = (job) { local tags = new Listing { "[native]" "[native-\(job.project)]" "[native-\(job.project)-\(job.os)]" "[native-\(job.project)-\(job.os)-\(job.arch)]" "[native-\(job.project)-\(job.os)-\(job.arch)]" when (job.musl) { "[native-\(job.project)-alpine-\(job.os)-\(job.arch)]" } } `if` = tags .toList() .map((it) -> "contains(github.event.pull_request.body, '\(it)')") .join(" || ") } } } local prbJobs2 = (prbJobs) { [[true]] { // better SLA when not running on nightly nightlyMacOS = false } } jobs = prbJobs2 |> toWorkflowJobs } build { jobs = buildAndTestJobs |> toWorkflowJobs } main { jobs = (buildAndTestJobs) { ["deploy-snapshot"] = ( new DeployJob { extraGradleArgs { "--no-parallel" } command = "publishToSonatype" } ) { needs = buildAndTestJobs.keys.toListing() } } |> toWorkflowJobs } releaseBranch { jobs = releaseJobs |> toWorkflowJobs } release { jobs = (releaseJobs) { ["deploy-release"] = ( new DeployJob { isRelease = true command = "publishToSonatype closeAndReleaseSonatypeStagingRepository" } ) { needs = releaseJobs.keys.toListing() } ["github-release"] = new GithubRelease { needs = "deploy-release" } } |> toWorkflowJobs } ================================================ FILE: .github/jobs/BuildJavaExecutableJob.pkl ================================================ extends "GradleJob.pkl" import "@gha/catalog.pkl" // Keep this in sync with projects that build java executables local projects: List = List("pkl-doc", "pkl-cli", "pkl-codegen-java", "pkl-codegen-kotlin") local command = new Listing { "./gradlew" module.gradleArgs for (project in projects) { // NOTE: `build` doesn't build native executables "\(project):build" } }.join(" ") steps { catalog.`actions/checkout@v6` new { name = "gradle build java executables" shell = "bash" run = command } (catalog.`actions/upload-artifact@v5`) { name = "Upload executable artifacts" with { name = "executable-java" path = "*/build/executable/**/*" } } } ================================================ FILE: .github/jobs/BuildNativeJob.pkl ================================================ extends "GradleJob.pkl" import "@gha/catalog.pkl" import "@gha/context.pkl" /// Whether to link to musl. Otherwise, links to glibc. musl: Boolean(implies(module.os == "linux")) = false /// The Gradle project under which to generate the executable project: String extraGradleArgs { when (os == "macOS" && arch == "amd64") { "-Dpkl.targetArch=\(module.arch)" #""-Dpkl.native--native-compiler-path=${GITHUB_WORKSPACE}/.github/scripts/cc_macos_amd64.sh""# } when (musl) { "-Dpkl.musl=true" } } preSteps { when (os == "linux" && !musl) { new { name = "Install deps" run = "dnf install -y git binutils gcc glibc-devel zlib-devel libstdc++-static glibc-langpack-en" } } } steps { when (musl) { new { name = "Install musl and zlib" run = read("../scripts/install_musl.sh").text } } // workaround for https://github.com/actions/checkout/issues/1048 when (os == "linux" && !musl) { new { name = "Fix git ownership" // language=bash run = #"git status || git config --system --add safe.directory "$GITHUB_WORKSPACE""# } } new { name = "gradle buildNative" shell = "bash" run = "./gradlew \(module.gradleArgs) \(project):buildNative" } (catalog.`actions/upload-artifact@v5`) { name = "Upload executable artifacts" with { name = if (musl) "executable-\(project)-alpine-\(module.os)-\(module.arch)" else "executable-\(project)-\(module.os)-\(module.arch)" // Need to insert a wildcard to make actions/upload-artifact preserve the folder hierarchy. // See https://github.com/actions/upload-artifact/issues/206 path = "\(project)*/build/executable/**/*" } } } fixed job { when (os == "linux" && !musl) { container { image = "redhat/ubi8:8.10" } } } ================================================ FILE: .github/jobs/DeployJob.pkl ================================================ extends "GradleJob.pkl" import "@gha/catalog.pkl" import "@gha/Workflow.pkl" import "@pkl.impl.ghactions/helpers.pkl" local self = this command: String arch = "amd64" os = "linux" steps { catalog.`actions/checkout@v6` (catalog.`actions/download-artifact@v6`) { with { pattern = "executable-**" `merge-multiple` = true } } new Workflow.Step { run = "./gradlew \(self.gradleArgs) \(module.command)" } |> helpers.withMavenPublishSecrets } fixed job { environment = "maven-release" } ================================================ FILE: .github/jobs/GithubRelease.pkl ================================================ module GithubRelease extends "PklJob.pkl" import "@gha/catalog.pkl" import "@gha/context.pkl" fixed job { `runs-on` = "ubuntu-latest" permissions { contents = "write" } needs = "deploy-release" steps { (catalog.`actions/download-artifact@v6`) { with { pattern = "executable-**" `merge-multiple` = true } } new { name = "Publish release on GitHub" env { ["GH_TOKEN"] = context.github.token ["TAG_NAME"] = context.github.refName ["GIT_SHA"] = context.github.sha ["GH_REPO"] = context.github.repository } // language=bash run = #""" # exclude build_artifacts.txt from publish rm -f */build/executable/*.build_artifacts.txt find */build/executable/* -type d | xargs rm -rf gh release create ${TAG_NAME} \ --title "${TAG_NAME}" \ --target "${GIT_SHA}" \ --verify-tag \ --notes "Release notes: https://pkl-lang.org/main/current/release-notes/changelog.html#release-${TAG_NAME}" \ */build/executable/* """# } } } ================================================ FILE: .github/jobs/GradleJob.pkl ================================================ abstract module GradleJob extends "PklJob.pkl" import "@gha/Workflow.pkl" import "@pkl.impl.ghactions/catalog.pkl" /// Whether this is a release build or not. isRelease: Boolean = false /// The architecture to use arch: "amd64" | "aarch64" = "amd64" /// The OS to run on. os: "macOS" | "linux" | "windows" = "linux" // TODO flip this to `true` when nightly macOS is available /// Whether to run on nightly macOS. nightlyMacOS: Boolean(implies(os == "macOS")) = false extraGradleArgs: Listing steps: Listing preSteps: Listing /// The fetch depth to use when doing a git checkout. fetchDepth: Int? fixed gradleArgs = new Listing { "--info" "--stacktrace" "--no-daemon" "-DpklMultiJdkTesting=true" when (isRelease) { "-DreleaseBuild=true" } ...extraGradleArgs }.join(" ") fixed job { env { ["LANG"] = "en_US.UTF-8" when (os == "windows") { ["JAVA_HOME"] = "/jdk" } } when (os == "macOS") { `if` = let (cond = "github.repository_owner == 'apple'") if (super.`if` != null) "(\(super.`if`)) && \(cond)" else cond } `runs-on` = if (os == "linux" && arch == "amd64") "ubuntu-latest" else if (os == "linux" && arch == "aarch64") "ubuntu-24.04-arm" else if (os == "windows") "windows-latest" else new Listing { "self-hosted" "macos" when (nightlyMacOS) { "nightly" } } steps { ...preSteps // full checkout (needed for spotless) (catalog.`actions/checkout@v6`) { when (fetchDepth != null) { with { `fetch-depth` = fetchDepth } } } (catalog.`actions/setup-java@v5`) { with { `java-version` = "21" distribution = "temurin" architecture = if (arch == "amd64") "x64" else "aarch64" } } catalog.`gradle/actions/setup-gradle@v5` ...module.steps } } ================================================ FILE: .github/jobs/PklJob.pkl ================================================ abstract module PklJob extends "@pkl.impl.ghactions/jobs/PklJob.pkl" /// Identify any jobs that must complete successfully before this job will run. /// /// It can be a string or array of strings. /// If a job fails or is skipped, all jobs that need it are skipped unless the jobs use a conditional expression that /// causes the job to continue. /// If a run contains a series of jobs that need each other, a failure or skip applies to all jobs in the dependency /// chain from the point of failure or skip onwards. If you would like a job to run even if a job it is dependent on /// did not succeed, use the `always()` conditional expression in `jobs..if`. /// /// See: needs: (String | *Listing)? /// A conditional to prevent a job from running unless a condition is met. /// /// You can use any supported context and expression to create a conditional. /// For more information on which contexts are supported in this key, see /// [Contexts reference](https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#context-availability). @SourceCode { language = "GithubExpressionLanguage" } `if`: String? /// The underlying workflow job fixed job { `if` = module.`if` needs = module.needs } ================================================ FILE: .github/jobs/SimpleGradleJob.pkl ================================================ extends "GradleJob.pkl" name: String = command command: String os = "linux" steps { new { name = module.name shell = "bash" run = """ ./gradlew \(module.gradleArgs) \(module.command) """ } } ================================================ FILE: .github/scripts/cc_macos_amd64.sh ================================================ #!/usr/bin/env bash clang -arch x86_64 "$@" ================================================ FILE: .github/scripts/install_musl.sh ================================================ set -e mkdir -p ~/staticdeps/ ZLIB_VERSION="1.2.13" MUSL_VERSION="1.2.5" # install zlib if [[ ! -f ~/staticdeps/include/zlib.h ]]; then # Download zlib tarball and signature curl -Lf "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz" -o /tmp/zlib.tar.gz curl -Lf "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz.asc" -o /tmp/zlib.tar.gz.asc # Import zlib GPG key gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 5ED46A6721D365587791E2AA783FCD8E58BCAFBA # Verify GPG signature echo "Verifying zlib GPG signature..." gpg --verify /tmp/zlib.tar.gz.asc /tmp/zlib.tar.gz mkdir -p "/tmp/dep_zlib-${ZLIB_VERSION}" cd "/tmp/dep_zlib-${ZLIB_VERSION}" # shellcheck disable=SC2002 cat /tmp/zlib.tar.gz | tar --strip-components=1 -xzC . echo "zlib-${ZLIB_VERSION}: configure..." ./configure --static --prefix="$HOME"/staticdeps > /dev/null echo "zlib-${ZLIB_VERSION}: make..." make -s -j4 echo "zlib-${ZLIB_VERSION}: make install..." make -s install rm -rf /tmp/dep_zlib-${ZLIB_VERSION} fi # install musl if [[ ! -f ~/staticdeps/bin/x86_64-linux-musl-gcc ]]; then # Download musl tarball and signature curl -Lf "https://musl.libc.org/releases/musl-${MUSL_VERSION}.tar.gz" -o /tmp/musl.tar.gz curl -Lf "https://musl.libc.org/releases/musl-${MUSL_VERSION}.tar.gz.asc" -o /tmp/musl.tar.gz.asc # Import musl GPG key gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 836489290BB6B70F99FFDA0556BCDB593020450F # Verify GPG signature echo "Verifying musl GPG signature..." gpg --verify /tmp/musl.tar.gz.asc /tmp/musl.tar.gz mkdir -p "/tmp/dep_musl-${MUSL_VERSION}" cd "/tmp/dep_musl-${MUSL_VERSION}" # shellcheck disable=SC2002 cat /tmp/musl.tar.gz | tar --strip-components=1 -xzC . echo "musl-${MUSL_VERSION}: configure..." ./configure --disable-shared --prefix="$HOME"/staticdeps > /dev/null echo "musl-${MUSL_VERSION}: make..." make -s -j4 echo "musl-${MUSL_VERSION}: make install..." make -s install rm -rf "/tmp/dep_musl-${MUSL_VERSION}" # native-image expects to find an executable at this path. ln -s ~/staticdeps/bin/musl-gcc ~/staticdeps/bin/x86_64-linux-musl-gcc fi echo "${HOME}/staticdeps/bin" >> "$GITHUB_PATH" ================================================ FILE: .github/workflows/__lockfile__.yml ================================================ #file: noinspection MandatoryParamsAbsent,UndefinedAction # This is a fake workflow that never runs. # It's used to pin actions to specific git SHAs when generating actual workflows. # It also gets updated by dependabot (see .github/dependabot.yml). # Generated from Workflow.pkl. DO NOT EDIT. name: __lockfile__ 'on': push: branches-ignore: - '**' tags-ignore: - '**' permissions: {} jobs: locks: if: 'false' runs-on: nothing steps: - name: EnricoMi/publish-unit-test-result-action@v2 uses: EnricoMi/publish-unit-test-result-action@c950f6fb443cb5af20a377fd0dfaa78838901040 # v2 - name: actions/checkout@v6 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: actions/create-github-app-token@v2 uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2 - name: actions/download-artifact@v6 uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 - name: actions/setup-java@v5 uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 - name: actions/upload-artifact@v5 uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 - name: dawidd6/action-download-artifact@v11 uses: dawidd6/action-download-artifact@ac66b43f0e6a346234dd65d4d0c8fbb31cb316e5 # v11 - name: gradle/actions/setup-gradle@v5 uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 ================================================ FILE: .github/workflows/build.yml ================================================ # Generated from Workflow.pkl. DO NOT EDIT. name: Build 'on': push: branches-ignore: - main - release/* - dependabot/** tags-ignore: - '**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: false permissions: contents: read jobs: gradle-check: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false fetch-depth: 0 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: check shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true check - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-gradle-check path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-gradle-check path: '**/build/reports/tests/**/*' if-no-files-found: ignore gradle-check-windows: runs-on: windows-latest env: LANG: en_US.UTF-8 JAVA_HOME: /jdk steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false fetch-depth: 0 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: check shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true check - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-gradle-check-windows path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-gradle-check-windows path: '**/build/reports/tests/**/*' if-no-files-found: ignore bench: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: bench:jmh shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true bench:jmh gradle-compatibility: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false fetch-depth: 0 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: :pkl-gradle:build :pkl-gradle:compatibilityTestReleases shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true :pkl-gradle:build :pkl-gradle:compatibilityTestReleases - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-gradle-compatibility path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-gradle-compatibility path: '**/build/reports/tests/**/*' if-no-files-found: ignore java-executables-snapshot: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false fetch-depth: 0 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - name: gradle build java executables shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-doc:build pkl-cli:build pkl-codegen-java:build pkl-codegen-kotlin:build - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-java path: '*/build/executable/**/*' - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-java-executables-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-java-executables-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-cli-macOS-amd64-snapshot: if: github.repository_owner == 'apple' runs-on: - self-hosted - macos env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -Dpkl.targetArch=amd64 "-Dpkl.native--native-compiler-path=${GITHUB_WORKSPACE}/.github/scripts/cc_macos_amd64.sh" pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-macOS-amd64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-macOS-amd64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-macOS-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-cli-linux-amd64-snapshot: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - name: Install deps run: dnf install -y git binutils gcc glibc-devel zlib-devel libstdc++-static glibc-langpack-en - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Fix git ownership run: git status || git config --system --add safe.directory "$GITHUB_WORKSPACE" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-linux-amd64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-linux-amd64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-linux-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore container: image: redhat/ubi8:8.10 pkl-cli-macOS-aarch64-snapshot: if: github.repository_owner == 'apple' runs-on: - self-hosted - macos env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: aarch64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-macOS-aarch64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-macOS-aarch64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-macOS-aarch64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-cli-linux-aarch64-snapshot: runs-on: ubuntu-24.04-arm env: LANG: en_US.UTF-8 steps: - name: Install deps run: dnf install -y git binutils gcc glibc-devel zlib-devel libstdc++-static glibc-langpack-en - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: aarch64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Fix git ownership run: git status || git config --system --add safe.directory "$GITHUB_WORKSPACE" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-linux-aarch64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-linux-aarch64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-linux-aarch64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore container: image: redhat/ubi8:8.10 pkl-cli-alpine-linux-amd64-snapshot: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Install musl and zlib run: | set -e mkdir -p ~/staticdeps/ ZLIB_VERSION="1.2.13" MUSL_VERSION="1.2.5" # install zlib if [[ ! -f ~/staticdeps/include/zlib.h ]]; then # Download zlib tarball and signature curl -Lf "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz" -o /tmp/zlib.tar.gz curl -Lf "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz.asc" -o /tmp/zlib.tar.gz.asc # Import zlib GPG key gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 5ED46A6721D365587791E2AA783FCD8E58BCAFBA # Verify GPG signature echo "Verifying zlib GPG signature..." gpg --verify /tmp/zlib.tar.gz.asc /tmp/zlib.tar.gz mkdir -p "/tmp/dep_zlib-${ZLIB_VERSION}" cd "/tmp/dep_zlib-${ZLIB_VERSION}" # shellcheck disable=SC2002 cat /tmp/zlib.tar.gz | tar --strip-components=1 -xzC . echo "zlib-${ZLIB_VERSION}: configure..." ./configure --static --prefix="$HOME"/staticdeps > /dev/null echo "zlib-${ZLIB_VERSION}: make..." make -s -j4 echo "zlib-${ZLIB_VERSION}: make install..." make -s install rm -rf /tmp/dep_zlib-${ZLIB_VERSION} fi # install musl if [[ ! -f ~/staticdeps/bin/x86_64-linux-musl-gcc ]]; then # Download musl tarball and signature curl -Lf "https://musl.libc.org/releases/musl-${MUSL_VERSION}.tar.gz" -o /tmp/musl.tar.gz curl -Lf "https://musl.libc.org/releases/musl-${MUSL_VERSION}.tar.gz.asc" -o /tmp/musl.tar.gz.asc # Import musl GPG key gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 836489290BB6B70F99FFDA0556BCDB593020450F # Verify GPG signature echo "Verifying musl GPG signature..." gpg --verify /tmp/musl.tar.gz.asc /tmp/musl.tar.gz mkdir -p "/tmp/dep_musl-${MUSL_VERSION}" cd "/tmp/dep_musl-${MUSL_VERSION}" # shellcheck disable=SC2002 cat /tmp/musl.tar.gz | tar --strip-components=1 -xzC . echo "musl-${MUSL_VERSION}: configure..." ./configure --disable-shared --prefix="$HOME"/staticdeps > /dev/null echo "musl-${MUSL_VERSION}: make..." make -s -j4 echo "musl-${MUSL_VERSION}: make install..." make -s install rm -rf "/tmp/dep_musl-${MUSL_VERSION}" # native-image expects to find an executable at this path. ln -s ~/staticdeps/bin/musl-gcc ~/staticdeps/bin/x86_64-linux-musl-gcc fi echo "${HOME}/staticdeps/bin" >> "$GITHUB_PATH" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -Dpkl.musl=true pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-alpine-linux-amd64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-alpine-linux-amd64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-alpine-linux-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-cli-windows-amd64-snapshot: runs-on: windows-latest env: LANG: en_US.UTF-8 JAVA_HOME: /jdk steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-windows-amd64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-windows-amd64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-windows-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-doc-macOS-amd64-snapshot: if: github.repository_owner == 'apple' runs-on: - self-hosted - macos env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -Dpkl.targetArch=amd64 "-Dpkl.native--native-compiler-path=${GITHUB_WORKSPACE}/.github/scripts/cc_macos_amd64.sh" pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-macOS-amd64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-macOS-amd64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-macOS-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-doc-linux-amd64-snapshot: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - name: Install deps run: dnf install -y git binutils gcc glibc-devel zlib-devel libstdc++-static glibc-langpack-en - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Fix git ownership run: git status || git config --system --add safe.directory "$GITHUB_WORKSPACE" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-linux-amd64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-linux-amd64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-linux-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore container: image: redhat/ubi8:8.10 pkl-doc-macOS-aarch64-snapshot: if: github.repository_owner == 'apple' runs-on: - self-hosted - macos env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: aarch64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-macOS-aarch64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-macOS-aarch64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-macOS-aarch64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-doc-linux-aarch64-snapshot: runs-on: ubuntu-24.04-arm env: LANG: en_US.UTF-8 steps: - name: Install deps run: dnf install -y git binutils gcc glibc-devel zlib-devel libstdc++-static glibc-langpack-en - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: aarch64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Fix git ownership run: git status || git config --system --add safe.directory "$GITHUB_WORKSPACE" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-linux-aarch64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-linux-aarch64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-linux-aarch64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore container: image: redhat/ubi8:8.10 pkl-doc-alpine-linux-amd64-snapshot: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Install musl and zlib run: | set -e mkdir -p ~/staticdeps/ ZLIB_VERSION="1.2.13" MUSL_VERSION="1.2.5" # install zlib if [[ ! -f ~/staticdeps/include/zlib.h ]]; then # Download zlib tarball and signature curl -Lf "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz" -o /tmp/zlib.tar.gz curl -Lf "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz.asc" -o /tmp/zlib.tar.gz.asc # Import zlib GPG key gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 5ED46A6721D365587791E2AA783FCD8E58BCAFBA # Verify GPG signature echo "Verifying zlib GPG signature..." gpg --verify /tmp/zlib.tar.gz.asc /tmp/zlib.tar.gz mkdir -p "/tmp/dep_zlib-${ZLIB_VERSION}" cd "/tmp/dep_zlib-${ZLIB_VERSION}" # shellcheck disable=SC2002 cat /tmp/zlib.tar.gz | tar --strip-components=1 -xzC . echo "zlib-${ZLIB_VERSION}: configure..." ./configure --static --prefix="$HOME"/staticdeps > /dev/null echo "zlib-${ZLIB_VERSION}: make..." make -s -j4 echo "zlib-${ZLIB_VERSION}: make install..." make -s install rm -rf /tmp/dep_zlib-${ZLIB_VERSION} fi # install musl if [[ ! -f ~/staticdeps/bin/x86_64-linux-musl-gcc ]]; then # Download musl tarball and signature curl -Lf "https://musl.libc.org/releases/musl-${MUSL_VERSION}.tar.gz" -o /tmp/musl.tar.gz curl -Lf "https://musl.libc.org/releases/musl-${MUSL_VERSION}.tar.gz.asc" -o /tmp/musl.tar.gz.asc # Import musl GPG key gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 836489290BB6B70F99FFDA0556BCDB593020450F # Verify GPG signature echo "Verifying musl GPG signature..." gpg --verify /tmp/musl.tar.gz.asc /tmp/musl.tar.gz mkdir -p "/tmp/dep_musl-${MUSL_VERSION}" cd "/tmp/dep_musl-${MUSL_VERSION}" # shellcheck disable=SC2002 cat /tmp/musl.tar.gz | tar --strip-components=1 -xzC . echo "musl-${MUSL_VERSION}: configure..." ./configure --disable-shared --prefix="$HOME"/staticdeps > /dev/null echo "musl-${MUSL_VERSION}: make..." make -s -j4 echo "musl-${MUSL_VERSION}: make install..." make -s install rm -rf "/tmp/dep_musl-${MUSL_VERSION}" # native-image expects to find an executable at this path. ln -s ~/staticdeps/bin/musl-gcc ~/staticdeps/bin/x86_64-linux-musl-gcc fi echo "${HOME}/staticdeps/bin" >> "$GITHUB_PATH" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -Dpkl.musl=true pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-alpine-linux-amd64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-alpine-linux-amd64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-alpine-linux-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-doc-windows-amd64-snapshot: runs-on: windows-latest env: LANG: en_US.UTF-8 JAVA_HOME: /jdk steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-windows-amd64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-windows-amd64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-windows-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore publish-test-results: if: '!cancelled()' needs: - gradle-check - gradle-check-windows - gradle-compatibility - java-executables-snapshot - pkl-cli-macOS-amd64-snapshot - pkl-cli-linux-amd64-snapshot - pkl-cli-macOS-aarch64-snapshot - pkl-cli-linux-aarch64-snapshot - pkl-cli-alpine-linux-amd64-snapshot - pkl-cli-windows-amd64-snapshot - pkl-doc-macOS-amd64-snapshot - pkl-doc-linux-amd64-snapshot - pkl-doc-macOS-aarch64-snapshot - pkl-doc-linux-aarch64-snapshot - pkl-doc-alpine-linux-amd64-snapshot - pkl-doc-windows-amd64-snapshot permissions: checks: write runs-on: ubuntu-latest steps: - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 with: pattern: test-results-xml-* - name: Publish test results if: '!cancelled()' uses: EnricoMi/publish-unit-test-result-action@c950f6fb443cb5af20a377fd0dfaa78838901040 # v2 with: comment_mode: 'off' files: test-results-xml-*/**/*.xml - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-publish-test-results path: '**/build/reports/tests/**/*' if-no-files-found: ignore ================================================ FILE: .github/workflows/main.yml ================================================ # Generated from Workflow.pkl. DO NOT EDIT. name: Build (main) 'on': push: branches: - main tags-ignore: - '**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: false permissions: contents: read jobs: gradle-check: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false fetch-depth: 0 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: check shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true check - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-gradle-check path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-gradle-check path: '**/build/reports/tests/**/*' if-no-files-found: ignore gradle-check-windows: runs-on: windows-latest env: LANG: en_US.UTF-8 JAVA_HOME: /jdk steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false fetch-depth: 0 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: check shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true check - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-gradle-check-windows path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-gradle-check-windows path: '**/build/reports/tests/**/*' if-no-files-found: ignore bench: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: bench:jmh shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true bench:jmh gradle-compatibility: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false fetch-depth: 0 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: :pkl-gradle:build :pkl-gradle:compatibilityTestReleases shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true :pkl-gradle:build :pkl-gradle:compatibilityTestReleases - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-gradle-compatibility path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-gradle-compatibility path: '**/build/reports/tests/**/*' if-no-files-found: ignore java-executables-snapshot: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false fetch-depth: 0 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - name: gradle build java executables shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-doc:build pkl-cli:build pkl-codegen-java:build pkl-codegen-kotlin:build - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-java path: '*/build/executable/**/*' - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-java-executables-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-java-executables-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-cli-macOS-amd64-snapshot: if: github.repository_owner == 'apple' runs-on: - self-hosted - macos env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -Dpkl.targetArch=amd64 "-Dpkl.native--native-compiler-path=${GITHUB_WORKSPACE}/.github/scripts/cc_macos_amd64.sh" pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-macOS-amd64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-macOS-amd64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-macOS-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-cli-linux-amd64-snapshot: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - name: Install deps run: dnf install -y git binutils gcc glibc-devel zlib-devel libstdc++-static glibc-langpack-en - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Fix git ownership run: git status || git config --system --add safe.directory "$GITHUB_WORKSPACE" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-linux-amd64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-linux-amd64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-linux-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore container: image: redhat/ubi8:8.10 pkl-cli-macOS-aarch64-snapshot: if: github.repository_owner == 'apple' runs-on: - self-hosted - macos env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: aarch64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-macOS-aarch64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-macOS-aarch64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-macOS-aarch64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-cli-linux-aarch64-snapshot: runs-on: ubuntu-24.04-arm env: LANG: en_US.UTF-8 steps: - name: Install deps run: dnf install -y git binutils gcc glibc-devel zlib-devel libstdc++-static glibc-langpack-en - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: aarch64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Fix git ownership run: git status || git config --system --add safe.directory "$GITHUB_WORKSPACE" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-linux-aarch64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-linux-aarch64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-linux-aarch64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore container: image: redhat/ubi8:8.10 pkl-cli-alpine-linux-amd64-snapshot: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Install musl and zlib run: | set -e mkdir -p ~/staticdeps/ ZLIB_VERSION="1.2.13" MUSL_VERSION="1.2.5" # install zlib if [[ ! -f ~/staticdeps/include/zlib.h ]]; then # Download zlib tarball and signature curl -Lf "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz" -o /tmp/zlib.tar.gz curl -Lf "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz.asc" -o /tmp/zlib.tar.gz.asc # Import zlib GPG key gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 5ED46A6721D365587791E2AA783FCD8E58BCAFBA # Verify GPG signature echo "Verifying zlib GPG signature..." gpg --verify /tmp/zlib.tar.gz.asc /tmp/zlib.tar.gz mkdir -p "/tmp/dep_zlib-${ZLIB_VERSION}" cd "/tmp/dep_zlib-${ZLIB_VERSION}" # shellcheck disable=SC2002 cat /tmp/zlib.tar.gz | tar --strip-components=1 -xzC . echo "zlib-${ZLIB_VERSION}: configure..." ./configure --static --prefix="$HOME"/staticdeps > /dev/null echo "zlib-${ZLIB_VERSION}: make..." make -s -j4 echo "zlib-${ZLIB_VERSION}: make install..." make -s install rm -rf /tmp/dep_zlib-${ZLIB_VERSION} fi # install musl if [[ ! -f ~/staticdeps/bin/x86_64-linux-musl-gcc ]]; then # Download musl tarball and signature curl -Lf "https://musl.libc.org/releases/musl-${MUSL_VERSION}.tar.gz" -o /tmp/musl.tar.gz curl -Lf "https://musl.libc.org/releases/musl-${MUSL_VERSION}.tar.gz.asc" -o /tmp/musl.tar.gz.asc # Import musl GPG key gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 836489290BB6B70F99FFDA0556BCDB593020450F # Verify GPG signature echo "Verifying musl GPG signature..." gpg --verify /tmp/musl.tar.gz.asc /tmp/musl.tar.gz mkdir -p "/tmp/dep_musl-${MUSL_VERSION}" cd "/tmp/dep_musl-${MUSL_VERSION}" # shellcheck disable=SC2002 cat /tmp/musl.tar.gz | tar --strip-components=1 -xzC . echo "musl-${MUSL_VERSION}: configure..." ./configure --disable-shared --prefix="$HOME"/staticdeps > /dev/null echo "musl-${MUSL_VERSION}: make..." make -s -j4 echo "musl-${MUSL_VERSION}: make install..." make -s install rm -rf "/tmp/dep_musl-${MUSL_VERSION}" # native-image expects to find an executable at this path. ln -s ~/staticdeps/bin/musl-gcc ~/staticdeps/bin/x86_64-linux-musl-gcc fi echo "${HOME}/staticdeps/bin" >> "$GITHUB_PATH" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -Dpkl.musl=true pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-alpine-linux-amd64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-alpine-linux-amd64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-alpine-linux-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-cli-windows-amd64-snapshot: runs-on: windows-latest env: LANG: en_US.UTF-8 JAVA_HOME: /jdk steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-windows-amd64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-windows-amd64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-windows-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-doc-macOS-amd64-snapshot: if: github.repository_owner == 'apple' runs-on: - self-hosted - macos env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -Dpkl.targetArch=amd64 "-Dpkl.native--native-compiler-path=${GITHUB_WORKSPACE}/.github/scripts/cc_macos_amd64.sh" pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-macOS-amd64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-macOS-amd64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-macOS-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-doc-linux-amd64-snapshot: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - name: Install deps run: dnf install -y git binutils gcc glibc-devel zlib-devel libstdc++-static glibc-langpack-en - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Fix git ownership run: git status || git config --system --add safe.directory "$GITHUB_WORKSPACE" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-linux-amd64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-linux-amd64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-linux-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore container: image: redhat/ubi8:8.10 pkl-doc-macOS-aarch64-snapshot: if: github.repository_owner == 'apple' runs-on: - self-hosted - macos env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: aarch64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-macOS-aarch64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-macOS-aarch64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-macOS-aarch64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-doc-linux-aarch64-snapshot: runs-on: ubuntu-24.04-arm env: LANG: en_US.UTF-8 steps: - name: Install deps run: dnf install -y git binutils gcc glibc-devel zlib-devel libstdc++-static glibc-langpack-en - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: aarch64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Fix git ownership run: git status || git config --system --add safe.directory "$GITHUB_WORKSPACE" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-linux-aarch64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-linux-aarch64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-linux-aarch64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore container: image: redhat/ubi8:8.10 pkl-doc-alpine-linux-amd64-snapshot: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Install musl and zlib run: | set -e mkdir -p ~/staticdeps/ ZLIB_VERSION="1.2.13" MUSL_VERSION="1.2.5" # install zlib if [[ ! -f ~/staticdeps/include/zlib.h ]]; then # Download zlib tarball and signature curl -Lf "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz" -o /tmp/zlib.tar.gz curl -Lf "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz.asc" -o /tmp/zlib.tar.gz.asc # Import zlib GPG key gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 5ED46A6721D365587791E2AA783FCD8E58BCAFBA # Verify GPG signature echo "Verifying zlib GPG signature..." gpg --verify /tmp/zlib.tar.gz.asc /tmp/zlib.tar.gz mkdir -p "/tmp/dep_zlib-${ZLIB_VERSION}" cd "/tmp/dep_zlib-${ZLIB_VERSION}" # shellcheck disable=SC2002 cat /tmp/zlib.tar.gz | tar --strip-components=1 -xzC . echo "zlib-${ZLIB_VERSION}: configure..." ./configure --static --prefix="$HOME"/staticdeps > /dev/null echo "zlib-${ZLIB_VERSION}: make..." make -s -j4 echo "zlib-${ZLIB_VERSION}: make install..." make -s install rm -rf /tmp/dep_zlib-${ZLIB_VERSION} fi # install musl if [[ ! -f ~/staticdeps/bin/x86_64-linux-musl-gcc ]]; then # Download musl tarball and signature curl -Lf "https://musl.libc.org/releases/musl-${MUSL_VERSION}.tar.gz" -o /tmp/musl.tar.gz curl -Lf "https://musl.libc.org/releases/musl-${MUSL_VERSION}.tar.gz.asc" -o /tmp/musl.tar.gz.asc # Import musl GPG key gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 836489290BB6B70F99FFDA0556BCDB593020450F # Verify GPG signature echo "Verifying musl GPG signature..." gpg --verify /tmp/musl.tar.gz.asc /tmp/musl.tar.gz mkdir -p "/tmp/dep_musl-${MUSL_VERSION}" cd "/tmp/dep_musl-${MUSL_VERSION}" # shellcheck disable=SC2002 cat /tmp/musl.tar.gz | tar --strip-components=1 -xzC . echo "musl-${MUSL_VERSION}: configure..." ./configure --disable-shared --prefix="$HOME"/staticdeps > /dev/null echo "musl-${MUSL_VERSION}: make..." make -s -j4 echo "musl-${MUSL_VERSION}: make install..." make -s install rm -rf "/tmp/dep_musl-${MUSL_VERSION}" # native-image expects to find an executable at this path. ln -s ~/staticdeps/bin/musl-gcc ~/staticdeps/bin/x86_64-linux-musl-gcc fi echo "${HOME}/staticdeps/bin" >> "$GITHUB_PATH" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -Dpkl.musl=true pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-alpine-linux-amd64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-alpine-linux-amd64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-alpine-linux-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-doc-windows-amd64-snapshot: runs-on: windows-latest env: LANG: en_US.UTF-8 JAVA_HOME: /jdk steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-windows-amd64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-windows-amd64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-windows-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore deploy-snapshot: needs: - gradle-check - gradle-check-windows - bench - gradle-compatibility - java-executables-snapshot - pkl-cli-macOS-amd64-snapshot - pkl-cli-linux-amd64-snapshot - pkl-cli-macOS-aarch64-snapshot - pkl-cli-linux-aarch64-snapshot - pkl-cli-alpine-linux-amd64-snapshot - pkl-cli-windows-amd64-snapshot - pkl-doc-macOS-amd64-snapshot - pkl-doc-linux-amd64-snapshot - pkl-doc-macOS-aarch64-snapshot - pkl-doc-linux-aarch64-snapshot - pkl-doc-alpine-linux-amd64-snapshot - pkl-doc-windows-amd64-snapshot runs-on: ubuntu-latest env: LANG: en_US.UTF-8 environment: maven-release steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 with: pattern: executable-** merge-multiple: true - env: ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.ORG_GRADLE_PROJECT_SIGNINGKEYID }} ORG_GRADLE_PROJECT_signingKey: ${{ secrets.ORG_GRADLE_PROJECT_SIGNINGKEY }} ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.ORG_GRADLE_PROJECT_SIGNINGPASSWORD }} ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPEPASSWORD }} ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPEUSERNAME }} run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true --no-parallel publishToSonatype publish-test-results: if: '!cancelled()' needs: - gradle-check - gradle-check-windows - gradle-compatibility - java-executables-snapshot - pkl-cli-macOS-amd64-snapshot - pkl-cli-linux-amd64-snapshot - pkl-cli-macOS-aarch64-snapshot - pkl-cli-linux-aarch64-snapshot - pkl-cli-alpine-linux-amd64-snapshot - pkl-cli-windows-amd64-snapshot - pkl-doc-macOS-amd64-snapshot - pkl-doc-linux-amd64-snapshot - pkl-doc-macOS-aarch64-snapshot - pkl-doc-linux-aarch64-snapshot - pkl-doc-alpine-linux-amd64-snapshot - pkl-doc-windows-amd64-snapshot permissions: checks: write runs-on: ubuntu-latest steps: - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 with: pattern: test-results-xml-* - name: Publish test results if: '!cancelled()' uses: EnricoMi/publish-unit-test-result-action@c950f6fb443cb5af20a377fd0dfaa78838901040 # v2 with: comment_mode: 'off' files: test-results-xml-*/**/*.xml - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-publish-test-results path: '**/build/reports/tests/**/*' if-no-files-found: ignore trigger-downstream-builds: if: github.repository_owner == 'apple' needs: - gradle-check - gradle-check-windows - bench - gradle-compatibility - java-executables-snapshot - pkl-cli-macOS-amd64-snapshot - pkl-cli-linux-amd64-snapshot - pkl-cli-macOS-aarch64-snapshot - pkl-cli-linux-aarch64-snapshot - pkl-cli-alpine-linux-amd64-snapshot - pkl-cli-windows-amd64-snapshot - pkl-doc-macOS-amd64-snapshot - pkl-doc-linux-amd64-snapshot - pkl-doc-macOS-aarch64-snapshot - pkl-doc-linux-aarch64-snapshot - pkl-doc-alpine-linux-amd64-snapshot - pkl-doc-windows-amd64-snapshot - deploy-snapshot - publish-test-results runs-on: ubuntu-latest steps: - name: Create app token id: app-token uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2 with: app-id: ${{ secrets.PKL_CI_CLIENT_ID }} private-key: ${{ secrets.PKL_CI }} owner: ${{ github.repository_owner }} - name: Trigger pkl-lang.org build env: GH_TOKEN: ${{ steps.app-token.outputs.token }} SOURCE_RUN: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: |- gh workflow run \ --repo apple/pkl-lang.org \ --ref main \ --field source_run="${SOURCE_RUN}" \ main.yml ================================================ FILE: .github/workflows/prb.yml ================================================ # Generated from Workflow.pkl. DO NOT EDIT. name: Pull Request 'on': pull_request: {} concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true permissions: contents: read jobs: gradle-check: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false fetch-depth: 0 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: check shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true check - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-gradle-check path: '**/build/test-results/**/*.xml' if-no-files-found: ignore - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-gradle-check path: '**/build/reports/tests/**/*' if-no-files-found: ignore gradle-check-windows: if: contains(github.event.pull_request.body, '[windows]') runs-on: windows-latest env: LANG: en_US.UTF-8 JAVA_HOME: /jdk steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false fetch-depth: 0 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: check shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true check - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-gradle-check-windows path: '**/build/test-results/**/*.xml' if-no-files-found: ignore - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-gradle-check-windows path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-cli-macOS-amd64-snapshot: if: (contains(github.event.pull_request.body, '[native]') || contains(github.event.pull_request.body, '[native-pkl-cli]') || contains(github.event.pull_request.body, '[native-pkl-cli-macOS]') || contains(github.event.pull_request.body, '[native-pkl-cli-macOS-amd64]') || contains(github.event.pull_request.body, '[native-pkl-cli-macOS-amd64]')) && github.repository_owner == 'apple' runs-on: - self-hosted - macos env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -Dpkl.targetArch=amd64 "-Dpkl.native--native-compiler-path=${GITHUB_WORKSPACE}/.github/scripts/cc_macos_amd64.sh" pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-macOS-amd64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-macOS-amd64-snapshot path: '**/build/test-results/**/*.xml' if-no-files-found: ignore - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-macOS-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-cli-linux-amd64-snapshot: if: contains(github.event.pull_request.body, '[native]') || contains(github.event.pull_request.body, '[native-pkl-cli]') || contains(github.event.pull_request.body, '[native-pkl-cli-linux]') || contains(github.event.pull_request.body, '[native-pkl-cli-linux-amd64]') || contains(github.event.pull_request.body, '[native-pkl-cli-linux-amd64]') runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - name: Install deps run: dnf install -y git binutils gcc glibc-devel zlib-devel libstdc++-static glibc-langpack-en - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Fix git ownership run: git status || git config --system --add safe.directory "$GITHUB_WORKSPACE" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-linux-amd64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-linux-amd64-snapshot path: '**/build/test-results/**/*.xml' if-no-files-found: ignore - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-linux-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore container: image: redhat/ubi8:8.10 pkl-cli-macOS-aarch64-snapshot: if: (contains(github.event.pull_request.body, '[native]') || contains(github.event.pull_request.body, '[native-pkl-cli]') || contains(github.event.pull_request.body, '[native-pkl-cli-macOS]') || contains(github.event.pull_request.body, '[native-pkl-cli-macOS-aarch64]') || contains(github.event.pull_request.body, '[native-pkl-cli-macOS-aarch64]')) && github.repository_owner == 'apple' runs-on: - self-hosted - macos env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: aarch64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-macOS-aarch64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-macOS-aarch64-snapshot path: '**/build/test-results/**/*.xml' if-no-files-found: ignore - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-macOS-aarch64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-cli-linux-aarch64-snapshot: if: contains(github.event.pull_request.body, '[native]') || contains(github.event.pull_request.body, '[native-pkl-cli]') || contains(github.event.pull_request.body, '[native-pkl-cli-linux]') || contains(github.event.pull_request.body, '[native-pkl-cli-linux-aarch64]') || contains(github.event.pull_request.body, '[native-pkl-cli-linux-aarch64]') runs-on: ubuntu-24.04-arm env: LANG: en_US.UTF-8 steps: - name: Install deps run: dnf install -y git binutils gcc glibc-devel zlib-devel libstdc++-static glibc-langpack-en - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: aarch64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Fix git ownership run: git status || git config --system --add safe.directory "$GITHUB_WORKSPACE" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-linux-aarch64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-linux-aarch64-snapshot path: '**/build/test-results/**/*.xml' if-no-files-found: ignore - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-linux-aarch64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore container: image: redhat/ubi8:8.10 pkl-cli-alpine-linux-amd64-snapshot: if: contains(github.event.pull_request.body, '[native]') || contains(github.event.pull_request.body, '[native-pkl-cli]') || contains(github.event.pull_request.body, '[native-pkl-cli-linux]') || contains(github.event.pull_request.body, '[native-pkl-cli-linux-amd64]') || contains(github.event.pull_request.body, '[native-pkl-cli-linux-amd64]') || contains(github.event.pull_request.body, '[native-pkl-cli-alpine-linux-amd64]') runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Install musl and zlib run: | set -e mkdir -p ~/staticdeps/ ZLIB_VERSION="1.2.13" MUSL_VERSION="1.2.5" # install zlib if [[ ! -f ~/staticdeps/include/zlib.h ]]; then # Download zlib tarball and signature curl -Lf "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz" -o /tmp/zlib.tar.gz curl -Lf "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz.asc" -o /tmp/zlib.tar.gz.asc # Import zlib GPG key gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 5ED46A6721D365587791E2AA783FCD8E58BCAFBA # Verify GPG signature echo "Verifying zlib GPG signature..." gpg --verify /tmp/zlib.tar.gz.asc /tmp/zlib.tar.gz mkdir -p "/tmp/dep_zlib-${ZLIB_VERSION}" cd "/tmp/dep_zlib-${ZLIB_VERSION}" # shellcheck disable=SC2002 cat /tmp/zlib.tar.gz | tar --strip-components=1 -xzC . echo "zlib-${ZLIB_VERSION}: configure..." ./configure --static --prefix="$HOME"/staticdeps > /dev/null echo "zlib-${ZLIB_VERSION}: make..." make -s -j4 echo "zlib-${ZLIB_VERSION}: make install..." make -s install rm -rf /tmp/dep_zlib-${ZLIB_VERSION} fi # install musl if [[ ! -f ~/staticdeps/bin/x86_64-linux-musl-gcc ]]; then # Download musl tarball and signature curl -Lf "https://musl.libc.org/releases/musl-${MUSL_VERSION}.tar.gz" -o /tmp/musl.tar.gz curl -Lf "https://musl.libc.org/releases/musl-${MUSL_VERSION}.tar.gz.asc" -o /tmp/musl.tar.gz.asc # Import musl GPG key gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 836489290BB6B70F99FFDA0556BCDB593020450F # Verify GPG signature echo "Verifying musl GPG signature..." gpg --verify /tmp/musl.tar.gz.asc /tmp/musl.tar.gz mkdir -p "/tmp/dep_musl-${MUSL_VERSION}" cd "/tmp/dep_musl-${MUSL_VERSION}" # shellcheck disable=SC2002 cat /tmp/musl.tar.gz | tar --strip-components=1 -xzC . echo "musl-${MUSL_VERSION}: configure..." ./configure --disable-shared --prefix="$HOME"/staticdeps > /dev/null echo "musl-${MUSL_VERSION}: make..." make -s -j4 echo "musl-${MUSL_VERSION}: make install..." make -s install rm -rf "/tmp/dep_musl-${MUSL_VERSION}" # native-image expects to find an executable at this path. ln -s ~/staticdeps/bin/musl-gcc ~/staticdeps/bin/x86_64-linux-musl-gcc fi echo "${HOME}/staticdeps/bin" >> "$GITHUB_PATH" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -Dpkl.musl=true pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-alpine-linux-amd64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-alpine-linux-amd64-snapshot path: '**/build/test-results/**/*.xml' if-no-files-found: ignore - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-alpine-linux-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-cli-windows-amd64-snapshot: if: contains(github.event.pull_request.body, '[native]') || contains(github.event.pull_request.body, '[native-pkl-cli]') || contains(github.event.pull_request.body, '[native-pkl-cli-windows]') || contains(github.event.pull_request.body, '[native-pkl-cli-windows-amd64]') || contains(github.event.pull_request.body, '[native-pkl-cli-windows-amd64]') runs-on: windows-latest env: LANG: en_US.UTF-8 JAVA_HOME: /jdk steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-windows-amd64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-windows-amd64-snapshot path: '**/build/test-results/**/*.xml' if-no-files-found: ignore - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-windows-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-doc-macOS-amd64-snapshot: if: (contains(github.event.pull_request.body, '[native]') || contains(github.event.pull_request.body, '[native-pkl-doc]') || contains(github.event.pull_request.body, '[native-pkl-doc-macOS]') || contains(github.event.pull_request.body, '[native-pkl-doc-macOS-amd64]') || contains(github.event.pull_request.body, '[native-pkl-doc-macOS-amd64]')) && github.repository_owner == 'apple' runs-on: - self-hosted - macos env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -Dpkl.targetArch=amd64 "-Dpkl.native--native-compiler-path=${GITHUB_WORKSPACE}/.github/scripts/cc_macos_amd64.sh" pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-macOS-amd64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-macOS-amd64-snapshot path: '**/build/test-results/**/*.xml' if-no-files-found: ignore - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-macOS-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-doc-linux-amd64-snapshot: if: contains(github.event.pull_request.body, '[native]') || contains(github.event.pull_request.body, '[native-pkl-doc]') || contains(github.event.pull_request.body, '[native-pkl-doc-linux]') || contains(github.event.pull_request.body, '[native-pkl-doc-linux-amd64]') || contains(github.event.pull_request.body, '[native-pkl-doc-linux-amd64]') runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - name: Install deps run: dnf install -y git binutils gcc glibc-devel zlib-devel libstdc++-static glibc-langpack-en - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Fix git ownership run: git status || git config --system --add safe.directory "$GITHUB_WORKSPACE" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-linux-amd64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-linux-amd64-snapshot path: '**/build/test-results/**/*.xml' if-no-files-found: ignore - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-linux-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore container: image: redhat/ubi8:8.10 pkl-doc-macOS-aarch64-snapshot: if: (contains(github.event.pull_request.body, '[native]') || contains(github.event.pull_request.body, '[native-pkl-doc]') || contains(github.event.pull_request.body, '[native-pkl-doc-macOS]') || contains(github.event.pull_request.body, '[native-pkl-doc-macOS-aarch64]') || contains(github.event.pull_request.body, '[native-pkl-doc-macOS-aarch64]')) && github.repository_owner == 'apple' runs-on: - self-hosted - macos env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: aarch64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-macOS-aarch64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-macOS-aarch64-snapshot path: '**/build/test-results/**/*.xml' if-no-files-found: ignore - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-macOS-aarch64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-doc-linux-aarch64-snapshot: if: contains(github.event.pull_request.body, '[native]') || contains(github.event.pull_request.body, '[native-pkl-doc]') || contains(github.event.pull_request.body, '[native-pkl-doc-linux]') || contains(github.event.pull_request.body, '[native-pkl-doc-linux-aarch64]') || contains(github.event.pull_request.body, '[native-pkl-doc-linux-aarch64]') runs-on: ubuntu-24.04-arm env: LANG: en_US.UTF-8 steps: - name: Install deps run: dnf install -y git binutils gcc glibc-devel zlib-devel libstdc++-static glibc-langpack-en - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: aarch64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Fix git ownership run: git status || git config --system --add safe.directory "$GITHUB_WORKSPACE" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-linux-aarch64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-linux-aarch64-snapshot path: '**/build/test-results/**/*.xml' if-no-files-found: ignore - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-linux-aarch64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore container: image: redhat/ubi8:8.10 pkl-doc-alpine-linux-amd64-snapshot: if: contains(github.event.pull_request.body, '[native]') || contains(github.event.pull_request.body, '[native-pkl-doc]') || contains(github.event.pull_request.body, '[native-pkl-doc-linux]') || contains(github.event.pull_request.body, '[native-pkl-doc-linux-amd64]') || contains(github.event.pull_request.body, '[native-pkl-doc-linux-amd64]') || contains(github.event.pull_request.body, '[native-pkl-doc-alpine-linux-amd64]') runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Install musl and zlib run: | set -e mkdir -p ~/staticdeps/ ZLIB_VERSION="1.2.13" MUSL_VERSION="1.2.5" # install zlib if [[ ! -f ~/staticdeps/include/zlib.h ]]; then # Download zlib tarball and signature curl -Lf "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz" -o /tmp/zlib.tar.gz curl -Lf "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz.asc" -o /tmp/zlib.tar.gz.asc # Import zlib GPG key gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 5ED46A6721D365587791E2AA783FCD8E58BCAFBA # Verify GPG signature echo "Verifying zlib GPG signature..." gpg --verify /tmp/zlib.tar.gz.asc /tmp/zlib.tar.gz mkdir -p "/tmp/dep_zlib-${ZLIB_VERSION}" cd "/tmp/dep_zlib-${ZLIB_VERSION}" # shellcheck disable=SC2002 cat /tmp/zlib.tar.gz | tar --strip-components=1 -xzC . echo "zlib-${ZLIB_VERSION}: configure..." ./configure --static --prefix="$HOME"/staticdeps > /dev/null echo "zlib-${ZLIB_VERSION}: make..." make -s -j4 echo "zlib-${ZLIB_VERSION}: make install..." make -s install rm -rf /tmp/dep_zlib-${ZLIB_VERSION} fi # install musl if [[ ! -f ~/staticdeps/bin/x86_64-linux-musl-gcc ]]; then # Download musl tarball and signature curl -Lf "https://musl.libc.org/releases/musl-${MUSL_VERSION}.tar.gz" -o /tmp/musl.tar.gz curl -Lf "https://musl.libc.org/releases/musl-${MUSL_VERSION}.tar.gz.asc" -o /tmp/musl.tar.gz.asc # Import musl GPG key gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 836489290BB6B70F99FFDA0556BCDB593020450F # Verify GPG signature echo "Verifying musl GPG signature..." gpg --verify /tmp/musl.tar.gz.asc /tmp/musl.tar.gz mkdir -p "/tmp/dep_musl-${MUSL_VERSION}" cd "/tmp/dep_musl-${MUSL_VERSION}" # shellcheck disable=SC2002 cat /tmp/musl.tar.gz | tar --strip-components=1 -xzC . echo "musl-${MUSL_VERSION}: configure..." ./configure --disable-shared --prefix="$HOME"/staticdeps > /dev/null echo "musl-${MUSL_VERSION}: make..." make -s -j4 echo "musl-${MUSL_VERSION}: make install..." make -s install rm -rf "/tmp/dep_musl-${MUSL_VERSION}" # native-image expects to find an executable at this path. ln -s ~/staticdeps/bin/musl-gcc ~/staticdeps/bin/x86_64-linux-musl-gcc fi echo "${HOME}/staticdeps/bin" >> "$GITHUB_PATH" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -Dpkl.musl=true pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-alpine-linux-amd64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-alpine-linux-amd64-snapshot path: '**/build/test-results/**/*.xml' if-no-files-found: ignore - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-alpine-linux-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-doc-windows-amd64-snapshot: if: contains(github.event.pull_request.body, '[native]') || contains(github.event.pull_request.body, '[native-pkl-doc]') || contains(github.event.pull_request.body, '[native-pkl-doc-windows]') || contains(github.event.pull_request.body, '[native-pkl-doc-windows-amd64]') || contains(github.event.pull_request.body, '[native-pkl-doc-windows-amd64]') runs-on: windows-latest env: LANG: en_US.UTF-8 JAVA_HOME: /jdk steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-windows-amd64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-windows-amd64-snapshot path: '**/build/test-results/**/*.xml' if-no-files-found: ignore - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-windows-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore upload-event-file: runs-on: ubuntu-latest steps: - name: Upload event file if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-event-file path: ${{ github.event_path }} check-pkl-github-actions: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - name: Setup Pkl id: setup-pkl env: PKL_VERSION: 0.30.0 PKL_FILENAME: pkl PKL_DOWNLOAD_URL: https://github.com/apple/pkl/releases/download/0.30.0/pkl-linux-amd64 shell: bash run: |- DIR="$(mktemp -d /tmp/pkl-$PKL_VERSION-XXXXXX)" PKL_EXEC="$DIR/$PKL_FILENAME" curl -sfL -o $PKL_EXEC "$PKL_DOWNLOAD_URL" chmod +x $PKL_EXEC echo "$DIR" >> "$GITHUB_PATH" echo "pkl_exec=$PKL_EXEC" >> "$GITHUB_OUTPUT" - shell: bash run: rm -rf .github/**/[a-z]*.yml - shell: bash run: pkl eval -m .github/ --project-dir .github/ .github/index.pkl - name: check git status shell: bash run: |- if [ -n "$(git status --porcelain)" ]; then echo "Running pkl resulted in a diff! You likely need to run 'pkl eval' and commit the changes." git diff --name-only exit 1 fi ================================================ FILE: .github/workflows/release-branch.yml ================================================ # Generated from Workflow.pkl. DO NOT EDIT. name: Build (release branch) 'on': push: branches: - release/* tags-ignore: - '**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true permissions: contents: read jobs: gradle-check: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false fetch-depth: 0 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: check shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true check - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-gradle-check path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-gradle-check path: '**/build/reports/tests/**/*' if-no-files-found: ignore gradle-check-windows: runs-on: windows-latest env: LANG: en_US.UTF-8 JAVA_HOME: /jdk steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false fetch-depth: 0 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: check shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true check - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-gradle-check-windows path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-gradle-check-windows path: '**/build/reports/tests/**/*' if-no-files-found: ignore bench: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: bench:jmh shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true bench:jmh gradle-compatibility: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false fetch-depth: 0 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: :pkl-gradle:build :pkl-gradle:compatibilityTestReleases shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true :pkl-gradle:build :pkl-gradle:compatibilityTestReleases - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-gradle-compatibility path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-gradle-compatibility path: '**/build/reports/tests/**/*' if-no-files-found: ignore java-executables-snapshot: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false fetch-depth: 0 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - name: gradle build java executables shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-doc:build pkl-cli:build pkl-codegen-java:build pkl-codegen-kotlin:build - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-java path: '*/build/executable/**/*' - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-java-executables-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-java-executables-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-cli-macOS-amd64-snapshot: if: github.repository_owner == 'apple' runs-on: - self-hosted - macos env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -Dpkl.targetArch=amd64 "-Dpkl.native--native-compiler-path=${GITHUB_WORKSPACE}/.github/scripts/cc_macos_amd64.sh" pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-macOS-amd64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-macOS-amd64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-macOS-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-cli-linux-amd64-snapshot: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - name: Install deps run: dnf install -y git binutils gcc glibc-devel zlib-devel libstdc++-static glibc-langpack-en - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Fix git ownership run: git status || git config --system --add safe.directory "$GITHUB_WORKSPACE" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-linux-amd64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-linux-amd64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-linux-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore container: image: redhat/ubi8:8.10 pkl-cli-macOS-aarch64-snapshot: if: github.repository_owner == 'apple' runs-on: - self-hosted - macos env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: aarch64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-macOS-aarch64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-macOS-aarch64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-macOS-aarch64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-cli-linux-aarch64-snapshot: runs-on: ubuntu-24.04-arm env: LANG: en_US.UTF-8 steps: - name: Install deps run: dnf install -y git binutils gcc glibc-devel zlib-devel libstdc++-static glibc-langpack-en - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: aarch64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Fix git ownership run: git status || git config --system --add safe.directory "$GITHUB_WORKSPACE" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-linux-aarch64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-linux-aarch64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-linux-aarch64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore container: image: redhat/ubi8:8.10 pkl-cli-alpine-linux-amd64-snapshot: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Install musl and zlib run: | set -e mkdir -p ~/staticdeps/ ZLIB_VERSION="1.2.13" MUSL_VERSION="1.2.5" # install zlib if [[ ! -f ~/staticdeps/include/zlib.h ]]; then # Download zlib tarball and signature curl -Lf "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz" -o /tmp/zlib.tar.gz curl -Lf "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz.asc" -o /tmp/zlib.tar.gz.asc # Import zlib GPG key gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 5ED46A6721D365587791E2AA783FCD8E58BCAFBA # Verify GPG signature echo "Verifying zlib GPG signature..." gpg --verify /tmp/zlib.tar.gz.asc /tmp/zlib.tar.gz mkdir -p "/tmp/dep_zlib-${ZLIB_VERSION}" cd "/tmp/dep_zlib-${ZLIB_VERSION}" # shellcheck disable=SC2002 cat /tmp/zlib.tar.gz | tar --strip-components=1 -xzC . echo "zlib-${ZLIB_VERSION}: configure..." ./configure --static --prefix="$HOME"/staticdeps > /dev/null echo "zlib-${ZLIB_VERSION}: make..." make -s -j4 echo "zlib-${ZLIB_VERSION}: make install..." make -s install rm -rf /tmp/dep_zlib-${ZLIB_VERSION} fi # install musl if [[ ! -f ~/staticdeps/bin/x86_64-linux-musl-gcc ]]; then # Download musl tarball and signature curl -Lf "https://musl.libc.org/releases/musl-${MUSL_VERSION}.tar.gz" -o /tmp/musl.tar.gz curl -Lf "https://musl.libc.org/releases/musl-${MUSL_VERSION}.tar.gz.asc" -o /tmp/musl.tar.gz.asc # Import musl GPG key gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 836489290BB6B70F99FFDA0556BCDB593020450F # Verify GPG signature echo "Verifying musl GPG signature..." gpg --verify /tmp/musl.tar.gz.asc /tmp/musl.tar.gz mkdir -p "/tmp/dep_musl-${MUSL_VERSION}" cd "/tmp/dep_musl-${MUSL_VERSION}" # shellcheck disable=SC2002 cat /tmp/musl.tar.gz | tar --strip-components=1 -xzC . echo "musl-${MUSL_VERSION}: configure..." ./configure --disable-shared --prefix="$HOME"/staticdeps > /dev/null echo "musl-${MUSL_VERSION}: make..." make -s -j4 echo "musl-${MUSL_VERSION}: make install..." make -s install rm -rf "/tmp/dep_musl-${MUSL_VERSION}" # native-image expects to find an executable at this path. ln -s ~/staticdeps/bin/musl-gcc ~/staticdeps/bin/x86_64-linux-musl-gcc fi echo "${HOME}/staticdeps/bin" >> "$GITHUB_PATH" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -Dpkl.musl=true pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-alpine-linux-amd64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-alpine-linux-amd64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-alpine-linux-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-cli-windows-amd64-snapshot: runs-on: windows-latest env: LANG: en_US.UTF-8 JAVA_HOME: /jdk steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-windows-amd64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-windows-amd64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-windows-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-doc-macOS-amd64-snapshot: if: github.repository_owner == 'apple' runs-on: - self-hosted - macos env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -Dpkl.targetArch=amd64 "-Dpkl.native--native-compiler-path=${GITHUB_WORKSPACE}/.github/scripts/cc_macos_amd64.sh" pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-macOS-amd64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-macOS-amd64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-macOS-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-doc-linux-amd64-snapshot: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - name: Install deps run: dnf install -y git binutils gcc glibc-devel zlib-devel libstdc++-static glibc-langpack-en - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Fix git ownership run: git status || git config --system --add safe.directory "$GITHUB_WORKSPACE" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-linux-amd64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-linux-amd64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-linux-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore container: image: redhat/ubi8:8.10 pkl-doc-macOS-aarch64-snapshot: if: github.repository_owner == 'apple' runs-on: - self-hosted - macos env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: aarch64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-macOS-aarch64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-macOS-aarch64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-macOS-aarch64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-doc-linux-aarch64-snapshot: runs-on: ubuntu-24.04-arm env: LANG: en_US.UTF-8 steps: - name: Install deps run: dnf install -y git binutils gcc glibc-devel zlib-devel libstdc++-static glibc-langpack-en - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: aarch64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Fix git ownership run: git status || git config --system --add safe.directory "$GITHUB_WORKSPACE" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-linux-aarch64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-linux-aarch64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-linux-aarch64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore container: image: redhat/ubi8:8.10 pkl-doc-alpine-linux-amd64-snapshot: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Install musl and zlib run: | set -e mkdir -p ~/staticdeps/ ZLIB_VERSION="1.2.13" MUSL_VERSION="1.2.5" # install zlib if [[ ! -f ~/staticdeps/include/zlib.h ]]; then # Download zlib tarball and signature curl -Lf "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz" -o /tmp/zlib.tar.gz curl -Lf "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz.asc" -o /tmp/zlib.tar.gz.asc # Import zlib GPG key gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 5ED46A6721D365587791E2AA783FCD8E58BCAFBA # Verify GPG signature echo "Verifying zlib GPG signature..." gpg --verify /tmp/zlib.tar.gz.asc /tmp/zlib.tar.gz mkdir -p "/tmp/dep_zlib-${ZLIB_VERSION}" cd "/tmp/dep_zlib-${ZLIB_VERSION}" # shellcheck disable=SC2002 cat /tmp/zlib.tar.gz | tar --strip-components=1 -xzC . echo "zlib-${ZLIB_VERSION}: configure..." ./configure --static --prefix="$HOME"/staticdeps > /dev/null echo "zlib-${ZLIB_VERSION}: make..." make -s -j4 echo "zlib-${ZLIB_VERSION}: make install..." make -s install rm -rf /tmp/dep_zlib-${ZLIB_VERSION} fi # install musl if [[ ! -f ~/staticdeps/bin/x86_64-linux-musl-gcc ]]; then # Download musl tarball and signature curl -Lf "https://musl.libc.org/releases/musl-${MUSL_VERSION}.tar.gz" -o /tmp/musl.tar.gz curl -Lf "https://musl.libc.org/releases/musl-${MUSL_VERSION}.tar.gz.asc" -o /tmp/musl.tar.gz.asc # Import musl GPG key gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 836489290BB6B70F99FFDA0556BCDB593020450F # Verify GPG signature echo "Verifying musl GPG signature..." gpg --verify /tmp/musl.tar.gz.asc /tmp/musl.tar.gz mkdir -p "/tmp/dep_musl-${MUSL_VERSION}" cd "/tmp/dep_musl-${MUSL_VERSION}" # shellcheck disable=SC2002 cat /tmp/musl.tar.gz | tar --strip-components=1 -xzC . echo "musl-${MUSL_VERSION}: configure..." ./configure --disable-shared --prefix="$HOME"/staticdeps > /dev/null echo "musl-${MUSL_VERSION}: make..." make -s -j4 echo "musl-${MUSL_VERSION}: make install..." make -s install rm -rf "/tmp/dep_musl-${MUSL_VERSION}" # native-image expects to find an executable at this path. ln -s ~/staticdeps/bin/musl-gcc ~/staticdeps/bin/x86_64-linux-musl-gcc fi echo "${HOME}/staticdeps/bin" >> "$GITHUB_PATH" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -Dpkl.musl=true pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-alpine-linux-amd64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-alpine-linux-amd64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-alpine-linux-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-doc-windows-amd64-snapshot: runs-on: windows-latest env: LANG: en_US.UTF-8 JAVA_HOME: /jdk steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-windows-amd64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-windows-amd64-snapshot path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-windows-amd64-snapshot path: '**/build/reports/tests/**/*' if-no-files-found: ignore publish-test-results: if: '!cancelled()' needs: - gradle-check - gradle-check-windows - gradle-compatibility - java-executables-snapshot - pkl-cli-macOS-amd64-snapshot - pkl-cli-linux-amd64-snapshot - pkl-cli-macOS-aarch64-snapshot - pkl-cli-linux-aarch64-snapshot - pkl-cli-alpine-linux-amd64-snapshot - pkl-cli-windows-amd64-snapshot - pkl-doc-macOS-amd64-snapshot - pkl-doc-linux-amd64-snapshot - pkl-doc-macOS-aarch64-snapshot - pkl-doc-linux-aarch64-snapshot - pkl-doc-alpine-linux-amd64-snapshot - pkl-doc-windows-amd64-snapshot permissions: checks: write runs-on: ubuntu-latest steps: - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 with: pattern: test-results-xml-* - name: Publish test results if: '!cancelled()' uses: EnricoMi/publish-unit-test-result-action@c950f6fb443cb5af20a377fd0dfaa78838901040 # v2 with: comment_mode: 'off' files: test-results-xml-*/**/*.xml - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-publish-test-results path: '**/build/reports/tests/**/*' if-no-files-found: ignore trigger-downstream-builds: if: github.repository_owner == 'apple' needs: - gradle-check - gradle-check-windows - bench - gradle-compatibility - java-executables-snapshot - pkl-cli-macOS-amd64-snapshot - pkl-cli-linux-amd64-snapshot - pkl-cli-macOS-aarch64-snapshot - pkl-cli-linux-aarch64-snapshot - pkl-cli-alpine-linux-amd64-snapshot - pkl-cli-windows-amd64-snapshot - pkl-doc-macOS-amd64-snapshot - pkl-doc-linux-amd64-snapshot - pkl-doc-macOS-aarch64-snapshot - pkl-doc-linux-aarch64-snapshot - pkl-doc-alpine-linux-amd64-snapshot - pkl-doc-windows-amd64-snapshot - publish-test-results runs-on: ubuntu-latest steps: - name: Create app token id: app-token uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2 with: app-id: ${{ secrets.PKL_CI_CLIENT_ID }} private-key: ${{ secrets.PKL_CI }} owner: ${{ github.repository_owner }} - name: Trigger pkl-lang.org build env: GH_TOKEN: ${{ steps.app-token.outputs.token }} SOURCE_RUN: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: |- gh workflow run \ --repo apple/pkl-lang.org \ --ref main \ --field source_run="${SOURCE_RUN}" \ main.yml ================================================ FILE: .github/workflows/release.yml ================================================ # Generated from Workflow.pkl. DO NOT EDIT. name: Release 'on': push: branches-ignore: - '**' tags: - '**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: false permissions: contents: read jobs: gradle-check: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false fetch-depth: 0 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: check shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true check - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-gradle-check path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-gradle-check path: '**/build/reports/tests/**/*' if-no-files-found: ignore gradle-check-windows: runs-on: windows-latest env: LANG: en_US.UTF-8 JAVA_HOME: /jdk steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false fetch-depth: 0 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: check shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true check - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-gradle-check-windows path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-gradle-check-windows path: '**/build/reports/tests/**/*' if-no-files-found: ignore bench: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: bench:jmh shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true bench:jmh gradle-compatibility: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false fetch-depth: 0 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: :pkl-gradle:build :pkl-gradle:compatibilityTestReleases shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true :pkl-gradle:build :pkl-gradle:compatibilityTestReleases - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-gradle-compatibility path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-gradle-compatibility path: '**/build/reports/tests/**/*' if-no-files-found: ignore java-executables-release: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false fetch-depth: 0 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - name: gradle build java executables shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -DreleaseBuild=true pkl-doc:build pkl-cli:build pkl-codegen-java:build pkl-codegen-kotlin:build - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-java path: '*/build/executable/**/*' - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-java-executables-release path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-java-executables-release path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-cli-macOS-amd64-release: if: github.repository_owner == 'apple' runs-on: - self-hosted - macos env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -DreleaseBuild=true -Dpkl.targetArch=amd64 "-Dpkl.native--native-compiler-path=${GITHUB_WORKSPACE}/.github/scripts/cc_macos_amd64.sh" pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-macOS-amd64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-macOS-amd64-release path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-macOS-amd64-release path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-cli-linux-amd64-release: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - name: Install deps run: dnf install -y git binutils gcc glibc-devel zlib-devel libstdc++-static glibc-langpack-en - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Fix git ownership run: git status || git config --system --add safe.directory "$GITHUB_WORKSPACE" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -DreleaseBuild=true pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-linux-amd64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-linux-amd64-release path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-linux-amd64-release path: '**/build/reports/tests/**/*' if-no-files-found: ignore container: image: redhat/ubi8:8.10 pkl-cli-macOS-aarch64-release: if: github.repository_owner == 'apple' runs-on: - self-hosted - macos env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: aarch64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -DreleaseBuild=true pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-macOS-aarch64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-macOS-aarch64-release path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-macOS-aarch64-release path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-cli-linux-aarch64-release: runs-on: ubuntu-24.04-arm env: LANG: en_US.UTF-8 steps: - name: Install deps run: dnf install -y git binutils gcc glibc-devel zlib-devel libstdc++-static glibc-langpack-en - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: aarch64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Fix git ownership run: git status || git config --system --add safe.directory "$GITHUB_WORKSPACE" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -DreleaseBuild=true pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-linux-aarch64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-linux-aarch64-release path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-linux-aarch64-release path: '**/build/reports/tests/**/*' if-no-files-found: ignore container: image: redhat/ubi8:8.10 pkl-cli-alpine-linux-amd64-release: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Install musl and zlib run: | set -e mkdir -p ~/staticdeps/ ZLIB_VERSION="1.2.13" MUSL_VERSION="1.2.5" # install zlib if [[ ! -f ~/staticdeps/include/zlib.h ]]; then # Download zlib tarball and signature curl -Lf "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz" -o /tmp/zlib.tar.gz curl -Lf "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz.asc" -o /tmp/zlib.tar.gz.asc # Import zlib GPG key gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 5ED46A6721D365587791E2AA783FCD8E58BCAFBA # Verify GPG signature echo "Verifying zlib GPG signature..." gpg --verify /tmp/zlib.tar.gz.asc /tmp/zlib.tar.gz mkdir -p "/tmp/dep_zlib-${ZLIB_VERSION}" cd "/tmp/dep_zlib-${ZLIB_VERSION}" # shellcheck disable=SC2002 cat /tmp/zlib.tar.gz | tar --strip-components=1 -xzC . echo "zlib-${ZLIB_VERSION}: configure..." ./configure --static --prefix="$HOME"/staticdeps > /dev/null echo "zlib-${ZLIB_VERSION}: make..." make -s -j4 echo "zlib-${ZLIB_VERSION}: make install..." make -s install rm -rf /tmp/dep_zlib-${ZLIB_VERSION} fi # install musl if [[ ! -f ~/staticdeps/bin/x86_64-linux-musl-gcc ]]; then # Download musl tarball and signature curl -Lf "https://musl.libc.org/releases/musl-${MUSL_VERSION}.tar.gz" -o /tmp/musl.tar.gz curl -Lf "https://musl.libc.org/releases/musl-${MUSL_VERSION}.tar.gz.asc" -o /tmp/musl.tar.gz.asc # Import musl GPG key gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 836489290BB6B70F99FFDA0556BCDB593020450F # Verify GPG signature echo "Verifying musl GPG signature..." gpg --verify /tmp/musl.tar.gz.asc /tmp/musl.tar.gz mkdir -p "/tmp/dep_musl-${MUSL_VERSION}" cd "/tmp/dep_musl-${MUSL_VERSION}" # shellcheck disable=SC2002 cat /tmp/musl.tar.gz | tar --strip-components=1 -xzC . echo "musl-${MUSL_VERSION}: configure..." ./configure --disable-shared --prefix="$HOME"/staticdeps > /dev/null echo "musl-${MUSL_VERSION}: make..." make -s -j4 echo "musl-${MUSL_VERSION}: make install..." make -s install rm -rf "/tmp/dep_musl-${MUSL_VERSION}" # native-image expects to find an executable at this path. ln -s ~/staticdeps/bin/musl-gcc ~/staticdeps/bin/x86_64-linux-musl-gcc fi echo "${HOME}/staticdeps/bin" >> "$GITHUB_PATH" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -DreleaseBuild=true -Dpkl.musl=true pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-alpine-linux-amd64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-alpine-linux-amd64-release path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-alpine-linux-amd64-release path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-cli-windows-amd64-release: runs-on: windows-latest env: LANG: en_US.UTF-8 JAVA_HOME: /jdk steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -DreleaseBuild=true pkl-cli:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-cli-windows-amd64 path: pkl-cli*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-cli-windows-amd64-release path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-cli-windows-amd64-release path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-doc-macOS-amd64-release: if: github.repository_owner == 'apple' runs-on: - self-hosted - macos env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -DreleaseBuild=true -Dpkl.targetArch=amd64 "-Dpkl.native--native-compiler-path=${GITHUB_WORKSPACE}/.github/scripts/cc_macos_amd64.sh" pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-macOS-amd64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-macOS-amd64-release path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-macOS-amd64-release path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-doc-linux-amd64-release: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - name: Install deps run: dnf install -y git binutils gcc glibc-devel zlib-devel libstdc++-static glibc-langpack-en - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Fix git ownership run: git status || git config --system --add safe.directory "$GITHUB_WORKSPACE" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -DreleaseBuild=true pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-linux-amd64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-linux-amd64-release path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-linux-amd64-release path: '**/build/reports/tests/**/*' if-no-files-found: ignore container: image: redhat/ubi8:8.10 pkl-doc-macOS-aarch64-release: if: github.repository_owner == 'apple' runs-on: - self-hosted - macos env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: aarch64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -DreleaseBuild=true pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-macOS-aarch64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-macOS-aarch64-release path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-macOS-aarch64-release path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-doc-linux-aarch64-release: runs-on: ubuntu-24.04-arm env: LANG: en_US.UTF-8 steps: - name: Install deps run: dnf install -y git binutils gcc glibc-devel zlib-devel libstdc++-static glibc-langpack-en - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: aarch64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Fix git ownership run: git status || git config --system --add safe.directory "$GITHUB_WORKSPACE" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -DreleaseBuild=true pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-linux-aarch64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-linux-aarch64-release path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-linux-aarch64-release path: '**/build/reports/tests/**/*' if-no-files-found: ignore container: image: redhat/ubi8:8.10 pkl-doc-alpine-linux-amd64-release: runs-on: ubuntu-latest env: LANG: en_US.UTF-8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: Install musl and zlib run: | set -e mkdir -p ~/staticdeps/ ZLIB_VERSION="1.2.13" MUSL_VERSION="1.2.5" # install zlib if [[ ! -f ~/staticdeps/include/zlib.h ]]; then # Download zlib tarball and signature curl -Lf "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz" -o /tmp/zlib.tar.gz curl -Lf "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz.asc" -o /tmp/zlib.tar.gz.asc # Import zlib GPG key gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 5ED46A6721D365587791E2AA783FCD8E58BCAFBA # Verify GPG signature echo "Verifying zlib GPG signature..." gpg --verify /tmp/zlib.tar.gz.asc /tmp/zlib.tar.gz mkdir -p "/tmp/dep_zlib-${ZLIB_VERSION}" cd "/tmp/dep_zlib-${ZLIB_VERSION}" # shellcheck disable=SC2002 cat /tmp/zlib.tar.gz | tar --strip-components=1 -xzC . echo "zlib-${ZLIB_VERSION}: configure..." ./configure --static --prefix="$HOME"/staticdeps > /dev/null echo "zlib-${ZLIB_VERSION}: make..." make -s -j4 echo "zlib-${ZLIB_VERSION}: make install..." make -s install rm -rf /tmp/dep_zlib-${ZLIB_VERSION} fi # install musl if [[ ! -f ~/staticdeps/bin/x86_64-linux-musl-gcc ]]; then # Download musl tarball and signature curl -Lf "https://musl.libc.org/releases/musl-${MUSL_VERSION}.tar.gz" -o /tmp/musl.tar.gz curl -Lf "https://musl.libc.org/releases/musl-${MUSL_VERSION}.tar.gz.asc" -o /tmp/musl.tar.gz.asc # Import musl GPG key gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 836489290BB6B70F99FFDA0556BCDB593020450F # Verify GPG signature echo "Verifying musl GPG signature..." gpg --verify /tmp/musl.tar.gz.asc /tmp/musl.tar.gz mkdir -p "/tmp/dep_musl-${MUSL_VERSION}" cd "/tmp/dep_musl-${MUSL_VERSION}" # shellcheck disable=SC2002 cat /tmp/musl.tar.gz | tar --strip-components=1 -xzC . echo "musl-${MUSL_VERSION}: configure..." ./configure --disable-shared --prefix="$HOME"/staticdeps > /dev/null echo "musl-${MUSL_VERSION}: make..." make -s -j4 echo "musl-${MUSL_VERSION}: make install..." make -s install rm -rf "/tmp/dep_musl-${MUSL_VERSION}" # native-image expects to find an executable at this path. ln -s ~/staticdeps/bin/musl-gcc ~/staticdeps/bin/x86_64-linux-musl-gcc fi echo "${HOME}/staticdeps/bin" >> "$GITHUB_PATH" - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -DreleaseBuild=true -Dpkl.musl=true pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-alpine-linux-amd64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-alpine-linux-amd64-release path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-alpine-linux-amd64-release path: '**/build/reports/tests/**/*' if-no-files-found: ignore pkl-doc-windows-amd64-release: runs-on: windows-latest env: LANG: en_US.UTF-8 JAVA_HOME: /jdk steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - name: gradle buildNative shell: bash run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -DreleaseBuild=true pkl-doc:buildNative - name: Upload executable artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: executable-pkl-doc-windows-amd64 path: pkl-doc*/build/executable/**/* - name: Upload Test Result XML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-xml-pkl-doc-windows-amd64-release path: '**/build/test-results/**/*.xml' - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-pkl-doc-windows-amd64-release path: '**/build/reports/tests/**/*' if-no-files-found: ignore deploy-release: needs: - gradle-check - gradle-check-windows - bench - gradle-compatibility - java-executables-release - pkl-cli-macOS-amd64-release - pkl-cli-linux-amd64-release - pkl-cli-macOS-aarch64-release - pkl-cli-linux-aarch64-release - pkl-cli-alpine-linux-amd64-release - pkl-cli-windows-amd64-release - pkl-doc-macOS-amd64-release - pkl-doc-linux-amd64-release - pkl-doc-macOS-aarch64-release - pkl-doc-linux-aarch64-release - pkl-doc-alpine-linux-amd64-release - pkl-doc-windows-amd64-release runs-on: ubuntu-latest env: LANG: en_US.UTF-8 environment: maven-release steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: temurin architecture: x64 - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 with: {} - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 with: pattern: executable-** merge-multiple: true - env: ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.ORG_GRADLE_PROJECT_SIGNINGKEYID }} ORG_GRADLE_PROJECT_signingKey: ${{ secrets.ORG_GRADLE_PROJECT_SIGNINGKEY }} ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.ORG_GRADLE_PROJECT_SIGNINGPASSWORD }} ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPEPASSWORD }} ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPEUSERNAME }} run: ./gradlew --info --stacktrace --no-daemon -DpklMultiJdkTesting=true -DreleaseBuild=true publishToSonatype closeAndReleaseSonatypeStagingRepository github-release: needs: deploy-release permissions: contents: write runs-on: ubuntu-latest steps: - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 with: pattern: executable-** merge-multiple: true - name: Publish release on GitHub env: GH_TOKEN: ${{ github.token }} TAG_NAME: ${{ github.ref_name }} GIT_SHA: ${{ github.sha }} GH_REPO: ${{ github.repository }} run: |- # exclude build_artifacts.txt from publish rm -f */build/executable/*.build_artifacts.txt find */build/executable/* -type d | xargs rm -rf gh release create ${TAG_NAME} \ --title "${TAG_NAME}" \ --target "${GIT_SHA}" \ --verify-tag \ --notes "Release notes: https://pkl-lang.org/main/current/release-notes/changelog.html#release-${TAG_NAME}" \ */build/executable/* publish-test-results: if: '!cancelled()' needs: - gradle-check - gradle-check-windows - gradle-compatibility - java-executables-release - pkl-cli-macOS-amd64-release - pkl-cli-linux-amd64-release - pkl-cli-macOS-aarch64-release - pkl-cli-linux-aarch64-release - pkl-cli-alpine-linux-amd64-release - pkl-cli-windows-amd64-release - pkl-doc-macOS-amd64-release - pkl-doc-linux-amd64-release - pkl-doc-macOS-aarch64-release - pkl-doc-linux-aarch64-release - pkl-doc-alpine-linux-amd64-release - pkl-doc-windows-amd64-release permissions: checks: write runs-on: ubuntu-latest steps: - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 with: pattern: test-results-xml-* - name: Publish test results if: '!cancelled()' uses: EnricoMi/publish-unit-test-result-action@c950f6fb443cb5af20a377fd0dfaa78838901040 # v2 with: comment_mode: 'off' files: test-results-xml-*/**/*.xml - name: Upload Test Result HTML if: '!cancelled()' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-html-publish-test-results path: '**/build/reports/tests/**/*' if-no-files-found: ignore trigger-downstream-builds: if: github.repository_owner == 'apple' needs: - gradle-check - gradle-check-windows - bench - gradle-compatibility - java-executables-release - pkl-cli-macOS-amd64-release - pkl-cli-linux-amd64-release - pkl-cli-macOS-aarch64-release - pkl-cli-linux-aarch64-release - pkl-cli-alpine-linux-amd64-release - pkl-cli-windows-amd64-release - pkl-doc-macOS-amd64-release - pkl-doc-linux-amd64-release - pkl-doc-macOS-aarch64-release - pkl-doc-linux-aarch64-release - pkl-doc-alpine-linux-amd64-release - pkl-doc-windows-amd64-release - deploy-release - github-release - publish-test-results runs-on: ubuntu-latest steps: - name: Create app token id: app-token uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2 with: app-id: ${{ secrets.PKL_CI_CLIENT_ID }} private-key: ${{ secrets.PKL_CI }} owner: ${{ github.repository_owner }} - name: Trigger pkl-lang.org build env: GH_TOKEN: ${{ steps.app-token.outputs.token }} SOURCE_RUN: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: |- gh workflow run \ --repo apple/pkl-lang.org \ --ref main \ --field source_run="${SOURCE_RUN}" \ main.yml ================================================ FILE: .github/workflows/test_report.yml ================================================ # Generated from Workflow.pkl. DO NOT EDIT. name: PR Test Reports 'on': workflow_run: types: - completed workflows: - Pull Request permissions: contents: read jobs: test-results: name: Test Results if: github.event.workflow_run.conclusion == 'success' || github.event.workflow_run.conclusion == 'failure' permissions: actions: read checks: write runs-on: ubuntu-latest steps: - name: Download artifacts uses: dawidd6/action-download-artifact@ac66b43f0e6a346234dd65d4d0c8fbb31cb316e5 # v11 with: path: artifacts name: test-results-.* name_is_regexp: true run_id: ${{ github.event.workflow_run.id }} - name: Publish test results uses: EnricoMi/publish-unit-test-result-action@c950f6fb443cb5af20a377fd0dfaa78838901040 # v2 with: commit: ${{ github.event.workflow_run.head_sha }} comment_mode: 'off' files: artifacts/**/*.xml event_file: artifacts/test-results-event-file/event.json event_name: ${{ github.event.workflow_run.event }} ================================================ FILE: .gitignore ================================================ # macOS .DS_STORE # Gradle .gradle/ build/ generated/ testgenerated/ # IntelliJ .idea/ !.idea/icon.svg !.idea/codestyles/ !.idea/inspectionProfiles/ !.idea/runConfigurations/ !.idea/scopes/ !.idea/vcs.xml .intellijPlatform/ .vscode/ .pkl-lsp/ .kotlin/ ================================================ FILE: .idea/vcs.xml ================================================ ================================================ FILE: .java-version ================================================ 21 ================================================ FILE: .mailmap ================================================ Jen Basch <421772+HT154@users.noreply.github.com> Jen Basch ================================================ FILE: CODE_OF_CONDUCT.md ================================================ ## Code of Conduct ### Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ### Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others’ private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ### Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ### Scope This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ### Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the open source team at opensource-conduct@group.apple.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project’s leadership. ### Attribution This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html ================================================ FILE: CONTRIBUTING.adoc ================================================ :uri-github-discussion: https://github.com/apple/pkl/discussions :uri-github-issue-pkl: https://github.com/apple/pkl/issues/new :uri-seven-rules: https://cbea.ms/git-commit/#seven-rules = Pkl Contributor's Guide Welcome to the Pkl community, and thank you for contributing! This guide explains how to get involved. * <> * <> * <> == Licensing Pkl is released under the Apache 2.0 license. This is why we require that, by submitting a pull request, you acknowledge that you have the right to license your contribution to Apple and the community, and agree that your contribution is licensed under the Apache 2.0 license. == Issue Tracking To file a bug or feature request, use {uri-github-issue-pkl}[GitHub]. Be sure to include the following information: * Context ** What are/were you trying to achieve? ** What's the impact of this bug/feature? For bug reports, additionally include the following information: * The output of `pkl --version`. * The complete error message. * The simplest possible steps to reproduce. == Pull Requests When preparing a pull request, follow this checklist: * Imitate the conventions of surrounding code. * Format code with `./gradlew spotlessApply` (otherwise the build will fail). * Verify that both the JVM build (`./gradlew build`) and native build (`./gradlew buildNative`) succeed. * Follow the {uri-seven-rules}[seven rules] of great Git commit messages: ** Separate subject from body with a blank line. ** Limit the subject line to 50 characters.footnote:not-enforced[This rule is not enforced in the Pkl project.] ** Capitalize the subject line. ** Do not end the subject line with a period. ** Use the imperative mood in the subject line. ** Wrap the body at 72 characters.footnote:not-enforced[] ** Use the body to explain what and why vs. how. IMPORTANT: If you plan to make substantial changes or add new features, we encourage you to first discuss them with the wider Pkl developer community. You can do this by filing a {uri-github-issue-pkl}[GitHub Issue] or by starting {uri-github-discussion}[GitHub Discussion]. This will save time and increases the chance of your pull request being accepted. == Maintainers The project’s maintainers (those with write access to the upstream repository) are listed in link:MAINTAINERS.adoc[]. ================================================ FILE: DEVELOPMENT.adoc ================================================ = Development :uri-gng: https://gng.dsun.org :uri-jenv: https://www.jenv.be :uri-intellij: https://www.jetbrains.com/idea/download/ :uri-native-prerequisites-linux: https://www.graalvm.org/latest/getting-started/linux/#prerequisites-for-native-image-on-linux :uri-native-prerequisites-windows: https://www.graalvm.org/latest/getting-started/windows/#prerequisites-for-native-image-on-windows == Setup . (mandatory) Install a JDK (JDK 21+ required). . (recommended) Install {uri-intellij}[IntelliJ IDEA] + To import the project into IntelliJ, go to File->Open and select the project's root directory. If the project is opened but not imported, look for a popup in the lower right corner and click its "Import Gradle Project" link. . (recommended) Install {uri-gng}[gng] + _gng_ enables you to run Gradle commands with `gw` (instead of `./gradlew`) from any subdirectory. . (recommended) Set up Git ignore-revs + `git config blame.ignoreRevsFile .git-blame-ignore-revs` . (recommended) Install {uri-jenv}[jenv] and plugins + _jenv_ uses specific JDK versions in certain subdirectories. _Pkl_ comes with a `.java-version` file specifying JDK 21. + Enable _jenv_ plugins for better handling by `gradle`: + [source,shell] ---- jenv enable-plugin gradle jenv enable-plugin export ---- . (optional) If you've named the original apple/pkl git repository something other than `origin`, set env var `PKL_ORIGINAL_REMOTE_NAME` to that name in your `.bashrc`, `.zshrc`, `config.fish` or however you manage your local environment. + This will allow spotless to pick the correct starting branch when formatting source code files. Otherwise, you might see that _every_ file has its copyright year updated. === Additional Linux Setup . (optional) To build the native executable (`./gradlew buildNative`), install {uri-native-prerequisites-linux}[Prerequisites For Native Image on Linux]. === Additional Windows Setup . (optional) Go to `System->For developers` and enable `Developer Mode`. Otherwise, some tests may fail due to insufficient file system privileges. . (optional) To build the native executable (`./gradlew buildNative`), install {uri-native-prerequisites-windows}[Prerequisites For Native Image on Windows]. == Common Build Commands [source,shell] ---- gw clean gw test # run all tests except native executable tests gw spotlessApply # fix code formatting gw build # build everything except native executables gw pkl-cli:testNative # run native executable tests gw pkl-cli:buildNative # build native executable for current platform pkl-cli/build/executable/jpkl # run Java executable pkl-cli/build/executable/pkl-macos-aarch64 # run Mac executable pkl-cli/build/executable/pkl-macos-amd64 # run Intel Mac executable pkl-cli/build/executable/pkl-linux-amd64 # run Linux executable pkl-cli/build/executable/pkl-alpine-linux-amd64 # run Alpine Linux executable pkl-cli/build/executable/pkl-windows-amd64.exe # run Windows executable ---- == Update Gradle . Go to https://gradle.org/release-checksums/ and copy the checksum for the new Gradle version . Run the following command *twice* (until it prints UP-TO-DATE): + [source,shell] ---- gw wrapper --gradle-version [version] --gradle-distribution-sha256-sum [sha] ---- . Commit the updated wrapper files == Update Dependencies . (optional) Update _gradle/libs.version.toml_ based on version information from https://search.maven.org, https://plugins.gradle.org, and GitHub repos . Run `gw updateDependencyLocks` . Validate changes with `gw build buildNative` . Review and commit the updated dependency lock files == Code Generation * Truffle code generation is performed by Truffle's annotation processor, which runs as part of task `:pkl-core:compileJava` ** Output dir is `generated/truffle/` == Remote JVM Debugging To enable remote JVM debugging when running Gradle tasks (e.g. test), add the flag `-Djvmdebug=true`. This will listen on port 5005. Example: `./gradlew test -Djvmdebug=true` == Snippet Test Plugin There is an IntelliJ plugin meant for development on the Pkl project itself. This plugin provides a split pane window when viewing snippet tests such as LanguageSnippetTests and FormatterSnippetTests. To install: 1. Run `./gradlew pkl-internal-intellij-plugin:buildPlugin`. 2. Within IntelliJ, run the action "Install Plugin From Disk...". 3. Select the zip file within `pkl-internal-intellij-plugin/build/distributions`. == Resources For automated build setup examples see our https://github.com/apple/pkl/blob/main/.github/[GitHub Actions] jobs like our https://github.com/apple/pkl/blob/main/.github/jobs/BuildNativeJob.pkl[BuildNativeJob.pkl], where we build Pkl automatically. === Truffle * http://ssw.jku.at/Research/Projects/JVM/Truffle.html[Homepage] * https://github.com/graalvm/truffle[GitHub] * http://lafo.ssw.uni-linz.ac.at/javadoc/truffle/latest/[Javadoc] * https://mail.openjdk.org/pipermail/graal-dev/[Mailing List] * https://medium.com/@octskyward/graal-truffle-134d8f28fb69#.2db370y2g[Graal & Truffle (Article)] * https://comserv.cs.ut.ee/home/files/Pool_ComputerScience_2016.pdf?study=ATILoputoo&reference=6319668E7151D556131810BC3F4A627D7FEF5F3B[Truffle Overview (see chapter 1)] * https://gist.github.com/smarr/d1f8f2101b5cc8e14e12[Truffle: Languages and Material] * https://github.com/smarr/truffle-notes[Truffle Notes] * https://www.graalvm.org/latest/graalvm-as-a-platform/language-implementation-framework/[Truffle Language Implementation Framework] === Other Config Languages * https://github.com/google/jsonnet[Jsonnet] * https://github.com/dhall-lang/dhall-lang[Dhall] * https://cuelang.org[CUE] * https://nickel-lang.org[Nickel] * https://kcl-lang.io[KCL] * https://github.com/google/skylark[Skylark] * https://github.com/typesafehub/config[Typesafe Config] * https://www.flabbergast.org[Flabbergast] (defunct, http://artefacts.masella.name/2015-srecon-andre_masella.pdf[paper]) * https://medium.com/@MrJamesFisher/nix-by-example-a0063a1a4c55[Nix by example: The Nix expression language] * http://lethalman.blogspot.co.at/2014/07/nix-pill-4-basics-of-language.html[Nix pill 4: the basics of the language] * https://docs.puppetlabs.com/puppet/latest/reference/lang_summary.html[Puppet Configuration Language] ================================================ FILE: LICENSE.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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 http://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. ================================================ FILE: MAINTAINERS.adoc ================================================ = MAINTAINERS This page lists all active Maintainers of this repository. See link:CONTRIBUTING.adoc[] for general contribution guidelines. == Maintainers (in alphabetical order) * https://github.com/bioball[Daniel Chao] * https://github.com/stackoverflow[Islon Scherer] * https://github.com/HT154[Jen Basch] * https://github.com/holzensp[Philip Hölzenspies] ================================================ FILE: NOTICE.txt ================================================ Copyright © 2024-2025 Apple Inc. and the Pkl project authors Portions of this software were originally based on 'SnakeYAML' developed by Andrey Somov. (https://bitbucket.org/asomov/snakeyaml-engine/) The Apache License Copyright © 2008-2010 Andrey Somov 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 http://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. Portions of this software were originally based on 'jline3' developed by the JLine authors. (https://github.com/jline/jline3) Copyright (c) 2002-2023, the original author(s) Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of JLine nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Portions of this software were originally based on 'minimal-json' developed by EclipseSource. (https://github.com/ralfstx/minimal-json) Copyright (c) 2015, 2016 EclipseSource. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Portions of this software were originally based on 'gson' developed by the Google Inc. (https://github.com/google/gson) The Apache License Copyright © 2008 Google Inc 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 http://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. Portions of this software were originally based on 'guava' developed by Google Inc. (https://github.com/google/guava) The Apache License Copyright © 2009 The Guava Authors 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 http://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. Portions of this software were originally based on 'java-string-similarity' developed by Thibault Debatty. (https://github.com/tdebatty/java-string-similarity) Copyright 2015 Thibault Debatty Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Portions of this software were originally based on 'xerxes2-j' developed by the Apache Software Foundation. (https://github.com/apache/xerces2-j) The Apache License Copyright © 1999-2018 The Apache Software Foundation 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 http://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. Portions of this software were originally based on 'scroll-into-view-if-needed' developed by Cody Olsen. (https://github.com/scroll-into-view/scroll-into-view-if-needed) Copyright (c) 2023 Cody Olsen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This product ships with third party licenses that may be distributed under a different license. This information is detailed in THIRD-PARTY-NOTICES.txt. Portions of this software includes code from "Gradle" by Gradle, Inc. Copyright 2015 the original author or authors. 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. ================================================ FILE: README.adoc ================================================ = image:.idea/icon.svg[Pkl,30] Pkl :uri-homepage: https://pkl-lang.org :uri-docs: {uri-homepage}/main/current :uri-docs-introduction: {uri-docs}/introduction :uri-docs-release-notes: {uri-docs}/release-notes :uri-docs-language: {uri-docs}/language :uri-docs-tools: {uri-docs}/tools :uri-github-issue: https://github.com/apple/pkl/issues :uri-github-discussions: https://github.com/apple/pkl/discussions :uri-pkl-examples: https://pkl-lang.org/main/current/examples.html :uri-installation: https://pkl-lang.org/main/current/pkl-cli/index.html#installation :uri-lang-reference: https://pkl-lang.org/main/current/language-reference/index.html :uri-ci-artifacts: https://s01.oss.sonatype.org/content/groups/public/org/pkl-lang/ :uri-ci-pipeline: https://github.com/apple/pkl/actions A configuration as code language with rich validation and tooling. == Quick Links * {uri-installation}[Installation] * {uri-lang-reference}[Language Reference] == Documentation * {uri-homepage}[Home Page] ** {uri-docs-introduction}[Introduction] ** {uri-docs-language}[Language] ** {uri-docs-tools}[Tools] ** {uri-pkl-examples}[Examples] ** {uri-docs-release-notes}[Release Notes] == Community We'd love to hear from you! * Create an {uri-github-issue}[issue] * Ask a question on {uri-github-discussions}[GitHub Discussions] == Development image:https://github.com/apple/pkl/actions/workflows/main.yml/badge.svg?style=svg["Build (main)", link="https://github.com/apple/pkl/actions/workflows/main.yml"] * link:CONTRIBUTING.adoc[] for tips on pull requests and filing issues * link:DEVELOPMENT.adoc[] for build instructions * {uri-ci-artifacts}[Sonatype Repository] for the artifacts/binaries built by our {uri-ci-pipeline}[CI pipelines] (and those of our other tools and packages repositories). == Pkl GitHub Repositories [%autowidth] |=== |Name |Description |`apple/pkl` |A configuration as code language with rich validation and tooling. |https://github.com/apple/pkl-evolution[`apple/pkl-evolution`] |Suggested Pkl Improvements, Changes, or Enhancements (SPICEs) |https://github.com/apple/pkl-go[`apple/pkl-go`] |Pkl bindings for the Go programming language |https://github.com/apple/pkl-go-examples[`apple/pkl-go-examples`] |Examples for using Pkl within Go applications |https://github.com/apple/highlightjs-pkl[`apple/highlightjs-pkl`] |Highlight.js syntax highlighting for Pkl |https://github.com/apple/pkl-intellij[`apple/pkl-intellij`] |JetBrains editor plugins providing Pkl language support |https://github.com/apple/pkl-jvm-examples[`apple/pkl-jvm-examples`] |Examples for using Pkl within JVM applications |https://github.com/apple/pkl-k8s[`apple/pkl-k8s`] |Templates for using Pkl with Kubernetes |https://github.com/apple/pkl-k8s-examples[`apple/pkl-k8s-examples`] |Examples for using Pkl with Kubernetes |https://github.com/apple/pkl-lang.org[`apple/pkl-lang.org`] |The pkl-lang.org website |https://github.com/apple/pkl-lsp[`apple/pkl-lsp`] |Language server for Pkl, implementing the server-side of the Language Server Protocol |https://github.com/apple/pkl-neovim[`apple/pkl-neovim`] |Pkl language support for Neovim |https://github.com/apple/pkl-package-docs[`apple/pkl-package-docs`] |Documentation for Pkl packages |https://github.com/apple/pkl-pantry[`apple/pkl-pantry`] |Shared Pkl packages |https://github.com/apple/pkl-project-commons[`apple/pkl-project-commons`] |Utility libraries for Pkl |https://github.com/apple/pkl-readers[`apple/pkl-readers`] |Shared Pkl https://pkl-lang.org/main/current/language-reference/index.html#external-readers[external reader] tools |https://github.com/apple/pkl-spring[`apple/pkl-spring`] |Spring Boot extension for configuring Boot apps with Pkl |https://github.com/apple/pkl-swift[`apple/pkl-swift`] |Pkl bindings for the Swift programming language |https://github.com/apple/pkl-swift-examples[`apple/pkl-swift-examples`] |Examples for using Pkl within Swift applications |https://github.com/apple/pkl-vscode[`apple/pkl-vscode`] |Pkl language support for VS Code |https://github.com/apple/pkl.tmbundle[`apple/pkl.tmbundle`] |TextMate bundle for Pkl |https://github.com/apple/tree-sitter-pkl[`apple/tree-sitter-pkl`] |Tree-sitter parser for Pkl |https://github.com/apple/rules_pkl[`apple/rules_pkl`] |Bazel build rules for Pkl |=== ================================================ FILE: SECURITY.md ================================================ # Security For the protection of our community, the Pkl team does not disclose, discuss, or confirm security issues until our investigation is complete and any necessary updates are generally available. ## Reporting a security vulnerability If you have discovered a security vulnerability within the Pkl project, please report it to us. We welcome reports from everyone, including security researchers, developers, and users. Security vulnerabilities may be reported on the [Report a vulnerability](https://security.apple.com/submit) form. When submitting a vulnerability, select "Apple Devices and Software" as the affected platform, and "Open Source" as the affected area. For more information, see https://pkl-lang.org/security.html. ================================================ FILE: THIRD-PARTY-NOTICES.txt ================================================ Pkl ships with third-party libraries that may be distributed under a different license than Pkl's own license. These libraries and their licenses are listed below: 1) Clikt (https://github.com/ajalt/clikt) POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License Copyright © 2018 AJ Alt 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 http://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. 2) JavaPoet (http://github.com/square/javapoet/) POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License Copyright © 2015 Square, Inc. 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 http://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. 3) KotlinPoet (https://github.com/square/kotlinpoet) POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License Copyright © 2017 Square, Inc. 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 http://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. 4) GeantyRef (https://github.com/leangen/geantyref) Manifest license URL: https://www.apache.org/licenses/LICENSE-2.0 POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License Copyright © 2017 Kaqqao 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 http://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. 5) commonmark-java core POM License: The 2-Clause BSD License - https://opensource.org/licenses/BSD-2-Clause Embedded license: **************************************** Copyright (c) 2015, Atlassian Pty Ltd All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 6) commonmark-java extension for tables POM License: The 2-Clause BSD License - https://opensource.org/licenses/BSD-2-Clause Embedded license: **************************************** Copyright (c) 2015, Atlassian Pty Ltd All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 7) jansi (http://fusesource.com/) Manifest license URL: https://www.apache.org/licenses/LICENSE-2.0 POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License Copyright © Fusesource 2023 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 http://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. 8) Graal Sdk (https://github.com/oracle/graal) POM License: Universal Permissive License, Version 1.0 - http://opensource.org/licenses/UPL 9) Truffle API (http://openjdk.java.net/projects/graal) POM License: Universal Permissive License, Version 1.0 - http://opensource.org/licenses/UPL 10) IntelliJ IDEA Annotations (http://www.jetbrains.org) POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License Copyright © Jetbrains 2023 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 http://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. 11) kotlin-reflect (https://kotlinlang.org/) POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License Copyright © Wuseal 2018 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 http://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. 12) kotlin-stdlib (https://kotlinlang.org/) POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License Copyright © 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributor 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 http://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. 13) kotlin-stdlib-common (https://kotlinlang.org/) POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License Copyright © 2023 Kotlin Team 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 http://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. 14) kotlin-stdlib-jdk7 (https://kotlinlang.org/) POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License Copyright © 2023 Kotlin Team 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 http://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. 15) kotlin-stdlib-jdk8 (https://kotlinlang.org/) POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License Copyright © 2023 Kotlin Team 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 http://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. 16) kotlinx.html (https://github.com/Kotlin/kotlinx.html) POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License Copyright © 2017 Yole 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 http://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. 17) kotlinx-serialization-core (https://github.com/Kotlin/kotlinx.serialization) POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License 2017-2019 JetBrains s.r.o and respective authors and developers 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 http://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. 18) kotlinx-serialization-json (https://github.com/Kotlin/kotlinx.serialization) POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License 2017-2019 JetBrains s.r.o and respective authors and developers 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 http://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. 19) JLine Reader Manifest license URL: https://opensource.org/licenses/BSD-3-Clause POM License: The 3-Clause BSD License - https://opensource.org/licenses/BSD-3-Clause Copyright (c) 2002-2023, the original author(s) Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of JLine nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20) JLine Terminal Manifest license URL: https://opensource.org/licenses/BSD-3-Clause POM License: The 3-Clause BSD License - https://opensource.org/licenses/BSD-3-Clause Copyright (c) 2002-2023, the original author(s) Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of JLine nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 21) JLine JANSI Terminal Manifest license URL: https://opensource.org/licenses/BSD-3-Clause POM License: The 3-Clause BSD License - https://opensource.org/licenses/BSD-3-Clause Copyright (c) 2002-2023, the original author(s) Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of JLine nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22) msgpack-core (https://msgpack.org/) POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License Copyright © 2016 Sadayuki Furuhashi, Muga Nishizawa, Taro L. Saito, Mitsunori Komatsu, Ozawa Tsuyoshi 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 http://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. 23) Paguro (https://github.com/GlenKPeterson/Paguro) POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License Copyright © 2021 Glen K. Peterson 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 http://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. POM License: Eclipse Public License - v 1.0 - http://www.eclipse.org/legal/epl-v10.html NOTE: The files in this project that came from Clojure (Persistent...) MUST only be used under the Eclipse 1.0 license. At the user's choice, any other files in this project can be used under Eclipse 1.0 or the Apache 2.0 license. Eclipse Public License - v 1.0 THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 1. DEFINITIONS "Contribution" means: a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and b) in the case of each subsequent Contributor: i) changes to the Program, and ii) additions to the Program; where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. "Contributor" means any person or entity that distributes the Program. "Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. "Program" means the Contributions distributed in accordance with this Agreement. "Recipient" means anyone who receives the Program under this Agreement, including all Contributors. 2. GRANT OF RIGHTS a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. 3. REQUIREMENTS A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: a) it complies with the terms and conditions of this Agreement; and b) its license agreement: i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. When the Program is made available in source code form: a) it must be made available under this Agreement; and b) a copy of this Agreement must be included with each copy of the Program. Contributors may not remove or alter any copyright notices contained within the Program. Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. 4. COMMERCIAL DISTRIBUTION Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. 5. NO WARRANTY EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. 6. DISCLAIMER OF LIABILITY EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 7. GENERAL If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. 24) SnakeYAML Engine (http://www.snakeyaml.org) Manifest license URL: https://www.apache.org/licenses/LICENSE-2.0 POM License: Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 The Apache License Copyright © 2008-2010 Andrey Somov 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 http://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. ================================================ FILE: bench/bench.gradle.kts ================================================ /* * Copyright © 2024-2025 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. */ plugins { pklAllProjects pklJavaLibrary id("me.champeau.jmh") } val truffle: Configuration by configurations.creating val graal: Configuration by configurations.creating dependencies { jmh(projects.pklCore) jmh(projects.pklCommonsTest) jmh(projects.pklParser) truffle(libs.truffleApi) graal(libs.graalCompiler) } jmh { // include = ["fib_class_java"] // include = ["fib_class_constrained1", "fib_class_constrained2"] jmhVersion.set(libs.versions.jmh) // jvmArgsAppend = "-Dgraal.TruffleCompilationExceptionsAreFatal=true " + // "-Dgraal.Dump=Truffle,TruffleTree -Dgraal.TraceTruffleCompilation=true " + // "-Dgraal.TruffleFunctionInlining=false" jvm.set("${buildInfo.graalVmAmd64.baseDir}/bin/java") // see: // https://docs.oracle.com/en/graalvm/enterprise/20/docs/graalvm-as-a-platform/implement-language/#disable-class-path-separation jvmArgs.set( listOf( // one JVM arg per list element doesn't work, but the following does "-Dgraalvm.locatorDisabled=true --module-path=${truffle.asPath} --upgrade-module-path=${graal.asPath}" ) ) includeTests.set(false) // threads = Runtime.runtime.availableProcessors() / 2 + 1 // synchronizeIterations = false } tasks.named("jmh") { dependsOn(":installGraalVmAmd64") } // Prevent this error which occurs when building in IntelliJ: // "Entry org/pkl/core/fib_class_typed.pkl is a duplicate but no duplicate handling strategy has // been set." tasks.processJmhResources { duplicatesStrategy = DuplicatesStrategy.EXCLUDE } ================================================ FILE: bench/gradle.lockfile ================================================ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. com.github.ben-manes.caffeine:caffeine:2.9.3=swiftExportClasspathResolvable com.google.errorprone:error_prone_annotations:2.28.0=swiftExportClasspathResolvable io.github.java-diff-utils:java-diff-utils:4.12=kotlinInternalAbiValidation io.opentelemetry:opentelemetry-api:1.41.0=swiftExportClasspathResolvable io.opentelemetry:opentelemetry-context:1.41.0=swiftExportClasspathResolvable net.bytebuddy:byte-buddy:1.17.7=jmh,jmhRuntimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.sf.jopt-simple:jopt-simple:5.0.4=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath org.apache.commons:commons-math3:3.6.1=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=jmhCompileClasspath,jmhImplementationDependenciesMetadata,testCompileClasspath,testImplementationDependenciesMetadata org.assertj:assertj-core:3.27.6=jmh,jmhRuntimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.bouncycastle:bcpg-jdk18on:1.80=kotlinBouncyCastleConfiguration org.bouncycastle:bcpkix-jdk18on:1.80=kotlinBouncyCastleConfiguration org.bouncycastle:bcprov-jdk18on:1.80=kotlinBouncyCastleConfiguration org.bouncycastle:bcutil-jdk18on:1.80=kotlinBouncyCastleConfiguration org.checkerframework:checker-qual:3.43.0=swiftExportClasspathResolvable org.graalvm.compiler:compiler:25.0.0=graal org.graalvm.polyglot:polyglot:25.0.0=jmh,jmhRuntimeClasspath,truffle org.graalvm.sdk:collections:25.0.0=graal,jmh,jmhRuntimeClasspath,truffle org.graalvm.sdk:graal-sdk:25.0.0=jmh,jmhRuntimeClasspath org.graalvm.sdk:nativeimage:25.0.0=jmh,jmhRuntimeClasspath,truffle org.graalvm.sdk:word:25.0.0=graal,jmh,jmhRuntimeClasspath,truffle org.graalvm.truffle:truffle-api:25.0.0=jmh,jmhRuntimeClasspath,truffle org.graalvm.truffle:truffle-compiler:25.0.0=graal org.jetbrains.kotlin:abi-tools-api:2.2.20=kotlinInternalAbiValidation org.jetbrains.kotlin:abi-tools:2.2.20=kotlinInternalAbiValidation org.jetbrains.kotlin:kotlin-build-tools-api:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-build-tools-impl:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-compiler-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlin:kotlin-compiler-runner:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-daemon-client:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-daemon-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:2.2.20=kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-metadata-jvm:2.2.20=kotlinInternalAbiValidation org.jetbrains.kotlin:kotlin-native-prebuilt:2.0.21=kotlinNativeBundleConfiguration org.jetbrains.kotlin:kotlin-reflect:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlin:kotlin-script-runtime:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathJmh,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlin:kotlin-scripting-common:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathJmh,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathJmh,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathJmh,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-jvm:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathJmh,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.2.20=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.20=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib:2.2.20=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathJmh,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.kotlin:swift-export-embeddable:2.2.20=swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-serialization-bom:1.7.3=swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.7.3=swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3=swiftExportClasspathResolvable org.jetbrains:annotations:13.0=jmh,jmhCompileClasspath,jmhRuntimeClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathJmh,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable,testCompileClasspath,testRuntimeClasspath org.junit.jupiter:junit-jupiter-api:5.14.0=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.jupiter:junit-jupiter-engine:5.14.0=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath,testRuntimeClasspath org.junit.jupiter:junit-jupiter-params:5.14.0=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.platform:junit-platform-commons:1.14.0=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.platform:junit-platform-engine:1.14.0=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath,testRuntimeClasspath org.junit.platform:junit-platform-launcher:1.14.0=testRuntimeClasspath org.junit:junit-bom:5.14.0=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.msgpack:msgpack-core:0.9.8=jmh,jmhRuntimeClasspath org.openjdk.jmh:jmh-core:1.37=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath org.openjdk.jmh:jmh-generator-asm:1.37=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath org.openjdk.jmh:jmh-generator-bytecode:1.37=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath org.openjdk.jmh:jmh-generator-reflection:1.37=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath org.opentest4j:opentest4j:1.3.0=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.organicdesign:Paguro:3.10.3=jmh,jmhRuntimeClasspath org.ow2.asm:asm:9.0=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath org.snakeyaml:snakeyaml-engine:2.10=jmh,jmhRuntimeClasspath empty=annotationProcessor,apiDependenciesMetadata,compileClasspath,compileOnlyDependenciesMetadata,implementationDependenciesMetadata,intransitiveDependenciesMetadata,jmhAnnotationProcessor,jmhApiDependenciesMetadata,jmhCompileOnlyDependenciesMetadata,jmhIntransitiveDependenciesMetadata,jmhKotlinScriptDefExtensions,kotlinCompilerPluginClasspath,kotlinNativeCompilerPluginClasspath,kotlinScriptDefExtensions,runtimeClasspath,sourcesJar,testAnnotationProcessor,testApiDependenciesMetadata,testCompileOnlyDependenciesMetadata,testIntransitiveDependenciesMetadata,testKotlinScriptDefExtensions ================================================ FILE: bench/src/jmh/java/org/pkl/core/Fibonacci.java ================================================ /* * Copyright © 2024 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; import static org.pkl.core.ModuleSource.modulePath; import java.util.concurrent.TimeUnit; import org.openjdk.jmh.annotations.*; @SuppressWarnings("unused") @Warmup(iterations = 5, time = 2) @Measurement(iterations = 5, time = 2) @OutputTimeUnit(TimeUnit.SECONDS) @Fork(1) public class Fibonacci { @Benchmark public long fib_class_java() { return new FibJavaImpl().fib(35); } @Benchmark public long fib_class() { try (var evaluator = Evaluator.preconfigured()) { var module = evaluator.evaluate(modulePath("org/pkl/core/fib_class.pkl")); return (long) module.getProperties().get("result"); } } @Benchmark public long fib_class_explicitThis() { try (var evaluator = Evaluator.preconfigured()) { var module = evaluator.evaluate(modulePath("org/pkl/core/fib_class_explicitThis.pkl")); return (long) module.getProperties().get("result"); } } @Benchmark public long fib_class_typed() { try (var evaluator = Evaluator.preconfigured()) { var module = evaluator.evaluate(modulePath("org/pkl/core/fib_class_typed.pkl")); return (long) module.getProperties().get("result"); } } @Benchmark public long fib_class_constrained1() { try (var evaluator = Evaluator.preconfigured()) { var module = evaluator.evaluate(modulePath("org/pkl/core/fib_class_constrained1.pkl")); return (long) module.getProperties().get("result"); } } @Benchmark public long fib_class_constrained2() { try (var evaluator = Evaluator.preconfigured()) { var module = evaluator.evaluate(modulePath("org/pkl/core/fib_class_constrained2.pkl")); return (long) module.getProperties().get("result"); } } @Benchmark public long fib_module() { try (var evaluator = Evaluator.preconfigured()) { var module = evaluator.evaluate(modulePath("org/pkl/core/fib_module.pkl")); return (long) module.getProperties().get("result"); } } @Benchmark public long fib_module_explicitThis() { try (var evaluator = Evaluator.preconfigured()) { var module = evaluator.evaluate(modulePath("org/pkl/core/fib_module_explicitThis.pkl")); return (long) module.getProperties().get("result"); } } @Benchmark public long fib_lambda() { try (var evaluator = Evaluator.preconfigured()) { var module = evaluator.evaluate(modulePath("org/pkl/core/fib_lambda.pkl")); return (long) module.getProperties().get("result"); } } } // kept similar to pkl code (class, instance method, long argument) class FibJavaImpl { long fib(long n) { return n < 2 ? n : fib(n - 1) + fib(n - 2); } } ================================================ FILE: bench/src/jmh/java/org/pkl/core/ListSort.java ================================================ /* * Copyright © 2024-2025 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; import java.io.FileWriter; import java.io.IOException; import java.io.UncheckedIOException; import java.util.*; import java.util.concurrent.TimeUnit; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.util.TempFile; import org.openjdk.jmh.util.TempFileManager; import org.pkl.core.evaluatorSettings.TraceMode; import org.pkl.core.http.HttpClient; import org.pkl.core.module.ModuleKeyFactories; import org.pkl.core.repl.ReplRequest; import org.pkl.core.repl.ReplResponse; import org.pkl.core.repl.ReplServer; import org.pkl.core.resource.ResourceReaders; import org.pkl.core.util.IoUtils; @Warmup(iterations = 5, time = 2) @Measurement(iterations = 5, time = 2) @OutputTimeUnit(TimeUnit.SECONDS) @Fork(1) @SuppressWarnings("unused") public class ListSort { private static final ReplServer repl = new ReplServer( SecurityManagers.defaultManager, HttpClient.dummyClient(), Loggers.stdErr(), List.of(ModuleKeyFactories.standardLibrary), List.of(ResourceReaders.file()), Map.of(), Map.of(), null, null, null, IoUtils.getCurrentWorkingDir(), StackFrameTransformers.defaultTransformer, false, TraceMode.COMPACT); private static final List list = new ArrayList<>(100000); static { var random = new Random(2786433088656064171L); for (var i = 0; i < 100000; i++) { list.add(random.nextLong()); } TempFile tempFile; try { tempFile = new TempFileManager().create("bench-nums.txt"); } catch (IOException e) { throw new UncheckedIOException(e); } try (var fw = new FileWriter(tempFile.getAbsolutePath())) { for (var elem : list) { fw.append(elem.toString()).append('\n'); } fw.flush(); } catch (IOException e) { throw new UncheckedIOException(e); } var responses = repl.handleRequest( new ReplRequest.Eval( "setup", "import \"pkl:test\"\n" + "random = test.random\n" + "nums = read(\"file://" + tempFile.getAbsolutePath() + "\").text.split(\"\\n\").dropLast(1).map((it) -> it.toInt())\n" + "cmp = (x, y) -> if (x < y) -1 else if (x == y) 0 else 1", false, false)); if (!responses.isEmpty()) { throw new AssertionError(responses.get(0)); } } @Benchmark public String sortPkl() { var response = repl.handleRequest( // append `.length` to avoid rendering the list new ReplRequest.Eval("sort", "nums.sort().length", false, false)) .get(0); if (!(response instanceof ReplResponse.EvalSuccess)) { throw new AssertionError(response); } return ((ReplResponse.EvalSuccess) response).getResult(); } @Benchmark public String sortWithPkl() { var response = repl.handleRequest( // append `.length` to avoid rendering the list new ReplRequest.Eval("sort", "nums.sortWith(cmp).length", false, false)) .get(0); if (!(response instanceof ReplResponse.EvalSuccess success)) { throw new AssertionError(response); } return success.getResult(); } // note that this is an uneven comparison // (timsort vs. merge sort, java.util.ArrayList vs. persistent vector @Benchmark public List sortJava() { return sort(list); } private List sort(List self) { var array = self.toArray(); Arrays.sort(array); return Arrays.asList(array); } // note that this is an uneven comparison // (timsort vs. merge sort, java.util.ArrayList vs. persistent vector @Benchmark public List sortWithJava() { return sortWith(list, Comparator.comparingLong(x -> (long) x)); } private List sortWith(List self, Comparator comparator) { var array = self.toArray(); Arrays.sort(array, comparator); return Arrays.asList(array); } } ================================================ FILE: bench/src/jmh/java/org/pkl/core/parser/ParserBenchmark.java ================================================ /* * Copyright © 2024-2025 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.parser; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.util.concurrent.TimeUnit; import org.openjdk.jmh.annotations.*; import org.pkl.commons.test.FileTestUtils; import org.pkl.commons.test.FileTestUtilsKt; import org.pkl.core.Release; import org.pkl.core.util.IoUtils; import org.pkl.parser.Parser; import org.pkl.parser.ParserError; @SuppressWarnings("unused") @Warmup(iterations = 5, time = 2) @Measurement(iterations = 5, time = 2) @OutputTimeUnit(TimeUnit.SECONDS) @Fork(1) public class ParserBenchmark { @Benchmark public void parseStdlib() { for (var stdlibModule : Release.current().standardLibrary().modules()) { try { var moduleSource = IoUtils.readClassPathResourceAsString( getClass(), "/org/pkl/core/stdlib/%s.pkl".formatted(stdlibModule.substring(4))); new Parser().parseModule(moduleSource); } catch (IOException e) { throw new UncheckedIOException(e); } } } @Benchmark public void parseSnippetTests() { var snippetTestDir = FileTestUtils.getRootProjectDir() .resolve("pkl-core/src/test/files/LanguageSnippetTests/input"); for (var snippet : FileTestUtilsKt.listFilesRecursively(snippetTestDir)) { try { var moduleSource = Files.readString(snippet); new Parser().parseModule(moduleSource); } catch (IOException e) { throw new UncheckedIOException(e); } catch (ParserError ignore) { } } } } ================================================ FILE: bench/src/jmh/resources/org/pkl/core/fib_class.pkl ================================================ //===----------------------------------------------------------------------===// // Copyright © 2024 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. //===----------------------------------------------------------------------===// class Fibonacci { function fib(n) = if (n < 2) n else fib(n - 1) + fib(n - 2) } result = new Fibonacci {}.fib(35) ================================================ FILE: bench/src/jmh/resources/org/pkl/core/fib_class_constrained1.pkl ================================================ //===----------------------------------------------------------------------===// // Copyright © 2024 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. //===----------------------------------------------------------------------===// class Fibonacci { function fib(n: Int(this >= 0)): Int(this >= 0) = if (n < 2) n else fib(n - 1) + fib(n - 2) } result = new Fibonacci {}.fib(35) ================================================ FILE: bench/src/jmh/resources/org/pkl/core/fib_class_constrained2.pkl ================================================ //===----------------------------------------------------------------------===// // Copyright © 2024 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. //===----------------------------------------------------------------------===// class Fibonacci { function fib(n: Int(isPositive)): Int(isPositive) = if (n < 2) n else fib(n - 1) + fib(n - 2) } result = new Fibonacci {}.fib(35) ================================================ FILE: bench/src/jmh/resources/org/pkl/core/fib_class_explicitThis.pkl ================================================ //===----------------------------------------------------------------------===// // Copyright © 2024 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. //===----------------------------------------------------------------------===// class Fibonacci { function fib(n) = if (n < 2) n else this.fib(n - 1) + this.fib(n - 2) } result = new Fibonacci {}.fib(35) ================================================ FILE: bench/src/jmh/resources/org/pkl/core/fib_class_typed.pkl ================================================ //===----------------------------------------------------------------------===// // Copyright © 2024 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. //===----------------------------------------------------------------------===// class Fibonacci { function fib(n: Int): Int = if (n < 2) n else fib(n - 1) + fib(n - 2) } result = new Fibonacci {}.fib(35) ================================================ FILE: bench/src/jmh/resources/org/pkl/core/fib_lambda.pkl ================================================ //===----------------------------------------------------------------------===// // Copyright © 2024 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. //===----------------------------------------------------------------------===// hidden fib = (n) -> if (n < 2) n else fib.apply(n - 1) + fib.apply(n - 2) result = fib.apply(35) ================================================ FILE: bench/src/jmh/resources/org/pkl/core/fib_module.pkl ================================================ //===----------------------------------------------------------------------===// // Copyright © 2024 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. //===----------------------------------------------------------------------===// function fib(n) = if (n < 2) n else fib(n - 1) + fib(n - 2) result = fib(35) ================================================ FILE: bench/src/jmh/resources/org/pkl/core/fib_module_explicitThis.pkl ================================================ //===----------------------------------------------------------------------===// // Copyright © 2024 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. //===----------------------------------------------------------------------===// function fib(n) = if (n < 2) n else this.fib(n - 1) + this.fib(n - 2) result = fib(35) ================================================ FILE: build.gradle.kts ================================================ /* * Copyright © 2024-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. */ // https://youtrack.jetbrains.com/issue/KTIJ-19369 import org.jetbrains.gradle.ext.ActionDelegationConfig import org.jetbrains.gradle.ext.ActionDelegationConfig.TestRunner.PLATFORM import org.jetbrains.gradle.ext.ProjectSettings plugins { pklAllProjects pklGraalVm alias(libs.plugins.ideaExt) alias(libs.plugins.jmh) apply false alias(libs.plugins.nexusPublish) } nexusPublishing { repositories { sonatype { nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/")) snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/")) } } } idea { project { this as ExtensionAware configure { this as ExtensionAware configure { delegateBuildRunToGradle = true testRunner = PLATFORM } } } } val clean by tasks.existing { delete(layout.buildDirectory) } val printVersion by tasks.registering { doFirst { println(buildInfo.pklVersion) } } val message = """ ==== Gradle version : ${gradle.gradleVersion} Java version : ${System.getProperty("java.version")} isParallel : ${gradle.startParameter.isParallelProjectExecutionEnabled} maxWorkerCount : ${gradle.startParameter.maxWorkerCount} Architecture : ${buildInfo.arch} Project Version : ${project.version} Pkl Version : ${buildInfo.pklVersion} Pkl Non-Unique Version : ${buildInfo.pklVersionNonUnique} Git Commit ID : ${buildInfo.commitId} ==== """ val formattedMessage = message.replace("\n====", "\n" + "=".repeat(message.lines().maxByOrNull { it.length }!!.length)) logger.info(formattedMessage) ================================================ FILE: buildSrc/build.gradle.kts ================================================ /* * Copyright © 2024-2025 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. */ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { `kotlin-dsl` `jvm-toolchains` } // Keep this in sync with the constants in `BuildInfo.kt` (those are not addressable here). val toolchainVersion = 21 dependencies { implementation(libs.downloadTaskPlugin) implementation(libs.spotlessPlugin) implementation(libs.kotlinPlugin) { exclude(module = "kotlin-android-extensions") } implementation(libs.shadowPlugin) // fix from the Gradle team: makes version catalog symbols available in build scripts // see here for more: https://github.com/gradle/gradle/issues/15383 implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) } java { toolchain { languageVersion = JavaLanguageVersion.of(toolchainVersion) vendor = JvmVendorSpec.ADOPTIUM } } tasks.withType().configureEach { options.release = toolchainVersion } kotlin { jvmToolchain(toolchainVersion) compilerOptions { jvmTarget = JvmTarget.fromTarget(toolchainVersion.toString()) freeCompilerArgs.add("-Xjdk-release=$toolchainVersion") } } ================================================ FILE: buildSrc/settings.gradle.kts ================================================ /* * Copyright © 2024-2025 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. */ @file:Suppress("UnstableApiUsage") rootProject.name = "buildSrc" pluginManagement { repositories { mavenCentral() gradlePluginPortal() } } plugins { id("org.gradle.toolchains.foojay-resolver-convention") } // makes ~/.gradle/init.gradle unnecessary and ~/.gradle/gradle.properties optional dependencyResolutionManagement { // use same version catalog as main build versionCatalogs { register("libs") { from(files("../gradle/libs.versions.toml")) } } repositories { repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) mavenCentral() gradlePluginPortal() } } ================================================ FILE: buildSrc/src/main/kotlin/BuildInfo.kt ================================================ /* * Copyright © 2024-2025 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. */ @file:Suppress("MemberVisibilityCanBePrivate") import java.io.File import org.gradle.api.Project import org.gradle.api.artifacts.VersionCatalog import org.gradle.api.artifacts.VersionCatalogsExtension import org.gradle.api.attributes.Category import org.gradle.api.provider.Provider import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.testing.Test import org.gradle.internal.extensions.stdlib.capitalized import org.gradle.jvm.toolchain.* import org.gradle.kotlin.dsl.* import org.gradle.kotlin.dsl.support.serviceOf import org.gradle.process.CommandLineArgumentProvider /** * JVM bytecode target; this is pinned at a reasonable version, because downstream JVM projects * which consume Pkl will need a minimum Bytecode level at or above this one. * * Kotlin and Java need matching bytecode targets, so this is expressed as a build setting and * constant default. To override, pass `-DpklJdkToolchain=X` to the Gradle command line, where X is * a major Java version. */ const val PKL_JVM_TARGET_DEFAULT_MAXIMUM = 17 /** * The Pkl build requires JDK 21+ to build, because JDK 17 is no longer within the default set of * supported JDKs for GraalVM. This is a build-time requirement, not a runtime requirement. */ const val PKL_JDK_VERSION_MIN = 21 /** * The JDK minimum is set to match the bytecode minimum, to guarantee that fat JARs work against the * earliest supported bytecode target. */ const val PKL_TEST_JDK_MINIMUM = PKL_JVM_TARGET_DEFAULT_MAXIMUM /** * Maximum JDK version which Pkl is tested with; this should be bumped when new JDK stable releases * are issued. At the time of this writing, JDK 23 is the latest available release. */ const val PKL_TEST_JDK_MAXIMUM = 23 /** * Test the full suite of JDKs between [PKL_TEST_JDK_MINIMUM] and [PKL_TEST_JDK_MAXIMUM]; if this is * set to `false` (or overridden on the command line), only LTS releases are tested by default. */ const val PKL_TEST_ALL_JDKS = false // `buildInfo` in main build scripts // `project.extensions.getByType()` in precompiled script plugins open class BuildInfo(private val project: Project) { inner class GraalVm(val arch: String) { val homeDir: String by lazy { System.getenv("GRAALVM_HOME") ?: "${System.getProperty("user.home")}/.graalvm" } val version: String by lazy { libs.findVersion("graalVm").get().toString() } val graalVmJdkVersion: String by lazy { libs.findVersion("graalVmJdkVersion").get().toString() } val osName: String by lazy { when { os.isMacOsX -> "macos" os.isLinux -> "linux" os.isWindows -> "windows" else -> throw RuntimeException("${os.familyName} is not supported.") } } val baseName: String by lazy { "graalvm-community-jdk-${graalVmJdkVersion}_${osName}-${arch}_bin" } val downloadUrl: String by lazy { val extension = if (os.isWindows) "zip" else "tar.gz" "https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-${graalVmJdkVersion}/$baseName.$extension" } val downloadFile: File by lazy { val extension = if (os.isWindows) "zip" else "tar.gz" File(homeDir, "${baseName}.$extension") } val installDir: File by lazy { File(homeDir, baseName) } val baseDir: String by lazy { if (os.isMacOsX) "$installDir/Contents/Home" else installDir.toString() } } /** The target architecture to build, defaulting to the system architecture. */ val targetArch by lazy { System.getProperty("pkl.targetArch") ?: arch } /** Tells if this is a cross-arch build (e.g. targeting amd64 when on an aarch64 machine). */ val isCrossArch by lazy { arch != targetArch } /** Tells if cross-arch builds are supported on this machine. */ val isCrossArchSupported by lazy { os.isMacOsX } /** Whether to build native executables using the musl toolchain or not. */ val musl: Boolean by lazy { java.lang.Boolean.getBoolean("pkl.musl") } /** Same logic as [org.gradle.internal.os.OperatingSystem#arch], which is protected. */ val arch: String by lazy { when (val arch = System.getProperty("os.arch")) { "x86" -> "i386" "x86_64" -> "amd64" "powerpc" -> "ppc" else -> arch } } val graalVmAarch64: GraalVm = GraalVm("aarch64") val graalVmAmd64: GraalVm = GraalVm("x64") val isCiBuild: Boolean by lazy { System.getenv("CI") != null } val isReleaseBuild: Boolean by lazy { java.lang.Boolean.getBoolean("releaseBuild") } val isNativeArch: Boolean by lazy { java.lang.Boolean.getBoolean("nativeArch") } val jvmTarget: Int by lazy { System.getProperty("pklJvmTarget")?.toInt() ?: PKL_JVM_TARGET_DEFAULT_MAXIMUM } // JPMS exports for Truffle; needed on some versions of Java, and transitively within some JARs. private val jpmsExports = arrayOf( "org.graalvm.truffle/com.oracle.truffle.api.exception=ALL-UNNAMED", "org.graalvm.truffle/com.oracle.truffle.api=ALL-UNNAMED", "org.graalvm.truffle/com.oracle.truffle.api.nodes=ALL-UNNAMED", "org.graalvm.truffle/com.oracle.truffle.api.source=ALL-UNNAMED", ) // Extra JPMS modules forced onto the module path via `--add-modules` in some cases. private val jpmsAddModules = arrayOf("jdk.unsupported") // Formats `jpmsExports` for use in JAR manifest attributes. val jpmsExportsForJarManifest: String by lazy { jpmsExports.joinToString(" ") { it.substringBefore("=") } } // Formats `jpmsExports` for use on the command line with `--add-exports`. val jpmsExportsForAddExportsFlags: Collection by lazy { jpmsExports.map { "--add-exports=$it" } } // Formats `jpmsAddModules` for use on the command line with `--add-modules`. val jpmsAddModulesFlags: Collection by lazy { jpmsAddModules.map { "--add-modules=$it" } } // JVM properties to set during testing. val testProperties = mapOf( // @TODO: this should be removed once pkl supports JPMS as a true Java Module. "polyglotimpl.DisableClassPathIsolation" to true ) val jdkVendor: JvmVendorSpec = JvmVendorSpec.ADOPTIUM val jdkToolchainVersion: JavaLanguageVersion by lazy { JavaLanguageVersion.of(System.getProperty("pklJdkToolchain")?.toInt() ?: PKL_JDK_VERSION_MIN) } val jdkTestFloor: JavaLanguageVersion by lazy { JavaLanguageVersion.of(PKL_TEST_JDK_MINIMUM) } val jdkTestCeiling: JavaLanguageVersion by lazy { JavaLanguageVersion.of(PKL_TEST_JDK_MAXIMUM) } val testAllJdks: Boolean by lazy { // By default, Pkl is tested against LTS JDK releases within the bounds of `PKL_TEST_JDK_TARGET` // and `PKL_TEST_JDK_MAXIMUM`. To test against the full suite of JDK versions, past and present, // set `-DpklTestAllJdks=true` on the Gradle command line. This results in non-LTS releases, old // releases, and "experimental releases" (newer than the toolchain version) being included in // the default `check` suite. System.getProperty("pklTestAllJdks")?.toBoolean() ?: PKL_TEST_ALL_JDKS } val testExperimentalJdks: Boolean by lazy { System.getProperty("pklTestFutureJdks")?.toBoolean() ?: false } val testJdkVendors: Sequence by lazy { // By default, only OpenJDK is tested during multi-JDK testing. Flip `-DpklTestAllVendors=true` // to additionally test against a suite of JDK vendors, including Azul, Oracle, and GraalVM. when (System.getProperty("pklTestAllVendors")?.toBoolean()) { true -> sequenceOf(JvmVendorSpec.ADOPTIUM, JvmVendorSpec.GRAAL_VM, JvmVendorSpec.ORACLE) else -> sequenceOf(JvmVendorSpec.ADOPTIUM) } } private val isArmWindows: Boolean get() { if (!os.isWindows) { return false } // System.getProperty("os.arch") returns the architecture of the JVM, not the host OS. val procArch = System.getenv("PROCESSOR_ARCHITECTURE") return "ARM64".equals(procArch, ignoreCase = true) } // Assembles a collection of JDK versions which tests can be run against, considering ancillary // parameters like `testAllJdks` and `testExperimentalJdks`. val jdkTestRange: Collection by lazy { if (isArmWindows) { // Java toolchains does not work on ARM windows: https://github.com/gradle/gradle/issues/29807 // prevent creating tasks to test different JDKs if developing on a Windows ARM machine. return@lazy listOf() } JavaVersionRange.inclusive(jdkTestFloor, jdkTestCeiling).toList() } val JavaLanguageVersion.isEnabled: Boolean get() = isVersionEnabled(this) fun isVersionEnabled(version: JavaLanguageVersion): Boolean { return when { testAllJdks -> true multiJdkTesting -> JavaVersionRange.isLTS(version) testExperimentalJdks -> version > jdkToolchainVersion else -> false } } private fun JavaToolchainSpec.pklJdkToolchain() { languageVersion.set(jdkToolchainVersion) vendor.set(jdkVendor) } private fun labelForVendor(vendor: JvmVendorSpec): String = when (vendor) { JvmVendorSpec.AZUL -> "Zulu" JvmVendorSpec.GRAAL_VM -> "GraalVm" JvmVendorSpec.ORACLE -> "Oracle" JvmVendorSpec.ADOPTIUM -> "Adoptium" else -> error("Unrecognized JDK vendor: $vendor") } private fun testNamer(baseName: () -> String): (JavaLanguageVersion, JvmVendorSpec?) -> String = { jdkTarget, vendor -> val targetToken = when (vendor) { null -> "Jdk${jdkTarget.asInt()}" else -> "Jdk${jdkTarget.asInt()}${labelForVendor(vendor).capitalized()}" } if (jdkTarget > jdkToolchainVersion) { // test targets above the toolchain target are considered "experimental". "${baseName()}${targetToken}Experimental" } else { "${baseName()}${targetToken}" } } @Suppress("UnstableApiUsage") fun multiJdkTestingWith( templateTask: TaskProvider, configurator: MultiJdkTestConfigurator = {}, ): Iterable> = with(project) { val isMultiVendor = testJdkVendors.count() > 1 val baseNameProvider = { templateTask.get().name } val namer = testNamer(baseNameProvider) val applyConfig: MultiJdkTestConfigurator = { (version, jdk) -> // 1) copy configurations from the template task dependsOn(templateTask) templateTask.get().let { template -> classpath = template.classpath testClassesDirs = template.testClassesDirs jvmArgs.addAll(template.jvmArgs) jvmArgumentProviders.addAll(template.jvmArgumentProviders) forkEvery = template.forkEvery maxParallelForks = template.maxParallelForks minHeapSize = template.minHeapSize maxHeapSize = template.maxHeapSize exclude(template.excludes) template.systemProperties.forEach { prop -> systemProperty(prop.key, prop.value) } } // 2) assign launcher javaLauncher = jdk // 3) dispatch the user's configurator configurator(version to jdk) } serviceOf().let { toolchains -> jdkTestRange .flatMap { targetVersion -> // multiply out by jdk vendor testJdkVendors.map { vendor -> (targetVersion to vendor) } } .map { (jdkTarget, vendor) -> if (jdkToolchainVersion == jdkTarget) tasks.register(namer(jdkTarget, vendor)) { // alias to `test` dependsOn(templateTask) group = Category.VERIFICATION description = "Alias for regular '${baseNameProvider()}' task, on JDK ${jdkTarget.asInt()}" } else tasks.register(namer(jdkTarget, vendor.takeIf { isMultiVendor }), Test::class) { enabled = jdkTarget.isEnabled group = Category.VERIFICATION description = "Run tests against JDK ${jdkTarget.asInt()}" applyConfig(jdkTarget to toolchains.launcherFor { languageVersion = jdkTarget }) // fix: on jdk17, we must force the polyglot module on to the modulepath if (jdkTarget.asInt() == 17) jvmArgumentProviders.add( CommandLineArgumentProvider { buildList { listOf("--add-modules=org.graalvm.polyglot") } } ) } } .toList() } } val javaCompiler: Provider by lazy { project.serviceOf().let { toolchainService -> toolchainService.compilerFor { pklJdkToolchain() } } } val javaTestLauncher: Provider by lazy { project.serviceOf().let { toolchainService -> toolchainService.launcherFor { pklJdkToolchain() } } } val multiJdkTesting: Boolean by lazy { // Test Pkl against a full range of JDK versions, past and present, within the // supported bounds of `PKL_TEST_JDK_TARGET` and `PKL_TEST_JDK_MAXIMUM`. // // In CI, this defaults to `true` to catch potential cross-JDK compat regressions or other bugs. // In local dev, this defaults to `false` to speed up the build and reduce contributor load. System.getProperty("pklMultiJdkTesting")?.toBoolean() ?: isCiBuild } val hasMuslToolchain: Boolean by lazy { // see .github/scripts/install_musl.sh File(System.getProperty("user.home"), "staticdeps/bin/x86_64-linux-musl-gcc").exists() } val os: org.gradle.internal.os.OperatingSystem by lazy { org.gradle.internal.os.OperatingSystem.current() } // could be `commitId: Provider = project.provider { ... }` val commitId: String by lazy { // allow -DcommitId=abc123 for build environments that don't have git. System.getProperty("commitId").let { if (it != null) return@lazy it } // only run command once per build invocation if (project === project.rootProject) { val process = ProcessBuilder() .command("git", "rev-parse", "--short", "HEAD") .directory(project.rootDir) .start() process.waitFor().also { exitCode -> if (exitCode == -1) throw RuntimeException(process.errorStream.reader().readText()) } process.inputStream.reader().readText().trim() } else { project.rootProject.extensions.getByType(BuildInfo::class.java).commitId } } val commitish: String by lazy { if (isReleaseBuild) project.version.toString() else commitId } val pklVersion: String by lazy { if (isReleaseBuild) { project.version.toString() } else { project.version.toString().replace("-SNAPSHOT", "-dev+$commitId") } } val pklVersionNonUnique: String by lazy { if (isReleaseBuild) { project.version.toString() } else { project.version.toString().replace("-SNAPSHOT", "-dev") } } // https://melix.github.io/blog/2021/03/version-catalogs-faq.html#_but_how_can_i_use_the_catalog_in_em_plugins_em_defined_in_code_buildsrc_code val libs: VersionCatalog by lazy { project.extensions.getByType().named("libs") } init { if (!isReleaseBuild) { project.version = "${project.version}-SNAPSHOT" } } } // Shape of a function which is applied to configure multi-JDK testing. private typealias MultiJdkTestConfigurator = Test.(Pair>) -> Unit ================================================ FILE: buildSrc/src/main/kotlin/ExecutableJar.kt ================================================ /* * Copyright © 2024-2025 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. */ import org.gradle.api.DefaultTask import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.ListProperty import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction /** * Builds a self-contained Pkl CLI Jar that is directly executable on Windows, macOS, and Linux. * * For direct execution, the `java` command must be on the PATH. * * Technique borrowed from [Mill](https://mill-build.org/blog/5-executable-jars.html). */ abstract class ExecutableJar : DefaultTask() { @get:InputFile abstract val inJar: RegularFileProperty @get:OutputFile abstract val outJar: RegularFileProperty @get:Input abstract val jvmArgs: ListProperty @TaskAction fun buildJar() { val inFile = inJar.get().asFile val outFile = outJar.get().asFile val escapedJvmArgs = jvmArgs.get().joinToString(separator = " ") { "\"$it\"" } val unixStartScript = """ @ 2>/dev/null # 2>nul & echo off & goto BOF : exec java $escapedJvmArgs -jar "$0" "$@" exit """ .trimIndent() val windowsStartScript = """ :BOF setlocal @echo off java $escapedJvmArgs -jar "%~dpnx0" %* endlocal exit /B %errorlevel% """ .trimIndent() // need crlf endings for Windows portion of script .replace("\n", "\r\n") val startScript = unixStartScript + "\r\n" + windowsStartScript + "\r\n".repeat(3) outFile.outputStream().use { outStream -> startScript.byteInputStream().use { it.copyTo(outStream) } inFile.inputStream().use { it.copyTo(outStream) } } // chmod a+x outFile.setExecutable(true, false) } } ================================================ FILE: buildSrc/src/main/kotlin/ExecutableSpec.kt ================================================ /* * Copyright © 2025 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. */ import org.gradle.api.provider.Property abstract class ExecutableSpec { /** The main entrypoint Java class of the executable. */ abstract val mainClass: Property /** * The name of the native executable. * * Not required if not building a native executable. */ abstract val name: Property /** The name of the Java executable. */ abstract val javaName: Property /** The name of the executable that shows in the description when published to Maven. */ abstract val documentationName: Property /** * The base name of the Maven publication. * * This becomes the base name of the Artifact ID, with the os and arch suffixed. * * For example, `pkl` becomes `pkl-macos-aarch` for the macOS/aarch64 variant. */ abstract val publicationName: Property /** The name of the artifact ID for the Java executable. */ abstract val javaPublicationName: Property /** The website for this executable. */ abstract val website: Property } ================================================ FILE: buildSrc/src/main/kotlin/GradlePluginTests.kt ================================================ /* * Copyright © 2024 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. */ import org.gradle.util.GradleVersion open class GradlePluginTests { lateinit var minGradleVersion: GradleVersion lateinit var maxGradleVersion: GradleVersion var skippedGradleVersions: List = listOf() } ================================================ FILE: buildSrc/src/main/kotlin/GradleVersionInfo.kt ================================================ /* * Copyright © 2024 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. */ import groovy.json.JsonSlurper import java.net.URI import org.gradle.util.GradleVersion @Suppress("unused") class GradleVersionInfo(json: Map) { val version: String by json val gradleVersion: GradleVersion by lazy { GradleVersion.version(version) } val isReleaseVersion: Boolean by lazy { // for some reason, `gradleVersion == gradleVersion.baseVersion` is a compile error gradleVersion.version == gradleVersion.baseVersion.version } val buildTime: String by json val current: Boolean by json val snapshot: Boolean by json val nightly: Boolean by json val releaseNightly: Boolean by json val activeRc: Boolean by json val rcFor: String by json val milestoneFor: String by json val broken: Boolean by json val downloadUrl: String by json val checksumUrl: String by json val wrapperChecksumUrl: String by json companion object { private fun fetchAll(): List = fetchMultiple("https://services.gradle.org/versions/all") fun fetchReleases(): List = fetchAll().filter { it.isReleaseVersion } fun fetchCurrent(): GradleVersionInfo = fetchSingle("https://services.gradle.org/versions/current") fun fetchRc(): GradleVersionInfo? = fetchSingleOrNull("https://services.gradle.org/versions/release-candidate") fun fetchNightly(): GradleVersionInfo = fetchSingle("https://services.gradle.org/versions/nightly") private fun fetchSingle(url: String): GradleVersionInfo { @Suppress("UNCHECKED_CAST") return GradleVersionInfo(JsonSlurper().parse(URI(url).toURL()) as Map) } private fun fetchSingleOrNull(url: String): GradleVersionInfo? { @Suppress("UNCHECKED_CAST") val json = JsonSlurper().parse(URI(url).toURL()) as Map return if (json.isEmpty()) null else GradleVersionInfo(json) } private fun fetchMultiple(url: String): List { @Suppress("UNCHECKED_CAST") return (JsonSlurper().parse(URI(url).toURL()) as List>).map { GradleVersionInfo(it) } } } } ================================================ FILE: buildSrc/src/main/kotlin/HtmlValidator.kt ================================================ /* * Copyright © 2024 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. */ import org.gradle.api.Project import org.gradle.api.file.FileCollection open class HtmlValidator(project: Project) { var sources: FileCollection = project.files() } ================================================ FILE: buildSrc/src/main/kotlin/InstallGraalVm.kt ================================================ /* * Copyright © 2024-2025 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. */ import java.nio.file.Files import java.nio.file.Paths import java.nio.file.StandardCopyOption import java.util.* import javax.inject.Inject import kotlin.io.path.createDirectories import org.gradle.api.DefaultTask import org.gradle.api.internal.file.FileOperations import org.gradle.api.provider.Property import org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction import org.gradle.process.ExecOperations abstract class InstallGraalVm @Inject constructor( private val fileOperations: FileOperations, private val execOperations: ExecOperations, ) : DefaultTask() { @get:Input abstract val graalVm: Property init { @Suppress("LeakingThis") onlyIf("GraalVM not installed") { !graalVm.get().installDir.exists() } } @TaskAction @Suppress("unused") fun run() { // minimize chance of corruption by extract-to-random-dir-and-flip-symlink val distroDir = Paths.get(graalVm.get().homeDir, UUID.randomUUID().toString()) try { distroDir.createDirectories() println("Extracting ${graalVm.get().downloadFile} into $distroDir") // faster and more reliable than Gradle's `copy { from tarTree() }` execOperations.exec { workingDir = distroDir.toFile() executable = "tar" args("--strip-components=1", "-xzf", graalVm.get().downloadFile) } val os = org.gradle.internal.os.OperatingSystem.current() val distroBinDir = if (os.isMacOsX) distroDir.resolve("Contents/Home/bin") else distroDir.resolve("bin") println("Installing native-image into $distroDir") val gvmVersionMajor = requireNotNull(graalVm.get().version.split(".").first().toIntOrNull()) { "Invalid GraalVM JDK version: ${graalVm.get().graalVmJdkVersion}" } if (gvmVersionMajor < 24) { execOperations.exec { val executableName = if (os.isWindows) "gu.cmd" else "gu" executable = distroBinDir.resolve(executableName).toString() args("install", "--no-progress", "native-image") } } println("Creating symlink ${graalVm.get().installDir} for $distroDir") val tempLink = Paths.get(graalVm.get().homeDir, UUID.randomUUID().toString()) Files.createSymbolicLink(tempLink, distroDir) try { Files.move(tempLink, graalVm.get().installDir.toPath(), StandardCopyOption.ATOMIC_MOVE) } catch (e: Exception) { try { fileOperations.delete(tempLink.toFile()) } catch (ignored: Exception) {} throw e } } catch (e: Exception) { try { fileOperations.delete(distroDir) } catch (ignored: Exception) {} throw e } } } ================================================ FILE: buildSrc/src/main/kotlin/JavaVersionRange.kt ================================================ /* * Copyright © 2024-2025 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. */ @file:Suppress("MemberVisibilityCanBePrivate") import java.util.* import org.gradle.jvm.toolchain.JavaLanguageVersion typealias JavaVersionPair = Pair // All LTS releases. private val ltsReleases = sortedSetOf( JavaLanguageVersion.of(8), JavaLanguageVersion.of(11), JavaLanguageVersion.of(17), JavaLanguageVersion.of(21), ) /** Describes an inclusive range of JVM versions, based on the [JavaLanguageVersion] type. */ @JvmInline value class JavaVersionRange private constructor(private val bounds: JavaVersionPair) : Iterable { @Suppress("unused") companion object { fun isLTS(version: JavaLanguageVersion): Boolean = version in ltsReleases fun inclusive(floor: JavaLanguageVersion, ceiling: JavaLanguageVersion): JavaVersionRange = JavaVersionRange(floor to ceiling) fun startingAt(floor: JavaLanguageVersion): JavaVersionRange = inclusive(floor, JavaLanguageVersion.of(PKL_TEST_JDK_MAXIMUM)) fun upTo(ceiling: JavaLanguageVersion): JavaVersionRange = inclusive(JavaLanguageVersion.of(PKL_TEST_JDK_MINIMUM), ceiling) } operator fun contains(version: JavaLanguageVersion): Boolean = version >= bounds.first && version <= bounds.second fun asSequence(): Sequence = sequence { var current = bounds.first while (current <= bounds.second) { yield(current) current = JavaLanguageVersion.of(current.asInt() + 1) } } fun asSortedSet(): SortedSet = asSequence().toSortedSet() override fun iterator(): Iterator = asSortedSet().iterator() } ================================================ FILE: buildSrc/src/main/kotlin/MergeSourcesJars.kt ================================================ /* * Copyright © 2024-2025 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. */ import java.io.File import java.util.regex.Matcher import java.util.regex.Pattern import org.gradle.api.DefaultTask import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.FileVisitDetails import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.ListProperty import org.gradle.api.provider.MapProperty import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.listProperty import org.gradle.kotlin.dsl.mapProperty open class MergeSourcesJars : DefaultTask() { @get:InputFiles val inputJars: ConfigurableFileCollection = project.objects.fileCollection() @get:InputFiles val mergedBinaryJars: ConfigurableFileCollection = project.objects.fileCollection() @get:Input val relocatedPackages: MapProperty = project.objects.mapProperty() @get:Input var sourceFileExtensions: ListProperty = project.objects.listProperty().convention(listOf(".java", ".kt")) @get:OutputFile val outputJar: RegularFileProperty = project.objects.fileProperty() @TaskAction @Suppress("unused") fun merge() { val binaryPaths = collectBinaryPaths() val relocatedPkgs = relocatedPackages.get() val relocatedPaths = relocatedPkgs.entries.associate { (key, value) -> toPath(key) to toPath(value) } // use negative lookbehind to match any that don't precede with // a word or a period character. should catch most cases. val importPattern = Pattern.compile( "(? { val result = mutableSetOf() for (jar in mergedBinaryJars) { // as of Gradle 2.4 doesn't visit dirs despite the claims project.zipTree(jar).visit { val details = this if (details.isDirectory) return@visit // avoid adding empty dirs result.add(details.relativePath.parent!!.pathString) } } return result } private fun fixImports( relocatedPkgs: Map, details: FileVisitDetails, sourceText: String, importPattern: Pattern, ): String { val matcher = importPattern.matcher(sourceText) val buffer = StringBuffer() logger.debug("Inspecting file: {}", details.relativePath) while (matcher.find()) { val newStat = relocatedPkgs[matcher.group(2)] logger.debug("Old: {}", matcher.group()) logger.debug("New: {}", newStat) matcher.appendReplacement(buffer, Matcher.quoteReplacement(newStat)) } matcher.appendTail(buffer) return buffer.toString() } private fun toPath(packageName: String): String = packageName.replace(".", "/") } ================================================ FILE: buildSrc/src/main/kotlin/NativeImageBuild.kt ================================================ /* * Copyright © 2025 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. */ import javax.inject.Inject import org.gradle.api.DefaultTask import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.services.BuildService import org.gradle.api.services.BuildServiceParameters import org.gradle.api.tasks.ClasspathNormalizer import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.registerIfAbsent import org.gradle.kotlin.dsl.withNormalizer import org.gradle.process.ExecOperations enum class Architecture { AMD64, AARCH64, } abstract class NativeImageBuildService : BuildService abstract class NativeImageBuild : DefaultTask() { @get:Input abstract val imageName: Property @get:Input abstract val extraNativeImageArgs: ListProperty @get:Input abstract val arch: Property @get:Input abstract val mainClass: Property @get:InputFiles abstract val classpath: ConfigurableFileCollection private val outputDir = project.layout.buildDirectory.dir("executable") @get:OutputFile val outputFile = outputDir.flatMap { it.file(imageName) } @get:Inject protected abstract val execOperations: ExecOperations private val graalVm: Provider = arch.map { a -> when (a) { Architecture.AMD64 -> buildInfo.graalVmAmd64 Architecture.AARCH64 -> buildInfo.graalVmAarch64 } } private val buildInfo: BuildInfo = project.extensions.getByType(BuildInfo::class.java) private val nativeImageCommandName = if (buildInfo.os.isWindows) "native-image.cmd" else "native-image" private val nativeImageExecutable = graalVm.map { "${it.baseDir}/bin/$nativeImageCommandName" } private val extraArgsFromProperties by lazy { System.getProperties() .filter { it.key.toString().startsWith("pkl.native") } .map { "${it.key}=${it.value}".substring("pkl.native".length) } } private val buildService = project.gradle.sharedServices.registerIfAbsent( "nativeImageBuildService", NativeImageBuildService::class, ) { maxParallelUsages.set(1) } init { // ensure native-image builds run in serial (prevent `gw buildNative` from consuming all host // CPU resources). usesService(buildService) group = "build" inputs .files(classpath) .withPropertyName("runtimeClasspath") .withNormalizer(ClasspathNormalizer::class) inputs .files(nativeImageExecutable) .withPropertyName("graalVmNativeImage") .withPathSensitivity(PathSensitivity.ABSOLUTE) } @TaskAction protected fun run() { execOperations.exec { val exclusions = listOf(buildInfo.libs.findLibrary("graalSdk").get()).map { it.get().module.name } executable = nativeImageExecutable.get() workingDir(outputDir) args = buildList { // must be emitted before any experimental options are used add("-H:+UnlockExperimentalVMOptions") // currently gives a deprecation warning, but we've been told // that the "initialize everything at build time" *CLI* option is likely here to stay 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") add("--no-fallback") add("-H:IncludeResources=org/pkl/core/stdlib/.*\\.pkl") add("-H:IncludeResources=org/jline/utils/.*") add("-H:IncludeResourceBundles=org.pkl.core.errorMessages") add("-H:IncludeResourceBundles=org.pkl.parser.errorMessages") add("-H:IncludeResources=org/pkl/commons/cli/PklCARoots.pem") add("-H:Class=${mainClass.get()}") add("-o") add(imageName.get()) // the actual limit (currently) used by native-image is this number + 1400 (idea is to // compensate for Truffle's own nodes) add("-H:MaxRuntimeCompileMethods=1800") add("-H:+EnforceMaxRuntimeCompileMethods") add("--enable-url-protocols=http,https") add("-H:+ReportExceptionStackTraces") // disable automatic support for JVM CLI options (puts our main class in full control of // argument parsing) add("-H:-ParseRuntimeOptions") // quick build mode: 40% faster compilation, 20% smaller (but presumably also slower) // executable if (!buildInfo.isReleaseBuild) { add("-Ob") } if (buildInfo.isNativeArch) { add("-march=native") } else { add("-march=compatibility") } // native-image rejects non-existing class path entries -> filter add("--class-path") val pathInput = classpath.filter { it.exists() && !exclusions.any { exclude -> it.name.contains(exclude) } } add(pathInput.asPath) // make sure dev machine stays responsive (15% slowdown on my laptop) val processors = Runtime.getRuntime().availableProcessors() / if (buildInfo.os.isMacOsX && !buildInfo.isCiBuild) 4 else 1 add("-J-XX:ActiveProcessorCount=${processors}") // Pass through all `HOMEBREW_` prefixed environment variables to allow build with shimmed // tools. addAll(environment.keys.filter { it.startsWith("HOMEBREW_") }.map { "-E$it" }) addAll(extraNativeImageArgs.get()) addAll(extraArgsFromProperties) } } } } ================================================ FILE: buildSrc/src/main/kotlin/PklFormatterSpotless.kt ================================================ /* * Copyright © 2025-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. */ import com.diffplug.spotless.FormatterFunc import com.diffplug.spotless.FormatterStep import java.io.Serial import java.io.Serializable import java.net.URLClassLoader import org.gradle.api.artifacts.Configuration class PklFormatterStep(@Transient private val configuration: Configuration) : Serializable { companion object { @Serial private const val serialVersionUID: Long = 1L } fun create(): FormatterStep { return FormatterStep.createLazy( "pkl", { PklFormatterStep(configuration) }, { PklFormatterFunc(configuration) }, ) } } class PklFormatterFunc(@Transient private val configuration: Configuration) : FormatterFunc, Serializable { companion object { @Serial private const val serialVersionUID: Long = 1L } private val classLoader by lazy { val urls = configuration.files.map { it.toURI().toURL() } URLClassLoader(urls.toTypedArray()) } private val formatterClass by lazy { classLoader.loadClass("org.pkl.formatter.Formatter") } private val formatMethod by lazy { formatterClass.getMethod("format", String::class.java) } private val formatterInstance by lazy { formatterClass.getConstructor().newInstance() } override fun apply(input: String): String { return formatMethod(formatterInstance, input) as String } } ================================================ FILE: buildSrc/src/main/kotlin/PklPublishing.kt ================================================ /* * Copyright © 2024-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. */ import java.nio.charset.StandardCharsets import java.util.Base64 import org.gradle.api.Project import org.gradle.api.publish.PublishingExtension import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.publish.maven.tasks.AbstractPublishToMaven import org.gradle.api.publish.maven.tasks.GenerateMavenPom import org.gradle.kotlin.dsl.* import org.gradle.plugins.signing.SigningExtension /** Configures common POM metadata (licenses, developers, SCM, etc.) for all Pkl publications. */ fun Project.configurePklPomMetadata() { extensions.configure { publications.withType().configureEach { pom { name.set(artifactId) licenses { license { name.set("The Apache Software License, Version 2.0") url.set("https://github.com/apple/pkl/blob/main/LICENSE.txt") } } developers { developer { id.set("pkl-authors") name.set("The Pkl Authors") email.set("pkl-oss@group.apple.com") } } scm { connection.set("scm:git:git://github.com/apple/pkl.git") developerConnection.set("scm:git:ssh://github.com/apple/pkl.git") val buildInfo = extensions.getByType() url.set("https://github.com/apple/pkl/tree/${buildInfo.commitish}") } issueManagement { system.set("GitHub Issues") url.set("https://github.com/apple/pkl/issues") } ciManagement { system.set("GitHub Actions") url.set("https://github.com/apple/pkl/actions") } } } } } /** Configures POM validation task to check for unresolved versions and snapshots in releases. */ fun Project.configurePomValidation() { val validatePom by tasks.registering { if (tasks.findByName("generatePomFileForLibraryPublication") == null) { return@registering } val generatePomFileForLibraryPublication by tasks.existing(GenerateMavenPom::class) val outputFile = layout.buildDirectory.file("validatePom") // dummy output to satisfy up-to-date check dependsOn(generatePomFileForLibraryPublication) inputs.file(generatePomFileForLibraryPublication.get().destination) outputs.file(outputFile) doLast { outputFile.get().asFile.delete() val pomFile = generatePomFileForLibraryPublication.get().destination assert(pomFile.exists()) val text = pomFile.readText() run { val unresolvedVersion = Regex(".*[+,()\\[\\]].*") val matches = unresolvedVersion.findAll(text).toList() if (matches.isNotEmpty()) { throw org.gradle.api.GradleException( """ Found unresolved version selector(s) in generated POM: ${matches.joinToString("\n") { it.groupValues[0] }} """ .trimIndent() ) } } val buildInfo = project.extensions.getByType() if (buildInfo.isReleaseBuild) { val snapshotVersion = Regex(".*-SNAPSHOT") val matches = snapshotVersion.findAll(text).toList() if (matches.isNotEmpty()) { throw org.gradle.api.GradleException( """ Found snapshot version(s) in generated POM of Pkl release version: ${matches.joinToString("\n") { it.groupValues[0] }} """ .trimIndent() ) } } outputFile.get().asFile.writeText("OK") } } tasks.named("publish") { dependsOn(validatePom) } } /** Configures signing for Pkl publications. */ fun Project.configurePklSigning() { // Workaround for maven publish plugin not setting up dependencies correctly. // Taken from https://github.com/gradle/gradle/issues/26091#issuecomment-1798137734 val dependsOnTasks = mutableListOf() tasks.withType().configureEach { dependsOnTasks.add(name.replace("publish", "sign").replaceAfter("Publication", "")) dependsOn(dependsOnTasks) } extensions.configure { // provided as env vars `ORG_GRADLE_PROJECT_signingKey` and // `ORG_GRADLE_PROJECT_signingPassword` in CI. val signingKey = (findProperty("signingKey") as String?)?.let { Base64.getDecoder().decode(it).toString(StandardCharsets.US_ASCII) } val signingPassword = findProperty("signingPassword") as String? if (signingKey != null && signingPassword != null) { useInMemoryPgpKeys(signingKey, signingPassword) } extensions.getByType().publications.findByName("library")?.let { sign(it) } } } ================================================ FILE: buildSrc/src/main/kotlin/ResolveSourcesJars.kt ================================================ /* * Copyright © 2024 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. */ import org.gradle.api.DefaultTask import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.result.ResolvedArtifactResult import org.gradle.api.artifacts.result.ResolvedDependencyResult import org.gradle.api.file.DirectoryProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction import org.gradle.jvm.JvmLibrary import org.gradle.kotlin.dsl.property import org.gradle.language.base.artifact.SourcesArtifact open class ResolveSourcesJars : DefaultTask() { @get:InputFiles val configuration: Property = project.objects.property() @get:OutputDirectory val outputDir: DirectoryProperty = project.objects.directoryProperty() @TaskAction @Suppress("UnstableApiUsage", "unused") fun resolve() { val componentIds = configuration.get().incoming.resolutionResult.allDependencies.map { (it as ResolvedDependencyResult).selected.id } val resolutionResult = project.dependencies .createArtifactResolutionQuery() .forComponents(componentIds) .withArtifacts(JvmLibrary::class.java, SourcesArtifact::class.java) .execute() val resolvedJars = resolutionResult.resolvedComponents .flatMap { it.getArtifacts(SourcesArtifact::class.java) } .map { (it as ResolvedArtifactResult).file } // copying to an output dir because I don't know how else to describe task outputs project.sync { from(resolvedJars) into(outputDir) } } } ================================================ FILE: buildSrc/src/main/kotlin/pklAllProjects.gradle.kts ================================================ /* * Copyright © 2024-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. */ import com.diffplug.gradle.spotless.KotlinGradleExtension import org.gradle.accessors.dm.LibrariesForLibs import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile plugins { id("com.diffplug.spotless") } val buildInfo = extensions.create("buildInfo", project) dependencyLocking { lockAllConfigurations() } configurations { val rejectedVersionSuffix = Regex("-alpha|-beta|-eap|-m|-rc|-snapshot", RegexOption.IGNORE_CASE) configureEach { resolutionStrategy { componentSelection { all { if (rejectedVersionSuffix.containsMatchIn(candidate.version)) { reject( "Rejected dependency $candidate " + "because it has a prelease version suffix matching `$rejectedVersionSuffix`." ) } } } } } } configurations.all { resolutionStrategy.eachDependency { if (requested.group == "org.jetbrains.kotlin") { // prevent transitive deps from bumping Koltin version useVersion(libs.versions.kotlin.get()) } } } plugins.withType(JavaPlugin::class).configureEach { tasks.withType().configureEach { options.release = 17 } } tasks.withType().configureEach { compilerOptions { jvmTarget = JvmTarget.JVM_17 freeCompilerArgs.addAll("-Xjsr305=strict", "-Xjvm-default=all") freeCompilerArgs.add("-Xjdk-release=17") } } plugins.withType(IdeaPlugin::class).configureEach { val errorMessage = "Use IntelliJ Gradle import instead of running the `idea` task. See README for more information." tasks.named("idea") { doFirst { throw GradleException(errorMessage) } } tasks.named("ideaModule") { doFirst { throw GradleException(errorMessage) } } if (project == rootProject) { tasks.named("ideaProject") { doFirst { throw GradleException(errorMessage) } } } } plugins.withType(MavenPublishPlugin::class).configureEach { configure { // CI builds pick up artifacts from this repo. // It's important that this repo is only declared once per project. repositories { maven { name = "projectLocal" // affects task names url = rootDir.resolve("build/m2").toURI() } } // use resolved/locked (e.g., `1.15`) // instead of declared (e.g., `1.+`) // dependency versions in generated POMs publications { withType(MavenPublication::class.java) { versionMapping { allVariants { fromResolutionResult() } } } } } } // settings.gradle.kts sets `--write-locks` // if Gradle command line contains this task name val updateDependencyLocks by tasks.registering { doLast { configurations.filter { it.isCanBeResolved }.forEach { it.resolve() } } } val allDependencies by tasks.registering(DependencyReportTask::class) tasks.withType(Test::class).configureEach { System.getProperty("testReportsDir")?.let { reportsDir -> reports.junitXml.outputLocation.set(file(reportsDir).resolve(project.name).resolve(name)) } debugOptions { enabled = System.getProperty("jvmdebug")?.toBoolean() ?: false @Suppress("UnstableApiUsage") host = "*" port = 5005 suspend = true server = true } } tasks.withType(JavaExec::class).configureEach { debugOptions { enabled = System.getProperty("jvmdebug")?.toBoolean() ?: false @Suppress("UnstableApiUsage") host = "*" port = 5005 suspend = true server = true } } // Version Catalog library symbols. private val libs = the() private val licenseHeaderFile by lazy { rootProject.file("buildSrc/src/main/resources/license-header.star-block.txt") } private fun KotlinGradleExtension.configureFormatter() { ktfmt(libs.versions.ktfmt.get()).googleStyle() licenseHeaderFile(licenseHeaderFile, "([a-zA-Z]|@file|//)") } val originalRemoteName = System.getenv("PKL_ORIGINAL_REMOTE_NAME") ?: "origin" // if we're running against a release branch (or a PR targeted at one), use that branch for // ratcheting // these env vars are set by GitHub actions: // https://docs.github.com/en/actions/reference/workflows-and-actions/variables#default-environment-variables val ratchetBranchName = (System.getenv("GITHUB_BASE_REF") ?: System.getenv("GITHUB_REF_NAME"))?.let { if (it.startsWith("release/")) it else null } ?: "main" spotless { ratchetFrom = "$originalRemoteName/$ratchetBranchName" // When building root project, format buildSrc files too. // We need this because buildSrc is not a subproject of the root project, so a top-level // `spotlessApply` will not trigger `buildSrc:spotlessApply`. if (project === rootProject) { kotlinGradle { configureFormatter() target("*.kts", "buildSrc/*.kts", "buildSrc/src/*/kotlin/**/*.kts") } kotlin { ktfmt(libs.versions.ktfmt.get()).googleStyle() target("buildSrc/src/*/kotlin/**/*.kt") licenseHeaderFile(licenseHeaderFile) } } else { kotlinGradle { configureFormatter() target("*.kts") } } } ================================================ FILE: buildSrc/src/main/kotlin/pklFatJar.gradle.kts ================================================ /* * Copyright © 2024-2025 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. */ import org.gradle.api.GradleException import org.gradle.api.artifacts.Configuration import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.tasks.bundling.Jar import org.gradle.api.tasks.testing.Test import org.gradle.kotlin.dsl.* plugins { `java-library` `maven-publish` id("com.gradleup.shadow") } // make fat Jar available to other subprojects val fatJarConfiguration: Configuration = configurations.create("fatJar") val fatJarPublication: MavenPublication = publishing.publications.create("fatJar") // ideally we'd configure this automatically based on project dependencies val firstPartySourcesJarsConfiguration: Configuration = configurations.create("firstPartySourcesJars") val relocations = mapOf( // pkl-core dependencies "org.organicdesign.fp." to "org.pkl.thirdparty.paguro.", "org.snakeyaml.engine." to "org.pkl.thirdparty.snakeyaml.engine.", "org.msgpack." to "org.pkl.thirdparty.msgpack.", "org.w3c.dom." to "org.pkl.thirdparty.w3c.dom.", "com.oracle.svm.core." to "org.pkl.thirdparty.svm.", // pkl-cli dependencies "org.jline." to "org.pkl.thirdparty.jline.", "com.github.ajalt.clikt." to "org.pkl.thirdparty.clikt.", "com.github.ajalt.colormath." to "org.pkl.thirdparty.colormath.", "com.github.ajalt.mordant." to "org.pkl.thirdparty.mordant.", "com.sun.jna." to "org.pkl.thirdparty.jna.", "kotlin." to "org.pkl.thirdparty.kotlin.", "kotlinx." to "org.pkl.thirdparty.kotlinx.", "org.intellij." to "org.pkl.thirdparty.intellij.", "org.fusesource.jansi." to "org.pkl.thirdparty.jansi.", "org.fusesource.hawtjni." to "org.pkl.thirdparty.hawtjni.", // pkl-doc dependencies "org.commonmark." to "org.pkl.thirdparty.commonmark.", "org.jetbrains." to "org.pkl.thirdparty.jetbrains.", "_COROUTINE." to "org.pkl.thirdparty.kotlinx._COROUTINE.", // pkl-config-java dependencies "io.leangen.geantyref." to "org.pkl.thirdparty.geantyref.", // pkl-codegen-java dependencies "com.palantir.javapoet." to "org.pkl.thirdparty.javapoet.", // pkl-codegen-kotlin dependencies "com.squareup.kotlinpoet." to "org.pkl.thirdparty.kotlinpoet.", ) for ((key, value) in relocations) { if (!key.endsWith(".")) { throw GradleException( "Invalid relocation `\"$key\" to \"$value\"`: `$key` should end with a dot" ) } if (!value.endsWith(".")) { throw GradleException( "Invalid relocation `\"$key\" to \"$value\"`: `$value` should end with a dot" ) } } val nonRelocations = listOf("com/oracle/truffle/", "org/graalvm/") tasks.shadowJar { inputs.property("relocations", relocations) archiveClassifier.set(null as String?) configurations = listOf(project.configurations.runtimeClasspath.get()) addMultiReleaseAttribute = true // not required at runtime / fat JARs can't be used in native-image builds anyway exclude("org/pkl/cli/svm/**") exclude("META-INF/maven/**") exclude("META-INF/upgrade/**") exclude("DebugProbesKt.bin") val info = project.extensions.getByType() val minimumJvmTarget = JavaVersion.toVersion(info.jvmTarget) manifest.attributes( // Certain exports need to be added to the Java modulepath for Java 17 to work properly with // shaded JARs. See the following link for an explanation of this syntax: // https://bugs.openjdk.org/browse/JDK-8335225 "Add-Exports" to info.jpmsExportsForJarManifest ) // effectively, this results in calls excluding: // `META-INF/versions/{18-25}/**` // at the time of this writing; multi-release JARs beyond JDK 21 break the current // version of the Shadow plugin, and aren't needed for Truffle's use by Pkl. JavaVersionRange.startingAt(JavaLanguageVersion.of(minimumJvmTarget.majorVersion.toInt() + 1)) .forEach { exclude("META-INF/versions/${it.asInt()}/**") } exclude("module-info.*") for ((from, to) in relocations) { relocate(from, to) } // necessary for service files to be adapted to relocation mergeServiceFiles() } shadow { addShadowVariantIntoJavaComponent = false } val testFatJar by tasks.registering(Test::class) { testClassesDirs = files(tasks.test.get().testClassesDirs) classpath = // compiled test classes sourceSets.test.get().output + // fat Jar tasks.shadowJar.get().outputs.files + // test-only dependencies // (test dependencies that are also main dependencies must already be contained in fat Jar; // to verify that, we don't want to include them here) (configurations.testRuntimeClasspath.get() - configurations.runtimeClasspath.get()) } tasks.check { dependsOn(testFatJar) } val validateFatJar by tasks.registering { val outputFile = layout.buildDirectory.file("validateFatJar/result.txt") inputs.files(tasks.shadowJar) inputs.property("nonRelocations", nonRelocations) outputs.file(outputFile) doLast { val unshadowedFiles = mutableListOf() zipTree(tasks.shadowJar.get().outputs.files.singleFile).visit { val fileDetails = this val path = fileDetails.relativePath.pathString if ( !(fileDetails.isDirectory || path.startsWith("org/pkl/") || path.startsWith("META-INF/") || nonRelocations.any { path.startsWith(it) }) ) { // don't throw exception inside `visit` // as this gives a misleading "Could not expand ZIP" error message unshadowedFiles.add(path) } } if (unshadowedFiles.isEmpty()) { outputFile.get().asFile.writeText("SUCCESS") } else { outputFile.get().asFile.writeText("FAILURE") throw GradleException("Found unshadowed files:\n" + unshadowedFiles.joinToString("\n")) } } } tasks.check { dependsOn(validateFatJar) } val resolveSourcesJars by tasks.registering(ResolveSourcesJars::class) { configuration.set(configurations.runtimeClasspath) outputDir.set(layout.buildDirectory.dir("resolveSourcesJars")) } val fatSourcesJar by tasks.registering(MergeSourcesJars::class) { plugins.withId("pklJavaLibrary") { inputJars.from(tasks.named("sourcesJar")) } inputJars.from(firstPartySourcesJarsConfiguration) inputJars.from(resolveSourcesJars.map { fileTree(it.outputDir) }) mergedBinaryJars.from(tasks.shadowJar) relocatedPackages.set(relocations) outputJar.fileProvider( provider { file(tasks.shadowJar.get().archiveFile.get().asFile.path.replace(".jar", "-sources.jar")) } ) } artifacts { add("fatJar", tasks.shadowJar) } publishing { publications { named("fatJar") { from(components["shadow"]) // sources Jar is fat artifact(fatSourcesJar.flatMap { it.outputJar.asFile }) { classifier = "sources" } plugins.withId("pklJavaLibrary") { val javadocJar by tasks.existing(Jar::class) // Javadoc Jar is not fat (didn't invest effort) artifact(javadocJar.flatMap { it.archiveFile }) { classifier = "javadoc" } } } } } ================================================ FILE: buildSrc/src/main/kotlin/pklGraalVm.gradle.kts ================================================ /* * Copyright © 2024 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. */ import de.undercouch.gradle.tasks.download.Download import de.undercouch.gradle.tasks.download.Verify plugins { id("de.undercouch.download") } val buildInfo = project.extensions.getByType() // tries to minimize chance of corruption by download-to-temp-file-and-move val downloadGraalVmAarch64 by tasks.registering(Download::class) { configureDownloadGraalVm(buildInfo.graalVmAarch64) } val downloadGraalVmAmd64 by tasks.registering(Download::class) { configureDownloadGraalVm(buildInfo.graalVmAmd64) } fun Download.configureDownloadGraalVm(graalvm: BuildInfo.GraalVm) { onlyIf { !graalvm.installDir.exists() } doLast { println("Downloaded GraalVm to ${graalvm.downloadFile}") } src(graalvm.downloadUrl) dest(graalvm.downloadFile) overwrite(false) tempAndMove(true) } val verifyGraalVmAarch64 by tasks.registering(Verify::class) { configureVerifyGraalVm(buildInfo.graalVmAarch64) dependsOn(downloadGraalVmAarch64) } val verifyGraalVmAmd64 by tasks.registering(Verify::class) { configureVerifyGraalVm(buildInfo.graalVmAmd64) dependsOn(downloadGraalVmAmd64) } fun Verify.configureVerifyGraalVm(graalvm: BuildInfo.GraalVm) { onlyIf { !graalvm.installDir.exists() } src(graalvm.downloadFile) checksum( buildInfo.libs.findVersion("graalVmSha256-${graalvm.osName}-${graalvm.arch}").get().toString() ) algorithm("SHA-256") } @Suppress("unused") val installGraalVmAarch64 by tasks.registering(InstallGraalVm::class) { dependsOn(verifyGraalVmAarch64) graalVm = buildInfo.graalVmAarch64 } @Suppress("unused") val installGraalVmAmd64 by tasks.registering(InstallGraalVm::class) { dependsOn(verifyGraalVmAmd64) graalVm = buildInfo.graalVmAmd64 } ================================================ FILE: buildSrc/src/main/kotlin/pklGradlePluginTest.gradle.kts ================================================ /* * Copyright © 2024-2025 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. */ plugins { java } val gradlePluginTests = extensions.create("gradlePluginTests") tasks.addRule("Pattern: compatibilityTest[All|Releases|Latest|Candidate|Nightly|]") { val taskName = this val matchResult = Regex("compatibilityTest(.+)").matchEntire(taskName) ?: return@addRule when (val taskNameSuffix = matchResult.groupValues[1]) { "All" -> task("compatibilityTestAll") { dependsOn( "compatibilityTestReleases", "compatibilityTestCandidate", "compatibilityTestNightly", ) } // releases in configured range "Releases" -> task("compatibilityTestReleases") { val versionInfos = GradleVersionInfo.fetchReleases() val allVersions = versionInfos .filter { versionInfo -> val v = versionInfo.gradleVersion !versionInfo.broken && v in gradlePluginTests.minGradleVersion..gradlePluginTests.maxGradleVersion && v !in gradlePluginTests.skippedGradleVersions } .sortedBy { it.gradleVersion } val versionsToTestAgainst = listOf(allVersions.first(), allVersions.last()) dependsOn(versionsToTestAgainst.map { createCompatibilityTestTask(it) }) } // latest release (if not developing against latest) "Latest" -> task("compatibilityTestLatest") { val versionInfo = GradleVersionInfo.fetchCurrent() if (versionInfo.version == gradle.gradleVersion) { doLast { println( "No new Gradle release available. " + "(Run `gradlew test` to test against ${versionInfo.version}.)" ) } } else { dependsOn(createCompatibilityTestTask(versionInfo)) } } // active release candidate (if any) "Candidate" -> task("compatibilityTestCandidate") { val versionInfo = GradleVersionInfo.fetchRc() if (versionInfo?.activeRc == true) { dependsOn(createCompatibilityTestTask(versionInfo)) } else { doLast { println("No active Gradle release candidate available.") } } } // latest nightly "Nightly" -> task("compatibilityTestNightly") { val versionInfo = GradleVersionInfo.fetchNightly() dependsOn(createCompatibilityTestTask(versionInfo)) } // explicit version else -> createCompatibilityTestTask( taskNameSuffix, "https://services.gradle.org/distributions-snapshots/gradle-$taskNameSuffix-bin.zip", ) } } fun createCompatibilityTestTask(versionInfo: GradleVersionInfo): TaskProvider = createCompatibilityTestTask(versionInfo.version, versionInfo.downloadUrl) fun createCompatibilityTestTask(version: String, downloadUrl: String): TaskProvider { return tasks.register("compatibilityTest$version", Test::class.java) { mustRunAfter(tasks.test) maxHeapSize = tasks.test.get().maxHeapSize jvmArgs = tasks.test.get().jvmArgs classpath = tasks.test.get().classpath systemProperty("testGradleVersion", version) systemProperty("testGradleDistributionUrl", downloadUrl) doFirst { if (version == gradle.gradleVersion && gradle.taskGraph.hasTask(tasks.test.get())) { // don't test same version twice println("This version has already been tested by the `test` task.") throw StopExecutionException() } } } } ================================================ FILE: buildSrc/src/main/kotlin/pklHtmlValidator.gradle.kts ================================================ /* * Copyright © 2024 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. */ plugins { base } val htmlValidator = extensions.create("htmlValidator", project) val buildInfo = project.extensions.getByType() val validatorConfiguration: Configuration = configurations.create("validator") { resolutionStrategy.eachDependency { if (requested.group == "log4j" && requested.name == "log4j") { @Suppress("UnstableApiUsage") useTarget(buildInfo.libs.findLibrary("log4j12Api").get()) because("mitigate critical security vulnerabilities") } } } dependencies { @Suppress("UnstableApiUsage") validatorConfiguration(buildInfo.libs.findLibrary("nuValidator").get()) { // we only want jetty-util and jetty-util-ajax (with the right version) // couldn't find a more robust way to express this exclude(group = "org.eclipse.jetty", module = "jetty-continuation") exclude(group = "org.eclipse.jetty", module = "jetty-http") exclude(group = "org.eclipse.jetty", module = "jetty-io") exclude(group = "org.eclipse.jetty", module = "jetty-security") exclude(group = "org.eclipse.jetty", module = "jetty-server") exclude(group = "org.eclipse.jetty", module = "jetty-servlets") exclude(group = "javax.servlet") exclude(group = "commons-fileupload") } } val validateHtml by tasks.registering(JavaExec::class) { val resultFile = layout.buildDirectory.file("validateHtml/result.txt") inputs.files(htmlValidator.sources) outputs.file(resultFile) classpath = validatorConfiguration mainClass.set("nu.validator.client.SimpleCommandLineValidator") args( "--skip-non-html" ) // --also-check-css doesn't work (still checks css as html), so limit to html files args("--filterpattern", "(.*)Consider adding “lang=(.*)") args("--filterpattern", "(.*)Consider adding a “lang” attribute(.*)") args("--filterpattern", "(.*)unrecognized media “amzn-kf8”(.*)") // kindle // for debugging // args "--verbose" args(htmlValidator.sources) // write a basic result file s.t. gradle can consider task up-to-date // writing a result file in case validation fails is not easily possible with JavaExec, but also // not strictly necessary doFirst { project.delete(resultFile) } doLast { resultFile.get().asFile.writeText("Success.") } } tasks.check { dependsOn(validateHtml) } ================================================ FILE: buildSrc/src/main/kotlin/pklJavaExecutable.gradle.kts ================================================ /* * Copyright © 2025 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. */ import kotlin.io.path.createDirectories import kotlin.io.path.writeText import org.gradle.kotlin.dsl.support.serviceOf plugins { id("pklJavaLibrary") // id("pklPublishLibrary") id("com.gradleup.shadow") } val executableSpec = project.extensions.create("executable", ExecutableSpec::class.java) val buildInfo = project.extensions.getByType() val javaExecutable by tasks.registering(ExecutableJar::class) { group = "build" dependsOn(tasks.jar) inJar = tasks.shadowJar.flatMap { it.archiveFile } val effectiveJavaName = executableSpec.javaName.map { name -> if (buildInfo.os.isWindows) "$name.bat" else name } outJar = layout.buildDirectory.dir("executable").flatMap { it.file(effectiveJavaName) } // uncomment for debugging // jvmArgs.addAll("-ea", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005") } fun Task.setupTestStartJavaExecutable(launcher: Provider? = null) { group = "verification" dependsOn(javaExecutable) // dummy output to satisfy up-to-date check val outputFile = layout.buildDirectory.file("testStartJavaExecutable/$name") outputs.file(outputFile) val execOutput = providers.exec { val executablePath = javaExecutable.get().outputs.files.singleFile if (launcher?.isPresent == true) { commandLine( launcher.get().executablePath.asFile.absolutePath, "-jar", executablePath.absolutePath, "--version", ) } else { commandLine(executablePath.absolutePath, "--version") } } doLast { val outputText = execOutput.standardOutput.asText.get() if (!outputText.contains(buildInfo.pklVersionNonUnique)) { throw GradleException( "Expected version output to contain current version (${buildInfo.pklVersionNonUnique}), but got '$outputText'" ) } outputFile.get().asFile.toPath().apply { try { parent.createDirectories() } catch (ignored: java.nio.file.FileAlreadyExistsException) {} writeText("OK") } } } val testStartJavaExecutable by tasks.registering { setupTestStartJavaExecutable() } // Setup `testStartJavaExecutable` tasks for multi-JDK testing. val testStartJavaExecutableOnOtherJdks = buildInfo.jdkTestRange.map { jdkTarget -> tasks.register("testStartJavaExecutableJdk${jdkTarget.asInt()}") { enabled = buildInfo.isVersionEnabled(jdkTarget) val toolChainService: JavaToolchainService = serviceOf() val launcher = toolChainService.launcherFor { languageVersion = jdkTarget } setupTestStartJavaExecutable(launcher) } } tasks.assemble { dependsOn(javaExecutable) } tasks.check { dependsOn(testStartJavaExecutable) dependsOn(testStartJavaExecutableOnOtherJdks) } // publishing { // publications { // // need to put in `afterEvaluate` because `artifactId` cannot be set lazily. // project.afterEvaluate { // register("javaExecutable") { // artifactId = executableSpec.javaPublicationName.get() // // artifact(javaExecutable.map { it.outputs.files.singleFile }) { // classifier = null // extension = "bin" // builtBy(javaExecutable) // } // // pom { // url = executableSpec.website // description = // executableSpec.documentationName.map { name -> // """ // $name executable for Java. // Can be executed directly, or with `java -jar `. // Requires Java 17 or higher. // """ // .trimIndent() // } // } // } // } // } // } // signing { project.afterEvaluate { sign(publishing.publications["javaExecutable"]) } } ================================================ FILE: buildSrc/src/main/kotlin/pklJavaLibrary.gradle.kts ================================================ /* * Copyright © 2024-2025 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. */ @file:Suppress("HttpUrlsUsage", "unused") import org.gradle.accessors.dm.LibrariesForLibs plugins { `java-library` `jvm-toolchains` `jvm-test-suite` id("pklKotlinTest") id("com.diffplug.spotless") } // make sources Jar available to other subprojects val sourcesJarConfiguration: Provider = configurations.register("sourcesJar") // Version Catalog library symbols. val libs = the() // Build configuration. val info = project.extensions.getByType() java { withSourcesJar() // creates `sourcesJar` task withJavadocJar() toolchain { languageVersion = info.jdkToolchainVersion vendor = info.jdkVendor } } artifacts { // make sources Jar available to other subprojects add("sourcesJar", tasks["sourcesJar"]) } spotless { java { googleJavaFormat(libs.versions.googleJavaFormat.get()) target("src/*/java/**/*.java") licenseHeaderFile(rootProject.file("buildSrc/src/main/resources/license-header.star-block.txt")) } kotlin { ktfmt(libs.versions.ktfmt.get()).googleStyle() target("src/*/kotlin/**/*.kt") licenseHeaderFile(rootProject.file("buildSrc/src/main/resources/license-header.star-block.txt")) } } tasks.compileKotlin { enabled = false } tasks.jar { manifest { attributes += mapOf( "Automatic-Module-Name" to "org.${project.name.replace("-", ".")}", "Add-Exports" to info.jpmsExportsForJarManifest, ) } } tasks.javadoc { classpath = sourceSets.main.get().output + sourceSets.main.get().compileClasspath source = sourceSets.main.get().allJava title = "${project.name} ${project.version} API" (options as StandardJavadocDocletOptions).addStringOption("Xdoclint:none", "-quiet") } val workAroundKotlinGradlePluginBug by tasks.registering { doLast { // Works around this problem, which sporadically appears and disappears in different // subprojects: // A problem was found with the configuration of task ':pkl-executor:compileJava' (type // 'JavaCompile'). // > Directory '[...]/pkl/pkl-executor/build/classes/kotlin/main' // specified for property 'compileKotlinOutputClasses' does not exist. layout.buildDirectory.dir("classes/kotlin/main").get().asFile.mkdirs() } } val truffleJavacArgs = listOf( // TODO: determine correct limits for Truffle specializations // (see https://graalvm.slack.com/archives/CNQSB2DHD/p1712380902746829) "-Atruffle.dsl.SuppressWarnings=truffle-limit" ) tasks.compileJava { javaCompiler = info.javaCompiler dependsOn(workAroundKotlinGradlePluginBug) options.compilerArgs.addAll(truffleJavacArgs + info.jpmsAddModulesFlags) } tasks.withType().configureEach { javaCompiler = info.javaCompiler options.release = info.jvmTarget } tasks.withType().configureEach { jvmArgs(info.jpmsAddModulesFlags) } fun Test.configureJdkTestTask(launcher: Provider) { useJUnitPlatform() javaLauncher = launcher systemProperties.putAll(info.testProperties) jvmArgs.addAll(info.jpmsAddModulesFlags) } tasks.test { configureJdkTestTask(info.javaTestLauncher) } // Prepare test tasks for each JDK version which is within the test target suite for Pkl. Each task // uses a pinned JDK toolchain version, and is named for the major version which is tested. // // Test tasks configured in this manner are executed manually by name, e.g. `./gradlew testJdk11`, // and automatically as dependencies of `check`. // // We omit the current JDK from this list because it is already tested, in effect, by the default // `test` task. // // Pkl subprojects may elect to further configure these tasks as needed; by default, each task // inherits the configuration of the default `test` task (aside from an overridden launcher). val jdkTestTasks = info.multiJdkTestingWith(tasks.test) { (_, jdk) -> configureJdkTestTask(jdk) } tasks.check { dependsOn(jdkTestTasks) } ================================================ FILE: buildSrc/src/main/kotlin/pklKotlinLibrary.gradle.kts ================================================ /* * Copyright © 2024-2025 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. */ import org.gradle.accessors.dm.LibrariesForLibs import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.KotlinVersion import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile plugins { id("pklJavaLibrary") kotlin("jvm") } // Build configuration. val buildInfo = project.extensions.getByType() // Version Catalog library symbols. val libs = the() dependencies { // At least some of our kotlin APIs contain Kotlin stdlib types // that aren't compiled away by kotlinc (e.g., `kotlin.Function`). // So let's be conservative and default to `api` for now. // For Kotlin APIs that only target Kotlin users (e.g., pkl-config-kotlin), // it won't make a difference. api(buildInfo.libs.findLibrary("kotlinStdLib").get()) } tasks.compileKotlin { enabled = true // disabled by pklJavaLibrary } tasks.withType().configureEach { compilerOptions { languageVersion = KotlinVersion.KOTLIN_2_1 jvmTarget = JvmTarget.fromTarget(buildInfo.jvmTarget.toString()) freeCompilerArgs.addAll("-Xjdk-release=${buildInfo.jvmTarget}") } } ================================================ FILE: buildSrc/src/main/kotlin/pklKotlinTest.gradle.kts ================================================ /* * Copyright © 2024-2025 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. */ import java.net.URI import org.gradle.accessors.dm.LibrariesForLibs import org.gradle.api.tasks.testing.logging.TestExceptionFormat plugins { `jvm-test-suite` kotlin("jvm") } val buildInfo = project.extensions.getByType() val libs = the() dependencies { testImplementation(libs.assertj) testImplementation(libs.junitApi) testImplementation(libs.junitParams) testImplementation(libs.kotlinStdLib) testRuntimeOnly(libs.junitEngine) testRuntimeOnly(libs.junitLauncher) } tasks.withType().configureEach { val testTask = this useJUnitPlatform() // enable checking of stdlib return types systemProperty("org.pkl.testMode", "true") reports.named("html") { enabled = true } testLogging { exceptionFormat = TestExceptionFormat.FULL } addTestListener( object : TestListener { override fun beforeSuite(suite: TestDescriptor) {} override fun beforeTest(testDescriptor: TestDescriptor) {} override fun afterTest(testDescriptor: TestDescriptor, result: TestResult) {} // print report link at end of task, not just at end of build override fun afterSuite(descriptor: TestDescriptor, result: TestResult) { if (descriptor.parent != null) return // only interested in overall result if (result.resultType == TestResult.ResultType.FAILURE) { println( "\nThere were failing tests. See the report at: ${fixFileUri(testTask.reports.html.entryPoint.toURI())}" ) } } // makes links clickable on macOS private fun fixFileUri(uri: URI): URI { if ("file" == uri.scheme && !uri.schemeSpecificPart.startsWith("//")) { return URI.create("file://" + uri.schemeSpecificPart) } return uri } } ) } ================================================ FILE: buildSrc/src/main/kotlin/pklNativeExecutable.gradle.kts ================================================ /* * Copyright © 2025 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. */ import java.lang.Runtime.Version import kotlin.io.path.createDirectories import kotlin.io.path.writeText import org.gradle.accessors.dm.LibrariesForLibs plugins { id("pklGraalVm") id("pklJavaLibrary") id("pklNativeLifecycle") id("pklPublishLibrary") id("com.gradleup.shadow") } // assumes that `pklJavaExecutable` is also applied val executableSpec = project.extensions.getByType() val buildInfo = project.extensions.getByType() val stagedMacAmd64Executable: Configuration by configurations.creating val stagedMacAarch64Executable: Configuration by configurations.creating val stagedLinuxAmd64Executable: Configuration by configurations.creating val stagedLinuxAarch64Executable: Configuration by configurations.creating val stagedAlpineLinuxAmd64Executable: Configuration by configurations.creating val stagedWindowsAmd64Executable: Configuration by configurations.creating val nativeImageClasspath by configurations.creating { extendsFrom(configurations.runtimeClasspath.get()) // Ensure native-image version uses GraalVM C SDKs instead of Java FFI or JNA // (comes from artifact `mordant-jvm-graal-ffi`). exclude("com.github.ajalt.mordant", "mordant-jvm-ffm") exclude("com.github.ajalt.mordant", "mordant-jvm-ffm-jvm") exclude("com.github.ajalt.mordant", "mordant-jvm-jna") exclude("com.github.ajalt.mordant", "mordant-jvm-jna-jvm") } val libs = the() dependencies { fun executableFile(suffix: String) = files( layout.buildDirectory.dir("executable").map { dir -> dir.file(executableSpec.name.map { "$it-$suffix" }) } ) nativeImageClasspath(libs.truffleRuntime) nativeImageClasspath(libs.graalSdk) stagedMacAarch64Executable(executableFile("macos-aarch64")) stagedMacAmd64Executable(executableFile("macos-amd64")) stagedLinuxAmd64Executable(executableFile("linux-amd64")) stagedLinuxAarch64Executable(executableFile("linux-aarch64")) stagedAlpineLinuxAmd64Executable(executableFile("alpine-linux-amd64")) stagedWindowsAmd64Executable(executableFile("windows-amd64.exe")) } private fun NativeImageBuild.amd64() { arch = Architecture.AMD64 dependsOn(":installGraalVmAmd64") } private fun NativeImageBuild.aarch64() { arch = Architecture.AARCH64 dependsOn(":installGraalVmAarch64") } private fun NativeImageBuild.setClasspath() { classpath.from(sourceSets.main.map { it.output }) classpath.from( project(":pkl-commons-cli").extensions.getByType(SourceSetContainer::class)["svm"].output ) classpath.from(nativeImageClasspath) } val macExecutableAmd64 by tasks.registering(NativeImageBuild::class) { imageName = executableSpec.name.map { "$it-macos-amd64" } mainClass = executableSpec.mainClass amd64() setClasspath() } val macExecutableAarch64 by tasks.registering(NativeImageBuild::class) { imageName = executableSpec.name.map { "$it-macos-aarch64" } mainClass = executableSpec.mainClass aarch64() setClasspath() } val linuxExecutableAmd64 by tasks.registering(NativeImageBuild::class) { imageName = executableSpec.name.map { "$it-linux-amd64" } mainClass = executableSpec.mainClass amd64() setClasspath() } val linuxExecutableAarch64 by tasks.registering(NativeImageBuild::class) { imageName = executableSpec.name.map { "$it-linux-aarch64" } mainClass = executableSpec.mainClass aarch64() setClasspath() // Ensure compatibility for kernels with page size set to 4k, 16k and 64k // (e.g. Raspberry Pi 5, Asahi Linux) extraNativeImageArgs.add("-H:PageSize=65536") } val alpineExecutableAmd64 by tasks.registering(NativeImageBuild::class) { imageName = executableSpec.name.map { "$it-alpine-linux-amd64" } mainClass = executableSpec.mainClass amd64() setClasspath() extraNativeImageArgs.addAll(listOf("--static", "--libc=musl")) } val windowsExecutableAmd64 by tasks.registering(NativeImageBuild::class) { imageName = executableSpec.name.map { "$it-windows-amd64" } mainClass = executableSpec.mainClass amd64() setClasspath() } val assembleNative by tasks.existing val testStartNativeExecutable by tasks.registering { dependsOn(assembleNative) // dummy file for up-to-date checking val outputFile = project.layout.buildDirectory.file("testStartNativeExecutable/output.txt") outputs.file(outputFile) val execOutput = providers.exec { commandLine(assembleNative.get().outputs.files.singleFile, "--version") } doLast { val outputText = execOutput.standardOutput.asText.get() if (!outputText.contains(buildInfo.pklVersionNonUnique)) { throw GradleException( "Expected version output to contain current version (${buildInfo.pklVersionNonUnique}), but got '$outputText'" ) } outputFile.get().asFile.toPath().apply { try { parent.createDirectories() } catch (ignored: java.nio.file.FileAlreadyExistsException) {} writeText("OK") } } } val requiredGlibcVersion: Version = Version.parse("2.17") val checkGlibc by tasks.registering { enabled = buildInfo.os.isLinux && !buildInfo.musl dependsOn(assembleNative) doLast { val exec = providers.exec { commandLine("objdump", "-T", assembleNative.get().outputs.files.singleFile) } val output = exec.standardOutput.asText.get() val minimumGlibcVersion = output .split("\n") .mapNotNull { line -> val match = Regex("GLIBC_([.0-9]*)").find(line) match?.groups[1]?.let { Version.parse(it.value) } } .maxOrNull() if (minimumGlibcVersion == null) { throw GradleException( "Could not determine glibc version from executable. objdump output: $output" ) } if (minimumGlibcVersion > requiredGlibcVersion) { throw GradleException( "Incorrect glibc version. Found: $minimumGlibcVersion, required: $requiredGlibcVersion" ) } } } // Expose underlying task's outputs private fun Task.wraps(other: TaskProvider) { dependsOn(other) outputs.files(other) } val testNative by tasks.existing { dependsOn(testStartNativeExecutable, checkGlibc) } val assembleNativeMacOsAarch64 by tasks.existing { wraps(macExecutableAarch64) } val assembleNativeMacOsAmd64 by tasks.existing { wraps(macExecutableAmd64) } val assembleNativeLinuxAarch64 by tasks.existing { wraps(linuxExecutableAarch64) } val assembleNativeLinuxAmd64 by tasks.existing { wraps(linuxExecutableAmd64) } val assembleNativeAlpineLinuxAmd64 by tasks.existing { wraps(alpineExecutableAmd64) } val assembleNativeWindowsAmd64 by tasks.existing { wraps(windowsExecutableAmd64) } publishing { publications { // need to put in `afterEvaluate` because `artifactId` cannot be set lazily. project.afterEvaluate { create("macExecutableAmd64") { artifactId = "${executableSpec.publicationName.get()}-macos-amd64" artifact(stagedMacAmd64Executable.singleFile) { classifier = null extension = "bin" builtBy(stagedMacAmd64Executable) } pom { name = "${executableSpec.publicationName.get()}-macos-amd64" url = executableSpec.website description = executableSpec.documentationName.map { name -> "Native $name executable for macOS/amd64." } } } create("macExecutableAarch64") { artifactId = "${executableSpec.publicationName.get()}-macos-aarch64" artifact(stagedMacAarch64Executable.singleFile) { classifier = null extension = "bin" builtBy(stagedMacAarch64Executable) } pom { name = "${executableSpec.publicationName.get()}-macos-aarch64" url = executableSpec.website description = executableSpec.documentationName.map { name -> "Native $name executable for macOS/aarch64." } } } create("linuxExecutableAmd64") { artifactId = "${executableSpec.publicationName.get()}-linux-amd64" artifact(stagedLinuxAmd64Executable.singleFile) { classifier = null extension = "bin" builtBy(stagedLinuxAmd64Executable) } pom { name = "${executableSpec.publicationName.get()}-linux-amd64" url = executableSpec.website description = executableSpec.documentationName.map { name -> "Native $name executable for linux/amd64." } } } create("linuxExecutableAarch64") { artifactId = "${executableSpec.publicationName.get()}-linux-aarch64" artifact(stagedLinuxAarch64Executable.singleFile) { classifier = null extension = "bin" builtBy(stagedLinuxAarch64Executable) } pom { name = "${executableSpec.publicationName.get()}-linux-aarch64" url = executableSpec.website description = executableSpec.documentationName.map { name -> "Native $name executable for linux/aarch64." } } } create("alpineLinuxExecutableAmd64") { artifactId = "${executableSpec.publicationName.get()}-alpine-linux-amd64" artifact(stagedAlpineLinuxAmd64Executable.singleFile) { classifier = null extension = "bin" builtBy(stagedAlpineLinuxAmd64Executable) } pom { name = "${executableSpec.publicationName.get()}-alpine-linux-amd64" url = executableSpec.website description = executableSpec.documentationName.map { name -> "Native $name executable for linux/amd64 and statically linked to musl." } } } create("windowsExecutableAmd64") { artifactId = "${executableSpec.publicationName.get()}-windows-amd64" artifact(stagedWindowsAmd64Executable.singleFile) { classifier = null extension = "exe" builtBy(stagedWindowsAmd64Executable) } pom { name = "${executableSpec.publicationName.get()}-windows-amd64" url = executableSpec.website description = executableSpec.documentationName.map { name -> "Native $name executable for windows/amd64." } } } } } } signing { project.afterEvaluate { sign(publishing.publications["linuxExecutableAarch64"]) sign(publishing.publications["linuxExecutableAmd64"]) sign(publishing.publications["macExecutableAarch64"]) sign(publishing.publications["macExecutableAmd64"]) sign(publishing.publications["alpineLinuxExecutableAmd64"]) sign(publishing.publications["windowsExecutableAmd64"]) } } ================================================ FILE: buildSrc/src/main/kotlin/pklNativeLifecycle.gradle.kts ================================================ /* * Copyright © 2024-2025 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. */ val assembleNativeMacOsAarch64 by tasks.registering { group = "build" } val assembleNativeMacOsAmd64 by tasks.registering { group = "build" } val assembleNativeLinuxAarch64 by tasks.registering { group = "build" } val assembleNativeLinuxAmd64 by tasks.registering { group = "build" } val assembleNativeAlpineLinuxAmd64 by tasks.registering { group = "build" } val assembleNativeWindowsAmd64 by tasks.registering { group = "build" } val testNativeMacOsAarch64 by tasks.registering { group = "verification" } val testNativeMacOsAmd64 by tasks.registering { group = "verification" } val testNativeLinuxAarch64 by tasks.registering { group = "verification" } val testNativeLinuxAmd64 by tasks.registering { group = "verification" } val testNativeAlpineLinuxAmd64 by tasks.registering { group = "verification" } val testNativeWindowsAmd64 by tasks.registering { group = "verification" } val buildInfo = project.extensions.getByType() private fun Task.wraps(other: TaskProvider) { dependsOn(other) outputs.files(other) } val assembleNative by tasks.registering { group = "build" if (!buildInfo.isCrossArchSupported && buildInfo.isCrossArch) { throw GradleException("Cross-arch builds are not supported on ${buildInfo.os.name}") } when { buildInfo.os.isMacOsX && buildInfo.targetArch == "aarch64" -> { wraps(assembleNativeMacOsAarch64) } buildInfo.os.isMacOsX && buildInfo.targetArch == "amd64" -> { wraps(assembleNativeMacOsAmd64) } buildInfo.os.isLinux && buildInfo.targetArch == "aarch64" -> { wraps(assembleNativeLinuxAarch64) } buildInfo.os.isLinux && buildInfo.targetArch == "amd64" -> { if (buildInfo.musl) wraps(assembleNativeAlpineLinuxAmd64) else wraps(assembleNativeLinuxAmd64) } buildInfo.os.isWindows && buildInfo.targetArch == "amd64" -> { wraps(assembleNativeWindowsAmd64) } else -> { doLast { throw GradleException( "Cannot build targeting ${buildInfo.os.name}/${buildInfo.targetArch} with musl=${buildInfo.musl}" ) } } } } val testNative by tasks.registering { group = "verification" dependsOn(assembleNative) if (!buildInfo.isCrossArchSupported && buildInfo.isCrossArch) { throw GradleException("Cross-arch builds are not supported on ${buildInfo.os.name}") } when { buildInfo.os.isMacOsX && buildInfo.targetArch == "aarch64" -> { dependsOn(testNativeMacOsAarch64) } buildInfo.os.isMacOsX && buildInfo.targetArch == "amd64" -> { dependsOn(testNativeMacOsAmd64) } buildInfo.os.isLinux && buildInfo.targetArch == "aarch64" -> { dependsOn(testNativeLinuxAarch64) } buildInfo.os.isLinux && buildInfo.targetArch == "amd64" -> { if (buildInfo.musl) dependsOn(testNativeAlpineLinuxAmd64) else dependsOn(testNativeLinuxAmd64) } buildInfo.os.isWindows && buildInfo.targetArch == "amd64" -> { dependsOn(testNativeWindowsAmd64) } else -> { doLast { throw GradleException( "Cannot build targeting ${buildInfo.os.name}/${buildInfo.targetArch} with musl=${buildInfo.musl}" ) } } } } val checkNative by tasks.registering { group = "verification" dependsOn(testNative) } val buildNative by tasks.registering { group = "build" dependsOn(checkNative) } ================================================ FILE: buildSrc/src/main/kotlin/pklPublishLibrary.gradle.kts ================================================ /* * Copyright © 2024-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. */ plugins { `maven-publish` signing } publishing { publications { components.findByName("java")?.let { javaComponent -> create("library") { from(javaComponent) } } } } configurePklPomMetadata() configurePomValidation() configurePklSigning() artifacts { project.tasks.findByName("javadocJar")?.let { archives(it) } project.tasks.findByName("sourcesJar")?.let { archives(it) } } ================================================ FILE: buildSrc/src/main/kotlin/pklSpotlessFormat.gradle.kts ================================================ /* * Copyright © 2025 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. */ plugins { id("com.diffplug.spotless") } val pklFormatter by configurations.creating dependencies { pklFormatter(rootProject.project("pkl-formatter")) } spotless { format("pkl") { target("**/*.pkl") addStep(PklFormatterStep(pklFormatter).create()) licenseHeaderFile( rootProject.file("buildSrc/src/main/resources/license-header.line-comment.txt"), "/// ", ) // disable ratcheting for Pkl sources // make any change to pkl-formatter reformat the stdlib ratchetFrom = null } } for (taskName in listOf("spotlessPkl", "spotlessPklApply", "spotlessPklCheck", "spotlessPklDiagnose")) { tasks.named(taskName) { dependsOn(":pkl-formatter:assemble") } } ================================================ FILE: buildSrc/src/main/resources/license-header.line-comment.txt ================================================ //===----------------------------------------------------------------------===// // Copyright © $YEAR 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. //===----------------------------------------------------------------------===// ================================================ FILE: buildSrc/src/main/resources/license-header.star-block.txt ================================================ /* * Copyright © $YEAR 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. */ ================================================ FILE: docs/antora.yml ================================================ name: main title: Main Project version: 0.32.0-dev prerelease: true nav: - nav.adoc ================================================ FILE: docs/docs.gradle.kts ================================================ /* * Copyright © 2024-2025 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. */ import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension plugins { pklAllProjects pklKotlinTest } sourceSets { test { java { srcDir(file("modules/pkl-core/examples")) srcDir(file("modules/pkl-config-java/examples")) srcDir(file("modules/java-binding/examples")) } val kotlin = project.extensions.getByType().sourceSets[name].kotlin kotlin.srcDir(file("modules/kotlin-binding/examples")) } } dependencies { testImplementation(projects.pklCore) testImplementation(projects.pklConfigJava) testImplementation(projects.pklConfigKotlin) testImplementation(projects.pklCommonsTest) testImplementation(projects.pklParser) testImplementation(libs.junitEngine) } tasks.test { inputs .files(fileTree("modules").matching { include("**/pages/*.adoc") }) .withPropertyName("asciiDocFiles") .withPathSensitivity(PathSensitivity.RELATIVE) } ================================================ FILE: docs/gradle.lockfile ================================================ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. com.github.ben-manes.caffeine:caffeine:2.9.3=swiftExportClasspathResolvable com.google.errorprone:error_prone_annotations:2.28.0=swiftExportClasspathResolvable io.github.java-diff-utils:java-diff-utils:4.12=kotlinInternalAbiValidation io.leangen.geantyref:geantyref:1.3.16=testRuntimeClasspath io.opentelemetry:opentelemetry-api:1.41.0=swiftExportClasspathResolvable io.opentelemetry:opentelemetry-context:1.41.0=swiftExportClasspathResolvable net.bytebuddy:byte-buddy:1.17.7=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata org.assertj:assertj-core:3.27.6=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.bouncycastle:bcpg-jdk18on:1.80=kotlinBouncyCastleConfiguration org.bouncycastle:bcpkix-jdk18on:1.80=kotlinBouncyCastleConfiguration org.bouncycastle:bcprov-jdk18on:1.80=kotlinBouncyCastleConfiguration org.bouncycastle:bcutil-jdk18on:1.80=kotlinBouncyCastleConfiguration org.checkerframework:checker-qual:3.43.0=swiftExportClasspathResolvable org.graalvm.polyglot:polyglot:25.0.0=testRuntimeClasspath org.graalvm.sdk:collections:25.0.0=testRuntimeClasspath org.graalvm.sdk:graal-sdk:25.0.0=testRuntimeClasspath org.graalvm.sdk:nativeimage:25.0.0=testRuntimeClasspath org.graalvm.sdk:word:25.0.0=testRuntimeClasspath org.graalvm.truffle:truffle-api:25.0.0=testRuntimeClasspath org.jetbrains.kotlin:abi-tools-api:2.2.20=kotlinInternalAbiValidation org.jetbrains.kotlin:abi-tools:2.2.20=kotlinInternalAbiValidation org.jetbrains.kotlin:kotlin-build-tools-api:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-build-tools-impl:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-compiler-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlin:kotlin-compiler-runner:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-daemon-client:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-daemon-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:2.2.20=kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-metadata-jvm:2.2.20=kotlinInternalAbiValidation org.jetbrains.kotlin:kotlin-native-prebuilt:2.0.21=kotlinNativeBundleConfiguration org.jetbrains.kotlin:kotlin-reflect:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable,testRuntimeClasspath org.jetbrains.kotlin:kotlin-script-runtime:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlin:kotlin-scripting-common:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-jvm:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.2.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.kotlin:swift-export-embeddable:2.2.20=swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-serialization-bom:1.7.3=swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.7.3=swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3=swiftExportClasspathResolvable org.jetbrains:annotations:13.0=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable,testCompileClasspath,testRuntimeClasspath org.junit.jupiter:junit-jupiter-api:5.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.jupiter:junit-jupiter-engine:5.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.jupiter:junit-jupiter-params:5.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.platform:junit-platform-commons:1.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.platform:junit-platform-engine:1.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.platform:junit-platform-launcher:1.14.0=testRuntimeClasspath org.junit:junit-bom:5.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.msgpack:msgpack-core:0.9.8=testRuntimeClasspath org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.organicdesign:Paguro:3.10.3=testRuntimeClasspath org.snakeyaml:snakeyaml-engine:2.10=testRuntimeClasspath empty=annotationProcessor,apiDependenciesMetadata,compileClasspath,compileOnlyDependenciesMetadata,implementationDependenciesMetadata,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinNativeCompilerPluginClasspath,kotlinScriptDefExtensions,runtimeClasspath,testAnnotationProcessor,testApiDependenciesMetadata,testCompileOnlyDependenciesMetadata,testIntransitiveDependenciesMetadata,testKotlinScriptDefExtensions ================================================ FILE: docs/modules/ROOT/pages/community.adoc ================================================ = Community :uri-github-issue: https://github.com/apple/pkl/issues :uri-github-discussions: https://github.com/apple/pkl/discussions We'd love to hear from you! * Create an {uri-github-issue}[issue] * Ask questions on {uri-github-discussions}[GitHub Discussions] ================================================ FILE: docs/modules/ROOT/pages/evolution-and-roadmap.adoc ================================================ = Evolution and Roadmap :uri-pkl-roadmap: https://github.com/orgs/apple/projects/12/views/1 :uri-pkl-evolution: https://github.com/apple/pkl-evolution == Evolution Sometimes, a change to Pkl is large enough that it makes sense to create a proposal for the change so that it can be discussed in detail and vetted. Pkl has a process for managing such designs in a repository called {uri-pkl-evolution}[Pkl Evolution]. == Roadmap To discover what might be coming in future versions, reference the {uri-pkl-roadmap}[Pkl Roadmap] project on GitHub. The roadmap describes estimates. The Pkl team aims to cut a release in February, June, and October of each year. If an item is not complete by the release cutoff date, the roadmap item may be bumped to the next release. Additionally, as priorities change, it is possible that items can be moved around or backlogged. ================================================ FILE: docs/modules/ROOT/pages/examples.adoc ================================================ = Examples :uri-github-apple: https://github.com/apple :uri-jvm-examples: {uri-github-apple}/pkl-jvm-examples :uri-go-examples: {uri-github-apple}/pkl-go-examples :uri-swift-examples: {uri-github-apple}/pkl-swift-examples :uri-k8s-examples: {uri-github-apple}/pkl-k8s-examples For ready-to-go examples with full source code, see the various repositories that are available for you. * {uri-jvm-examples}[pkl-jvm-examples] -- for using Pkl within the JVM * {uri-swift-examples}[pkl-swift-examples] -- for using Pkl with Swift * {uri-go-examples}[pkl-go-examples] -- for using Pkl with Go * {uri-k8s-examples}[pkl-k8s-examples] -- for using Pkl with Kubernetes ================================================ FILE: docs/modules/ROOT/pages/index.adoc ================================================ = User Manual include::../partials/component-attributes.adoc[] Quick Links: xref:pkl-cli:index.adoc#installation[Installation] | xref:language-reference:index.adoc[Language Reference] | https://pkl-lang.org/package-docs/pkl/current/index.html[Standard Library] Pkl -- pronounced _Pickle_ -- is an embeddable configuration language which provides rich support for data templating and validation. It can be used from the command line, integrated in a build pipeline, or embedded in a program. Pkl scales from small to large, simple to complex, ad-hoc to repetitive configuration tasks. xref:introduction:index.adoc[Introduction]:: Why we created Pkl and what it can do for you. xref:language.adoc[Language]:: Get to know the language and standard library. xref:language-bindings.adoc[Bindings]:: Libraries for embedding Pkl into general-purpose languages. xref:tools.adoc[Tools]:: CLI, Gradle plugin, code generators, and other tools. link:{uri-pkl-examples-repo}[Examples]:: Ready-to-go examples with full source code. xref:release-notes:index.adoc[Release Notes]:: What's new in each release. ================================================ FILE: docs/modules/ROOT/pages/integrations.adoc ================================================ = Framework Integrations * xref:spring:ROOT:index.adoc[Spring (Boot) Integration] ================================================ FILE: docs/modules/ROOT/pages/language-bindings.adoc ================================================ = Language Bindings * xref:java-binding:index.adoc[Java] * xref:kotlin-binding:index.adoc[Kotlin] * xref:swift:ROOT:index.adoc[Swift] * xref:go:ROOT:index.adoc[Go] * xref:bindings-specification:index.adoc[Specification] ** xref:bindings-specification:message-passing-api.adoc[Message Passing API] ** xref:bindings-specification:binary-encoding.adoc[Pkl Binary Encoding] ================================================ FILE: docs/modules/ROOT/pages/language.adoc ================================================ = Language * xref:language-tutorial:index.adoc[Tutorial] * xref:language-reference:index.adoc[Language Reference] * xref:standard-library.adoc[Standard Library] ================================================ FILE: docs/modules/ROOT/pages/resources.adoc ================================================ = Resources include::../partials/component-attributes.adoc[] There's more to explore! * Visit Pkl's repositories on https://github.com/apple?q=pkl[GitHub] * Browse the standard library's https://pkl-lang.org/package-docs/pkl/{pkl-version}/[API Docs] ================================================ FILE: docs/modules/ROOT/pages/standard-library.adoc ================================================ = Standard Library include::../partials/component-attributes.adoc[] The standard library is a set of Pkl modules, versioned and distributed together with the language. It is documented in the link:{uri-pkl-stdlib-docs-index}[API Docs]. To import a standard library module, use `import "pkl:"`. For example, `import "pkl:json"` imports the `pkl.json` module. The `pkl.base` module defines the most fundamental properties, methods, and classes for using Pkl. Its members are automatically available in every module and hence, it does not need to be imported. The default module allowlist (`--allowed-modules`) grants access to all standard library modules. ================================================ FILE: docs/modules/ROOT/pages/tools.adoc ================================================ = Tools include::ROOT:partial$component-attributes.adoc[] * xref:pkl-cli:index.adoc[CLI] * xref:pkl-doc:index.adoc[Pkldoc] * xref:pkl-gradle:index.adoc[Gradle Plugin] * Editor support ** xref:intellij:ROOT:index.adoc[IntelliJ] ** xref:vscode:ROOT:index.adoc[VSCode] ** xref:neovim:ROOT:index.adoc[Neovim] ================================================ FILE: docs/modules/ROOT/partials/component-attributes.adoc ================================================ // TODO: move to antora.yml once supported // 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-no-suffix: 0.32.0 // tells whether pkl version corresponding to current git commit // is a release version (:is-release-version: '') or dev version (:!is-release-version:) :!is-release-version: // the remaining attributes do not need to be updated regularly :pkl-version: {pkl-version-no-suffix}-dev ifdef::is-release-version[] :pkl-version: {pkl-version-no-suffix} endif::[] // use non-unique snapshot version because we have no way to determine unique snapshot version here :pkl-artifact-version: {pkl-version-no-suffix}-SNAPSHOT ifdef::is-release-version[] :pkl-artifact-version: {pkl-version} endif::[] :uri-maven-docsite: https://central.sonatype.com :uri-snapshot-repo: https://central.sonatype.com/repository/maven-snapshots :uri-maven-repo: https://central.sonatype.com/repository/maven-snapshots ifdef::is-release-version[] :uri-maven-repo: https://repo1.maven.org/maven2 endif::[] :symbolic-version-name: latest ifdef::is-release-version[] :symbolic-version-name: current endif::[] :uri-pkl-docs-base: https://pkl-lang.org/package-docs :uri-pkl-stdlib-docs-base: {uri-pkl-docs-base}/pkl :uri-pkl-stdlib-docs: {uri-pkl-stdlib-docs-base}/{pkl-version} :uri-pkl-stdlib-docs-index: {uri-pkl-stdlib-docs}/ :github-branch: main ifdef::is-release-version[] :github-branch: {pkl-version-no-suffix} endif::[] :uri-github-tree: https://github.com/apple/pkl/tree/{github-branch} :uri-pkl-stdlib-sources: {uri-github-tree}/stdlib :github-releases-base: https://github.com/apple/pkl/releases :github-releases: {github-releases-base}/download/{pkl-artifact-version} :uri-pkl-core-main-sources: {uri-github-tree}/pkl-core/src/main/java/org/pkl/core :uri-pkl-cli-main-sources: {uri-github-tree}/pkl-cli/src/main/kotlin/org/pkl/cli :uri-pkl-doc-main-sources: {uri-github-tree}/pkl-doc/src/main/kotlin/org/pkl/doc // This attribute is used as language for Pkl code blocks. // It can then be mapped to different languages in different environments (e.g., IntelliJ vs. Antora). :pkl: pkl :pkl-expr: pkl expression :uri-pkl-examples-repo: https://github.com/apple/pkl-jvm-examples :uri-pkl-examples-tree: {uri-pkl-examples-repo}/tree/main :uri-build-eval-example: {uri-pkl-examples-tree}/build-eval :uri-codegen-java-example: {uri-pkl-examples-tree}/codegen-java :uri-codegen-kotlin-example: {uri-pkl-examples-tree}/codegen-kotlin :uri-config-java-example: {uri-pkl-examples-tree}/config-java :uri-config-kotlin-example: {uri-pkl-examples-tree}/config-kotlin :uri-pkldoc-example: {uri-pkl-examples-tree}/pkldoc :uri-stdlib-baseModule: {uri-pkl-stdlib-docs}/base :uri-stdlib-CommandModule: {uri-pkl-stdlib-docs}/Command :uri-stdlib-analyzeModule: {uri-pkl-stdlib-docs}/analyze :uri-stdlib-jsonnetModule: {uri-pkl-stdlib-docs}/jsonnet :uri-stdlib-reflectModule: {uri-pkl-stdlib-docs}/reflect :uri-stdlib-mathModule: {uri-pkl-stdlib-docs}/math :uri-stdlib-xmlModule: {uri-pkl-stdlib-docs}/xml :uri-stdlib-protobufModule: {uri-pkl-stdlib-docs}/protobuf :uri-stdlib-pklbinaryModule: {uri-pkl-stdlib-docs}/pklbinary :uri-stdlib-yamlModule: {uri-pkl-stdlib-docs}/yaml :uri-stdlib-YamlParser: {uri-stdlib-yamlModule}/Parser :uri-stdlib-evaluatorSettingsModule: {uri-pkl-stdlib-docs}/EvaluatorSettings :uri-stdlib-evaluatorSettingsHttpClass: {uri-stdlib-evaluatorSettingsModule}/Http :uri-stdlib-Boolean: {uri-stdlib-baseModule}/Boolean :uri-stdlib-xor: {uri-stdlib-baseModule}/Boolean#xor() :uri-stdlib-implies: {uri-stdlib-baseModule}/Boolean#implies() :uri-stdlib-Any: {uri-stdlib-baseModule}/Any :uri-stdlib-String: {uri-stdlib-baseModule}/String :uri-stdlib-Collection: {uri-stdlib-baseModule}/Collection :uri-stdlib-StringToInt: {uri-stdlib-baseModule}/String#toInt() :uri-stdlib-Int: {uri-stdlib-baseModule}/Int :uri-stdlib-Float: {uri-stdlib-baseModule}/Float :uri-stdlib-Number: {uri-stdlib-baseModule}/Number :uri-stdlib-NaN: {uri-stdlib-baseModule}/#NaN :uri-stdlib-Infinity: {uri-stdlib-baseModule}/#Infinity :uri-stdlib-isBetween: {uri-stdlib-baseModule}/Number#isBetween :uri-stdlib-isFinite: {uri-stdlib-baseModule}/Number#isFinite :uri-stdlib-Int8: {uri-stdlib-baseModule}/#Int8 :uri-stdlib-Int16: {uri-stdlib-baseModule}/#Int16 :uri-stdlib-Int32: {uri-stdlib-baseModule}/#Int32 :uri-stdlib-UInt8: {uri-stdlib-baseModule}/#UInt8 :uri-stdlib-UInt16: {uri-stdlib-baseModule}/#UInt16 :uri-stdlib-UInt32: {uri-stdlib-baseModule}/#UInt32 :uri-stdlib-UInt: {uri-stdlib-baseModule}/#UInt :uri-stdlib-Uri: {uri-stdlib-baseModule}/#Uri :uri-stdlib-matches: {uri-stdlib-baseModule}/String#matches() :uri-stdlib-Null: {uri-stdlib-baseModule}/Null :uri-stdlib-ifNonNull: {uri-stdlib-baseModule}/Null#ifNonNull() :uri-stdlib-List: {uri-stdlib-baseModule}/List :uri-stdlib-Set: {uri-stdlib-baseModule}/Set :uri-stdlib-Map: {uri-stdlib-baseModule}/Map :uri-stdlib-Listing: {uri-stdlib-baseModule}/Listing :uri-stdlib-Listing-default: {uri-stdlib-baseModule}/Listing#default :uri-stdlib-Listing-isDistinct: {uri-stdlib-baseModule}/Listing#isDistinct :uri-stdlib-Listing-isDistinctBy: {uri-stdlib-baseModule}/Listing#isDistinctBy() :uri-stdlib-Mapping: {uri-stdlib-baseModule}/Mapping :uri-stdlib-Mapping-default: {uri-stdlib-baseModule}/Mapping#default :uri-stdlib-Duration: {uri-stdlib-baseModule}/Duration :uri-stdlib-Duration-value: {uri-stdlib-baseModule}/Duration#value :uri-stdlib-Duration-unit: {uri-stdlib-baseModule}/Duration#unit :uri-stdlib-DurationUnit: {uri-stdlib-baseModule}/#DurationUnit :uri-stdlib-DataSize: {uri-stdlib-baseModule}/DataSize :uri-stdlib-DataSize-value: {uri-stdlib-baseModule}/DataSize#value :uri-stdlib-DataSize-unit: {uri-stdlib-baseModule}/DataSize#unit :uri-stdlib-DataSizeUnit: {uri-stdlib-baseModule}/#DataSizeUnit :uri-stdlib-Dynamic: {uri-stdlib-baseModule}/Dynamic :uri-stdlib-Dynamic-toTyped: {uri-stdlib-baseModule}/Dynamic#toTyped() :uri-stdlib-Typed: {uri-stdlib-baseModule}/Typed :uri-stdlib-Regex: {uri-stdlib-baseModule}/Regex :uri-stdlib-Regex-method: {uri-stdlib-baseModule}/#Regex() :uri-stdlib-Regex-match: {uri-stdlib-baseModule}/Regex#match :uri-stdlib-RegexMatch: {uri-stdlib-baseModule}/RegexMatch :uri-stdlib-Pair: {uri-stdlib-baseModule}/Pair :uri-stdlib-IntSeq: {uri-stdlib-baseModule}/IntSeq :uri-stdlib-Class: {uri-stdlib-baseModule}/Class :uri-stdlib-TypeAlias: {uri-stdlib-baseModule}/TypeAlias :uri-stdlib-Deprecated: {uri-stdlib-baseModule}/Deprecated :uri-stdlib-BaseValueRenderer: {uri-stdlib-baseModule}/BaseValueRenderer :uri-stdlib-ValueRenderer: {uri-stdlib-baseModule}/ValueRenderer :uri-stdlib-BytesRenderer: {uri-stdlib-baseModule}/BytesRenderer :uri-stdlib-YamlRenderer: {uri-stdlib-baseModule}/YamlRenderer :uri-stdlib-PcfRenderer-converters: {uri-stdlib-baseModule}/PcfRenderer#converters :uri-stdlib-Function: {uri-stdlib-baseModule}/Function :uri-stdlib-Function0: {uri-stdlib-baseModule}/Function0 :uri-stdlib-Function1: {uri-stdlib-baseModule}/Function1 :uri-stdlib-Function1-apply: {uri-stdlib-baseModule}/Function1#apply() :uri-stdlib-Function2: {uri-stdlib-baseModule}/Function2 :uri-stdlib-Function3: {uri-stdlib-baseModule}/Function3 :uri-stdlib-Function4: {uri-stdlib-baseModule}/Function4 :uri-stdlib-Function5: {uri-stdlib-baseModule}/Function5 :uri-stdlib-Bytes: {uri-stdlib-baseModule}/Bytes :uri-stdlib-Resource: {uri-stdlib-baseModule}/Resource :uri-stdlib-outputFiles: {uri-stdlib-baseModule}/ModuleOutput#files :uri-stdlib-FileOutput: {uri-stdlib-baseModule}/FileOutput :uri-stdlib-Annotation: {uri-stdlib-baseModule}/Annotation :uri-stdlib-ConvertProperty: {uri-stdlib-baseModule}/ConvertProperty :uri-stdlib-Command-Flag: {uri-stdlib-CommandModule}/Flag :uri-stdlib-Command-BooleanFlag: {uri-stdlib-CommandModule}/BooleanFlag :uri-stdlib-Command-CountedFlag: {uri-stdlib-CommandModule}/CountedFlag :uri-stdlib-Command-Argument: {uri-stdlib-CommandModule}/Argument :uri-stdlib-Command-Import: {uri-stdlib-CommandModule}/Import :uri-messagepack: https://msgpack.org/index.html :uri-messagepack-spec: https://github.com/msgpack/msgpack/blob/master/spec.md :uri-pkl-roadmap: https://github.com/orgs/apple/projects/12/views/1 // TODO: figure out what the correct URL should be :uri-sonatype-snapshot-download: https://s01.oss.sonatype.org/service/local/artifact/maven/redirect?r=snapshots&g=org.pkl-lang&v={pkl-artifact-version} ================================================ FILE: docs/modules/bindings-specification/pages/binary-encoding.adoc ================================================ = `pkl-binary` Encoding include::ROOT:partial$component-attributes.adoc[] include::partial$component-attributes.adoc[] :uri-pkl-core-Evaluator: {uri-pkl-core-main-sources}/Evaluator.java Pkl values can be encoded into a binary format called "pkl-binary". This format is a lossless serialization of the underlying Pkl values. Pkl code can be rendered into this format using the {uri-stdlib-pklbinaryModule}[pkl:pklbinary] standard library module. Alternatively, many language bindings also provide methods to evaluate into `pkl-binary`, such as the `evaluateExpressionPklBinary` method in link:{uri-pkl-core-Evaluator}[org.pkl.core.Evaluator]. The binary format is uses link:{uri-messagepack}[MessagePack] encoding. == Primitives All Pkl primitives turn into their respective MessagePack primitive. |=== |Pkl Type|MessagePack format |link:{uri-stdlib-Int}[Int] |link:{uri-messagepack-int}[int] |link:{uri-stdlib-Float}[Float] |link:{uri-messagepack-float}[float] |link:{uri-stdlib-String}[String] |link:{uri-messagepack-str}[string] |link:{uri-stdlib-Boolean}[Boolean] |link:{uri-messagepack-bool}[bool] |link:{uri-stdlib-Null}[Null] |link:{uri-messagepack-nil}[nil] |=== NOTE: Pkl integers are encoded into the smallest int type that the number will fit into. For example, value `8` gets encoded as MessagePack `int8` format. == Non-primitives All non-primitive values are encoded as MessagePack arrays. The first slot of the array designates the value's type. The remaining slots have fixed meanings depending on the type. Additional slots may be added to types in future Pkl releases. Decoders *must* be designed to defensively discard values beyond the number of known slots for a type or provide meaningful error messages. The array's length is the number of slots that are filled. For example, xref:{uri-stdlib-List}[List] is encoded as an MessagePack array with two elements. |=== |Pkl type |Slot 1 2+|Slot 2 2+|Slot 3 2+|Slot 4 ||code |type |description |type |description |type |description |link:{uri-stdlib-Typed}[Typed], link:{uri-stdlib-Dynamic}[Dynamic] |`0x01` |link:{uri-messagepack-str}[str] |<> |link:{uri-messagepack-str}[str] |Enclosing module URI |link:{uri-messagepack-array}[array] |Array of <> |link:{uri-stdlib-Map}[Map] |`0x02` |link:{uri-messagepack-map}[map] |Map of `` to `` | | | | |link:{uri-stdlib-Mapping}[Mapping] |`0x03` |link:{uri-messagepack-map}[map] |Map of `` to `` | | | | |link:{uri-stdlib-List}[List] |`0x04` |link:{uri-messagepack-array}[array] |Array of `` | | | | |link:{uri-stdlib-Listing}[Listing] |`0x05` |link:{uri-messagepack-array}[array] |Array of `` | | | | |link:{uri-stdlib-Set}[Set] |`0x06` |link:{uri-messagepack-array}[array] |Array of `` | | | | |link:{uri-stdlib-Duration}[Duration] |`0x07` |{uri-messagepack-float}[float64] |Duration value |link:{uri-messagepack-str}[str] |link:{uri-stdlib-DurationUnit}[Duration unit] (`"ns"`, `"ms"`, etc.) | | |link:{uri-stdlib-DataSize}[DataSize] |`0x08` |link:{uri-messagepack-float}[float64] |Value (float64) |link:{uri-messagepack-str}[str] |link:{uri-stdlib-DataSizeUnit}[DataSize unit] (`"b"`, `"kb"`, etc.) | | |link:{uri-stdlib-Pair}[Pair] |`0x09` |`` |First value |`` |Second value | | |link:{uri-stdlib-IntSeq}[IntSeq] |`0x0A` |link:{uri-messagepack-int}[int] |Start |link:{uri-messagepack-int}[int] |End |link:{uri-messagepack-int}[int] |Step |link:{uri-stdlib-Regex}[Regex] |`0x0B` |link:{uri-messagepack-str}[str] |Regex string representation | | | | |link:{uri-stdlib-Class}[Class] |`0x0C` |link:{uri-messagepack-str}[str] |<> |link:{uri-messagepack-str}[str] |Module URI | | |link:{uri-stdlib-TypeAlias}[TypeAlias] |`0x0D` |link:{uri-messagepack-str}[str] |<> |link:{uri-messagepack-str}[str] |Module URI | | |link:{uri-stdlib-Function}[Function] |`0x0E` | | | | | | |link:{uri-stdlib-Bytes}[Bytes] |`0x0F` |link:{uri-messagepack-bin}[bin] |Binary contents | | | | |=== [[type-name-encoding]] [NOTE] ==== Type names have specific encoding rules: * When the module URI is `pkl:base`: ** If the type name is `ModuleClass`, this type represents the module class of `pkl:base`. ** Otherwise, the type name corresponds to a type in `pkl:base`. * For all other module URIs: ** When the type name contains `\#`, the string after the `#` character corresponds to a type in that module. The string before the `#` is the name of the module. ** Otherwise, the type name is the name of the module and represents the class of the module. ==== [[object-members]] == Object Members Like non-primitive values, object members are encoded as MessagePack arrays, where the first slot designates the value's type. |=== |Member type |Slot 1 2+|Slot 2 2+|Slot 3 | |code |type |description |type |description |Property |`0x10` |link:{uri-messagepack-str}[str] |key |`` |property value |Entry |`0x11` |`` |entry key |`` |entry value |Element |`0x12` |link:{uri-messagepack-int}[int] |index |`` |element value |=== ================================================ FILE: docs/modules/bindings-specification/pages/index.adoc ================================================ = Language Binding Specification :uri-pkl-go-github: https://github.com/apple/pkl-go :uri-pkl-swift-github: https://github.com/apple/pkl-swift Pkl can be embedded within any host application. The host application has access to low level controls. It is able to manage the lifecycle of evaluators, as well as provide custom modules and resources to Pkl. Currently, Pkl must be embedded as a child process, by shelling out to the CLI using the xref:pkl-cli:index.adoc#command-server[`pkl server`] command. In the future, a C library will also be provided. When embedded, communication between a host application and Pkl happens via message passing. The message passing specification can be found in xref:message-passing-api.adoc[]. For examples of language bindings in practice, review the link:{uri-pkl-go-github}[pkl-go], or the link:{uri-pkl-swift-github}[pkl-swift] codebases. NOTE: Pkl's Java and Kotlin libraries binds to Pkl directly, and do not use message passing. == Features of a language binding A language binding for Pkl should generally have the following components: . A client that spawns `pkl server`, and talks to it using message passing. . A deserializer that turns xref:binary-encoding.adoc[pkl binary encoding] into a structure in the host language. . A code generator that transforms Pkl schemas into schemas written in the host language. The code generator is mostly written in Pkl, with a lightweight executable that acts as the glue layer. For examples of code generators, consult link:{uri-pkl-go-github}/tree/main/codegen[pkl-go] and link:{uri-pkl-swift-github}/tree/main/codegen[pkl-swift]. == Sample flow Here is a sample flow for evaluating a module with the following contents: .\file:///path/to/myModule.pkl [source,{pkl}] ---- module MyModule theModules = import*("customfs:/*.pkl") ---- . Client sends xref:message-passing-api.adoc#create-evaluator-request[Create Evaluator Request], including `customfs` as a custom module reader. + [source,json] ---- [ 0x20, { "requestId": 135, "allowedModules": ["pkl:", "repl:", "file:", "customfs:"], "clientModuleReaders": [ { "scheme": "customfs", "hasHierarchicalUris": true, "isGlobbable": true, "isLocal": true } ] } ] ---- . Server sends xref:message-passing-api.adoc#create-evaluator-response[Create Evaluator Response], with an evaluator id. + [source,json] ---- [ 0x21, { "requestId": 135, "evaluatorId": -135901 } ] ---- . Client sends xref:message-passing-api.adoc#evaluate-request[Evaluate Request], providing the module's URI. + [source,json] ---- [ 0x23, { "requestId": 9805131, "evaluatorId": -13901, "moduleUri": "file:///path/to/myModule.pkl" } ] ---- . During evaluation, server evaluates `import*("customfs:/*.pkl")`, and sends xref:message-passing-api.adoc#list-modules-request[List Modules Request]. + [source,json] ---- [ 0x2c, { "requestId": -6478924, "evaluatorId": -13901, "uri": "customfs:/" } ] ---- . Client responds with xref:message-passing-api.adoc#list-modules-response[List Modules Response]. In our pretend scenario, there is only one file; `foo.pkl`. + [source,json] ---- [ 0x2d, { "requestId": -6478924, "evaluatorId": -13901, "pathElements": [ { "name": "foo.pkl", "isDirectory": false } ] } ] ---- . Server sends xref:message-passing-api.adoc#read-module-request[Read Module Request] to read `foo.pkl`. + [source,json] ---- [ 0x28, { "requestId": 36408291, "evaluatorId": -13901, "uri": "customfs:/foo.pkl" } ] ---- . Client responds with the module's contents + [source,json] ---- [ 0x29, { "requestId": 36408291, "evaluatorId": -13901, "contents": "foo = 1" } ] ---- . Server finishes evaluation, and responds with the xref:message-passing-api.adoc#evaluate-response[Evaluate Response]. + [source,json] ---- [ 0x24, { "requestId": 9805131, "evaluatorId": -13901, "result": } ] ---- . Client sends xref:message-passing-api.adoc#close-evaluator[Close Evaluator]. + [source,json] ---- [ 0x22, { "evaluatorId": -13901 } ] ---- == Debug logs Set the env var `PKL_DEBUG=1` to enable more verbose logging from Pkl. It is recommended that clients also use this environment variable to enable debug logs of their own. ================================================ FILE: docs/modules/bindings-specification/pages/message-passing-api.adoc ================================================ = Message Passing API include::ROOT:partial$component-attributes.adoc[] include::partial$component-attributes.adoc[] All messages are encoded in link:{uri-messagepack}[MessagePack], as an array with two elements. The first element of the array is a code that designates the message's type, encoded as an int. The second element of the array is the message body, encoded as a map. Messages are passed between the _client_ and the _server_. When hosting Pkl (for example, the Swift application when using pkl-swift), the _client_ is the host program and the _server_ is the entity that provides controls for interacting with Pkl. When implementing an xref:language-reference:index.adoc#external-readers[external reader], the _client_ is the external reader process and the _server_ is the Pkl evaluator. For example, in JSON representation: [source,json] ---- [ 1, // <1> { "someKey": "someValue" } // <2> ] ---- <1> Code indicating message type <2> Message body == Message types [[client-message]] === Client Message A message passed from the client to the server. [[server-message]] === Server Message A message passed from the server to the client. [[request-message]] === Request Message A message sent with a `requestId` value. The `requestId` should be a unique number at the time of message send. The other side is expected to respond with a <> with the same `requestId`. [[response-message]] === Response Message A message that is the response to a <>. It contains the same `requestId` of the request message. [[one-way-message]] === One Way Message A fire-and-forget message where no response is expected. == Messages All messages have their schema described in Pkl. A nullable type means that the property should be omitted (as opposed to the property's value being `nil`). [[create-evaluator-request]] === Create Evaluator Request Code: `0x20` + Type: <> <> Create an evaluator with the provided evaluator settings. Upon creating the evaluator, the server sends back a <> message. Schema: [source,pkl] ---- /// A number identifying this request requestId: Int /// Regex patterns to determine which modules are allowed for import. /// /// API version of the CLI's `--allowed-modules` flag allowedModules: Listing? /// Regex patterns to determine which resources are allowed to be read. /// /// API version of the CLI's `--allowed-resources` flag allowedResources: Listing? /// Register client-side module readers. clientModuleReaders: Listing? /// Register client-side resource readers. clientResourceReaders: Listing? /// Directories, ZIP archives, or JAR archives /// to search when resolving `modulepath:` URIs. /// /// API version of the CLI's `--module-path` flag. modulePaths: Listing? /// Environment variable to set. /// /// API version of the CLI's `--env-var` flag. env: Mapping? /// External properties to set. /// /// API version of the CLI's `--properties` flag. properties: Mapping? /// Duration, in seconds, after which evaluation of a source module will be timed out. /// /// API version of the CLI's `--timeout` flag. timeoutSeconds: Int? /// Restricts access to file-based modules and resources to those located under the root directory. rootDir: String? /// The cache directory for storing packages. cacheDir: String? /// The format to generate. /// /// This sets the `pkl.outputFormat` external property. outputFormat: String? /// The project dependency settings. project: Project? /// Configuration of outgoing HTTP(s) requests. /// /// Added in Pkl 0.26.0. http: Http? class ClientResourceReader { /// The URI scheme this reader is responsible for reading. scheme: String /// Tells whether the path part of this URI has a /// [hier-part](https://datatracker.ietf.org/doc/html/rfc3986#section-3). /// /// An example of a hierarchical URI is `file:///path/to/my/file`, where /// `/path/to/my/file` designates a nested path through the `/` character. /// /// An example of a non-hierarchical URI is `pkl.base`, where the `base` does not denote /// any form of hierarchy. hasHierarchicalUris: Boolean /// Tells whether this reader supports globbing. isGlobbable: Boolean } class ClientModuleReader { /// The URI scheme this reader is responsible for reading. scheme: String /// Tells whether the path part of this URI has a /// [hier-part](https://datatracker.ietf.org/doc/html/rfc3986#section-3). /// /// An example of a hierarchical URI is `file:///path/to/my/file`, where /// `/path/to/my/file` designates a nested path through the `/` character. /// /// An example of a non-hierarchical URI is `pkl.base`, where the `base` does not denote /// any form of hierarchy. hasHierarchicalUris: Boolean /// Tells whether this reader supports globbing. isGlobbable: Boolean /// Tells whether the module is local to the system. /// /// A local resource that [hasHierarchicalUris] supports triple-dot imports. isLocal: Boolean } class Project { type: "local" /// The canonical URI of this project's package packageUri: String? /// The URI pointing to the location of the project file. projectFileUri: String /// The dependencies of this project. dependencies: Mapping } class RemoteDependency { type: "remote" /// The canonical URI of this dependency packageUri: String? /// The checksums of this remote dependency checksums: Checksums? } class Checksums { /// The sha-256 checksum of this dependency's metadata. sha256: String } class Http { /// PEM format certificates to trust when making HTTP requests. /// /// If [null], Pkl will trust its own built-in certificates. caCertificates: Bytes? // <1> /// Configuration of the HTTP proxy to use. /// /// If [null], uses the operating system's proxy configuration. proxy: Proxy? /// HTTP rewrites, from source prefix to target prefix. /// /// Each rewrite must start with `http://` or `https://`, and must end with `/`. rewrites: Mapping? } /// Settings that control how Pkl talks to HTTP proxies. class Proxy { /// The proxy to use for HTTP(S) connections. /// /// At the moment, only HTTP proxies are supported. /// /// Example: /// ``` /// address = "http://my.proxy.example.com:5080" /// ``` address: Uri(startsWith("http://"))? /// Hosts to which all connections should bypass a proxy. /// /// Values can be either hostnames, or IP addresses. /// IP addresses can optionally be provided using [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation). /// /// The only wildcard is `"*"`, which disables all proxying. /// /// A hostname matches all subdomains. /// For example, `example.com` matches `foo.example.com`, but not `fooexample.com`. /// A hostname that is prefixed with a dot matches the hostname itself, /// so `.example.com` matches `example.com`. /// /// Optionally, a port can be specified. /// If a port is omitted, all ports are matched. /// /// Example: /// /// ``` /// noProxy { /// "127.0.0.1" /// "169.254.0.0/16" /// "example.com" /// "localhost:5050" /// } /// ``` noProxy: Listing(isDistinct) } ---- <1> link:{uri-messagepack-bin}[bin format] Example: [source,json5] ---- [ 0x20, { "requestId": 193501, "allowedModules": ["pkl:", "repl:"], "allowedResources": ["file:", "package:", "projectpackage:"] } ] ---- [[create-evaluator-response]] === Create Evaluator Response Code: `0x21` + Type: <> <> The response for a <>. If the evaluator was created successfully, `evaluatorId` is set. Otherwise, `error` is set to the resulting error. [source,pkl] ---- /// A number identifying this request requestId: Int /// A number identifying the created evaluator. evaluatorId: Int? /// A message detailing why the evaluator was not created. error: String? ---- [[close-evaluator]] === Close Evaluator Code: `0x22` + Type: <> <> Tells the Pkl server to close an evaluator, releasing any resources it may be holding. [source,pkl] ---- /// A number identifying this evaluator. evaluatorId: Int ---- [[evaluate-request]] === Evaluate Request Code: `0x23` + Type: <> <> Evaluate a module. [source,pkl] ---- /// A number identifying this request requestId: Int /// A number identifying this evaluator. evaluatorId: Int /// The absolute URI of the module to be evaluated. moduleUri: String /// The module's contents. /// /// If [null], Pkl will load the module at runtime. moduleText: String? /// The Pkl expression to be evaluated within the module. /// /// If [null], evaluates the whole module. expr: String? ---- [[evaluate-response]] === Evaluate Response Code: `0x24` + Type: <> <> The server's response to <>. If the evaluation is successful, the response is the Pkl value encoded in xref:binary-encoding.adoc[binary encoding]. [source,pkl] ---- /// The requestId of the Evaluate request requestId: Int /// A number identifying this evaluator. evaluatorId: Int /// The evaluation contents, if successful. result: Bytes? // <1> /// A message detailing why evaluation failed. error: String? ---- <1> xref:binary-encoding.adoc[Pkl Binary Encoding] in link:{uri-messagepack-bin}[bin format] [[log]] === Log Code: `0x25` + Type: <> <> Tells the client to emit a log message, during the execution of a Pkl program. A log can occur through a xref:language-reference:index.adoc#debugging[trace()] expression, or through a warning (for example, when encountering a link:{uri-stdlib-Deprecated}[Deprecated] value.) [source,pkl] ---- /// A number identifying this evaluator. evaluatorId: Int /// A number identifying the log level. /// /// - 0: trace /// - 1: warn level: Int(this == 0 || this == 1) /// The message to be logged message: String /// A string representing the source location within Pkl code producing this log output. frameUri: String ---- [[read-resource-request]] === Read Resource Request Code: `0x26` + Type: <> <> Read a resource at the given URI. This message occurs when a read expression (`read`/`read?`/`read*`) is encountered within a program, and its scheme matches a client resource reader. [source,pkl] ---- /// A number identifying this request. requestId: Int /// A number identifying this evaluator. evaluatorId: Int /// The URI of the resource. uri: String ---- [[read-resource-response]] === Read Resource Response Code: `0x27` + Type: <> <> The response to <>. If successful, `contents` is set. Otherwise, `error` is set. If neither is set, `contents` defaults to an empty byte array. [source,pkl] ---- /// A number identifying this request. requestId: Int /// A number identifying this evaluator. evaluatorId: Int /// The contents of the resource. contents: Bytes? // <1> /// The description of the error that occurred when reading this resource. error: String? ---- <1> MessagePack's link:https://github.com/msgpack/msgpack/blob/master/spec.md#bin-format-family[bin format family] [[read-module-request]] === Read Module Request Code: `0x28` + Type: <> <> Read a module at the given URI. This message occurs during the evaluation of an import statement or expression (`import`/`import*`), when a scheme matches a client module reader. [source,pkl] ---- /// A number identifying this request. requestId: Int /// A number identifying this evaluator. evaluatorId: Int /// The URI of the module. uri: String ---- [[read-module-response]] === Read Module Response Code: `0x29` + Type: <> <> The response to <>. If successful, `contents` is set. Otherwise, `error` is set. If neither is set, `contents` defaults to an empty string. [source,pkl] ---- /// A number identifying this request. requestId: Int /// A number identifying this evaluator. evaluatorId: Int /// The string contents of the module. contents: String? /// The description of the error that occurred when reading this resource. error: String? ---- [[list-resources-request]] === List Resources Request Code: `0x2a` + Type: <> <> List resources at the specified base path. This message occurs during the evaluation of a xref:language-reference:index.adoc#globbed-reads[globbed read], when a scheme matches a client resource reader's scheme. If the resource reader does not have hierarchical URIs, `dummy` is used as the path, and the response is expected to contain all resource elements for that scheme. [source,pkl] ---- /// A number identifying this request. requestId: Int /// A number identifying this evaluator. evaluatorId: Int /// The base URI to list resources. uri: String ---- [[list-resources-response]] === List Resources Response Code: `0x2b` + Type: <> <> The response to <>. If successful, `pathElements` is set. Otherwise, `error` is set. If neither are set, `pathElements` default to an empty list. [source,pkl] ---- /// A number identifying this request. requestId: Int /// A number identifying this evaluator. evaluatorId: Int /// The elements at the provided base path. pathElements: Listing? /// The description of the error that occurred when listing elements. error: String? class PathElement { /// The name of the element at this path name: String /// Tells whether the element is a directory. isDirectory: Boolean } ---- [[list-modules-request]] === List Modules Request Code: `0x2c` + Type: <> <> List modules at the specified base path. This message occurs during the evaluation of a xref:language-reference:index.adoc#globbed-imports[globbed import], when a scheme matches a client resource reader's scheme. If the module reader does not have hierarchical URIs, `dummy` is used as the path, and the response is expected to contain all module elements for that scheme. [source,pkl] ---- /// A number identifying this request. requestId: Int /// A number identifying this evaluator. evaluatorId: Int /// The base URI to list modules. uri: String ---- [[list-modules-response]] === List Modules Response Code: `0x2d` + Type: <> <> The response to <>. If successful, `pathElements` is set. Otherwise, `error` is set. If neither are set, `pathElements` default to an empty list. [source,pkl] ---- /// A number identifying this request. requestId: Int /// A number identifying this evaluator. evaluatorId: Int /// The elements at the provided base path. pathElements: Listing? /// The description of the error that occurred when listing elements. error: String? class PathElement { /// The name of the element at this path name: String /// Tells whether the element is a directory. isDirectory: Boolean } ---- [[initialize-module-reader-request]] === Initialize Module Reader Request Code: `0x2e` + Type: <> <> Initialize an xref:language-reference:index.adoc#external-readers[External Module Reader]. This message is sent to external reader processes the first time a module scheme it is registered for is read. [source,pkl] ---- /// A number identifying this request. requestId: Int /// The module scheme to initialize. scheme: String ---- [[initialize-module-reader-response]] === Initialize Module Reader Response Code: `0x2f` + Type: <> <> Return the requested external module reader specification. The `spec` field should be set to `null` when the external process does not implement the requested module scheme. [source,pkl] ---- /// A number identifying this request. requestId: Int /// Client-side module reader spec. /// /// Null when the external process does not implement the requested module scheme. spec: ClientModuleReader? ---- `ClientModuleReader` is defined above by <>. [[initialize-resource-reader-request]] === Initialize Resource Reader Request Code: `0x30` + Type: <> <> Initialize an xref:language-reference:index.adoc#external-readers[External Resource Reader]. This message is sent to external reader processes the first time a resource scheme it is registered for is read. [source,pkl] ---- /// A number identifying this request. requestId: Int /// The resource scheme to initialize. scheme: String ---- [[initialize-resource-reader-response]] === Initialize Resource Reader Response Code: `0x31` + Type: <> <> Return the requested external resource reader specification. The `spec` field should be set to `null` when the external process does not implement the requested resource scheme. [source,pkl] ---- /// A number identifying this request. requestId: Int /// Client-side resource reader spec. /// /// Null when the external process does not implement the requested resource scheme. spec: ClientResourceReader? ---- `ClientResourceReader` is defined above by <>. [[close-external-process]] === Close External Process Code: `0x32` + Type: <> <> Initiate graceful shutdown of the external reader process. [source,pkl] ---- /// This message has no properties. ---- ================================================ FILE: docs/modules/bindings-specification/partials/component-attributes.adoc ================================================ :uri-github-binary-encoding-snippet-tests: {uri-github-tree}/pkl-server/src/test/files/SnippetTests :uri-messagepack-bool: {uri-messagepack-spec}#bool-format-family :uri-messagepack-int: {uri-messagepack-spec}#int-format-family :uri-messagepack-float: {uri-messagepack-spec}#float-format-family :uri-messagepack-str: {uri-messagepack-spec}#str-format-family :uri-messagepack-bin: {uri-messagepack-spec}#bin-format-family :uri-messagepack-array: {uri-messagepack-spec}#array-format-family :uri-messagepack-map: {uri-messagepack-spec}#map-format-family :uri-messagepack-nil: {uri-messagepack-spec}#nil-format ================================================ FILE: docs/modules/introduction/pages/comparison.adoc ================================================ = Comparison include::ROOT:partial$component-attributes.adoc[] :uri-jsonnet: https://jsonnet.org :uri-hcl: https://github.com/hashicorp/hcl :uri-dhall: https://dhall-lang.org :uri-pkl-spring: https://github.com/apple/pkl-spring :uri-graalvm: https://www.graalvm.org Configuration is often described in a static configuration format or is generated with a general-purpose programming language. This page lists shortcomings of these approaches and explains how Pkl addresses them. Also, Pkl's strong and weak points in comparison to other configuration languages are discussed in this document. [[static-config-formats]] == Pkl vs. Static Config Formats Static configuration formats such as JSON, YAML, TOML, and XML work reasonably well for simple configuration needs. However, they do have some shortcomings, including: . They are not very human-friendly to read and write. (JSON, XML) . They do not provide a way to split a large file into multiple smaller ones. (JSON, YAML, TOML) . They offer no way or very limited ways to abstract over repetitive configuration. (JSON, YAML, TOML, XML) . They do not offer standardized or widely available schema validators. (JSON, YAML, TOML) . They offer little or no schema-aware tooling. (JSON, YAML, TOML) Pkl addresses these shortcomings as follows: . It has a clutter-free and familiar syntax with nestable comments. . Modules can import other modules from local and remote locations. . Every object can act as a template for other objects. The standard library offers strong support for data manipulation. . It has strong built-in support for describing and validating configuration schemas. . It is designed to enable schema-aware tooling, such as REPLs and editors with code completion support. [[general-purpose-langs]] == Pkl vs. General-purpose Languages When configuration needs outgrow the capabilities of static configuration formats, projects often turn to generate configuration with a general-purpose programming language such as Python. Given enough effort, this approach can satisfy complex configuration needs. However, expressing configuration in a full-blown programming language does have some shortcomings, including: . Reading, writing, and debugging configuration can become as challenging as reading, writing, and debugging application code. . The host language may not be a good fit for describing, manipulating, and abstracting over hierarchical configuration. . Configuration code may not visually resemble the configuration it generates. . The host language may not be a good fit for defining and validating configuration schemas. . Development environments may offer little help for developing and validating configuration written in the host language. . General-purpose languages are powerful and often difficult to sandbox. Are you certain your configuration script isn't erasing your hard disk or launching a rocket? Pkl addresses these shortcomings as follows: . As an expression-oriented and side-effect free language, it eliminates many potential sources of errors. . It is specifically designed for describing, manipulating, and abstracting over hierarchical configuration. . Pkl code often resembles the configuration it generates. . It has strong built-in support for defining and validating configuration schemas. . It is designed to enable advanced and schema-aware tooling. . It is comparatively powerless and strictly sandboxed, making fatal configuration mistakes and exploits less likely. Till now, we haven't spotted any Pkl script capable of erasing your hard disk. [[other-config-langs]] == Pkl vs. Other Config Languages Compared to open-source configuration languages such as link:{uri-jsonnet}[Jsonnet], link:{uri-hcl}[HCL], and link:{uri-dhall}[Dhall], Pkl's strong points are: General:: + * Pkl has a clean and familiar syntax, which makes it easier to read and learn. * Pkl supports writing sophisticated schemas, which enables config validation, code and documentation generation, and advanced IDE support. This is Pkl's most significant differentiator, and is the main reason why we created it. * Pkl has stronger templating capabilities than other config languages, reducing user code to the absolute minimum. Embedding:: + * Pkl is great for embedding into JVM applications. * Pkl offers modern xref:java-binding:pkl-config-java.adoc[JVM libraries] for runtime application configuration. * Pkl supports xref:java-binding:codegen.adoc[code generation] to enable statically typed access to configuration from programming languages. * Pkl integrates with third-party (link:{uri-pkl-spring}[Spring Boot]) JVM libraries and frameworks. Tooling:: + * Pkl has a polished xref:pkl-doc:index.adoc[documentation generator] that produces highly navigable and searchable documentation. * Pkl offers a xref:pkl-gradle:index.adoc[Gradle plugin] to easily integrate code evaluation, documentation generation, and code generation into your builds. * Pkl's native executables have a link:{uri-graalvm}[JIT compiler] that can speed up evaluation up to hundred times. On the other hand, we believe that Pkl's weak points are: * Pkl's native binaries are larger than those of other config languages. * Pkl is less known and has a smaller community than some other config languages. We are working towards making Pkl overcome these weakness. Please support us in reaching this goal! We hope that you will enjoy Pkl, and that you trust us to gradually improve its weak points. ================================================ FILE: docs/modules/introduction/pages/concepts.adoc ================================================ = Concepts include::ROOT:partial$component-attributes.adoc[] :uri-property-list: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/PropertyLists/UnderstandXMLPlist/UnderstandXMLPlist.html Let's get to know Pkl by discussing some of its concepts and features. [[abstraction]] == Abstraction Configuration tends to grow larger and more complex over time, making it increasingly difficult to understand and maintain. Pkl can reduce the size and complexity of configuration by * describing similar configuration elements in terms of their differences * introducing abstractions for common configuration elements * separating configuration structure from configuration data * computing instead of enumerating configuration [[evaluation]] == Evaluation Pkl code lives in _modules_, a more fancy and general term for _files_. Evaluating a module produces an in-memory _data model_ that is roughly comparable to a JSON data model. If evaluation completes successfully, the Pkl evaluator converts the data model to an external representation and terminates with the status code zero. Otherwise, the evaluator prints an error message and terminates with a non-zero status code. [[immutability]] == Immutability All Pkl data is immutable. Manipulating a value always returns a new value, leaving the original value unchanged. Immutability eliminates many potential sources of errors. [[isolation]] == Isolation Evaluation of Pkl code is strictly sandboxed. Except for a few well-defined and well-controlled exceptions, Pkl code cannot interact with the outside world. Leaving aside bugs in the language implementation, the worst thing that buggy or malicious Pkl code can do is to consume CPU and memory resources until the evaluator gets killed. Over time, sandboxing will be further strengthened to cover fine-grained CPU and memory boxing. [[rendering]] == Rendering Converting a data model to an external representation is called _rendering_ the model. Pkl ships with renderers for the following data formats: * JSON * Jsonnet * Pcf (a static subset of Pkl) * (Java) Properties * {uri-property-list}[Property List] * XML * YAML Support for other formats can be added by writing a custom renderer in Pkl or Java. See xref:language-reference:index.adoc#module-output[Module Output] and xref:pkl-core:index.adoc#value-visitor[Value Visitor] for more information. [[resemblance]] == Resemblance By design, Pkl code tends to structurally and visually resemble the configuration it generates. This makes the code easier to read and write. [[reuse]] == Reuse Modules can reuse other modules by xref:language-reference:index.adoc#import-module[importing] them from local or remote locations. Imports can also be used to split up one large module into multiple smaller ones, increasing maintainability. A configurable security policy helps to keep imports under control. [[schema]] == Schema Configuration is structured data. Pkl supports -- but does not require -- to express this structure as a _configuration schema_, a set of classes defining configuration properties, their defaults, types, and constraints. Writing and maintaining a configuration schema takes some effort but, in return, provides these benefits: * Independent evolution of configuration schema and configuration data, often by different teams (for example service providers and service consumers). * Automatic xref:pkl-doc:index.adoc[documentation generation]. * Strong validation of configuration, both during development time and runtime. * Statically typed access to configuration from xref:java-binding:codegen.adoc[Java] and other languages through code generation. * Schema-aware development tools, for example REPLs and editors with code completion support. [[template]] == Templating Pkl supports writing templates for objects and entire modules. Templates can be repeatedly turned into concrete configuration by filling in the blanks, and -- when necessary -- overriding defaults. Sharing template modules over the network can streamline complex configuration tasks for entire teams, organizations, and communities. [[usability]] == Usability Everybody needs a configuration solution, but nobody wants to spend a lot of time learning it. To reflect this reality, Pkl has a strong focus on usability. For example, error messages explain causes and possible solutions and object properties maintain definition order to avoid surprises. We hope that this focus on usability will make Pkl accessible to a wide audience of occasional users, while still leaving room for expert users and advanced use cases. ================================================ FILE: docs/modules/introduction/pages/index.adoc ================================================ = Introduction include::ROOT:partial$component-attributes.adoc[] Pkl -- pronounced _Pickle_ -- is a configuration-as-code language with rich validation and tooling. It can be used as a command line tool, software library, or build plugin. Pkl scales from small to large, simple to complex, ad-hoc to recurring configuration tasks. We created Pkl because we believe that configuration is best expressed in a special-purpose configuration language; a blend between a static configuration format, and a general-purpose programming language. * xref:use-cases.adoc[Use Cases] * xref:concepts.adoc[Concepts] * xref:comparison.adoc[Comparison] ================================================ FILE: docs/modules/introduction/pages/use-cases.adoc ================================================ = Use Cases include::ROOT:partial$component-attributes.adoc[] :uri-kotlin-homepage: https://kotlinlang.org :uri-xml-property-lists: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/PropertyLists/UnderstandXMLPlist/UnderstandXMLPlist.html Pkl is a good fit for: Generating Static Configuration:: Are you using a tool, service, or application that is configured with JSON, YAML, or any other static configuration format? + By generating this configuration with Pkl, you can reduce verbosity and increase maintainability through xref:concepts.adoc#reuse[reuse], xref:concepts.adoc#template[templating], and xref:concepts.adoc#abstraction[abstraction]. JSON, YAML, and {uri-xml-property-lists}[XML property lists] are supported out of the box; xref:concepts.adoc#rendering[renderers] for other configuration formats can be developed and shared by anyone. Automatic defaults, strong validation, and sensible error messages come in reach with configuration xref:concepts.adoc#schema[schemas]. + Generation can be triggered manually, by an automation pipeline, or by the target application. Application Runtime Configuration:: Are you the author of a tool, service, or application that consumes configuration? + By adopting Pkl as your "native" configuration solution (rather than, say, using it to generate JSON files), you benefit from a modern xref:java-binding:pkl-config-java.adoc[configuration library] that is safe, easy, and enjoyable to use. At the same time, anyone configuring your application -- whether that's your users, site reliability engineers (SREs), or yourself -- benefit from a well-defined, well-documented, and scalable configuration language. + At the time of writing, Pkl offers configuration libraries for the JVM runtime, Swift, and also for Golang. + We maintain the following libraries: + * xref:java-binding:pkl-config-java.adoc[pkl-config-java] for Java compatible languages * xref:kotlin-binding:pkl-config-kotlin.adoc[pkl-config-kotlin] for the {uri-kotlin-homepage}[Kotlin] language. * xref:swift:ROOT:index.adoc[pkl-swift] for the Swift language. * xref:go:ROOT:index.adoc[pkl-go] for the Go language. In the future, we hope to add support for other popular languages and platforms, realizing our vision of a polyglot config solution based on a single config language. :: We are just getting started. Tell us about _your_ Pkl success story! ================================================ FILE: docs/modules/java-binding/examples/JavaConfigExample.java ================================================ import org.pkl.config.java.Config; import org.pkl.config.java.ConfigEvaluator; import org.pkl.config.java.JavaType; import org.pkl.core.ModuleSource; import org.junit.jupiter.api.Test; @SuppressWarnings({"unused", "NewClassNamingConvention"}) // the pkl-jvm-examples repo has a similar example public class JavaConfigExample { @Test public void usage() { // tag::usage[] Config config; try (var evaluator = ConfigEvaluator.preconfigured()) { // <1> config = evaluator.evaluate( ModuleSource.text("pigeon { age = 5; diet = new Listing { \"Seeds\" } }")); // <2> } var pigeon = config.get("pigeon"); // <3> var age = pigeon.get("age").as(int.class); // <4> var diet = pigeon.get("diet").as(JavaType.listOf(String.class)); // <5> // end::usage[] } } ================================================ FILE: docs/modules/java-binding/pages/codegen.adoc ================================================ = Java Code Generator include::ROOT:partial$component-attributes.adoc[] :uri-pkl-codegen-java-maven-module: {uri-maven-docsite}/artifact/org.pkl-lang/pkl-codegen-java :uri-pkl-codegen-java-download: {uri-sonatype-snapshot-download}&a=pkl-cli-codegen-java&e=jar ifdef::is-release-version[] :uri-pkl-codegen-java-download: {github-releases}/pkl-codegen-java endif::[] The Java source code generator takes Pkl class definitions as an input, and generates corresponding Java classes with equally named properties. The benefits of code generation are: * Configuration can be conveniently consumed as statically typed Java objects. * The entire configuration tree can be code-completed in Java IDEs. * Any drift between Java code and Pkl configuration structure is caught at compile time. The generated classes are immutable and have component-wise implementations of `equals()`, `hashCode()`, and `toString()`. == Installation The code generator is offered as Gradle plugin, Java library, and CLI. === Gradle Plugin See xref:pkl-gradle:index.adoc#installation[Installation] in the Gradle plugin chapter. [[install-library]] === Java Library The `pkl-codegen-java` library is available {uri-pkl-codegen-java-maven-module}[from Maven Central]. It requires Java 17 or higher. ifndef::is-release-version[] NOTE: Snapshots are published to repository `{uri-snapshot-repo}`. endif::[] ==== Gradle To use the library in a Gradle project, declare the following dependency: [tabs] ==== Kotlin:: + .build.gradle.kts [source,kotlin,subs="+attributes"] ---- dependencies { implementation("org.pkl-lang:pkl-codegen-java:{pkl-artifact-version}") } repositories { ifdef::is-release-version[] mavenCentral() endif::[] ifndef::is-release-version[] maven(url = "{uri-sonatype}") endif::[] } ---- Groovy:: + .build.gradle [source,groovy,subs="+attributes"] ---- dependencies { implementation "org.pkl-lang:pkl-codegen-java:{pkl-artifact-version}" } repositories { ifdef::is-release-version[] mavenCentral() endif::[] ifndef::is-release-version[] maven { url "{uri-sonatype}" } endif::[] } ---- ==== ==== Maven To use the library in a Maven project, declare the following dependency: .pom.xml [source,xml,subs="+attributes"] ---- org.pkl-lang pkl-codegen-java {pkl-artifact-version} ifndef::is-release-version[] sonatype-s01 Sonatype S01 {uri-sonatype} endif::[] ---- [[install-cli]] === CLI The CLI is available as a Java executable. It works on multiple platforms, and requires a Java 17 (or higher) runtime on the system path. To download: [tabs] ==== macOS/Linux:: + [source,shell] [subs="+attributes"] ---- curl -L -o pkl-codegen-java '{uri-pkl-codegen-java-download}' chmod +x pkl-codegen-java ./pkl-codegen-java --version ---- Windows:: + [source,PowerShell] [subs="+attributes"] ---- Invoke-WebRequest '{uri-pkl-codegen-java-download}' -OutFile pkl-codegen-java.bat .\pkl-codegen-java --version ---- ==== This should print something similar to: [source,shell] [subs="+attributes"] ---- pkl-codegen-java {pkl-version} (macOS 14.2, Java 17.0.10) ---- [[codegen-java-usage]] == Usage The code generator is offered as Gradle plugin, Java library, and CLI. === Gradle Plugin See xref:pkl-gradle:index.adoc#java-code-gen[Java Code Generation] in the Gradle plugin chapter. === Java Library The Java library offers two APIs: a high-level API that corresponds to the CLI, and a lower-level API that provides additional features and control. The entry points for these APIs are `org.pkl.codegen.java.CliJavaCodeGenerator` and `org.pkl.codegen.java.JavaCodeGenerator`, respectively. For more information, refer to the Javadoc documentation. === CLI *Synopsis:* `pkl-codegen-java [] ` ``:: The absolute or relative URIs of the modules to generate classes for. Relative URIs are resolved against the working directory. ==== Options .--generate-getters [%collapsible] ==== Default: (flag not set) + Flag that indicates to generate private final fields and public getter methods instead of public final fields. ==== .--generate-javadoc [%collapsible] ==== Default: (flag not set) + Flag that indicates to preserve Pkl doc comments by generating corresponding Javadoc comments. ==== .--params-annotation [%collapsible] ==== Default: `none` if `--generate-spring-boot` is set, `org.pkl.config.java.mapper.Named` otherwise + Fully qualified name of the annotation type to use for annotating constructor parameters with their name. + The specified annotation type must have a `value` parameter of type `String` or the generated code may not compile. If set to `none`, constructor parameters are not annotated. Whether and how constructor parameters should be annotated depends on the library that instantiates the generated classes. For Spring Boot applications, and for users of `pkl-config-java` compiling the generated classes with `-parameters`, no annotation is required. ==== .--non-null-annotation [%collapsible] ==== Default: `org.pkl.config.java.mapper.NonNull` + Fully qualified name of the annotation type to use for annotating non-null types. + The specified annotation type must be annotated with `@java.lang.annotation.Target(ElementType.TYPE_USE)` or the generated code may not compile. ==== Common code generator options: include::{partialsdir}/cli-codegen-options.adoc[] Common CLI options: include::../../pkl-cli/partials/cli-common-options.adoc[] [[full-example]] == Full Example For a ready-to-go example with full source code, see link:{uri-codegen-java-example}[codegen-java] in the _pkl-jvm-examples_ repository. ================================================ FILE: docs/modules/java-binding/pages/index.adoc ================================================ = Integration with Java Pkl provides rich integration with Java. Our integration allows you to embed the Pkl runtime into your Java program, and also provides code generation from Pkl source files. ================================================ FILE: docs/modules/java-binding/pages/pkl-config-java.adoc ================================================ = pkl-config-java Library include::ROOT:partial$component-attributes.adoc[] :uri-pkl-core-PklException: {uri-pkl-core-main-sources}/PklException.java :uri-pkl-config-java-maven-module: {uri-maven-docsite}/artifact/org.pkl-lang/pkl-config-java-all :uri-pkl-config-java-main-sources: {uri-github-tree}/pkl-config-java/src/main/java/org/pkl/config/java :uri-pkl-config-java-test-sources: {uri-github-tree}/pkl-config-java/src/test/java/org/pkl/config/java :uri-pkl-config-java-test-resources: {uri-github-tree}/pkl-config-java/src/test/resources/org/pkl/config/java :uri-pkl-config-java-ConfigEvaluator: {uri-pkl-config-java-main-sources}/ConfigEvaluator.java :uri-pkl-config-java-Config: {uri-pkl-config-java-main-sources}/Config.java :uri-pkl-config-java-ValueMapper: {uri-pkl-config-java-main-sources}/mapper/ValueMapper.java :uri-pkl-config-java-Named: {uri-pkl-config-java-main-sources}/mapper/Named.java :uri-pkl-config-java-Conversion: {uri-pkl-config-java-main-sources}/mapper/Conversion.java :uri-pkl-config-java-Conversions: {uri-pkl-config-java-main-sources}/mapper/Conversions.java :uri-pkl-config-java-ConverterFactories: {uri-pkl-config-java-main-sources}/mapper/ConverterFactories.java :uri-pkl-config-java-Converter: {uri-pkl-config-java-main-sources}/mapper/Converter.java :uri-pkl-config-java-ConverterFactory: {uri-pkl-config-java-main-sources}/mapper/ConverterFactory.java :uri-pkl-config-java-PObjectToDataObjectTestJava: {uri-pkl-config-java-test-sources}/mapper/PObjectToDataObjectTest.java The _pkl-config-java_ library builds upon xref:pkl-core:index.adoc[pkl-core]. It offers a higher-level API specifically designed for consuming application runtime configuration. == Installation The _pkl-config-java_ library is available {uri-pkl-config-java-maven-module}[from Maven Central]. It requires Java 17 or higher. === Gradle To use the library in a Gradle project, declare the following dependency: [tabs] ==== Kotlin:: + .build.gradle.kts [source,kotlin,subs="+attributes"] ---- dependencies { implementation("org.pkl-lang:pkl-config-java:{pkl-artifact-version}") } repositories { ifdef::is-release-version[] mavenCentral() endif::[] ifndef::is-release-version[] maven(url = "{uri-sonatype}") endif::[] } ---- Groovy:: + .build.gradle [source,groovy,subs="+attributes"] ---- dependencies { implementation "org.pkl-lang:pkl-config-java:{pkl-artifact-version}" } ifdef::is-release-version[] repositories { mavenCentral() } endif::[] ifndef::is-release-version[] repositories { maven { url "{uri-sonatype}" } } endif::[] ---- ==== Unlike `pkl-config-java`, `pkl-config-java__-all__` is a fat Jar with renamed third-party packages to avoid version conflicts. === Maven To use the library in a Maven project, declare the following dependency: .pom.xml [source,xml,subs="+attributes"] ---- org.pkl-lang pkl-config-java {pkl-artifact-version} ifndef::is-release-version[] sonatype-s01 Sonatype S01 {uri-sonatype} endif::[] ---- Unlike `pkl-config-java`, `pkl-config-java__-all__` is a fat Jar with renamed third-party packages to avoid version conflicts. == Usage === Consuming Configuration The {uri-pkl-config-java-ConfigEvaluator}[`ConfigEvaluator`] class loads and evaluates Pkl modules. If evaluation succeeds, a {uri-pkl-config-java-Config}[`Config`] object is returned. Otherwise, a {uri-pkl-core-PklException}[`PklException`] with error details is thrown. The returned `Config` object represents the root of the Pkl configuration tree. Intermediate and leaf nodes are also represented as `Config` objects. `Config` objects offer methods to * convert their Pkl value to a Java value of the specified type. * navigate to child nodes. Let's see this in action: [[config-evaluator-java-example]] [source,java,indent=0] ---- include::{examplesdir}/JavaConfigExample.java[tags=usage] ---- <1> Create a preconfigured `ConfigEvaluator`. To create a customized evaluator, start from `ConfigEvaluatorBuilder.preconfigured()` or `ConfigEvaluatorBuilder.unconfigured()`. The evaluator should be closed once it is no longer needed. In this example, this is done with a try-with-resources statement. Note that objects returned by the evaluator remain valid after calling `close()`. <2> Evaluate the given text. Other `evaluate` methods read from files, URLs, and other sources. If evaluation fails, an {uri-pkl-core-PklException}[`PklException`] is thrown. <3> Navigate from the config root to its `"pigeon"` child. <4> Navigate from `"pigeon"` to `"age"` and get the latter's value as an `int`. If conversion to the requested type fails, a `ConversionException` is thrown. <5> Navigate from `"pigeon"` to `"diet"` and get the latter's value as a `List`. Note the use of `JavaType.listOf()` for creating a parameterized type literal. Similar methods exist for sets, maps, and other generic types. A `ConfigEvaluator` caches module sources and evaluation results. To clear the cache, for example to evaluate the same module again, close the evaluator and create a new one. For a ready-to-go example with full source code, see link:{uri-config-java-example}[config-java] in the _pkl-jvm-examples_ repository. [[object-mapping]] === Object Mapping When a `Config` object needs to convert its Pkl value to a Java value, it delegates the conversion to {uri-pkl-config-java-ValueMapper}[`ValueMapper`]. `ValueMapper` can convert an entire `PModule` or any part thereof. A `ValueMapper` instance can be configured with many different Pkl-to-Java value conversions. `ValueMapper.preconfigured()` creates an instance configured with conversions from Pkl values to: * Number types * Strings * Enums * Collections * Arrays * `java.util.Optional` * `java.time.Duration` * `java.net.URI/URL` * etc. Additionally, a preconfigured `ValueMapper` instance can convert Pkl objects to Java objects with equally named properties that are settable through a constructor. This conversion works as follows: . Find the Java class constructor with the highest number of parameters. . Match constructor parameters with Pkl object properties by name. + Unmatched constructor parameters result in a conversion error. Unmatched Pkl object properties are ignored. + . Convert each Pkl property value to the corresponding constructor parameter's type. . Invoke the constructor. The Pkl object's runtime type is irrelevant to this conversion. Hence, typed and dynamic Pkl objects are equally supported. To perform this conversion, `ValueMapper` needs a way to obtain the Java constructor's parameter names. They need to be provided in one of the following ways: * Annotate constructor with `java.beans.ConstructorProperties`. * Annotate parameters with {uri-pkl-config-java-Named}[`Named`]. * Annotate parameters with `javax.inject.Named`. * Set the Java compiler flag `-parameters`. For a complete object mapping example, see: * {uri-pkl-config-java-PObjectToDataObjectTestJava}[`PObjectToDataObjectTest.java`] TIP: Together with xref:java-binding:codegen.adoc[code generation], object mapping provides a complete solution for consuming Pkl configuration as statically typed Java objects. Java code never drifts from the configuration structure defined in Pkl, and the entire configuration tree can be code-completed in Java IDEs. ==== Value Conversions The Pkl-to-Java value conversions that ship with the library are defined in {uri-pkl-config-java-Conversions}[`Conversions`] (for individual conversions) and {uri-pkl-config-java-ConverterFactories}[`ConverterFactories`] (for families of conversions). To implement and register your own conversions, follow these steps: . For conversions from a single source type to a single target type, implement a {uri-pkl-config-java-Conversion}[`Conversion`]. + Example: `Conversions.pStringToCharacter` converts a single-character `pkl.base#String` to `java.lang.Character`. . For conversions from one or multiple source types to one or multiple target types, implement a {uri-pkl-config-java-ConverterFactory}[`ConverterFactory`]. + Example: `ConverterFactories.pCollectionToCollection` converts any `pkl.base#Collection` to any implementation of `java.util.Collection`, for any `E`. + Converter factories are called once per combination of source type and (possibly parameterized) target type. The returned `Converter`s are cached. . Create a `ValueMapperBuilder`, add all desired conversions, and build a `ValueMapper`. . Either use the `ValueMapper` directly, or connect it to a `ConfigEvaluator` through `ConfigEvaluatorBuilder`. == Further Information Refer to the Javadoc and sources published with the library, or browse the library's {uri-pkl-config-java-main-sources}[main] and {uri-pkl-config-java-test-sources}[test] sources. ================================================ FILE: docs/modules/java-binding/partials/cli-codegen-options.adoc ================================================ .--indent [%collapsible] ==== Default: `" "` (two spaces) + Example: `"\t"` (one tab) + The characters to use for indenting generated source code. ==== .-o, --output-dir [%collapsible] ==== Default: (not set) + Example: `generated/` + The directory where generated source code is placed. Relative paths are resolved against the working directory. ==== .--generate-spring-boot [%collapsible] ==== Default: (not set) + Flag that indicates to generate config classes for use with Spring Boot. ==== .--implement-serializable [%collapsible] ==== Default: (not set) + Flag that indicates to generate classes that implement `java.io.Serializable`. ==== .--add-generated-annotation [%collapsible] ==== Default: (not set) + Flag that indicates to add the `org.pkl.config.java.Generated` annotation to generated types. ==== .--rename [%collapsible] ==== Default: (none) + Example: `foo.=com.example.foo.` + Allows to change default class and package names (derived from Pkl module names) in the generated code. When you need the generated class or package names to be different from the default names derived from the Pkl module names, you can define a rename mapping, where the key is the original Pkl module name prefix, and the value is its replacement. When you do, the generated code's `package` declarations, class names, as well as file locations, will be modified according to this mapping. The prefixes are replaced literally, which means that dots at the end are important. If you want to rename packages only, in most cases, you must ensure that you have an ending dot on both sides of a mapping (except for an empty mapping, if you use it), otherwise you may get unexpected results: ---- // Assuming the following arguments: --rename com.foo.=x // Dot on the left only --rename org.bar=y. // Dot on the right only --rename net.baz=z // No dots // The following renames will be made: "com.foo.bar" -> "xbar" // Target prefix merged into the suffix "org.bar.baz" -> "y..baz" // Double dot, invalid name "net.baz.qux" -> "z.qux" // Looks okay, but... "net.bazqux" -> "zqux" // ...may cut the name in the middle. ---- When computing the appropriate target name, the longest matching prefix is used: ---- // Assuming the following arguments: --rename com.foo.Main=w.Main --rename com.foo.=x. --rename com.=y. --rename =z. // The following renames will be made: com.foo.Main -> w.Main com.foo.bar -> x.bar com.baz.qux -> y.baz.qux org.foo.bar -> z.org.foo.bar ---- Repeat this option to define multiple mappings. Keys can be arbitrary strings, including an empty string. Values must be valid dot-separated fully qualified class name prefixes, possibly terminated by a dot. ==== ================================================ FILE: docs/modules/kotlin-binding/examples/KotlinConfigExample.kt ================================================ @file:Suppress("UNUSED_VARIABLE") import org.pkl.config.java.ConfigEvaluator import org.pkl.config.kotlin.forKotlin import org.pkl.config.kotlin.to import org.pkl.core.ModuleSource import org.junit.jupiter.api.Test // the pkl-jvm-examples repo has a similar example class KotlinConfigExample { @Test fun usage() { // tag::usage[] val evaluator = ConfigEvaluator.preconfigured().forKotlin() // <1> val config = evaluator.use { // <2> it.evaluate(ModuleSource.text("""pigeon { age = 5; diet = new Listing { "Seeds" } }""")) } val pigeon = config["pigeon"] // <3> val age = pigeon["age"].to() // <4> val hobbies = pigeon["diet"].to>() // <5> // end::usage[] } @Test fun nullable() { // tag::nullable[] val evaluator = ConfigEvaluator.preconfigured().forKotlin() val config = evaluator.use { it.evaluate(ModuleSource.text("name = null")) // <1> } val name = config["name"].to() // <2> // end::nullable[] } } ================================================ FILE: docs/modules/kotlin-binding/pages/codegen.adoc ================================================ = Kotlin Code Generator include::ROOT:partial$component-attributes.adoc[] :uri-pkl-codegen-kotlin-maven-module: {uri-maven-docsite}/artifact/org.pkl-lang/pkl-codegen-kotlin :uri-pkl-codegen-kotlin-download: {uri-sonatype-snapshot-download}&a=pkl-cli-codegen-kotlin&e=jar ifdef::is-release-version[] :uri-pkl-codegen-kotlin-download: {github-releases}/pkl-codegen-kotlin endif::[] The Kotlin source code generator reads Pkl classes and generates corresponding Kotlin classes with equally named properties. Together with xref:java-binding:pkl-config-java.adoc#object-mapping[Object Mapping], code generation provides a complete solution for consuming Pkl configuration as statically typed Kotlin objects. Kotlin code never drifts from the configuration structure defined in Pkl, and the entire configuration tree can be code-completed in Kotlin IDEs. == Installation The code generator is offered as Gradle plugin, Java library, and CLI. === Gradle Plugin See xref:pkl-gradle:index.adoc#installation[Installation] in the Gradle plugin chapter. [[install-library]] === Java Library The `pkl-codegen-kotlin` library is available {uri-pkl-codegen-kotlin-maven-module}[from Maven Central]. It requires Java 17 or higher and Kotlin 1.3 or higher. ==== Gradle To use the library in a Gradle project, declare the following dependency: [tabs] ==== Kotlin:: + .build.gradle.kts [source,kotlin,subs="+attributes"] ---- dependencies { implementation("org.pkl-lang:pkl-codegen-kotlin:{pkl-artifact-version}") } repositories { ifdef::is-release-version[] mavenCentral() endif::[] ifndef::is-release-version[] maven(url = "{uri-sonatype}") endif::[] } ---- Groovy:: + .build.gradle [source,groovy,subs="+attributes"] ---- dependencies { implementation "org.pkl-lang:pkl-codegen-kotlin:{pkl-artifact-version}" } repositories { ifdef::is-release-version[] mavenCentral() endif::[] ifndef::is-release-version[] maven { url "{uri-sonatype}" } endif::[] } ---- ==== ==== Maven To use the library in a Maven project, declare the following dependency: .pom.xml [source,xml,subs="+attributes"] ---- org.pkl-lang pkl-codegen-kotlin {pkl-artifact-version} ---- [[install-cli]] === CLI The CLI is available as a Java executable. It works on multiple platforms, and requires a Java 17 (or higher) runtime on the system path. To download: [tabs] ==== macOS/Linux:: + [source,shell] [subs="+attributes"] ---- curl -L -o pkl-codegen-kotlin '{uri-pkl-codegen-kotlin-download}' chmod +x pkl-codegen-kotlin ./pkl-codegen-kotlin --version ---- Windows:: + [source,PowerShell] [subs="+attributes"] ---- Invoke-WebRequest '{uri-pkl-codegen-kotlin-download}' -OutFile pkl-codegen-kotlin.bat .\pkl-codegen-kotlin --version ---- ==== This should print something similar to: [source,shell] [subs="+attributes"] ---- pkl-codegen-kotlin {pkl-version} (macOS 14.2, Java 17.0.10) ---- [[usage]] == Usage The code generator is offered as Gradle plugin, Java library, and CLI. === Gradle Plugin See xref:pkl-gradle:index.adoc#kotlin-code-gen[Kotlin Code Generation] in the Gradle plugin chapter. === Java Library The library offers two APIs: a high-level API that corresponds to the CLI, and a lower-level API that provides additional features and control. The entry points for these APIs are `org.pkl.codegen.kotlin.CliKotlinCodeGenerator` and `org.pkl.codegen.kotlin.KotlinCodeGenerator`, respectively. For more information, refer to the KDoc documentation. === CLI *Synopsis:* `pkl-codegen-kotlin [] ` ``:: The absolute or relative URIs of the modules to generate classes for. Relative URIs are resolved against the working directory. ==== Options .--generate-kdoc [%collapsible] ==== Default: (flag not set) + Flag that indicates to preserve Pkl doc comments by generating corresponding KDoc comments. ==== Common code generator options: include::../../java-binding/partials/cli-codegen-options.adoc[] Common CLI options: include::../../pkl-cli/partials/cli-common-options.adoc[] [[full-example]] == Full Example For a ready-to-go example with full source code, see link:{uri-codegen-kotlin-example}[codegen-kotlin] in the _pkl-jvm-examples_ repository. ================================================ FILE: docs/modules/kotlin-binding/pages/index.adoc ================================================ = Integration with Kotlin Pkl provides rich integration with Kotlin. Our integration allows you to embed the Pkl runtime into your Kotlin application, and also provides code generation for from Pkl source code. ================================================ FILE: docs/modules/kotlin-binding/pages/pkl-config-kotlin.adoc ================================================ = pkl-config-kotlin Library include::ROOT:partial$component-attributes.adoc[] :uri-pkl-config-kotlin-maven-module: {uri-maven-docsite}/artifact/org.pkl-lang/pkl-config-kotlin :uri-pkl-config-kotlin-main-sources: {uri-github-tree}/pkl-config-kotlin/src/main/kotlin/org/pkl/config/kotlin :uri-pkl-config-kotlin-test-sources: {uri-github-tree}/pkl-config-kotlin/src/test/kotlin/org/pkl/config/kotlin :uri-pkl-config-kotlin-ConverterFactories: {uri-pkl-config-kotlin-main-sources}/ConverterFactories.kt :uri-pkl-config-kotlin-ConfigExtensions: {uri-pkl-config-kotlin-main-sources}/ConfigExtensions.kt The _pkl-config-kotlin_ library extends xref:java-binding:pkl-config-java.adoc[pkl-config-java] with Kotlin specific extension methods and object converters. We recommend that Kotlin projects depend on this library instead of _pkl-config-java_. == Installation The _pkl-config-kotlin_ library is available {uri-pkl-config-kotlin-maven-module}[from Maven Central]. It requires Java 17 or higher and Kotlin 1.5 or higher. === Gradle To use the library in a Gradle project, declare the following dependency: [tabs] ==== Kotlin:: + .build.gradle.kts [source,kotlin,subs="+attributes"] ---- dependencies { implementation("org.pkl-lang:pkl-config-kotlin:{pkl-artifact-version}") } repositories { ifdef::is-release-version[] mavenCentral() endif::[] ifndef::is-release-version[] maven(url = "{uri-sonatype}") endif::[] } ---- Groovy:: + .build.gradle [source,groovy,subs="+attributes"] ---- dependencies { implementation "org.pkl-lang:pkl-config-kotlin:{pkl-artifact-version}" } repositories { ifdef::is-release-version[] mavenCentral() endif::[] ifndef::is-release-version[] maven { url "{uri-sonatype}" } endif::[] } ---- ==== === Maven To use the library in a Maven project, declare the following dependency: .pom.xml [source,xml,subs="+attributes"] ---- org.pkl-lang pkl-config-kotlin {pkl-artifact-version} ifndef::is-release-version[] sonatype-s01 Sonatype S01 {uri-sonatype} endif::[] ---- == Usage Below is the Kotlin version of the Java xref:java-binding:pkl-config-java.adoc#config-evaluator-java-example[ConfigEvaluator] example. Differences to the Java version are called out. [source,kotlin,indent=0] ---- include::{examplesdir}/KotlinConfigExample.kt[tags=usage] ---- <1> Use the `forKotlin()` method to preconfigure the builder with Kotlin specific conversions. In particular, `forKotlin()` eliminates the need to annotate constructor parameters of Kotlin classes and Kotlin data classes with `@Named`. <2> The evaluator should be closed once it is no longer needed. Here this is done with a Kotlin `use {}` expression. Any data returned by the evaluator before calling `close()` remains valid. <3> Navigate to the `"pigeon"` child. The subscript notation is shorthand for `config.get("pigeon")`. <4> Convert `"age"` to `Int` with the `Config.to()` extension method. The target type is provided as a type argument. Always use `Config.to()` instead of `Config.as()` in Kotlin. <5> `Config.to()` makes conversions to parameterized types straightforward: `to>()` instead of `as(JavaType.listOf(String::class.java))`. For properties that are allowed to be `null`, convert to a nullable type: [source,kotlin,indent=0] ---- include::{examplesdir}/KotlinConfigExample.kt[tags=nullable] ---- <1> To indicate that `null` is an allowed value, convert to the nullable type `String?`. Converting to `String` would result in a `ConversionException`. For a ready-to-go example with full source code, see link:{uri-config-kotlin-example}[config-kotlin] in the _pkl-jvm-examples_ repository. == Further Information Refer to the Javadoc and sources published with the library, or browse the library's {uri-pkl-config-kotlin-main-sources}[main] and {uri-pkl-config-kotlin-test-sources}[test] sources. ================================================ FILE: docs/modules/language-reference/pages/index.adoc ================================================ = Language Reference include::ROOT:partial$component-attributes.adoc[] :uri-common-mark: https://commonmark.org/ :uri-newspeak: https://newspeaklanguage.org :uri-prototypical-inheritance: https://en.wikipedia.org/wiki/Prototype-based_programming :uri-double-precision: https://en.wikipedia.org/wiki/Double-precision_floating-point_format :uri-progressive-disclosure: https://en.wikipedia.org/wiki/Progressive_disclosure :uri-javadoc-Pattern: https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html :uri-github-PklLexer: {uri-github-tree}/pkl-core/src/main/java/org/pkl/core/parser/Lexer.java :uri-pkl-core-ModuleSchema: {uri-pkl-core-main-sources}/ModuleSchema.java :uri-pkl-core-SecurityManager: {uri-pkl-core-main-sources}/SecurityManager.java :uri-pkl-core-ResourceReader: {uri-pkl-core-main-sources}/resource/ResourceReader.java :uri-pkl-core-ModuleKey: {uri-pkl-core-main-sources}/module/ModuleKey.java :uri-pkl-core-PklException: {uri-pkl-core-main-sources}/PklException.java :uri-value-converters: {uri-pkl-stdlib-docs}/base/PcfRenderer#converters // TODO: double check this :uri-pkl-go-resource-reader-docs: https://github.com/apple/pkl-go/blob/main/pkl/reader.go :uri-pkl-swift-resource-reader-docs: https://github.com/apple/pkl-swift/blob/main/Sources/PklSwift/Reader.swift :uri-glob-7: https://man7.org/linux/man-pages/man7/glob.7.html :uri-unicode-identifier: https://unicode.org/reports/tr31/#R1-1 :uri-semver: https://semver.org :uri-mvs-build-list: https://research.swtch.com/vgo-mvs#algorithm_1 The language reference provides a comprehensive description of every Pkl language feature. <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> For a hands-on introduction, see xref:language-tutorial:index.adoc[Tutorial]. For ready-to-go examples with full source code, see xref:ROOT:examples.adoc[]. For API documentation, see xref:ROOT:standard-library.adoc[Standard Library]. [[comments]] == Comments Pkl has three forms of comments: Line comment:: A code comment that starts with a double-slash (`//`) and runs until the end of the line. + [source%parsed,{pkl}] ---- // Single-line comment ---- Block comment:: A nestable multiline comment, which is typically used to comment out code. Starts with `+/*+` and ends with `+*/+`. + [source%parsed,{pkl}] ---- /* Multiline comment */ ---- Doc comment:: A user-facing comment attached to a program member. It starts with a triple-slash (`///`) and runs until the end of the line. Doc comments on consecutive lines are merged. + [source%parsed,{pkl}] ---- /// A *bird* superstar. /// Unfortunately, extinct. dodo: Bird ---- Doc comments are processed by xref:pkl-doc:index.adoc[Pkldoc], Pkl's documentation generator. For details on their syntax, see <>. [[numbers]] == Numbers Pkl has two numeric types, link:{uri-stdlib-Int}[Int] and link:{uri-stdlib-Float}[Float]. Their common supertype is link:{uri-stdlib-Number}[Number]. === Integers A value of type link:{uri-stdlib-Int}[Int] is a 64-bit signed integer. Integer literals can be written in decimal, hexadecimal, binary, or octal notation: [source%tested,{pkl}] ---- num1 = 123 num2 = 0x012AFF // <1> num3 = 0b00010111 // <2> num4 = 0o755 // <3> ---- <1> decimal: 76543 <2> decimal: 23 <3> decimal: 493 Integers can optionally include an underscore as a separator to improve readability. An underscore does not affect the integer's value. [source%tested,{pkl}] ---- num1 = 1_000_000 // <1> num2 = 0x0134_64DE // <2> num3 = 0b0001_0111 // <3> num4 = 0o0134_6475 // <4> ---- <1> Equivalent to `1000000` <2> Equivalent to `0x013464DE` <3> Equivalent to `0b00010111` <4> Equivalent to `0o01346475` Negative integer literals start with a minus sign, as in `-123`. Integers support the standard comparison operators: [source%tested,{pkl}] ---- comparison1 = 5 == 2 comparison2 = 5 < 2 comparison3 = 5 > 2 comparison4 = 5 <= 2 comparison5 = 5 >= 2 ---- Integers support the following arithmetic operators: [source%tested,{pkl}] ---- num1 = 5 + 2 // <1> num2 = 5 - 2 // <2> num3 = 5 * 2 // <3> num4 = 5 / 2 // <4> num5 = 5 ~/ 2 // <5> num6 = 5 % 2 // <6> num7 = 5 ** 2 // <7> ---- <1> addition (result: `7`) <2> subtraction (result: `3`) <3> multiplication (result: `10`) <4> division (result: `2.5`, always `Float`) <5> integer division (result: `2`, always `Int`) <6> remainder (result: `1`) <7> exponentiation (result: `25`) Arithmetic overflows are caught and result in an error. To restrict an integer's range, use one of the predefined <> or an link:{uri-stdlib-isBetween}[isBetween] <>: [source,{pkl}] ---- clientPort: UInt16 serverPort: Int(isBetween(0, 1023)) ---- === Floats A value of type link:{uri-stdlib-Float}[Float] is a 64-bit link:{uri-double-precision}[double-precision] floating point number. Float literals use decimal notation. They consist of an integer part, decimal point, fractional part, and exponent part. The integer and exponent parts are optional. [source%tested,{pkl}] ---- num1 = .23 num2 = 1.23 num3 = 1.23e2 // <1> num4 = 1.23e-2 // <2> ---- <1> result: 1.23 * 10^2^ <2> result: 1.23 * 10^-2^ Negative float literals start with a minus sign, as in `-1.23`. The special float values _not a number_, _positive infinity_, and _negative infinity_ are written as: [source%tested,{pkl}] ---- notANumber = NaN positiveInfinity = Infinity negativeInfinity = -Infinity ---- The link:{uri-stdlib-NaN}[NaN] and link:{uri-stdlib-Infinity}[Infinity] properties are defined in the standard library. Floats support the same comparison and arithmetic operators as integers. Float literals with a fractional part of zero can be safely replaced with integer literals. For example, it is safe to write `1.3 * 42` instead of `1.3 * 42.0`. Floats can also include the same underscore separator as integers. For example, `1_000.4_400` is a float whose value is equivalent to `1000.4400`. TIP: As integers are more convenient to use than floats with a fractional part of zero, we recommend requiring `x: Number` instead of `x: Float` in type annotations. To restrict a float to a finite value, use the link:{uri-stdlib-isFinite}[isFinite] <>: [source,{pkl}] ---- x: Float(isFinite) ---- To restrict a float's range, use the link:{uri-stdlib-isBetween}[isBetween] type constraint: [source,{pkl}] ---- x: Float(isBetween(0, 10e6)) ---- [[booleans]] == Booleans A value of type link:{uri-stdlib-Boolean}[Boolean] is either `true` or `false`. Apart from the standard logical operators, `Boolean` has link:{uri-stdlib-xor}[xor] and link:{uri-stdlib-implies}[implies] methods. [source%tested,{pkl}] ---- res1 = true && false // <1> res2 = true || false // <2> res3 = !false // <3> res4 = true.xor(false) // <4> res5 = true.implies(false) // <5> ---- <1> logical conjunction (result: `false`) <2> logical disjunction (result: `true`) <3> logical negation (result: `true`) <4> exclusive disjunction (result: `true`) <5> logical implication (result: `false`) [[strings]] == Strings A value of type link:{uri-stdlib-String}[String] is a sequence of Unicode code points. String literals are enclosed in double quotes: [source%tested,{pkl-expr}] ---- "Hello, World!" ---- TIP: Except for a few minor differences footnote:[Pkl's string literals have fewer character escape sequences, have stricter rules for line indentation in multiline strings, and do not have a line continuation character.], String literals have the same syntax and semantics as in Swift 5. Learn one of them, know both of them! Inside a string literal, the following character escape sequences have special meaning: * `\t` - tab * `\n` - line feed * `\r` - carriage return * `\"` - verbatim quote * `\\` - verbatim backslash Unicode escape sequences have the form `\u{}`, where `` is a hexadecimal number between 0 and 10FFFF: [source%tested,{pkl-expr}] ---- "\u{26} \u{E9} \u{1F600}" // <1> ---- <1> result: `"& é 😀"` To concatenate strings, use the `+` (plus) operator, as in `"abc" + "def" + "ghi"`. === String Interpolation To embed the result of expression `` in a string, use `\()`: [source%tested,{pkl}] ---- name = "Dodo" greeting = "Hi, \(name)!" // <1> ---- <1> result: `"Hi, Dodo!"` Before a result is inserted, it is converted to a string: [source%tested,{pkl}] ---- x = 42 str = "\(x + 2) plus \(x * 2) is \(0x80)" // <1> ---- <1> result: `"44 plus 84 is 128"` === Multiline Strings To write a string that spans multiple lines, use a multiline string literal: [source%tested,{pkl-expr}] ---- """ Although the Dodo is extinct, the species will be remembered. """ ---- Multiline string literals are delimited by three double quotes (`"""`). String content and closing delimiter must each start on a new line. The content of a multiline string starts on the first line after the opening quotes and ends on the last line before the closing quotes. Line breaks are included in the string and normalized to `\n`. The previous multiline string is equivalent to this single-line string. Notice that there is no leading or trailing whitespace. [source%tested,{pkl-expr}] ---- "Although the Dodo is extinct,\nthe species will be remembered." ---- String interpolation, character escape sequences, and Unicode escape sequences work the same as for single-line strings: [source%tested,{pkl}] ---- bird = "Dodo" message = """ Although the \(bird) is extinct, the species will be remembered. """ ---- Each content line must begin with the same whitespace characters as the line containing the closing delimiter, which is not included in the string. Any further leading whitespace characters are preserved. In other words, line indentation is controlled by indenting lines relative to the closing delimiter. In the following string, lines have no leading whitespace: [source%tested,{pkl}] ---- str = """ Although the Dodo is extinct, the species will be remembered. """ ---- In the following string, lines are indented between three and five spaces: [source%tested,{pkl}] ---- str = """ Although the Dodo is extinct, the species will be remembered. """ ---- [[custom-string-delimiters]] === Custom String Delimiters Some strings contain many verbatim backslash (`\`) or quote (`"`) characters. A good example is regular expressions, which make frequent use of backslash characters for escaping. In such cases, using the escape sequences `\\` and `\"` quickly becomes tedious and hampers readability. Instead, leading/closing string delimiters can be customized to start/end with a pound sign (`\#`). This also affects the escape character, which changes from `\` to `\#`. All backslash and quote characters in the following string are interpreted verbatim: [source%tested,{pkl-expr}] ---- #" \\\\\ """"" "# ---- Escape sequences and string interpolation still work, and now start with `\#`: [source%tested,{pkl}] ---- bird = "Dodo" str = #" \\\\\ \#n \#u{12AF} \#(bird) """"" "# ---- More generally, string delimiters and escape characters can be customized to contain _n_ pound signs each, for n >= 1. In the following string, _n_ is 2. As a result, the string content is interpreted verbatim: [source%tested,{pkl-expr}] ---- ##" \\\\\ \#\#\# """"" "## ---- === String API The `String` class offers a link:{uri-stdlib-String}[rich API]. Here are just a few examples: [source%tested,{pkl}] ---- strLength = "dodo".length // <1> reversedStr = "dodo".reverse() // <2> hasAx = "dodo".contains("alive") // <3> trimmed = " dodo ".trim() // <4> ---- <1> result: `4` <2> result: `"odod"` <3> result: `false` <4> result: `"dodo"` [[durations]] == Durations A value of type link:{uri-stdlib-Duration}[Duration] has a _value_ component of type `Number` and a _unit_ component of type `String`. The unit component is constrained to the units defined in link:{uri-stdlib-DurationUnit}[DurationUnit]. Durations are constructed with the following `Number` properties: [source%tested,{pkl}] ---- duration1 = 5.ns // nanoseconds (smallest unit) duration2 = 5.us // microseconds duration3 = 5.ms // milliseconds duration4 = 5.s // seconds duration5 = 5.min // minutes duration6 = 5.h // hours duration7 = 3.d // days (largest unit) ---- A duration can be negative, as in `-5.min`. It can have a floating point value, as in `5.13.min`. The link:{uri-stdlib-Duration-value}[value] and link:{uri-stdlib-Duration-unit}[unit] properties provide access to the duration's components. Durations support the standard comparison operators: [source%tested,{pkl}] ---- comparison1 = 5.min == 3.s comparison2 = 5.min < 3.s comparison3 = 5.min > 3.s comparison4 = 5.min <= 3.s comparison5 = 5.min >= 3.s ---- Durations support the same arithmetic operators as numbers: [source%tested,{pkl}] ---- res1 = 5.min + 3.s // <1> res2 = 5.min - 3.s // <2> res3 = 5.min * 3 // <3> res4 = 5.min / 3 // <4> res5 = 5.min / 3.min // <5> res6 = 5.min ~/ 3 // <6> res7 = 5.min ~/ 3.min // <7> res8 = 5.min % 3 // <8> res9 = 5.min ** 3 // <9> ---- <1> result: `5.05.min` <2> result: `4.95.min` <3> result: `15.min` <4> result: `1.6666666666666667.min` <5> result: `1.6666666666666667` <6> result: `1.min` <7> result: `1` <8> result: `2.min` <9> result: `125.min` The value component can be an expression: [source%tested,{pkl}] ---- x = 5 xMinutes = x.min // <1> y = 3 xySeconds = (x + y).s // <2> ---- <1> result: `5.min` <2> result: `8.s` [[data-sizes]] == Data Sizes A value of type link:{uri-stdlib-DataSize}[DataSize] has a _value_ component of type `Number` and a _unit_ component of type `String`. The unit component is constrained to the units defined in link:{uri-stdlib-DataSizeUnit}[DataSizeUnit]. Data sizes with decimal units (factor 1000) are constructed with the following `Number` properties: [source%tested,{pkl}] ---- datasize1 = 5.b // bytes (smallest unit) datasize2 = 5.kb // kilobytes datasize3 = 5.mb // megabytes datasize4 = 5.gb // gigabytes datasize5 = 5.tb // terabytes datasize6 = 5.pb // petabytes (largest unit) ---- Data sizes with binary units (factor 1024) are constructed with the following `Number` properties: [source%tested,{pkl}] ---- datasize1 = 5.b // bytes (smallest unit) datasize2 = 5.kib // kibibytes datasize3 = 5.mib // mebibytes datasize4 = 5.gib // gibibytes datasize5 = 5.tib // tebibytes datasize6 = 5.pib // pebibytes (largest unit) ---- A data size can be negative, as in `-5.mb`. It can have a floating point value, as in `5.13.mb`. The link:{uri-stdlib-DataSize-value}[value] and link:{uri-stdlib-DataSize-unit}[unit] properties provide access to the data size's components. Data sizes support the standard comparison operators: [source%tested,{pkl}] ---- comparison1 = 5.mb == 3.kib comparison2 = 5.mb < 3.kib comparison3 = 5.mb > 3.kib comparison4 = 5.mb <= 3.kib comparison5 = 5.mb >= 3.kib ---- Data sizes support the same arithmetic operators as numbers: [source%tested,{pkl}] ---- res1 = 5.mb + 3.kib // <1> res2 = 5.mb - 3.kib // <2> res3 = 5.mb * 3 // <3> res4 = 5.mb / 3 // <4> res5 = 5.mb / 3.mb // <5> res6 = 5.mb ~/ 3 // <6> res7 = 5.mb ~/ 3.mb // <7> res8 = 5.mb % 3 // <8> res9 = 5.mb ** 3 // <9> ---- <1> result: `5.003072.mb` <2> result: `4.996928.mb` <3> result: `15.mb` <4> result: `1.6666666666666667.mb` <5> result: `1.6666666666666667` <6> result: `1.mb` <7> result: `1` <8> result: `2.mb` <9> result: `125.mb` The value component can be an expression: [source%tested,{pkl}] ---- x = 5 xMegabytes = x.mb // <1> y = 3 xyKibibytes = (x + y).kib // <2> ---- <1> result: `5.mb` <2> result: `8.kib` [[objects]] == Objects An object is an ordered collection of _values_ indexed by _name_. An object's key-value pairs are called its _properties_. Property values are lazily evaluated on the first read. Because Pkl's objects differ in important ways from objects in general-purpose programming languages, and because they are the backbone of most data models, understanding objects is the key to understanding Pkl. [[defining-objects]] === Defining Objects Let's define an object with properties `name` and `extinct`: [source%tested,{pkl}] ---- dodo { // <1> name = "Dodo" // <2> extinct = true // <3> } // <4> ---- <1> Defines a module property named `dodo`. The open curly brace (`{`) indicates that the value of this property is an object. <2> Defines an object property named `name` with string value `"Dodo"`. <3> Defines an object property named `extinct` with boolean value `true`. <4> The closing curly brace indicates the end of the object definition. To access an object property by name, use dot (`.`) notation: [source%tested,{pkl}] ---- dodoName = dodo.name dodoIsExtinct = dodo.extinct ---- Objects can be nested: [source%tested,{pkl}] ---- dodo { name = "Dodo" taxonomy { // <1> `class` = "Aves" // <2> } } ---- <1> Defines an object property named `taxonomy`. The open curly brace indicates that its value is another object. <2> The word `class` is a keyword of Pkl, and needs to be wrapped in backticks (```) to be used as a property. As you probably guessed, the nested property `class` can be accessed with `dodo.taxonomy.class`. Like all values, objects are _immutable_, which is just a fancy (and short!) way to say that their properties never change. So what happens when Dodo moves to a different street? Do we have to construct a new object from scratch? [[amending-objects]] === Amending Objects Fortunately, we don't have to. An object can be _amended_ to form a new object that only differs in selected properties. Here is how this looks: [source%parsed,{pkl}] ---- tortoise = (dodo) { // <1> name = "Galápagos tortoise" taxonomy { // <2> `class` = "Reptilia" // <3> } } ---- <1> Defines a module property named `tortoise`. Its value is an object that _amends_ `dodo`. Note that the amended object must be enclosed in parentheses. <2> Object property `tortoise.taxonomy` _amends_ `dodo.taxonomy`. <3> Object property `tortoise.taxonomy.class` _overrides_ `dodo.taxonomy.class`. As you can see, it is easy to construct a new object that overrides selected properties of an existing object, even if, as in our example, the overridden property is nested inside another object. NOTE: If this way of constructing new objects from existing objects reminds you of prototypical inheritance, you are spot-on: Pkl objects use prototypical inheritance as known from languages such as JavaScript. But unlike in JavaScript, their prototype chain cannot be directly accessed or even modified. Another difference is that in Pkl, object properties are late-bound. Read on to see what this means. [[amends-declaration]] [NOTE] .Amends declaration vs. amends expression ==== The <> and <> sections cover two notations that are both a form of amending; called an _amends declaration_ and an _amends expression_, respectively. [source%tested,{pkl}] ---- pigeon { // <1> name = "Turtle dove" extinct = false } parrot = (pigeon) { // <2> name = "Parrot" } ---- <1> Amends declaration. <2> Amends expression. An amends declaration amends a property of the same name if the property exists within a parent module. Otherwise, an amends declaration implicitly amends {uri-stdlib-Dynamic}[Dynamic]. Another way to think about an amends declaration is that it is shorthand for assignment. In practical terms, `pigeon {}` is the same as `pigeon = (super.pigeon) {}`. Amending object bodies can be chained for both an amends declaration and an amends expression. [source%tested,{pkl}] ---- pigeon { name = "Common wood pigeon" } { extinct = false } // <1> dodo = (pigeon) { name = "Dodo" } { extinct = true } // <2> ---- <1> Chained amends declaration. <2> Chained amends expression (`(pigeon) { ... } { ... }` is the amends expression). ==== [[late-binding]] === Late Binding Let's move on to Pkl's secret sauce: the ability to define an object property's value in terms of another property's value, and the resulting _late binding_ effect. Here is an example: [source%tested,{pkl}] ---- penguin { eggIncubation = 40.d adultWeightInGrams = eggIncubation.value * 100 // <1> } adultWeightInGrams = penguin.adultWeightInGrams ---- <1> result: `4000` We have defined a hypothetical `penguin` object whose `adultWeightInGrams` property is defined in terms of the `eggIncubation` duration. Can you guess what happens when `penguin` is amended and its `eggIncubation` overridden? [source%tested,{pkl}] ---- madeUpBird = (penguin) { eggIncubation = 11.d } adultWeightInGrams = madeUpBird.adultWeightInGrams // <1> ---- <1> result: `1100` As you can see, ``madeUpBird``'s `adultWeightInGrams` changed along with its `eggIncubation`. This is what we mean when we say that object properties are _late-bound_. [NOTE] .Spreadsheet Programming ==== A good analogy is that object properties behave like spreadsheet cells. When they are linked, changes to "downstream" properties automatically propagate to "upstream" properties. The main difference is that editing a spreadsheet cell changes the state of the spreadsheet, whereas "editing" a property results in a new object, leaving the original object untouched. It is as if you made a copy of the entire spreadsheet whenever you edited a cell! ==== Late binding of properties is an incredibly useful feature for a configuration language. It is used extensively in Pkl code (especially in templates) and is the key to understanding how Pkl works. === Transforming Objects Say we have the following object: [source%tested,{pkl}] ---- dodo { name = "Dodo" extinct = true } ---- How can property `name` be removed? The recipe for transforming an object is: . Convert the object to a map. . Transform the map using ``Map``'s link:{uri-stdlib-Map}[rich API]. . If necessary, convert the map back to an object. Equipped with this knowledge, let's try to accomplish our objective: [source%tested,{pkl-expr}] ---- dodo .toMap() .remove("name") .toDynamic() ---- The resulting dynamic object is equivalent to `dodo`, except that it no longer has a `name` property. [IMPORTANT] .Lazy vs. Eager Data Types ==== Converting an object to a map is a transition from a _lazy_ to an _eager_ data type. All of the object's properties are evaluated and all references between them are resolved. If the map is later converted back to an object, subsequent changes to the object's properties no longer propagate to (previously) dependent properties. To make these boundaries clear, transitioning between _lazy_ and _eager_ data types always requires an explicit method call, such as `toMap()` or `toDynamic()`. ==== [[typed-objects]] === Typed Objects :fn-typed-objects: footnote:[By "structure" we mean a list of property names and (optionally) property types.] Pkl has two kinds of objects: * A link:{uri-stdlib-Dynamic}[Dynamic] object has no predefined structure.{fn-typed-objects} When a dynamic object is amended, not only can existing properties be overridden or amended, but new properties can also be added. So far, we have only used dynamic objects in this chapter. * A link:{uri-stdlib-Typed}[Typed] object has a fixed structure described by a class definition. When a typed object is amended, its properties can be overridden or amended, but new properties cannot be added. In other words, the new object has the same class as the original object. [TIP] .When to Use Typed vs. Dynamic Objects ==== * Use typed objects to build schema-backed data models that are validatedfootnote:[By "Use typed objects" we mean to define classes and build data models out of instances of these classes.]. This is what most templates do. * Use dynamic objects to build schema-less data models that are not validated. Dynamic objects are useful for ad-hoc tasks, tasks that do not justify the effort of writing and maintaining a schema, and for representing data whose structure is unknown. ==== Note that every <> is a typed object. Its properties implicitly define a class, and new properties cannot be added when amending the module. A typed object is backed by a _class_. Let's look at an example: [source%tested,{pkl}] ---- class Bird { // <1> name: String lifespan: Int migratory: Boolean } pigeon = new Bird { // <2> name = "Pigeon" lifespan = 8 migratory = false } ---- <1> Defines a class named `Bird` with properties `name`, `lifespan` and `migratory`. <2> Defines a module property named `pigeon`. Its value is a typed object constructed by instantiating class `Bird`. A type only needs to be stated when the property does not have or inherit a <>. Otherwise, amend syntax (`pigeon { ... }`) or shorthand instantiation syntax (`pigeon = new { ... }`) should be used. Congratulations, you have constructed your first typed objectfootnote:[Not counting that every module is a typed object.]! How does it differ from a dynamic object? The answer is that a typed object has a fixed structure prescribed by its class, which cannot be changed when amending the object: [source%tested%error,{pkl}] ---- class Bird { // <1> name: String lifespan: Int } faultyPigeon = new Bird { name = "Pigeon" lifespan = 8 hobby = "singing" } ---- Evaluating this gives: [source,shell,subs="quotes"] ---- Cannot find property *hobby* in object of type *repl#Bird*. Available properties: lifespan name ---- Class structure is also enforced when instantiating a class. Let's try to override property `name` with a value of the wrong type: [source%tested%error,{pkl}] ---- faultyPigeon2 = new Bird { name = 3.min lifespan = 8 } ---- Evaluating this, also fails: [source,shell,subs="quotes"] ---- Expected value of type *String*, but got type *Duration*. Value: 3.min ---- Typed objects are the fundamental building block for constructing validated data models in Pkl. To dive deeper into this topic, continue with <>. [NOTE] .Converting untyped objects to typed objects ==== When you have a `Dynamic` that has all the properties (with the right types and meeting all constraints), you can convert it to a `Typed` by using link:{uri-stdlib-Dynamic-toTyped}[`toTyped()`]: [source,{pkl}] ---- class Bird { name: String lifespan: Int } pigeon = new Dynamic { // <1> name = "Pigeon" lifespan = 8 }.toTyped(Bird) // <2> ---- <1> Instead of a `new Bird`, `pigeon` can be defined with a `new Dynamic`. <2> That `Dynamic` is then converted to a `Bird`. ==== [[properties]] === Property Modifiers ==== Hidden Properties 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}] ---- class Bird { name: String lifespan: Int hidden nameAndLifespanInIndex = "\(name), \(lifespan)" // <1> nameSignWidth: UInt = nameAndLifespanInIndex.length // <2> } pigeon = new Bird { // <3> name = "Pigeon" lifespan = 8 } pigeonInIndex = pigeon.nameAndLifespanInIndex // <4> 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. <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. <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. <6> Objects that differ only in `hidden` property values are considered equal Invoking Pkl on this file produces the following result. [source,{pkl}] ---- pigeon { name = "Pigeon" lifespan = 8 nameSignWidth = 9 } pigeonInIndex = "Pigeon, 8" pigeonDynamic { name = "Pigeon" lifespan = 8 nameSignWidth = 9 } favoritePigeon { name = "Pigeon" lifespan = 8 nameSignWidth = 9 } samePigeon = true ---- ==== Local properties A property with the modifier `local` can only be referenced in the lexical scope of its definition. [source,{pkl}] ---- class Bird { name: String lifespan: Int local separator = "," // <1> hidden nameAndLifespanInIndex = "\(name)\(separator) \(lifespan)" // <2> } pigeon = new Bird { name = "Pigeon" lifespan = 8 } pigeonInIndex = pigeon.nameAndLifespanInIndex // <3> pigeonSeparator = pigeon.separator // Error <4> ---- <1> This property can only be accessed from inside this _class definition_. <2> Non-local properties can refer to the local property as usual. <3> The _value_ of `separator` occurs in `nameAndLifespanInIndex`. <4> Pkl does not accept this, as there is no property `separator` on a `Bird` instance. Because a `local` property is added to the lexical scope, but not (observably) to the object, you can add `local` properties to ``Listing``s and ``Mapping``s. [NOTE] .Import clauses define local properties ==== An _import clause_ defines a local property in the containing module. This means `import "someModule.pkl"` is effectively `const local someModule = import("someModule.pkl")`. Also, `import "someModule.pkl" as otherName` is effectively `const local otherName = import("someModule.pkl")`. ==== [[fixed-properties]] ==== Fixed properties A property with the `fixed` modifier cannot be assigned to or amended when defining an object of its class. .Bird.pkl [source%tested,{pkl}] ---- fixed laysEggs: Boolean = true fixed birds: Listing = new { "Pigeon" "Hawk" "Penguin" } ---- When amending, assigning to a `fixed` property is an error. Similarly, it is an error to use an <> on a fixed property: .invalid.pkl [source%tested,{pkl}] ---- amends "Bird.pkl" laysEggs = false // <1> birds { // <2> "Giraffe" } ---- <1> Error: cannot assign to fixed property `laysEggs` <2> Error: cannot amend fixed property `birds` When extending a class and overriding an existing property definition, the fixedness of the overridden property must be preserved. If the property in the parent class is declared `fixed`, the child property must also be declared `fixed`. If the property in the parent class is not declared `fixed`, the child property may not add the `fixed` modifier. [source%parsed,{pkl}] ---- abstract class Bird { fixed canFly: Boolean name: String } class Penguin extends Bird { canFly = false // <1> fixed name = "Penguin" // <2> } ---- <1> Error: missing modifier `fixed`. <2> Error: modifier `fixed` cannot be applied to property `name`. The `fixed` modifier is useful for defining properties that are meant to be derived from other properties. In the following snippet, the property `wingspanWeightRatio` is not meant to be assigned to, because it is derived from other properties. [source%parsed,{pkl}] ---- class Bird { wingspan: Int weight: Int fixed wingspanWeightRatio: Int = wingspan / weight } ---- Another use-case for `fixed` is to define properties that are meant to be fixed to a class definition. In the example below, the `species` of a bird is tied to the class, and therefore is declared `fixed`. Note that it is possible to define a `fixed` property without a value, for one of two reasons: 1. The type has a default value that makes an explicit default redundant. 2. The property is meant to be overridden by a child class. [source%tested,{pkl}] ---- abstract class Bird { fixed species: String // <1> } class Osprey extends Bird { fixed species: "Pandion haliaetus" // <2> } ---- <1> No explicit default because the property is overridden by a child class. <2> Overrides the type from `String` to the <> `"Pandion haliaetus"`. + Assigning an explicit default would be redundant, therefore it is omitted. ==== Const properties A property with the `const` modifier behaves like the <> modifier, with the additional rule that it cannot reference non-const properties or methods. .Bird.pkl [source%tested,{pkl}] ---- const laysEggs: Boolean = true const examples: Listing = new { "Pigeon" "Hawk" "Penguin" } ---- Referencing any non-const property or method is an error. .invalid.pkl [source%parsed,{pkl}] ---- pigeonName: String = "Pigeon" const function birdLifespan(i: Int): Int = (i / 4).toInt() class Bird { name: String lifespan: Int } const bird: Bird = new { name = pigeonName // <1> lifespan = birdLifespan(24) // <2> } ---- <1> Error: cannot reference non-const property `pigeonName` from a const property. <2> Allowed: `birdLifespan` is const. It is okay to reference another value _within_ the same const property. .valid.pkl [source%tested,{pkl}] ---- class Bird { lifespan: Int description: String speciesName: "Bird" } const bird: Bird = new { lifespan = 8 description = "species: \(speciesName), lifespan: \(lifespan)" // <1> } ---- <1> `lifespan` is declared within property `bird`. `speciesName` resolves to `this.speciesName`, where `this` is a value within property `bird`. NOTE: Because `const` members can only reference themselves and other `const` members, they are not <>. The `const` modifier implies that it is also <>. Therefore, the same rules that apply to `fixed` also apply to `const`: * A `const` property cannot be assigned to or amended when defining an object of its class. * The const-ness of a property or method must be preserved when it is overridden by a child class. [[class-and-annotation-const]] *Class, Annotation, and Typealias Scoping* In these following scenarios, any reference to a property or method of its enclosing module requires that the referenced member is `const`: * Class body * Annotation body * Typealiased constrained type .invalid2.pkl [source%parsed,{pkl}] ---- pigeonName: String = "Pigeon" class Bird { name: String = pigeonName // <1> } @Deprecated { message = "Replace with \(pigeonName)" } // <2> oldPigeonName: String typealias IsPigeonName = String(pigeonName) // <3> ---- <1> Error: cannot reference non-const property `pigeonName` from a class. <2> Error: cannot reference non-const property `pigeonName` from an annotation. <3> Error: cannot reference non-const property `pigeonname` from a typealias. This rule exists because classes, annotations, and typealiases are not <>; it is not possible to change the definition of these members by amending the module where it is defined. Generally, there are two strategies for referencing such properties: *Add the `const` modifier to the referenced property* One solution is to add the `const` modifier to the property being referenced. .Birds.pkl [source,diff] ---- -pigeonName: String = "Pigeon" +const pigeonName: String = "Pigeon" class Bird { name: String = pigeonName } ---- This solution makes sense if `pigeonName` does not get assigned/amended when amending module `Birds.pkl` (modules are regular objects that can be amended). *Self-import the module* .Birds.pkl [source,diff] ---- +import "Birds.pkl" // <1> + pigeonName: String = "Pigeon" class Bird { - name: String = pigeonName + name: String = Birds.pigeonName } ---- <1> module `Birds` imports itself This solution works because an import clause implicitly defines a `const local` property and amending this module does not affect a self-import. This makes sense if property `pigeonName` *does* get assigned/amended when amending module `Birds.pkl`. [[listings]] == Listings A value of type link:{uri-stdlib-Listing}[Listing] is an ordered, indexed collection of _elements_. A listing's elements have zero-based indexes and are lazily evaluated on the first read. Listings combine qualities of lists and objects: * Like lists, listings can contain arbitrary elements. * Like objects, listings excel at defining and amending nested literal data structures. * Like objects, listings can only be directly manipulated through amendment, but converting them to a list (and, if necessary, back to a listing) opens the door to arbitrary transformations. * Like object properties, listing elements are evaluated lazily, can be defined in terms of each other, and are late-bound. [TIP] .When to use Listing vs. <> ==== * When a collection of elements needs to be specified literally, use a listing. * When a collection of elements needs to be transformed in a way that cannot be achieved by <> a listing, use a list. * If in doubt, use a listing. Templates and schemas should almost always use listings instead of lists. Note that listings can be converted to lists when the need arises. ==== === Defining Listings Listings have a literal syntax that is similar to that of objects. Here is a listing with two elements: [source%tested,{pkl}] ---- birds = new Listing { // <1> new { // <2> name = "Pigeon" diet = "Seed" } new { // <3> name = "Parrot" diet = "Berries" } } ---- <1> Defines a module property named `birds` with a value of type `Listing`. A type only needs to be stated when the property does not have or inherit a <>. Otherwise, amend syntax (`birds { ... }`) or shorthand instantiation syntax (`birds = new { ... }`) should be used. <2> Defines a listing element of type `Dynamic`. <3> Defines another listing element of type `Dynamic`. The order of definitions is relevant. To access an element by index, use the `[]` (subscript) operator: [source%tested,{pkl}] ---- firstBirdName = birds[0].name // <1> secondBirdDiet = birds[1].diet // <2> ---- <1> result: `"Pigeon"` <2> result: `"Berries"` Listings can contain arbitrary types of elements: [source%tested,{pkl}] ---- listing = new Listing { "Pigeon" // <1> 3.min // <2> new Listing { // <3> "Barn owl" } } ---- <1> Defines a listing element of type `String`. <2> Defines a listing element of type `Duration`. <3> Defines a listing element of type `Listing`. Listings can have `local` properties: [source%tested,{pkl}] ---- listing = new Listing { local pigeon = "Pigeon" // <1> pigeon // <2> "A " + pigeon + " is a bird" // <3> } ---- <1> Defines a local property with the value `"Pigeon"`. Local properties can have a type annotation, as in `pigeon: String = "Pigeon"`. <2> Defines a listing element that references the local property. <3> Defines another listing element that references the local property. [[amending-listings]] === Amending Listings Let's say we have the following listing: [source%tested,{pkl}] ---- birds = new Listing { new { name = "Pigeon" diet = "Seeds" } new { name = "Parrot" diet = "Berries" } } ---- To add, override, or amend elements of this listing, amend the listing itself: [source%tested,{pkl}] ---- birds2 = (birds) { // <1> new { // <2> name = "Barn owl" diet = "Mice" } [0] { // <3> diet = "Worms" } [1] = new { // <4> name = "Albatross" diet = "Fish" } } ---- <1> Defines a module property named `birds2`. Its value is a listing that amends `birds`. <2> Defines a listing element of type `Dynamic`. <3> Amends the listing element at index 0 (whose name is `"Pigeon"`) and overrides property `diet`. <4> Overrides the listing element at index 1 (whose name is `"Parrot"`) with an entirely new dynamic object. === Late Binding A listing element can be defined in terms of another element. To reference the element at index ``, use `this[]`: [source%tested,{pkl}] ---- birds = new Listing { new { // <1> name = "Pigeon" diet = "Seeds" } (this[0]) { // <2> name = "Parrot" } } ---- <1> Defines a listing element of type `Dynamic`. <2> Defines a listing element that amends the element at index 0 and overrides `name`. Listing elements are late-bound: [source%tested,{pkl}] ---- newBirds = (birds) { // <1> [0] { diet = "Worms" } } secondBirdDiet = newBirds[1].diet // <2> ---- <1> Amends listing `birds` and overrides property `diet` of element 0 (whose name is "Pigeon"`) to have the value `"Worms"`. <2> Because element 1 is defined in terms of element 0, its `diet` property also changes to `"Worms"`. === Transforming Listings Say we have the following listing: [source%tested,{pkl}] ---- birds = new Listing { new { name = "Pigeon" diet = "Seeds" } new { name = "Parrot" diet = "Berries" } } ---- How can the order of elements be reversed programmatically? The recipe for transforming a listing is: . Convert the listing to a list. . Transform the list using ``List``'s link:{uri-stdlib-List}[rich API]. . If necessary, convert the list back to a listing. TIP: Often, transformations happen in a link:{uri-stdlib-PcfRenderer-converters}[converter] of a link:{uri-stdlib-BaseValueRenderer}[value renderer]. Because most value renderers treat lists the same as listings, it is often not necessary to convert back to a listing. Equipped with this knowledge, let's try to accomplish our objective: [source%tested,{pkl}] ---- reversedbirds = birds .toList() .reverse() .toListing() ---- `result` now contains the same elements as `birds`, but in reverse order. [IMPORTANT] .Lazy vs. Eager Data Types ==== Converting a listing to a list is a transition from a _lazy_ to an _eager_ data type. All of the listing's elements are evaluated and all references between them are resolved. If the list is later converted back to a listing, subsequent changes to the listing's elements no longer propagate to (previously) dependent elements. To make these boundaries clear, transitioning between _lazy_ and _eager_ data types always requires an explicit method call, such as `toList()` or `toListing()`. ==== === Default Element Listings can have a _default element_: [source%tested,{pkl}] ---- birds = new Listing { default { // <1> lifespan = 8 } new { // <2> name = "Pigeon" // <3> } new { // <4> name = "Parrot" lifespan = 20 // <5> } } ---- <1> Amends the `default` element and sets property `lifespan`. <2> Defines a new listing element that implicitly amends the default element. <3> Defines a new property called `name`. Property `lifespan` is inherited from the default element. <4> Defines a new listing element that implicitly amends the default element. <5> Overrides the default for property `lifespan`. `default` is a hidden (that is, not rendered) link:{uri-stdlib-Listing-default}[property] defined in class `Listing`. If `birds` had a <>, a suitable default element would be inferred from its type parameter. If, as in our example, no type annotation is provided or inherited, the default element is the empty `Dynamic` object. Like regular listing elements, the default element is late-bound. As a result, defaults can be changed retroactively: [source%tested,{pkl}] ---- birds2 = (birds) { default { lifespan = 8 diet = "Seeds" } } ---- Because both of ``birds``'s elements amend the default element, changing the default element also changes them. An equivalent literal definition of `birds2` would look as follows: [source%tested,{pkl}] ---- birds2 = new Listing { new { name = "Pigeon" lifespan = 8 diet = "Seeds" } new { name = "Parrot" lifespan = 20 diet = "Berries" } } ---- Note that Parrot kept its diet because its prior self defined it explicitly, overriding any default. If you are interested in the technical underpinnings of default elements (and not afraid of dragons!), continue with <>. [[listing-type-annotations]] === Type Annotations To declare the type of a property that is intended to hold a listing, use: [source,{pkl}] ---- x: Listing ---- This declaration has the following effects: * `x` is initialized with an empty listing. * If `ElementType` has a <>, that value becomes the listing's default element. * The first time `x` is read, ** its value is checked to have type `Listing`. ** the listing's elements are checked to have the type `ElementType`. Here is an example: [source%tested,{pkl}] ---- class Bird { name: String lifespan: Int } birds: Listing ---- Because the default value for type `Bird` is `new Bird {}`, that value becomes the listing's default element. Let's go ahead and populate `birds`: [source%tested,{pkl}] ---- birds { new { name = "Pigeon" lifespan = 8 } new { name = "Parrot" lifespan = 20 } } ---- Thanks to ``birds``'s default element, which was inferred from its type, it is not necessary to state the type of each list element (`new Bird { ... }`, `new Bird { ... }`, etc.). ==== Distinct Elements To constrain a listing to distinct elements, use ``Listing``'s link:{uri-stdlib-Listing-isDistinct}[isDistinct] property: [source%tested,{pkl}] ---- class Bird { name: String lifespan: Int } birds: Listing(isDistinct) ---- This is as close as Pkl's late-bound data types (objects, listings, and mappings) get to a <>. To demand distinct names instead of distinct `Bird` objects, use link:{uri-stdlib-Listing-isDistinctBy}[isDistinctBy()]: [source%tested,{pkl}] ---- birds: Listing(isDistinctBy((it) -> it.name)) ---- [[mappings]] == Mappings A value of type link:{uri-stdlib-Mapping}[Mapping] is an ordered collection of _values_ indexed by _key_. NOTE: Most of what has been said about <> also applies to mappings. Nevertheless, this section is written to stand on its own. A mapping's key-value pairs are called its _entries_. Keys are eagerly evaluated; values are lazily evaluated on the first read. Mappings combine qualities of maps and objects: * Like maps, mappings can contain arbitrary key-value pairs. * Like objects, mappings excel at defining and amending nested literal data structures. * Like objects, mappings can only be directly manipulated through amendment, but converting them to a map (and, if necessary, back to a mapping) opens the door to arbitrary transformations. * Like object properties, a mapping's values (but not its keys) are evaluated lazily, can be defined in terms of each other, and are late-bound. [TIP] .When to use Mapping vs. <> ==== * When key-value style data needs to be specified literally, use a mapping. * When key-value style data needs to be transformed in a way that cannot be achieved by <> a mapping, use a map. * If in doubt, use a mapping. Templates and schemas should almost always use mappings instead of maps. Note that mappings can be converted to maps when the need arises. ==== === Defining Mappings Mappings have the same literal syntax as objects, except that keys enclosed in `[]` take the place of property names. Here is a mapping with two entries: [source%tested,{pkl}] ---- birds = new Mapping { // <1> ["Pigeon"] { // <2> lifespan = 8 diet = "Seeds" } ["Parrot"] { // <3> lifespan = 20 diet = "Berries" } } ---- <1> Defines a module property named `birds` with a value of type `Mapping`. A type only needs to be stated when the property does not have or inherit a <>. Otherwise, amend syntax (`birds { ... }`) or shorthand instantiation syntax (`birds = new { ... }`) should be used. <2> Defines a mapping entry with the key `"Pigeon"` and a value of type `Dynamic`. <3> Defines a mapping entry with the key `"Parrot"` and a value of type `Dynamic`. To access a value by key, use the `[]` (subscript) operator: [source%tested,{pkl}] ---- pigeon = birds["Pigeon"] parrot = birds["Parrot"] ---- Mappings can contain arbitrary types of values: [source%tested,{pkl}] ---- mapping = new Mapping { ["number"] = 42 ["list"] = List("Pigeon", "Parrot") ["nested mapping"] { ["Pigeon"] { lifespan = 20 diet = "Seeds" } } } ---- Although string keys are most common, mappings can contain arbitrary types of keys: [source%tested,{pkl}] ---- mapping = new Mapping { [3.min] = 42 [new Dynamic { name = "Pigeon" }] = "abc" } ---- Keys can be computed: [source%tested,{pkl}] ---- mapping = new Mapping { ["Pigeon".reverse()] = 42 } ---- Mappings can have `local` properties: [source%tested,{pkl}] ---- mapping = new Mapping { local parrot = "Parrot" // <1> ["Pigeon"] { // <2> friend = parrot } } ---- <1> Defines a local property name `parrot` with the value `"Parrot"`. Local properties can have a type annotation, as in `parrot: String = "Parrot"`. <2> Defines a mapping entry whose value references `parrot`. The local property is visible to values but not keys. [[amending-mappings]] === Amending Mappings Let's say we have the following mapping: [source%tested,{pkl}] ---- birds = new Mapping { ["Pigeon"] { lifespan = 8 diet = "Seeds" } ["Parrot"] { lifespan = 20 diet = "Berries" } } ---- To add, override, or amend entries of this mapping, amend the mapping: [source%tested,{pkl}] ---- birds2 = (birds) { // <1> ["Barn owl"] { // <2> lifespan = 15 diet = "Mice" } ["Pigeon"] { // <3> diet = "Seeds" } ["Parrot"] = new { // <4> lifespan = 20 diet = "Berries" } } ---- <1> Defines a module property named `birds2`. Its value is a mapping that amends `birds`. <2> Defines a mapping entry with the key `"Barn owl"` and a value of type `Dynamic`. <3> Amends mapping entry `"Pigeon"` and overrides property `diet`. <4> Overrides mapping entry `"Parrot"` with an entirely new value of type `Dynamic`. === Late Binding A mapping entry's value can be defined in terms of another entry's value. To reference the value with key ``, use `this[]`: [source%tested,{pkl}] ---- birds = new Mapping { ["Pigeon"] { // <1> lifespan = 8 diet = "Seeds" } ["Parrot"] = (this["Pigeon"]) { // <2> lifespan = 20 } } ---- <1> Defines a mapping entry with the key `"Pigeon"` and a value of type `Dynamic`. <2> Defines a mapping entry with the key `"Parrot"` and a value that amends `"Pigeon"`. Mapping values are late-bound: [source%tested,{pkl}] ---- birds2 = (birds) { // <1> ["Pigeon"] { diet = "Seeds" } } parrotDiet = birds2["Parrot"].diet // <2> ---- <1> Amends mapping `birds` and overrides ``"Pigeon"``'s `diet` property to have value `"Seeds"`. <2> Because `"Parrot"` is defined in terms of `"Pigeon"`, its `diet` property also changes to `"Seeds"`. === Transforming Mappings Say we have the following mapping: [source%tested,{pkl}] ---- birds = new Mapping { ["Pigeon"] { lifespan = 8 diet = "Seeds" } ["Parrot"] = (this["Pigeon"]) { lifespan = 20 } } ---- How can ``birds``'s keys be reversed programmatically? The recipe for transforming a mapping is: . Convert the mapping to a map. . Transform the map using ``Map``'s link:{uri-stdlib-Map}[rich API]. . If necessary, convert the map back to a mapping. TIP: Often, transformations happen in a link:{uri-stdlib-PcfRenderer-converters}[converter] of a link:{uri-stdlib-BaseValueRenderer}[value renderer]. As most value renderers treat maps the same as mappings, it is often not necessary to convert back to a mapping. Equipped with this knowledge, let's try to accomplish our objective: [source%tested,{pkl}] ---- result = birds .toMap() .mapKeys((key, value) -> key.reverse()) .toMapping() ---- `result` contains the same values as `birds`, but its keys have changed to `"noegiP"` and `"torraP"`. [IMPORTANT] .Lazy vs. Eager Data Types ==== Converting a mapping to a map is a transition from a _lazy_ to an _eager_ data type. All of the mapping's values are evaluated and all references between them are resolved. (Mapping keys are eagerly evaluated.) If the map is later converted back to a mapping, changes to the mapping's values no longer propagate to (previously) dependent values. To make these boundaries clear, transitioning between _lazy_ and _eager_ data types always requires an explicit method call, such as `toMap()` or `toMapping()`. ==== === Default Value Mappings can have a _default value_: [source%tested,{pkl}] ---- birds = new Mapping { default { // <1> lifespan = 8 } ["Pigeon"] { // <2> diet = "Seeds" // <3> } ["Parrot"] { // <4> lifespan = 20 // <5> } } ---- <1> Amends the `default` value and sets property `lifespan`. <2> Defines a mapping entry with the key `"Pigeon"` that implicitly amends the default value. <3> Defines a new property called `diet`. Property `lifespan` is inherited from the default value. <4> Defines a mapping entry with the key `"Parrot"` that implicitly amends the default value. <5> Overrides the default for property `lifespan`. `default` is a hidden (that is, not rendered) link:{uri-stdlib-Mapping-default}[property] defined in class `Mapping`. If `birds` had a <>, a suitable default value would be inferred from its second type parameter. If, as in our example, no type annotation is provided or inherited, the default value is the empty `Dynamic` object. Like regular mapping values, the default value is late-bound. As a result, defaults can be changed retroactively: [source%tested,{pkl}] ---- birds2 = (birds) { default { lifespan = 8 diet = "Seeds" } } ---- Because both of ``birds``'s mapping values amend the default value, changing the default value also changes them. An equivalent literal definition of `birds2` would look as follows: [source%tested,{pkl}] ---- birds2 = new Mapping { ["Pigeon"] { lifespan = 8 diet = "Seeds" } ["Parrot"] { lifespan = 20 diet = "Berries" } } ---- Note that Parrot kept its lifespan because its prior self defined it explicitly, overriding any default. If you are interested in the technical underpinnings of default values, continue with <>. [[mapping-type-annotations]] === Type Annotations To declare the type of a property that is intended to hold a mapping, use: [source,{pkl}] ---- x: Mapping ---- This declaration has the following effects: * `x` is initialized with an empty mapping. * If `ValueType` has a <>, that value becomes the mapping's default value. * The first time `x` is read, ** its value is checked to have type `Mapping`. ** the mapping's keys are checked to have type `KeyType`. ** the mapping's values are checked to have type `ValueType`. Here is an example: [source%tested,{pkl}] ---- class Bird { lifespan: Int } birds: Mapping ---- Because the default value for type `Bird` is `new Bird {}`, that value becomes the mapping's default value. Let's go ahead and populate `birds`: [source%tested,{pkl}] ---- birds { ["Pigeon"] { lifespan = 8 } ["Parrot"] { lifespan = 20 } } ---- Thanks to ``birds``'s default value, which was inferred from its type, it is not necessary to state the type of each mapping value (`["Pigeon"] = new Bird { ... }`, `["Parrot"] = new Bird { ... }`, etc.). [[classes]] == Classes Classes are arranged in a single inheritance hierarchy. At the top of the hierarchy sits class link:{uri-stdlib-Any}[Any]; at the bottom, type <>. Classes contain properties and methods, which can be `local` to their declaring scope. Properties can also be `hidden` from rendering. [source%tested,{pkl}] ---- class Bird { name: String hidden taxonomy: Taxonomy } class Taxonomy { `species`: String } pigeon: Bird = new { name = "Common wood pigeon" taxonomy { species = "Columba palumbus" } } pigeonClass = pigeon.getClass() ---- Declaration of new class instances will fail when property names are misspelled: [source%tested%error,{pkl}] ---- // Detects the spelling mistake parrot = new Bird { namw = "Parrot" } ---- === Class Inheritance Pkl supports single inheritance with a Java(Script)-like syntax. [source%tested,{pkl}] ---- abstract class Bird { name: String } class ParentBird extends Bird { kids: List } pigeon: ParentBird = new { name = "Old Pigeon" kids = List("Pigeon Jr.", "Teen Pigeon") } ---- [[methods]] == 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. Like Java and most other object-oriented languages, Pkl uses _single dispatch_ -- methods are dynamically dispatched based on the receiver's runtime type. [source%tested,{pkl}] ---- class Bird { name: String function greet(bird: Bird): String = "Hello, \(bird.name)!" // <1> } function greetPigeon(bird: Bird): String = bird.greet(pigeon) // <2> pigeon: Bird = new { name = "Pigeon" } parrot: Bird = new { name = "Parrot" } greeting1 = pigeon.greet(parrot) // <3> greeting2 = greetPigeon(parrot) // <4> ---- <1> Instance method of class `Bird`. <2> Module method. <3> Call instance method on `pigeon`. <4> Call module method (on `this`). Like other object-oriented languages, methods defined on extended classes and modules may be overridden. The parent type's method may be called via the <>. 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 <> on the module or class. [[modules]] == Modules === Introduction Modules are the unit of loading, executing, and sharing Pkl code. Every file containing Pkl code is a module. By convention, module files have a `.pkl` extension. Modules have a <> and are loaded from a <>. At runtime, modules are represented as objects of type link:{uri-stdlib-baseModule}/Module[Module]. The precise runtime type of a module is a subclass of `Module` containing the module's property and method definitions. Like class members, module members may have type annotations, which are validated at runtime: [source%tested,{pkl}] ---- timeout: Duration(isPositive) = 5.ms function greet(name: String): String = "Hello, \(name)!" ---- Because modules are regular objects, they can be assigned to properties and passed to and returned from methods. Modules can be <> by other modules. In analogy to objects, modules can serve as templates for other modules through <>. In analogy to classes, modules can be <> to add additional module members. === Module Names Modules may declare their name by way of a _module clause_, which consists of the keyword `module` followed by a qualified name: [source%tested,{pkl}] ---- /// My bird module. module com.animals.Birds ---- A module clause must come first in a module. Its doc comment, if present, holds the module's overall documentation. In the absence of a module clause, a module's name is inferred from the module URI from which the module was first loaded. For example, the inferred name for a module first loaded from `+https://example.com/pkl/bird.pkl+` is `bird`. Module names do not affect evaluation but are used in diagnostic messages and Pkldoc. In particular, they are the first component (everything before the hash sign) of fully qualified member names such as `pkl.base#Int`. NOTE: Modules shared with other parties should declare a qualified module name, which is more unique and stable than an inferred name. === Module URIs Modules are loaded from _module URIs_. By default, the following URI types are available for import: ==== File URI: Example: `+file:///path/to/my_module.pkl+` Represents a module located on a file system. ==== HTTP(S) URI: Example: `+https://example.com/my_module.pkl+` Represents a module imported via an HTTP(S) GET request. NOTE: Modules loaded from HTTP(S) URIs are only cached until the `pkl` command exits or the `Evaluator` object is closed. [[module-path-uri]] ==== Module path URI: Example: `+modulepath:/path/to/my_module.pkl+` Module path URIs are resolved relative to the _module path_, a search path for modules similar to Java's class path (see the `--module-path` CLI option). For example, given the module path `/dir1:/zip1.zip:/jar1.jar`, module `+modulepath:/path/to/my_module.pkl+` will be searched for in the following locations: . `/dir1/path/to/my_module.pkl` . `/path/to/my_module.pkl` within `/zip1.zip` . `/path/to/my_module.pkl` within `/jar1.jar` When evaluating Pkl code from Java, `+modulepath:/path/to/my_module.pkl+` corresponds to class path location `path/to/my_module.pkl`. In a typical Java project, this corresponds to file path `src/main/resources/path/to/my_module.pkl` or `src/test/resources/path/to/my_module.pkl`. [[package-asset-uri]] ==== Package asset URI: Example: `+package://example.com/mypackage@1.0.0#/my_module.pkl+` Represent a module within a _package_. A package is a shareable archive of modules and resources that are published to the internet. To import `package://example.com/mypackage@1.0.0#/my_module.pkl`, Pkl follows these steps: 1. Make an HTTPS GET request to `\https://example.com/mypackage@1.0.0` to retrieve the package's metadata. 2. From the package metadata, download the referenced zip archive, and validate its checksum. 3. Resolve path `/my_module.pkl` within the package's zip archive. A package asset URI has the following form: ---- 'package://' '@' '#' ---- Optionally, the SHA-256 checksum of the package can also be specified: [source] ---- 'package://' '@' '::sha256:' '#' ---- Packages can be managed as dependencies within a _project_. For more details, consult the <> section of the language reference. Packages can also be downloaded from a mirror. For more details, consult the <> section of the language reference. ==== Standard Library URI Example: `+pkl:math+` Standard library modules are named `pkl.` and have module URIs of the form `pkl:`. For example, module `pkl.math` has module URI `pkl:math`. See the link:{uri-pkl-stdlib-docs-index}[API Docs] for the complete list of standard library modules. ==== Relative URIs Relative module URIs are interpreted relative to the URI of the enclosing module. For example, a module with URI `modulepath:/animals/birds/pigeon.pkl` can import `modulepath:/animals/birds/parrot.pkl` with `import "parrot.pkl"` or `import "/animals/birds/parrot.pkl"`. [NOTE] .Paths on Windows ==== Relative paths use the `/` character as the directory separator on all platforms, including Windows. Paths that contain drive letters (e.g. `C:`) must be declared as an absolute file URI, for example: `import "file:///C:/path/to/my/module.pkl"`. Otherwise, they are interpreted as a URI scheme. ==== NOTE: When importing a relative directory or file that starts with `@`, the import string must be prefixed with `./`. Otherwise, this syntax will be interpreted as xref:dependency-notation[dependency notation]. [#dependency-notation] ==== Dependency notation URIs Example: `+@birds/bird.pkl+` Dependency notation URIs represent a path within a <>. For example, import `@birds/bird.pkl` represents path `/bird.pkl` in a dependency named "birds". A dependency is either a remote package or a local project dependency. ==== Extension points Pkl embedders can register additional module loaders that recognize other types of module URIs. ==== Evaluation Module URIs can be evaluated directly: [source,shell] ---- $ pkl eval path/to/mymodule.pkl $ pkl eval file:///path/to/my_module.pkl $ pkl eval https://apple.com/path/to/mymodule.pkl $ pkl eval --module-path=/pkl-modules modulepath:/path/to/my_module.pkl $ pkl eval pkl:math ---- [[triple-dot-module-uris]] ==== Triple-dot Module URIs To simplify referencing ancestor modules in a hierarchical module structure, relative file and module path URIs may start with `++...++/`, a generalization of `../`. Module URI `++...++/foo/bar/baz.pkl` resolves to the first existing module among `../foo/bar/baz.pkl`, `../../foo/bar/baz.pkl`, `../../../foo/bar/baz.pkl`, and so on. Furthermore, module URI `++...++` is equivalent to `++...++/`. Using triple-dot module URIs never resolve to the current module. For example, a module at path `foo/bar.pkl` that references module URI `++...++/foo/bar.pkl` does not resolve to itself. [[module-amend]] === Amending a Module Recall how an object is amended: [source%tested,{pkl}] ---- pigeon { name = "Pigeon" diet = "Seeds" } parrot = (pigeon) { // <1> name = "Parrot" // <2> } ---- <1> Object `parrot` amends object `pigeon`, inheriting all of its members. <2> `parrot` overrides `name`. Amending a module works in the same way, except that the syntax differs slightly: .pigeon.pkl [source%tested,{pkl}] ---- name = "Pigeon" diet = "Seeds" ---- .parrot.pkl [source%parsed,{pkl}] ---- amends "pigeon.pkl" // <1> name = "Parrot" // <2> ---- <1> Module `parrot` amends module `pigeon`, inheriting all of its members. <2> `parrot` overrides `name`. A module is amended by way of an _amends clause_, which consists of the keyword `amends` followed by the <> of the module to amend. An amends clause comes after the module clause (if present) and before any import clauses: .parrot.pkl [source%parsed,{pkl}] ---- module parrot amends "pigeon.pkl" import "other.pkl" name = "Parrot" ---- At most one amends clause is permitted. A module cannot have both an amends clause and an extends clause. An amending module has the same type (that is, module class) as the module it amends. As a consequence, it cannot define new properties, methods, or classes, unless they are declared as `local`. In our example, this means that module `parrot` can only define (and thus override) the property `name`. Spelling mistakes such as `namw` are caught immediately, rather than accidentally defining a new property. Amending is used to fill in _template modules_: . The template module defines which properties exist, their types, and what module output is desired (for example JSON indented with two spaces). . The amending module fills in property values as required, relying on the structure, defaults and validation provided by the template module. . The amending module is evaluated to produce the final result. Template modules are often provided by third parties and served over HTTPS. [[module-extend]] === Extending a Module Recall how a class is extended: .PigeonAndParrot.pkl [source%tested,{pkl}] ---- open class Pigeon { // <1> name = "Pigeon" diet = "Seeds" } class Parrot extends Pigeon { // <2> name = "Parrot" // <3> diet = "Berries" // <4> extinct = false // <5> function say() = "Pkl is great!" // <6> } ---- <1> Class `Pigeon` is declared as `open` for extension. <2> Class `Parrot` extends `Pigeon`, inheriting all of its members. <3> `Parrot` overrides `name`. <4> `Parrot` overrides `diet`. <5> `Parrot` defines a new property named `extinct`. <6> `Parrot` defines a new function named `say`. Extending a module works in the same way, except that the syntax differs slightly: .pigeon.pkl [source%tested,{pkl}] ---- open module pigeon // <1> name = "Pigeon" diet = "Seeds" ---- <1> Module `pigeon` is declared as `open` for extension. .parrot.pkl [source%parsed,{pkl}] ---- extends "pigeon.pkl" // <1> name = "Parrot" // <2> diet = "Berries" // <3> extinct = false // <4> function say() = "Pkl is great!" // <5> ---- <1> Module `parrot` extends module `pigeon`, inheriting all of its members. <2> `parrot` overrides `name`. <3> `parrot` overrides `diet`. <4> `parrot` defines a new property named `extinct`. <5> `parrot` defines a new function named `say`. A module is extended by way of an _extends clause_, which consists of the keyword `extends` followed by the <> of the module to extend. The extends clause comes after the module clause (if present) and before any import clauses. Only modules declared as `open` can be extended. .parrot.pkl [source%parsed,{pkl}] ---- module parrot extends "pigeon.pkl" import "other.pkl" name = "Parrot" diet = "Berries" extinct = false function say() = "Pkl is great!" ---- At most one extends clause is permitted. A module cannot have both an amends clause and an extends clause. Extending a module implicitly defines a new module class that extends the original module's class. [[import-module]] === Importing a Module A module import makes the imported module accessible to the importing module. A module is imported by way of either an <>, or an <>. [[import-clause]] ==== Import Clauses An import clause consists of the keyword `import` followed by the <> of the module to import. An import clause comes after module, amends and extends clauses (if present), and before the module body: .parrot.pkl [source%parsed,{pkl}] ---- module parrot amends "pigeon.pkl" import "module1.pkl" import "module2.pkl" name = "Parrot" ---- Multiple import clauses are permitted. A module import implicitly defines a new `const local` property through which the imported module can be accessed. (Remember that modules are regular objects.) The name of this property, called _import name_, is constructed from the module URI as follows: . Strip the URI scheme, including the colon (`:`). . Strip everything up to and including the last forward slash (`/`). . Strip any trailing `.pkl` file extension. Here are some examples: .Local file import [source%parsed,{pkl}] ---- import "modules/pigeon.pkl" // relative to current module name = pigeon.name ---- .HTTPS import [source%parsed,{pkl}] ---- import "https://mycompany.com/modules/pigeon.pkl" name = pigeon.name ---- .Standard library import [source%parsed,{pkl}] ---- import "pkl:math" pi = math.Pi ---- .Package import [source%parsed,{pkl}] ---- import "package://example.com/birds@1.0.0#/sparrow.pkl" name = sparrow.name ---- Because its members are automatically visible in every module, the `pkl:base` module is typically not imported. Occasionally, the default import name for a module may not be convenient or appropriate: * If not a valid identifier, the import name needs to be enclosed in backticks on each use, for example, ``my-module`.someMember`. * The import name may clash with other names in the importing module. In such a case, a different import name can be chosen: .parrot.pkl [source%parsed,{pkl}] ---- import "pigeon.pkl" as piggy name = "Parrot" diet = piggy.diet ---- [TIP] .What makes a good module file name? ==== When creating a new module, especially one intended for import into other modules, try to choose a module file name that makes a good import name: * short + Less than six characters, not counting the `.pkl` file extension, is a good rule of thumb. * valid identifier + Stick to alphanumeric characters. Use an underscore (`_`) instead of a hyphen (`-`) as a name separator. * descriptive + An import name should make sense on its own and when used in qualified member names. ==== [[import-expression]] ==== Import Expressions (`import()`) An import expression consists of the keyword `import`, followed by a <> wrapped in parentheses: [source,{pkl}] ---- module birds pigeon = import("pigeon.pkl") parrot = import("parrot.pkl") ---- Unlike import clauses, import expressions only import a value, and do not import a type. A type is a name that can be used in type positions, for example, as a type annotation. [[globbed-imports]] === Globbed Imports Multiple modules may be imported at once with `import*`. When importing multiple modules, a glob pattern is used to match against existing resources. A globbed import evaluates to a `Mapping`, where keys are the expanded form of the glob and values are import expressions on each individual module. Globbed imports can be expressed as either a clause or as an expression. When expressed as a clause, they follow the same naming rules as a normal <>: they introduce a local property equal to the last path segment without the `.pkl` extension. A globbed import clause cannot be used as a type. [source,{pkl}] ---- import* "birds/*.pkl" as allBirds // <1> import* "reptiles/*.pkl" <2> birds = import*("birds/*.pkl") // <3> ---- <1> Globbed import clause <2> Globbed import clause without an explicit name (will import the name `*`) <3> Globbed import expression Assuming that a file system contains these files: [source,txt] ---- . ├── birds/ │ ├── pigeon.pkl │ ├── parrot.pkl │ └── falcon.pkl └── index.pkl ---- The following two snippets are logically identical: .index.pkl [source,{pkl}] ---- birds = import*("birds/*.pkl") ---- .index.pkl [source,{pkl}] ---- birds = new Mapping { ["birds/pigeon.pkl"] = import("birds/pigeon.pkl") ["birds/parrot.pkl"] = import("birds/parrot.pkl") ["birds/falcon.pkl"] = import("birds/falcon.pkl") } ---- By default, only the `file` and `package` schemes are globbable. Globbing another scheme will cause Pkl to throw. Pkl can be extended to provide custom globbable schemes through the link:{uri-pkl-core-ModuleKey}[ModuleKey] SPI. When globbing within <>, only the asset path (the fragment section) is globbable. Otherwise, characters are interpreted verbatim, and not treated as glob wildcards. For details on how glob patterns work, refer to <> in the Advanced Topics section. NOTE: When globbing files, symbolic links are not followed. Additionally, the `.` and `..` entries are skipped. + This behavior is similar to the behavior of Bash with `shopt -s dotglob` enabled. === Security Checks When attempting to directly evaluate a module, as in `pkl eval myModule.pkl`, the following security checks are performed: * The module URI is checked against the module allowlist (`--allowed-modules`). The module allowlist is a comma-separated list of regular expressions. For access to be granted, at least one regular expression must match a prefix of the module URI. For example, the allowlist `file:,https:` grants access to any module whose URI starts with `file:` or `https:`. When a module attempts to load another module (via `amends`, `extends`, or `imports`), the following security checks are performed: * The target module URI is checked against the module allowlist (`--allowed-modules`). * The source and target modules' _trust levels_ are determined and compared. For access to be granted, the source module's trust level must be greater than or equal to the target module's trust level. By default, there are five trust levels, listed from highest to lowest: . `repl:` modules (code evaluated in the REPL) . `file:` modules . `modulepath:` modules . All other modules (for example `https:`) . `pkl:` modules (standard library) For example, this means that `file:` modules can import `https:` modules, but not the other way around. If a module URI is resolved in multiple steps, all URIs are subject to the above security checks. An example of this is an HTTPS URL that results in a redirect. Pkl embedders can further customize security checks. [[module-output]] === Module Output By default, the output of evaluating a module is the entire module rendered as Pcf. There are two ways to change this behavior: 1. _Outside_ the language, by using the `--format` CLI option or the `outputFormat` Gradle task property. 2. _Inside_ the language, by configuring a module's `output` property. ==== CLI Given the following module: [source%tested,{pkl}] .config.pkl ---- a = 10 b { c = 20 } ---- `pkl eval config.pkl`, which is shorthand for `pkl eval --format pcf config.pkl`, renders the module as Pcf: [source,{pkl}] ---- a = 10 b { c = 20 } ---- `pkl eval --format yaml config.pkl` renders the module as YAML: [source,yaml] ---- a: 10 b: c: 20 ---- Likewise, `pkl eval --format json config.pkl` renders the module as JSON. [[in-language]] ==== In-language Now let's do the same -- and more -- inside the language. Modules have an link:{uri-stdlib-baseModule}/Module#output[output] property that controls what the module's output is and how that output is rendered. To control *what* the output is, set the link:{uri-stdlib-baseModule}/ModuleOutput#value[output.value] property: [source%parsed,{pkl}] ---- a = 10 b { c = 20 } output { value = b // defaults to `outer`, which is the entire module } ---- This produces: [source,{pkl}] ---- c = 20 ---- To control _how_ the output is rendered, set the link:{uri-stdlib-baseModule}/ModuleOutput#renderer[output.renderer] property: [source%parsed,{pkl}] ---- a = 10 b { c = 20 } output { renderer = new YamlRenderer {} } ---- The standard library provides these renderers: * link:{uri-stdlib-baseModule}/JsonRenderer[JsonRenderer] * link:{uri-stdlib-jsonnetModule}/Renderer[jsonnet.Renderer] * link:{uri-stdlib-baseModule}/PcfRenderer[PcfRenderer] * link:{uri-stdlib-baseModule}/PListRenderer[PListRenderer] * link:{uri-stdlib-baseModule}/PropertiesRenderer[PropertiesRenderer] * link:{uri-stdlib-protobufModule}/Renderer[protobuf.Renderer] * link:{uri-stdlib-xmlModule}/Renderer[xml.Renderer] * link:{uri-stdlib-baseModule}/YamlRenderer[YamlRenderer] To render a format that is not yet supported, you can implement your own renderer by extending the class link:{uri-stdlib-baseModule}/ValueRenderer[ValueRenderer]. The standard library renderers can be configured with _value converters_, which influence how particular values are rendered. For example, since YAML does not have a standard way to represent data sizes, a plain `YamlRenderer` cannot render `DataSize` values. However, we can teach it to: [source%parsed,{pkl}] ---- quota { memory = 100.mb disk = 20.gb } output { renderer = new YamlRenderer { converters { [DataSize] = (size) -> "\(size.value) \(size.unit)" } } } ---- This produces: [source,yaml] ---- quota: memory: 100 MB disk: 20 GB ---- In addition to _class_-based converters, renderers also support _path_-based converters: [source%parsed,{pkl}] ---- output { renderer = new YamlRenderer { converters { ["quota.memory"] = (size) -> "\(size.value) \(size.unit)" ["quota.disk"] = (size) -> "\(size.value) \(size.unit)" } } } ---- For more on path-based converters, see {uri-stdlib-PcfRenderer-converters}[PcfRenderer.converters]. Special <> may also be used to influence how class properties are rendered. The `ConvertProperty` annotation (and subclasses of it) are automatically applied during. For more on annotation-based converters, see link:{uri-stdlib-ConvertProperty}[ConvertProperty]. Sometimes it is useful to directly compute the final module output, bypassing `output.value` and `output.converters`. To do so, set the link:{uri-stdlib-baseModule}/ModuleOutput#text[output.text] property to a String value: [source%parsed,{pkl}] ---- output { // defaults to `renderer.render(value)` text = "this is the final output".toUpperCase() } ---- This produces: [source] ---- THIS IS THE FINAL OUTPUT ---- [[multiple-file-output]] ==== Multiple File Output It is sometimes desirable for a single module to produce multiple output files. This is possible by configuring a module's link:{uri-stdlib-outputFiles}[`output.files`] property // suppress inspection "AsciiDocLinkResolve" and specifying the xref:pkl-cli:index.adoc#multiple-file-output-path[`--multiple-file-output-path`] (or `-m` for short) CLI option. Here is an example that produces a JSON and a YAML file: .birds.pkl [source%parsed,{pkl}] ---- pigeon { name = "Pigeon" diet = "Seeds" } parrot { name = "Parrot" diet = "Seeds" } output { files { ["birds/pigeon.json"] { value = pigeon renderer = new JsonRenderer {} } ["birds/parrot.yaml"] { value = parrot renderer = new YamlRenderer {} } } } ---- Running `pkl eval -m output/ birds.pkl` produces the following output files: .output/birds/pigeon.json [source,json] ---- { "name": "Pigeon", "diet": "Seeds" } ---- .output/birds/parrot.yaml [source,yaml] ---- name: Parrot diet: Berries ---- Within `output.files`, a key determines a file's path relative to `--multiple-file-output-path`, and a value determines the file's contents. If a file's path resolves to a location outside `--multiple-file-output-path`, evaluation fails with an error. Non-existing parent directories are created. [[aggregating-module-outputs]] ===== Aggregating Module Outputs A value within `output.files` can be another module's `output`. With this, a module can aggregate the outputs of multiple other modules. Here is an example: .pigeon.pkl [source%parsed,{pkl}] ---- name = "Pigeon" diet = "Seeds" output { renderer = new JsonRenderer {} } ---- .parrot.pkl [source%parsed,{pkl}] ---- name = "Parrot" diet = "Seeds" output { renderer = new YamlRenderer {} } ---- .birds.pkl [source%parsed,{pkl}] ---- import "pigeon.pkl" import "parrot.pkl" output { files { ["birds/pigeon.json"] = pigeon.output ["birds/parrot.yaml"] = parrot.output } } ---- [TIP] ==== When aggregating module outputs, the appropriate file extensions can be obtained programmatically: .birds.pkl [source%parsed,{pkl}] ---- import "pigeon.pkl" import "parrot.pkl" output { files { ["birds/pigeon.\(pigeon.output.renderer.extension)"] = pigeon.output ["birds/parrot.\(parrot.output.renderer.extension)"] = parrot.output } } ---- ==== [[null-values]] == Null Values The keyword `null` indicates the absence of a value. `null` is an instance of link:{uri-stdlib-Null}[Null], a direct subclass of `Any`. === Non-Null Operator The `!!` (non-null) operator asserts that its operand is non-null. Here are some examples: [source%tested%error,{pkl}] ---- name = "Pigeon" nameNonNull = name!! // <1> name2 = null name2NonNull = name2!! // <2> ---- <1> result: `"Pigeon"` <2> result: _Error: Expected a non-null value, but got `null`._ === Null Coalescing The `??` (null coalescing) operator fills in a default for a `null` value. [source,{pkl-expr}] ---- value ?? default ---- The above expression evaluates to `value` if `value` is non-null, and to `default` otherwise. Here are some examples: [source%tested,{pkl}] ---- name = "Pigeon" nameOrParrot = name ?? "Parrot" // <1> name2 = null name2OrParrot = name2 ?? "Parrot" // <2> ---- <1> result: `"Pigeon"` <2> result: `"Parrot"` [NOTE] .Default non-null behavior ==== Many languages allow `null` for (almost) every type, but Pkl does not. Any type can be extended to include `null` by appending `?` to the type. For example, `parrot: Bird` will always be non-null, but `pigeon: Bird?` could be `null` - and _is_ by default, if `pigeon` is never amended. This means if you try to coalesce a (non-nullable) typed variable, the result is always that variable’s value. As per our example `parrot ?? pigeon == parrot` always holds, but `pigeon ?? parrot` could either be `pigeon` or `parrot`, depending on whether `pigeon` was ever amended with a non-null value. ==== [[null-propagation]] === Null Propagation The `?.` (null propagation) operator provides null-safe access to a member whose receiver may be `null`. [source,{pkl-expr}] ---- value?.member ---- The above expression evaluates to `value.member` if `value` is non-null, and to `null` otherwise. Here are some examples: [source%tested,{pkl}] ---- name = "Pigeon" nameLength = name?.length // <1> nameUpper = name?.toUpperCase() // <2> name2 = null name2Length = name2?.length // <3> name2Upper = name2?.toUpperCase() // <4> ---- <1> result: `6` <2> result: `"PIGEON"` <3> result: `null` <4> result: `null` The `?.` operator is often combined with `??`: [source%tested,{pkl}] ---- name = null nameLength = name?.length ?? 0 // <1> ---- <1> result: `0` === ifNonNull Method The link:{uri-stdlib-ifNonNull}[ifNonNull()] method is a generalization of the <> operator. [source%parsed,{pkl-expr}] ---- name.ifNonNull((it) -> doSomethingWith(it)) ---- The above expression evaluates to `doSomethingWith(name)` if `name` is non-null, and to `null` otherwise. Here are some examples: [source%tested,{pkl}] ---- name = "Pigeon" nameWithTitle = name.ifNonNull((it) -> "Dr." + it) // <1> name2 = null name2WithTitle = name2.ifNonNull((it) -> "Dr." + it) // <2> ---- <1> result: `"Dr. Pigeon"` <2> result: `null` === NonNull Type Alias To express that a property can have any type except `Null`, use the `NonNull` <>: [source,{pkl}] ---- x: NonNull ---- [[if-expressions]] == If Expressions An `if` expression serves the same role as the ternary operator (`? :`) in other languages. Every `if` expression must have an `else` branch. [source%tested,{pkl}] ---- num = if (2 + 2 == 5) 1984 else 42 // <1> ---- <1> result: `42` [[resources]] == Resources Pkl programs can read external resources, such as environment variables or text files. To read a resource, use a `read` expression: [source%parsed,{pkl}] ---- path = read("env:PATH") ---- By default, the following resource URI schemes are supported: env: :: Reads an environment variable. Result type is `String`. prop: :: Reads an external property set via the `-p name=value` CLI option. Result type is `String`. file: :: Reads a file from the file system. Result type is link:{uri-stdlib-Resource}[Resource]. http(s): :: Reads an HTTP(S) resource. Result type is link:{uri-stdlib-Resource}[Resource]. modulepath: :: Reads a resource from the module path (`--module-path`) or JVM class path. Result type is link:{uri-stdlib-Resource}[Resource]. See <> for further information. package: :: Reads a resource from a _package_. Result type is link:{uri-stdlib-Resource}[Resource]. See <> for further information. Relative resource URIs are resolved against the enclosing module's URI. Resources are cached in memory on the first read. Therefore, subsequent reads are guaranteed to return the same result. [[nullable-reads]] === Nullable Reads If a resource does not exist or cannot be read, `read()` fails with an error. To recover from the absence of a resource, use `read?()` instead, which returns `null` for absent resources: [source%parsed,{pkl}] ---- port = read?("env:PORT")?.toInt() ?? 1234 ---- [[globbed-reads]] === Globbed Reads Multiple resources may be read at the same time with `read*()`. When reading multiple resources, a glob pattern is used to match against existing resources. A globbed read returns a `Mapping`, where the keys are the expanded form of the glob, and values are `read` expressions on each individual resource. Assuming that a file system contains these files: [source,txt] ---- . ├── birds/ │ ├── pigeon.pkl │ ├── parrot.pkl │ └── falcon.pkl └── index.pkl ---- The following two snippets are logically identical: .index.pkl [source%parsed,{pkl}] ---- birdFiles = read*("birds/*.pkl") ---- .index.pkl [source%parsed,{pkl}] ---- birdFiles = new Mapping { ["birds/pigeon.pkl"] = read("birds/pigeon.pkl") ["birds/parrot.pkl"] = read("birds/parrot.pkl") ["birds/falcon.pkl"] = read("birds/falcon.pkl") } ---- By default, the following schemes support globbing: * `modulepath` * `file` * `env` * `prop` Globbing other resources results in an error. For details on how glob patterns work, reference <> in the Advanced Topics section. NOTE: When globbing files, symbolic links are not followed. Additionally, the `.` and `..` entries are skipped. + This behavior is similar to the behavior of Bash with `shopt -s dotglob` enabled. [NOTE] ==== The `env` and `prop` schemes are considered opaque, as they do not have traditional hierarchical elements like a host, path, or query string. While globbing is traditionally viewed as a way to match elements in a file system, a glob pattern is simply a way to match strings. Thus, environment variables and external properties can be globbed, where their names get matched according to the rules described by the glob pattern. To match all values within these schemes, use the `+++**+++` wildcard. This has the effect of matching names that contain a forward slash too (`/`). For example, the expression `read*("+++env:**+++")` will evaluate to a Mapping of all environment variables. ==== === Extending resource readers When Pkl is embedded within another runtime, it can be extended to read other kinds of resources. When embedded into a JVM application, new resources may be read by implementing the link:{uri-pkl-core-ResourceReader}[ResourceReader] SPI. When Pkl is embedded within Swift, new resources may be read by implementing the link:{uri-pkl-swift-resource-reader-docs}[ResourceReader] interface. When Pkl is embedded within Go, new resources may be read by implementing the link:{uri-pkl-go-resource-reader-docs}[ResourceReader] interface. === Resource Allowlist When attempting to read a resource, the resource URI is checked against the resource allowlist (`--allowed-resources`). In embedded mode, the allowlist is configured via an evaluator's link:{uri-pkl-core-SecurityManager}[SecurityManager]. The resource allowlist is a comma-separated list of regular expressions. For access to be granted, at least one regular expression must match a prefix of the resource URI. For example, the allowlist `file:,https:` grants access to any resource whose URI starts with `file:` or `https:`. [[errors]] == Errors By design, errors are fatal in Pkl -- there is no way to recover from them. To raise an error, use a `throw` expression: [source%tested%error,{pkl}] ---- myValue = throw("You won't be able to recover from this one!") // <1> ---- <1> `myValue` never receives a value because the program exits. The error message is printed to the console and the program exits. In embedded mode, a link:{uri-pkl-core-PklException}[PklException] is thrown. [[debugging]] == Debugging When debugging Pkl code, it can be useful to print the value of an expression. To do so, use a `trace` expression: [source%tested,{pkl}] ---- num1 = 42 num2 = 16 res = trace(num1 * num2) ---- Tracing an expression does not affect its result, but prints both its source code and result on standard error: [source,shell] ---- pkl: TRACE: num1 * num2 = 672 (at file:///some/module.pkl, line 42) ---- [[advanced-topics]] == Advanced Topics This section discusses language features that are generally more relevant to template and library authors than template consumers. <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> + <> [[meaning-of-new]] === Meaning of `new` Objects in Pkl always <> _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 `` but its value is undefined". [source,{pkl}] ---- class Bird { name: String = "polly" } bird: Bird // <1> birdListing: Listing // <2> birdMapping: Mapping // <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 { default = (_) -> new Bird { name = "polly" } }` <3> With an explicit value type argument, this property's default value is equivalent to `new Mapping { default = (_) -> new Bird { name = "polly" } }` ==== Explicitly Typed `new` Instantiating an object with `new ` 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 { default { isPredatory = true } new Bird { // <2> name = "Sand Piper" } } someMapping = new Mapping { 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 (<>). 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 <> 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): Listing = new { for (item in items) { "\(name):\(item)" } } } typedProperty: Bird = new { // <1> name = "Swift" } untypedProperty = new { // <2> hello = "world" } typedListing: Listing = new { new { // <3> name = "Kite" } } untypedListing: Listing = new { new { // <4> hello = "there" } } typedMapping: Mapping = new { default { entryKey -> name = entryKey } ["Saltmarsh Sparrow"] = new { // <5> name = "Sharp-tailed Sparrow" } } amendedMapping = (typedMapping) { ["Saltmarsh Sparrow"] = new {} // <6> } class Aviary { birds: Listing = 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, amending 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> Amending 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 A `let` expression is Pkl's version of an (immutable) local variable. Its syntax is: [source] ---- let ( = ) ---- A `let` expression is evaluated as follows: . `` is bound to ``, itself an expression. . `` is evaluated, which can refer to `` by its `` (this is the point). . The result of becomes the result of the overall expression. Here is an example: [source%tested,{pkl}] ---- birdDiets = let (diets = List("Seeds", "Berries", "Mice")) List(diets[2], diets[0]) // <1> ---- <1> result: `birdDiets = List("Mice", "Seeds")` `let` expressions serve two purposes: - They introduce a human-friendly name for a potentially complex expression. - They evaluate a potentially expensive expression that is used in multiple places only once. `let` expressions can have type annotations: [source%tested,{pkl}] ---- birdDiets = let (diets: List = List("Seeds", "Berries", "Mice")) diets[2] + diets[0] // <1> ---- <1> result: `birdDiets = List("Mice", "Seeds")` `let` expressions can be stacked: [source%tested,{pkl}] ---- birdDiets = let (birds = List("Pigeon", "Barn owl", "Parrot")) let (diet = List("Seeds", "Mice", "Berries")) birds.zip(diet) // <1> ---- <1> result: `birdDiets = List(Pair("Pigeon", "Seeds"), Pair("Barn owl", "Mice"), Pair("Parrot", "Berries"))` [[type-tests]] === Type Tests To test if a value conforms to a type, use the _is_ operator. All the following tests hold: [source%tested,{pkl}] ---- test1 = 42 is Int test2 = 42 is Number test3 = 42 is Any test4 = !(42 is String) open class Base class Derived extends Base base = new Base {} test5 = base is Base test6 = base is Any test7 = !(base is Derived) derived = new Derived {} test8 = derived is Derived test9 = derived is Base test10 = derived is Any ---- A value can be tested against any type, not just a class: [source,{pkl}] ---- test1 = email is String(contains("@")) // <1> test2 = map is Map // <2> test3 = name is "Pigeon"|"Barn owl"|"Parrot" // <3> ---- <1> `email` is tested for being a string that contains a `@` sign <2> `map` is tested for being a map from `Int` to `Base` values <3> `name` is tested for being one of `"Pigeon"`, `"Barn owl"`, or `"Parrot"` [[type-casts]] === Type Casts The _as_ (type cast) operator performs a runtime type check on its operand. If the type check succeeds, the operand is returned as-is; otherwise, an error is thrown. [source%tested,{pkl}] ---- birds { new { name = "Pigeon" } new { name = "Barn owl" } } names = birds.toList().map((it) -> it.name) as List ---- Although type casts are never mandatory in Pkl, they occasionally help humans and tools better understand an expression's type. [[lists]] === Lists A value of type link:{uri-stdlib-List}[List] is an ordered, indexed collection of _elements_. A list's elements have zero-based indexes and are eagerly evaluated. [TIP] .When to use List vs. <> ==== * When a collection of elements needs to be specified literally, use a listing. * When a collection of elements needs to be transformed in a way that cannot be achieved by <> a listing, use a list. * If in doubt, use a listing. Templates and schemas should almost always use listings instead of lists. Note that listings can be converted to lists when the need arises. ==== Lists are constructed with the `List()` methodfootnote:soft-keyword[Strictly speaking, `List`, `Set`, and `Map` are currently soft keywords. The goal is to eventually turn them into regular standard library methods.]: [source%tested,{pkl}] ---- list1 = List() // <1> list2 = List(1, 2, 3) // <2> list3 = List(1, "x", 5.min, List(1, 2, 3)) // <3> ---- <1> result: empty list <2> result: list of length 3 <3> result: heterogeneous list whose last element is another list To concatenate lists, use the `+` operator: [source%tested,{pkl-expr}] ---- List(1, 2) + List(3, 4) + List(5) ---- To access a list element by index, use the `[]` (subscript) operator: [source%tested,{pkl}] ---- list = List(1, 2, 3, 4) listElement = list[2] // <1> ---- <1> result: `3` Class `List` offers a link:{uri-stdlib-List}[rich API]. Here are just a few examples: [source%tested,{pkl}] ---- list = List(1, 2, 3, 4) res1 = list.contains(3) // <1> res2 = list.first // <2> res3 = list.rest // <3> res4 = list.reverse() // <4> res5 = list.drop(1).take(2) // <5> res6 = list.map((n) -> n * 3) // <6> ---- <1> result: `true` <2> result: `1` <3> result: `List(2, 3, 4)` <4> result: `List(4, 3, 2, 1)` <5> result: `List(2, 3)` <6> result: `List(3, 6, 9, 12)` [[sets]] === Sets A value of type link:{uri-stdlib-Set}[Set] is a collection of unique _elements_. Sets are constructed with the `Set()` methodfootnote:soft-keyword[]: [source%tested,{pkl}] ---- res1 = Set() // <1> res2 = Set(1, 2, 3) // <2> res3 = Set(1, 2, 3, 1) // <3> res4 = Set(1, "x", 5.min, List(1, 2, 3)) // <4> ---- <1> result: empty set <2> result: set of length 3 <3> result: same set of length 3 <4> result: heterogeneous set that contains a list as its last element Sets retain the order of elements when constructed, which impacts how they are iterated over. However, this order is not considered when determining equality of two sets. [source%tested,{pkl}] ---- res1 = Set(4, 3, 2) res2 = res1.first // <1> res3 = res1.toListing() // <2> res4 = Set(2, 3, 4) == res1 // <3> ---- <1> result: `4` <2> result: `new Listing { 4; 3; 2 }` <3> result: `true` To compute the union of sets, use the `+` operator: [source%tested,{pkl-expr}] ---- Set(1, 2) + Set(2, 3) + Set(5, 3) // <1> ---- <1> result: `Set(1, 2, 3, 5)` Class `Set` offers a link:{uri-stdlib-Set}[rich API]. Here are just a few examples: [source%tested,{pkl}] ---- set = Set(1, 2, 3, 4) res1 = set.contains(3) // <1> res2 = set.drop(1).take(2) // <2> res3 = set.map((n) -> n * 3) // <3> res4 = set.intersect(Set(3, 9, 2)) // <4> ---- <1> result: `true` <2> result: `Set(2, 3)` <3> result: `Set(3, 6, 9, 12)` <4> result: `Set(3, 2)` [[maps]] === Maps A value of type link:{uri-stdlib-Map}[Map] is a collection of _values_ indexed by _key_. A map's key-value pairs are called its _entries_. Keys and values are eagerly evaluated. [TIP] .When to use Map vs. <> ==== * When key-value style data needs to be specified literally, use a mapping. * When key-value style data needs to be transformed in ways that cannot be achieved by <> a mapping, use a map. * If in doubt, use a mapping. Templates and schemas should almost always use mappings instead of maps. (Note that mappings can be converted to maps when the need arises.) ==== Maps are constructed by passing alternating keys and values to the `Map()` methodfootnote:soft-keyword[]: [source%tested,{pkl}] ---- map1 = Map() // <1> map2 = Map(1, "one", 2, "two", 3, "three") // <2> map3 = Map(1, "x", 2, 5.min, 3, Map(1, 2)) // <3> ---- <1> result: empty map <2> result: set of length 3 <3> result: heterogeneous map whose last value is another map Any Pkl value can be used as a map key: [source%tested,{pkl-expr}] ---- Map(new Dynamic { name = "Pigeon" }, 10.gb) ---- Maps retain the order of entries when constructed, which impacts how they are iterated over. However, this order is not considered when determining equality of two maps. [source%tested,{pkl}] ---- res1 = Map(2, "hello", 1, "world") res2 = res1.entries.first // <1> res3 = res1.toMapping() // <2> res4 = res1 == Map(1, "world", 2, "hello") // <3> ---- <1> result: `Pair(2, "hello")` <2> result: `new Mapping { [2] = "hello"; [1] = "world" }` <3> result: `true` To merge maps, use the `+` operator: [source%tested,{pkl}] ---- combinedMaps = Map(1, "one") + Map(2, "two", 1, "three") + Map(4, "four") // <1> ---- <1> result: `Map(1, "three", 2, "two", 4, "four")` To access a value by key, use the `[]` (subscript) operator: [source%tested,{pkl}] ---- map = Map("Pigeon", 5.gb, "Parrot", 10.gb) parrotValue = map["Parrot"] // <1> ---- <1> result: `10.gb` Class `Map` offers a link:{uri-stdlib-Map}[rich API]. Here are just a few examples: [source%tested,{pkl}] ---- map = Map("Pigeon", 5.gb, "Parrot", 10.gb) res1 = map.containsKey("Parrot") // <1> res2 = map.containsValue(8.gb) // <2> res3 = map.isEmpty // <3> res4 = map.length // <4> res5 = map.getOrNull("Falcon") // <5> ---- <1> result: `true` <2> result: `false` <3> result: `false` <4> result: `2` <5> result: `null` [[bytes]] === Bytes A value of type `Bytes` is a sequence of `UInt8` elements. `Bytes` can be constructed by passing byte values into the constructor. [source,pkl] ---- bytes1 = Bytes(0xff, 0x00, 0x3f) // <1> bytes2 = Bytes() // <2> ---- <1> Result: a `Bytes` with 3 elements <2> Result: an empty `Bytes` `Bytes` can also be constructed from a base64-encoded string, via `base64DecodedBytes`: [source,pkl] ---- bytes3 = "cGFycm90".base64DecodedBytes // <1> ---- <1> Result: `Bytes(112, 97, 114, 114, 111, 116)` ==== `Bytes` vs `List` `Bytes` is similar to `List` in that they are both sequences of `UInt8` elements. However, they are semantically distinct. `Bytes` represent binary data, and is typically rendered differently. For example, they are rendered as `` tags when using `PListRenderer`. `Bytes` also have different performance characteristics; a value of type `Bytes` tends to be managed as a contiguous memory block. Thus, they are more compact and consume less memory. However, they are not optimized for transformations. For example, given two values of size `M` and `N`, concatenating two `Bytes` values allocates O(M + N) space, whereas concatenating two `List` values allocates O(1) space. [[regular-expressions]] === Regular Expressions A value of type link:{uri-stdlib-Regex}[Regex] is a regular expression with the same syntax and semantics as a link:{uri-javadoc-Pattern}[Java regular expression]. Regular expressions are constructed with the link:{uri-stdlib-Regex-method}[Regex()] method: [source%tested,{pkl}] ---- emailRegex = Regex(#"([\w\.]+)@([\w\.]+)"#) ---- // note: first \ on next line is asciidoc escape Notice the use of custom string delimiters `\#"` and `"#`, which change the string's escape character from `\` to `\#`. As a consequence, the regular expression's backslash escape character no longer requires escaping. To test if a string fully matches a regular expression, use link:{uri-stdlib-matches}[String.matches()]: [source%tested,{pkl-expr}] ---- "pigeon@example.com".matches(emailRegex) ---- Many `String` methods accept either a `String` or `Regex` argument. Here is an example: [source%tested,{pkl}] ---- res1 = "Pigeon".contains("pigeon@example.com") res2 = "Pigeon".contains(emailRegex) ---- To find all matches of a regex in a string, use link:{uri-stdlib-Regex-match}[Regex.findMatchesIn()]. The result is a list of link:{uri-stdlib-RegexMatch}[RegexMatch] objects containing details about each match: [source%tested,{pkl}] ---- matches = emailRegex.findMatchesIn("pigeon@example.com / falcon@example.com / parrot@example.com") list1 = matches.drop(1).map((it) -> it.start) // <1> list2 = matches.drop(1).map((it) -> it.value) // <2> list3 = matches.drop(1).map((it) -> it.groups[1].value) // <3> ---- <1> result: `List(0, 19, 40)` (the entire match, `matches[0]`, was dropped) <2> result: `List("pigeon@example.com", "falcon@example.com", "parrot@example.com")` <3> result: `List("pigeon, falcon, parrot")` [[type-aliases]] === Type Aliases A _type alias_ introduces a new name for a (potentially complicated) type: [source%tested,{pkl}] ---- typealias EmailAddress = String(matches(Regex(#".+@.+"#))) ---- Once a type alias has been defined, it can be used in type annotations: [source%tested,{pkl}] ---- email: EmailAddress = "pigeon@example.com" emailList: List = List("pigeon@example.com", "parrot@example.com") ---- New type aliases can be defined in terms of existing ones: [source%tested,{pkl}] ---- typealias EmailList = List emailList: EmailList = List("pigeon@example.com", "parrot@example.com") ---- Type aliases can have type parameters: [source%tested,{pkl}] ---- typealias StringMap = Map map: StringMap = Map("Pigeon", 42, "Falcon", 21) ---- Code generators have different strategies for dealing with type aliases: * the xref:java-binding:codegen.adoc[Java] code generator inlines them * the xref:kotlin-binding:codegen.adoc[Kotlin] code generator turns them into Kotlin type aliases. Type aliases for unions of <> are turned into enum classes by both code generators. [[predefined-type-aliases]] ==== Predefined Type Aliases The _pkl.base_ module defines the following type aliases: * link:{uri-stdlib-Int8}[Int8] (-128 to 127) * link:{uri-stdlib-Int16}[Int16] (-32,768 to 32,767) * link:{uri-stdlib-Int32}[Int32] (-2,147,483,648 to 2,147,483,647) //- * link:{uri-stdlib-UInt8}[UInt8] (0 to 255) * link:{uri-stdlib-UInt16}[UInt16] (0 to 65,535) * link:{uri-stdlib-UInt32}[UInt32] (0 to 4,294,967,295) * link:{uri-stdlib-UInt}[UInt] (0 to 9,223,372,036,854,775,807) //- * link:{uri-stdlib-Uri}[Uri] (any String value) WARNING: Note that `UInt` has the same maximum value as `Int`, half of what would normally be expected. The main purpose of the provided integer aliases is to enforce the range of an integer: [source%tested%error,{pkl}] ---- port: UInt16 = -1 ---- This gives: [source,shell,subs="quotes"] ---- Type constraint *isBetween(0, 65535)* violated. Value: -1 ---- To restrict a number to a custom range, use the link:{uri-stdlib-isBetween}[isBetween] method: [source%tested,{pkl}] ---- port: Int(isBetween(0, 1023)) = 443 ---- NOTE: Remember that numbers are always instances of `Int` or `Float`. Type aliases such as `UInt16` only check that numbers are within a certain range. The xref:java-binding:codegen.adoc[Java] and xref:kotlin-binding:codegen.adoc[Kotlin] code generators map predefined type aliases to the most suitable Java and Kotlin types. For example, `UInt8` is mapped to `java.lang.Byte` and `kotlin.Byte`, and `Uri` is mapped to `java.net.URI`. [[type-annotations]] === Type Annotations Property and method definitions may optionally contain type annotations. Type annotations serve the following purposes: * Documentation + Type annotations help to document data models. They are included in any generated documentation. * Validation + Type annotations are validated at runtime. * Defaults + Type-annotated properties have <>. * Code Generation + Type annotations enable statically typed access to configuration data through code generation. * Tooling + Type annotations enable advanced tooling features such as code completion in editors. ==== Class Types Any class can be used as a type: [source%parsed,{pkl}] ---- class Bird { name: String // <1> } bird: Bird // <2> ---- <1> Declares an instance property of type `String`. <2> Declares a module property of type `Bird`. ==== Module Types Any module import can be used as type: [source%parsed,{pkl}] .bird.pkl ---- name: String lifespan: Int ---- [source%parsed,{pkl}] .birds.pkl ---- import "bird.pkl" pigeon: bird // <1> parrot: bird // <1> ---- <1> Guaranteed to amend _bird.pkl_. As a special case, the `module` keyword denotes the _enclosing_ module's type: [source%parsed,{pkl}] .bird.pkl ---- name: String lifespan: Int friends: Listing ---- [source%parsed,{pkl}] .pigeon.pkl ---- amends "bird.pkl" name = "Pigeon" lifespan = 8 friends { import("falcon.pkl") // <1> } ---- <1> _falcon.pkl_ (not shown here) is guaranteed to amend _bird.pkl_. ==== Type Aliases Any <> can be used as a type: [source%parsed,{pkl}] ---- typealias EmailAddress = String(contains("@")) email: EmailAddress // <1> emailList: List // <2> ---- <1> equivalent to `email: String(contains("@"))` for type checking purposes <2> equivalent to `emailList: List` for type checking purposes [[nullable-types]] ==== Nullable Types Class types such as `Bird` (see above) do not admit `null` values. To turn them into _nullable types_, append a question mark (`?`): [source%parsed,{pkl}] ---- bird: Bird = null // <1> bird2: Bird? = null // <2> ---- <1> throws `Type mismatch: Expected a value of type Bird, but got null` <2> succeeds 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.) `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 The following class types are _generic types_: * `Pair` * `Collection` * `Listing` * `List` * `Mapping` * `Set` * `Map` * `Function0` * `Function1` * `Function2` * `Function3` * `Function4` * `Function5` * `Class` A generic type has constituent types written in angle brackets (`<>`): [source%parsed,{pkl}] ---- pair: Pair // <1> coll: Collection // <2> list: List // <3> set: Set // <4> map: Map // <5> mapping: Mapping // <6> ---- <1> a pair `String` and `Bird` as types for the first and second element, respectively <2> a collection of `Bird` elements <3> a list of `Bird` elements <4> a set of `Bird` elements <5> a map with `String` keys and `Bird` values <6> a mapping of `String` keys and `Bird` values Omitting the constituent types is equivalent to declaring them as `unknown`: [source%parsed,{pkl}] ---- pair: Pair // equivalent to `Pair` coll: Collection // equivalent to `Collection` list: List // equivalent to `List` set: Set // equivalent to `Set` map: Map // equivalent to `Map` mapping: Mapping // equivalent to `Mapping` ---- The `unknown` type is both a top and a bottom type. When a static type analyzer encounters an expression of `unknown` type, it backs off and trusts the user that they know what they are doing. [[union-types]] ==== Union Types A value of type `A | B`, read "A or B", is either a value of type `A` or a value of type `B`. [source%tested,{pkl}] ---- class Bird { name: String } bird1: String|Bird = "Pigeon" bird2: String|Bird = new Bird { name = "Pigeon" } ---- More complex union types can be formed: [source%parsed,{pkl}] ---- foo: List|Bird ---- Union types have no implicit default values, but an explicit type can be chosen using a `*` marker: [source%parsed,{pkl}] ---- foo: "a"|"b" // undefined. Will throw an error if not amended bar: "a"|*"b" // default value will be taken from type "b" baz: "a"|"b" = "a" // explicit value is given qux: String|*Int // default taken from Int, but Int has no default. Will throw if not amended ---- Union types often come in handy when writing schemas for legacy JSON or YAML files. [[string-literal-types]] ==== String Literal Types A string literal type admits a single string value: [source%parsed,{pkl}] ---- diet: "Seeds" ---- While occasionally useful on their own, string literal types are often combined with <> to form enumerated types: [source%parsed,{pkl}] ---- diet: "Seeds"|"Berries"|"Insects" ---- To reuse an enumerated type, introduce a type alias: [source%parsed,{pkl}] ---- typealias Diet = "Seeds"|"Berries"|"Insects" diet: Diet ---- The Java and Kotlin code generators turn type aliases for enumerated types into enum classes. [[nothing-type]] ==== Nothing Type The `nothing` type is the bottom type of Pkl's type system, the counterpart of top type `Any`. The bottom type is assignment-compatible with every other type, and no other type is assignment-compatible with it. Being assignment-compatible with every other type may sound too good to be true, but there is a catch -- the `nothing` type has no values! Despite being a lonely type, `nothing` has practical applications. For example, it is used in the standard library's `TODO()` method: [source%tested,{pkl}] ---- function TODO(): nothing = throw("TODO") ---- A `nothing` return type indicates that a method never returns normally but always throws an error. [[unknown-type]] ==== Unknown Type The `unknown` type footnote:[Also known as _dynamic type_. We do not use that term to avoid confusion with `Dynamic`, Pkl's dynamic object type.] is ``nothing``'s even stranger cousin: it is both a top and bottom type! This makes `unknown` assignment-compatible with every other type, and every other type assignment-compatible with `unknown`. When a static type analyzer encounters a value of `unknown` type, it backs off and trusts the code's author to know what they are doing -- for example, whether a method called on the value exists. ==== Progressive Disclosure In the spirit of link:{uri-progressive-disclosure}[progressive disclosure], type annotations are optional in Pkl. Omitting a type annotation is equivalent to specifying the type `unknown`: [source%parsed,{pkl}] ---- lifespan = 42 // <1> map: Map // <2> function say(name) = name // <3> ---- <1> shorthand for `lifespan: unknown = 42` (As a dynamically typed language, Pkl does not try to statically infer types.) <2> shorthand for `map: Map = Map()` <3> shorthand for `function say(name: unknown): unknown = name` [[default-values]] ==== Default Values Type-annotated properties have implicit "empty" default values depending on their type: [source%tested,{pkl}] ---- class Bird coll: Collection // = List() <1> list: List // = List() <2> set: Set // = Set() <3> map: Map // = Map() <4> listing: Listing // = new Listing { default = (index) -> new Bird {} } <5> mapping: Mapping // = new Mapping { default = (key) -> new Bird {} } <6> obj: Bird // = new Bird {} <7> nullable: Bird? // = Null(new Bird {}) <8> union: *Bird|String // = new Bird {} <9> stringLiteral: "Pigeon" // = "Pigeon" <10> nullish: Null // = null <11> ---- <1> Properties of type `Collection` default to the empty list. <2> Properties of type `List` default to the empty list. <3> Properties of type `Set` default to the empty set. <4> Properties of type `Map` default to the empty map. <5> Properties of type `Listing` default to an empty listing whose default element is the default for `X`. <6> Properties of type `Mapping` default to an empty mapping whose default value is the default for `Y`. <7> Properties of non-external class type `X` default to `new X {}`. <8> Properties of type `X?` default to `Null(x)` where `x` is the default for `X`. <9> Properties with a union type have no default value. By prefixing one of the types in a union with a `*`, the default of that type is chosen as the default for the union. <10> Properties with a string literal type default to the type's only value. <11> Properties of type `Null` default to `null`. See <> for further information. Properties of the following types do not have implicit default values: * `abstract` classes, including `Any` and `NotNull` * Union types, unless an explicit default is given by prefixing one of the types with `*`. * `external` (built-in) classes, including: ** `String` ** `Boolean` ** `Int` ** `Float` ** `Duration` ** `DataSize` ** `Pair` ** `Regex` Accessing a property that neither has an (implicit or explicit) default value nor has been overridden throws an error: [source%tested%error,{pkl}] ---- name: String ---- [[type-constraints]] ==== Type Constraints A type may be followed by a comma-separated list of _type constraints_ enclosed in round brackets (`()`). A type constraint is a boolean expression that must hold for the annotated element. Type constraints enable advanced runtime validation that goes beyond the capabilities of static type checking. [source%tested,{pkl}] ---- class Bird { name: String(length >= 3) // <1> parent: String(this != name) // <2> } pigeon: Bird = new { name = "Pigeon" parent = "Pigeon Sr." // <3> } ---- <1> Restricts `name` to have at least three characters. <2> The name of the bird (`this`) should not be the same as the name of the `parent`. <3> Note how `parent` is different from `name`. If they were the same, we would be thrown a constraint error. In the following example, we define a `Bird` with a name of only two characters. [source%tested%error,{pkl}] ---- pigeon: Bird = new { // fails the constraint because [name] is less than 3 characters name = "Pi" } ---- Boolean expressions are convenient for ad-hoc type constraints. Alternatively, type constraints can be given as lambda expressions accepting a single argument, namely the value to be validated. This allows for the abstraction and reuse of type constraints. [source%tested,{pkl}] ---- class Project { local emailAddress = (str) -> str.matches(Regex(#".+@.+"#)) email: String(emailAddress) } project: Project = new { email = "projectPigeon@example.com" } ---- [source%tested%error,{pkl}] ---- project: Project = new { // fails the constraint because `"projectPigeon-example.com"` doesn't match the regular expression. email = "projectPigeon-example.com" } ---- ===== Composite Type Constraints A composite type can have type constraints for the overall type, its constituent types, or both. [source%tested,{pkl}] ---- class Project { local emailAddress = (str) -> str.matches(Regex(#".+@.+"#)) // constrain the nullable type's element type type: String(contains("source"))? // constrain the map type and its key/value types contacts: Map(length <= 5) } project: Project = new { type = "open-source" contacts = Map("Pigeon", "pigeon@example.com") } ---- [[anonymous-functions]] === Anonymous Functions An _anonymous function_ is a function without a name. Most modern general-purpose programming languages support anonymous functions, under names such as _lamba expressions_, _arrow functions_, _function literals_, _closures_, or _procs_. Anonymous functions have their own literal syntax: [source] ---- () -> expr // <1> (param) -> expr // <2> (param1, param2, ..., paramN) -> expr // <3> ---- <1> Zero-parameter lambda expression <2> Single-parameter lambda expression <3> Multi-parameter lambda expression Here is an example: [source%tested,{pkl-expr}] ---- (n) -> n * 3 ---- This anonymous function accepts a parameter named `n`, multiplies it by 3, and returns the result. Anonymous functions are values of type link:{uri-stdlib-Function}[Function], more specifically link:{uri-stdlib-Function0}[Function0], link:{uri-stdlib-Function1}[Function1], link:{uri-stdlib-Function2}[Function2], link:{uri-stdlib-Function3}[Function3], link:{uri-stdlib-Function4}[Function4], or link:{uri-stdlib-Function5}[Function5]. They cannot have more than five parameters. To invoke an anonymous function, call its link:{uri-stdlib-Function1-apply}[apply] method: [source%tested,{pkl-expr}] ---- ((n) -> n * 3).apply(4) // 12 ---- Many standard library methods accept anonymous functions: [source%tested,{pkl-expr}] ---- List(1, 2, 3).map((n) -> n * 3) // List(3, 6, 9) ---- Anonymous functions can be assigned to properties, thereby giving them a name: [source%tested,{pkl}] ---- add = (a, b) -> a + b added = add.apply(2, 3) ---- [TIP] ==== If an anonymous function is not intended to be passed as value, it is customary to declare a method instead: [source%tested,{pkl}] ---- function add(a, b) = a + b added = add(2, 3) ---- ==== An anonymous function's parameters can have type annotations: [source%tested,{pkl-expr}] ---- (a: Number, b: Number) -> a + b ---- Applying this function to arguments not of type `Number` results in an error. Anonymous functions are _closures_: They can access members defined in a lexically enclosing scope, even after leaving that scope: [source%tested,{pkl}] ---- a = 42 addToA = (b: Number) -> a + b list = List(1, 2, 3).map(addToA) // List(43, 44, 45) ---- Single-parameter anonymous functions can also be applied with the `|>` (pipe) operator, which expects a function argument to the left and an anonymous function to the right. The pipe operator works especially well for chaining multiple functions: [source%tested,{pkl}] ---- mul3 = (n) -> n * 3 add2 = (n) -> n + 2 num = 4 |> mul3 |> add2 |> mul3 // <1> ---- <1> result: `42` Like methods, anonymous functions can be recursive: [source%tested,{pkl}] ---- factor = (n: Number(isPositive)) -> if (n < 2) n else n * factor.apply(n - 1) num = factor.apply(5) // <1> ---- <1> result: `120` [[mixins]] ==== Mixins A mixin is an anonymous function used to apply the same modification to different objects. Even though mixins are regular functions, they are best created with object syntax: [source%tested,{pkl}] ---- withDiet = new Mixin { diet = "Seeds" } ---- Mixins can optionally specify which type of object they apply to: [source%tested,{pkl}] ---- class Bird { diet: String } withDietTyped = new Mixin { diet = "Seeds" } ---- For properties with type annotation, the shorthand `new { ... }` syntax can be used: [source%tested,{pkl}] ---- withDietTyped: Mixin = new { diet = "Seeds" } ---- To apply a mixin, use the `|>` (pipe) operator: [source%tested,{pkl}] ---- pigeon { name = "Pigeon" } pigeonWithDiet = pigeon |> withDiet barnOwl { name = "Barn owl" } barnOwlWithDiet = barnOwl |> withDiet ---- `withDiet` can be generalized by turning it into a factory method for mixins: [source%tested,{pkl}] ---- function withDiet(_diet: String) = new Mixin { diet = _diet } seedPigeon = pigeon |> withDiet("Seeds") MiceBarnOwl = barnOwl |> withDiet("Mice") ---- Mixins can themselves be modified with <>. [[function-amending]] ==== Function Amending An anonymous function that returns an object can be amended with the same syntax as that object. The result is a new function that accepts the same number of parameters as the original function, applies the original function to them, and amends the returned object. Function amending is a special form of function composition. Thanks to function amending, link:{uri-stdlib-Listing-default}[Listing.default] and link:{uri-stdlib-Mapping-default}[Mapping.default] can be treated as if they were objects, only gradually revealing their true (single-parameter function) nature: [source%tested,{pkl}] ---- birds = new Mapping { default { // <1> diet = "Seeds" } ["Pigeon"] { // <2> lifespan = 8 } } ---- <1> Amends the `default` function, which returns a default mapping value given a mapping key, and sets property `diet`. <2> Implicitly applies the amended `default` function and amends the returned object with property `lifespan`. The result is a mapping whose entry `"Pigeon"` has both `diet` and `lifespan` set. When amending an anonymous function, it is possible to access its parameters by declaring a comma-separated, arrow (`->`) terminated parameter list after the opening curly brace (`{`). Once again, this is especially useful to configure a listing's or mapping's `default` function: [source%tested,{pkl}] ---- birds = new Mapping { default { key -> // <1> name = key } ["Pigeon"] {} // <2> ["Barn owl"] {} // <3> } ---- <1> Amends the `default` function and sets the `name` property to the mapping entry's key. To access the `default` function's key parameter, it is declared with `key ->`. (Any other parameter name could be chosen, but `key` is customary for default functions.) <2> Defines a mapping entry with key `"Pigeon"` <3> Defines a mapping entry with key `"Barn owl"` The result is a mapping with two entries `"Pigeon"` and `"Barn owl"` whose `name` properties are set to their keys. Function amending can also be used to refine <>. [[amend-null]] === Amending Null Values It's time to lift a secret: The predefined `null` value is just one of the potentially many values of type `Null`. First, here are the technical facts: * Null values are constructed with `pkl.base#Null()`. * `Null(x)` constructs a null value that is equivalent to `x` when amended. In other words, `Null(x) { ... }` is equivalent to `x { ... }`. * All null values are equal according to `==`. We say that `Null(x)` is a "null value with default x". But what is it useful for? ==== Null values with default are used to define properties that are null ("switched off") by default but have a default value once amended ("switched on"). ==== Here is an example: .template.pkl [source%tested,{pkl}] ---- // we don't have a pet yet, but already know that it is going to be a bird pet = Null(new Dynamic { animal = "bird" }) ---- [source%parsed,{pkl}] ---- amends "template.pkl" // We got a pet, let's fill in its name pet { name = "Parry the Parrot" } ---- A null value can be switched on without adding or overriding a property: [source%parsed,{pkl}] ---- amends "template.pkl" // We do not need to name anything if we have no pet yet pet {} ---- The predefined `null` value is defined as `Null(new Dynamic {})`. In other words, amending `null` is equivalent to amending `Dynamic {}` (the empty dynamic object): [source%tested,{pkl}] ---- pet = null ---- [source%tested,{pkl}] ---- pet { name = "Parry the Parrot" } ---- In most cases, the `Null()` method is not used directly. Instead, it is used under the hood to create implicit defaults for properties with nullable type: .template.pkl [source%tested,{pkl}] ---- class Pet { name: String animal: String = "bird" } // defaults to `Null(Pet {})` pet: Pet? ---- [source%tested,{pkl}] ---- amends "template.pkl" pet { name = "Perry the Parrot" } ---- The general rule is: A property with nullable type `X?` defaults to `Null(x)` if type `X` has default value `x`, and to `null` if `X` has no default value. [[when-generators]] === When Generators `when` generators conditionally generate object members. They come in two variants: . `when () { }` . `when () { } else { }` The following code conditionally generates properties `hobby` and `idol`: [source%tested,{pkl}] ---- isSinger = true parrot { lifespan = 20 when (isSinger) { hobby = "singing" idol = "Frank Sinatra" } } ---- `when` generators can have an `else` part: [source%tested,{pkl}] ---- isSinger = false parrot { lifespan = 20 when (isSinger) { hobby = "singing" idol = "Aretha Franklin" } else { hobby = "whistling" idol = "Wolfgang Amadeus Mozart" } } ---- Besides properties, `when` generators can generate elements and entries: [source%tested,{pkl}] ---- abilities { "chirping" when (isSinger) { "singing" // <1> } "whistling" } abilitiesByBird { ["Barn owl"] = "hooing" when (isSinger) { ["Parrot"] = "singing" // <2> } ["Parrot"] = "whistling" } ---- <1> conditional element <2> conditional entry [[for-generators]] === For Generators `for` generators generate object members in a loop. They come in two variants: . `for ( in ) { }` . `for (, in ) { }` The following code generates a `birds` object containing three elements. Each element is an object with properties `name` and `lifespan`. [source%tested,{pkl}] ---- names = List("Pigeon", "Barn owl", "Parrot") birds { for (_name in names) { new { name = _name lifespan = 42 } } } ---- The following code generates a `birdsByName` object containing three entries. Each entry is an object with properties `name` and `lifespan` keyed by name. [source%tested,{pkl}] ---- namesAndLifespans = Map("Pigeon", 8, "Barn owl", 15, "Parrot", 20) birdsByName { for (_name, _lifespan in namesAndLifespans) { [_name] { name = _name lifespan = _lifespan } } } ---- The following types are iterable: |=== |Type |Key |Value |`IntSeq` |element index (`Int`) |element value (`Int`) |`List` |element index (`Int`) |element value (`Element`) |`Set` |element index (`Int`) |element value (`Element`) |`Map` |entry key (`Key`) |entry value (`Value`) |`Bytes` |element index (`Int`) |element value (`UInt8`) |`Listing` |element index (`Int`) |element value (`Element`) |`Mapping` |entry key (`Key`) |entry value (`Value`) |`Dynamic` |element index (`Int`) + entry key + property name (`String`) |element value + entry value + property value |=== Indices are zero-based. Note that `for` generators can generate elements and entries but not properties.footnote:[More precisely, they cannot generate properties with a non-constant name.] [[spread-syntax]] === Spread Syntax (`\...`) Spread syntax generates object members from an iterable value. There are two variants of spread syntax, a non-nullable variant and a nullable variant. 1. `\...` 2. `\...?` Spreading an xref:objects[`Object`] (one of `Dynamic`, `Listing` and `Mapping`) will unpack all of its members into the enclosing object footnote:[Values that are xref:typed-objects[`Typed`] are not iterable.]. Entries become entries, elements become elements, and properties become properties. [source%tested,{pkl}] ---- entries1 { ["Pigeon"] = "Piggy the Pigeon" ["Barn owl"] = "Barney the Barn owl" } entries2 { ...entries1 // <1> } elements1 { 1; 2 } elements2 { ...elements1 // <2> } properties1 { name = "Pigeon" diet = "Seeds" } properties2 { ...properties1 // <3> } ---- <1> Spreads entries `["Pigeon"] = "Piggy the Pigeon"` and `["Barn owl"] = "Barney the Barn owl"` <2> Spreads elements `1` and `2` <3> Spreads properties `name = "Pigeon"` and `diet = "Seeds"` Spreading all other iterable types generates members determined by the iterable. The following table describes how different iterables turn into object members: |=== |Iterable type|Member type | `Map` | Entry | `List` | Element | `Set` | Element | `IntSeq` | Element | `Bytes` | Element |=== These types can only be spread into enclosing objects that support that member type. For example, a `List` can be spread into a `Listing`, but cannot be spread into a `Mapping`. In some ways, spread syntax can be thought of as a shorthand for a xref:for-generators[for generator]. One key difference is that spread syntax can generate properties, which is not possible with a for generator. [NOTE] ==== Look out for duplicate key conflicts when using spreads. Spreading entries or properties may cause conflicts due to matched existing key definitions. In the following code snippet, `"Pigeon"` is declared twice in the `newPets` object, and thus is an error. [source%tested%error,{pkl}] ---- oldPets { ["Pigeon"] = "Piggy the Pigeon" ["Parrot"] = "Perry the Parrot" } newPets { ...oldPets ["Pigeon"] = "Toby the Pigeon" // <1> } ---- <1> Error: Duplicate definition of member `"Pigeon"`. ==== ==== Nullable spread A non-nullable spread (`\...`) will error if the value being spread is `null`. In contrast, a nullable spread (`\...?`) is syntactic sugar for wrapping a spread in a xref:when-generators[`when`]. The following two snippets are logically identical. [source%parsed,{pkl}] ---- result { ...?myValue } ---- [source%parsed,{pkl}] ---- result { when (myValue != null) { ...myValue } } ---- [[member-predicates]] === Member Predicates (`[[...]]`) Occasionally it is useful to configure all object members matching a predicate. This is especially true when configuring elements, which—unlike entries—cannot be accessed by key: [source%tested,{pkl}] ---- environmentVariables { // <1> new { name = "PIGEON"; value = "pigeon-value" } new { name = "PARROT"; value = "parrot-value" } new { name = "BARN OWL"; value = "barn-owl-value" } } updated = (environmentVariables) { [[name == "PARROT"]] { // <2> value = "new-value" // <3> } } ---- <1> a listing of environment variables <2> amend element(s) whose name equals "PARROT" + (`name` is shorthand for `this.name`) <3> update value to "new-value" The predicate, enclosed in double brackets (`\[[...]]`), is matched against each member of the enclosing object. Within the predicate, `this` refers to the member that the predicate is matched against. Matching members are amended (`{ ... }`) or overridden (`= `). [[this-keyword]] === `this` keyword Normally, the `this` keyword references the enclosing object's receiver. Example: [source,pkl] ---- bird { eatsInsects = this is InsectavorousBird } ---- When used inside a <>, `this` refers to the value being tested. Example: [source,pkl] ---- port: UInt16(this > 1000) ---- When used inside a <>, `this` refers to the value being matched against. Example: [source,pkl] ---- animals { [[this is Bird]] { canFly = true } } ---- [[receiver]] ==== Receiver The receiver is the bottom-most object in the <>. That means that, within the context of an amending object, the receiver is the amending object. Example: [source,pkl] ---- hidden lawyerBird { title = "\(this.name), Esq." } polly = (lawyerBird) { name = "Polly" // <1> } ---- <1> Polly has title `"Polly, Esq."`. [[outer-keyword]] === `outer` keyword The `outer` keyword references the <> of the immediately outer lexical object. It can be useful to disambiguate a lookup that might otherwise resolve elsewhere. Example: [source%tested,pkl] ---- foo { bar = "bar" qux { bar = outer.bar // <1> } } ---- <1> References `bar` one level higher. Note that `outer` cannot be chained. In order to reference a value more than one level higher, a typical pattern is to declare a local property at that level. For example: [source%parsed,pkl] ---- foo { local self = this bar { baz { qux = self.qux } } } ---- [[super-keyword]] === `super` keyword The `super` keyword references the parent object in the <>. When used within a class, it refers to the superclass's prototype. When used within an object, it refers to the parent object in the amends chain. Example: [source%tested,pkl] ---- bird = new { name = "Quail" } bird2 = (bird) { name = "Ms. \(super.name)" } // <1> abstract class Bird { foods: Listing function canEat(food: String): Boolean = foods.contains(food) } class InsectavorousBird extends Bird { function canEat(food: String) = super.canEat(food) || food == "insect" // <2> } ---- <1> Result: `"Ms. Quail"` <2> Calls parent class method `canEat()` The `super` keyword must be followed by property/method access, or subscript. `super` by itself a syntax error; whereas `super.foo` and `super["foo"]` are valid expressions. [[module-keyword]] === `module` keyword The `module` keyword can be used as either a value, or as a type. When used as a value, it refers to the <> of the module itself. [source%tested,pkl] ---- name = "Quail" some { deep { object { name = module.name // <1> } } } ---- <1> Resolves to `"Quail"` When used as a type, it is the module's class. [source%parsed,pkl] ---- module Bird friend: module // <1> ---- <1> Is class `Bird` The `module` type is a _self type_. If the module is extended by another module, the `module` type refers to the extending module when in the context of that module. [[glob-patterns]] === Glob Patterns Resources and modules may be imported at the same time by globbing with the <> and <> features. Pkl's glob patterns mostly follow the rules described by link:{uri-glob-7}[glob(7)], with the following differences: * `*` includes names that start with a dot (`.`). * `+++**+++` behaves like `*`, except it also matches directory boundary characters (`/`). * Named character classes are not supported. * Collating symbols are not supported. * Equivalence class expressions are not supported. * Support for <> (patterns within `{` and `}`) are added. Here is a full specification of how globs work: ==== Wildcards The following tokens denote wildcards: [cols="1,2"] |=== |Wildcard |Meaning |`*` |Match zero or more characters, until a directory boundary (`/`) is reached. |`**` |Match zero or more characters, crossing directory boundaries. |`?` |Match a single character. |`[...]` |Match a single character represented by this <>. |=== NOTE: Unlike globs within shells, the `*` wildcard includes names that start with a dot (`.`). [[character-classes]] ==== Character Classes Character classes are sequences delimited by the `[` and `]` characters, and represent a single character as described by the sequence within the enclosed brackets. For example, the pattern `[abc]` means "a single character that is a, b, or c". Character classes may be negated using `!`. For example, the pattern `[!abc]` means "a single character that is not a, b, nor c". Character classes may use the `-` character to denote a range. The pattern `[a-f]` is equivalent to `[abcdef]`. If the `-` character exists at the beginning or the end of a character class, it does not carry any special meaning. Within a character class, the characters `{`, `}`, `\`, `*`, and `?` do not have any special meaning. A character class is not allowed to be empty. Thus, if the first character within the character class is `]`, it is treated literally and not as the closing delimiter of the character class. For example, the glob pattern `[]abc]` matches a single character that is either `]`, `a`, `b`, or `c`. [[glob-sub-patterns]] ==== Sub-patterns Sub-patterns are glob patterns delimited by the `{` and `}` characters, and separated by the `,` character. For example, the pattern `{pigeon,parrot}` will match either `pigeon` or `parrot`. Sub-patterns cannot be nested. The pattern `{foo,{bar,baz}}` is not a valid glob pattern, and an error will be thrown during evaluation. ==== Escapes The escape character (`\`) can be used to remove the special meaning of a character. The following escapes are valid: * `\[` * `\*` * `\?` * `\\` * `\{` All other escapes are considered a syntax error and an error is thrown. TIP: If incorporating escape characters into a glob pattern, use <> to express the glob pattern. For example, `+++import*(#"\{foo.pkl"#)+++`. This way, the backslash is interpreted as a backslash and not a string escape. ==== Examples [cols="1,2"] |=== |Pattern |Description |`*.pk[lg]` |Anything suffixed by `.pkl`, or `.pkg`. |`**.y{a,}ml` |Anything suffixed by either `yml` or `yaml`, crossing directory boundaries. |`birds/{\*.yml,*.json}` |Anything within the `birds` subdirectory that ends in `.yml` or `.json`. This pattern is equivalent to `birds/*.{yml,json}`. |`a?*.txt` |Anything starting with `a` and at least one more letter, and suffixed with `.txt`. |`modulepath:/**.pkl` |All Pkl files in the module path. |=== [[quoted-identifiers]] === Quoted Identifiers An identifier is the name part of an entity in Pkl. Entities that are named by identifiers include classes, properties, typealiases, and modules. For example, `class Bird` has the identifier `Bird`. Normally, an identifier must conform to Unicode's {uri-unicode-identifier}[UAX31-R1-1 syntax], with the additions of `_` and `$` permitted as identifier start characters. Additionally, an identifier cannot clash with a keyword. To define an identifier that is otherwise illegal, enclose them in backticks. This is called a _quoted identifier_. [source,{pkl}] ---- `A Bird's First Flight Time` = 5.s ---- [NOTE] ==== Backticks are not part of a quoted identifier's name, and surrounding an already legal identifier with backticks is redundant. [source,{pkl}] ---- `number` = 42 // <1> res1 = `number` // <2> res2 = number // <3> ---- <1> Equivalent to `number = 42` <2> References property `{backtick}number{backtick}` <3> Also references property `{backtick}number{backtick}` ==== [[doc-comments]] === Doc Comments Doc comments are the user-facing documentation of a module and its members. They consist of one or more lines starting with a triple slash (`///`). Here is a doc comment for a module: [source%tested,{pkl}] ---- /// An aviated animal going by the name of [bird](https://en.wikipedia.org/wiki/Bird). /// /// These animals live on the planet Earth. module com.animals.Birds ---- Doc comments are written in Markdown. The following Markdown features are supported: * all link:{uri-common-mark}[CommonMark] features * https://help.github.com/articles/organizing-information-with-tables[GitHub flavored Markdown tables] [NOTE] ==== Plaintext URLs are only rendered as links when enclosed in angle brackets: [source,{pkl}] ---- /// A link is *not* generated for https://example.com. /// A link *is* generated for . ---- ==== Doc comments are consumed by humans reading source code, the _Pkldoc_ documentation generator, code generators, and editor/IDE plugins. They are programmatically accessible via the link:{uri-stdlib-reflectModule}/[pkl.reflect] Pkl API and link:{uri-pkl-core-ModuleSchema}[ModuleSchema] Java API. [TIP] .Doc Comment Style Guidelines ==== * Use proper spelling and grammar. * Start each sentence on a new line and capitalize the first letter. * End each sentence with a punctuation mark. * The first paragraph of a doc comment is its _summary_. Keep the summary short (a single sentence is common) and insert an empty line (`///`) before the next paragraph. ==== Doc comments can be attached to module, class, type alias, property, and method declarations. Here is a comprehensive example: .Birds.pkl [source%tested,{pkl}] ---- /// An aviated animal going by the name of [bird](https://en.wikipedia.org/wiki/Bird). /// /// These animals live on the planet Earth. module com.animals.Birds /// A bird living on Earth. /// /// Has [name] and [lifespan] properties and an [isOlderThan()] method. class Bird { /// The name of this bird. name: String /// The lifespan of this bird. lifespan: UInt8 /// Tells if this bird is older than [bird]. function isOlderThan(bird: Bird): Boolean = lifespan > bird.lifespan } /// An adult [Bird]. typealias Adult = Bird(lifespan >= 2) /// A common [Bird] found in large cities. pigeon: Bird = new { name = "Pigeon" lifespan = 8 } /// Creates a [Bird] with the given [_name] and lifespan `0`. function Infant(_name: String): Bird = new { name = _name; lifespan = 0 } ---- [[member-links]] ==== Member Links To link to a member declaration, write the member's name enclosed in square brackets (`[]`): [source,{pkl}] ---- /// A common [Bird] found in large cities. ---- To customize the link text, insert the desired text, enclosed in square brackets, before the member name: [source,{pkl}] ---- /// A [common Bird][Bird] found in large cities. ---- Custom link text can use markup: [source,{pkl}] ---- /// A [*common* Bird][Bird] found in large cities. ---- The short link `[Bird]` is equivalent to `[{backtick}Bird{backtick}][Bird]`. Member links are resolved according to Pkl's normal name resolution rules. The syntax for linking to the members of _Birds.pkl_ (see above) is as follows: Module:: * `[module]` (from same module) * `[Birds]` (from a module that contains `import "Birds.pkl"`) Class:: * `[Bird]` (from same module) * `[Birds.Bird]` (from a module that contains `import "Birds.pkl"`) Type Alias:: * `[Adult]` (from same module) * `[Birds.Adult]` (from a module that contains `import "Birds.pkl"`) Class Property:: * `[name]` (from same class) * `[Bird.name]` (from same module) * `[Birds.Bird.name]` (from a module that contains `import "Birds.pkl"`) Class Method:: * `[greet()]` (from same class) * `[Bird.greet()]` (from same module) * `[Birds.Bird.greet()]` (from a module that contains `import "Birds.pkl"`) Class Method Parameter:: * `[bird]` (from same method) Module Property:: * `[pigeon]` (from same module) * `[Birds.pigeon]` (from a module that contains `import "Birds.pkl"`) Module Method:: * `[isPigeon()]` (from same module) * `[Birds.isPigeon()]` (from a module that contains `import "Birds.pkl"`) Module Method Parameter:: * `[bird]` (from same method) Members of `pkl.base` can be linked to by their simple name: [source,{pkl}] ---- /// Returns a [String]. ---- Module-level members can be prefixed with `module.` to resolve name conflicts: [source,{pkl}] ---- /// See [module.pigeon]. ---- To exclude a member from documentation and code completion, <> it with `@Unlisted`: [source%parsed,{pkl}] ---- @Unlisted pigeon: Bird ---- The following member links are marked up as code but not rendered as links:footnote:[Only applies to links without custom link text.] * `[null]`, `[true]`, `[false]`, `[this]`, `[unknown]`, `[nothing]` * self-links * subsequent links to the same member from the same doc comment * links to a method's own parameters Nevertheless, it is a good practice to use member links in the above cases. [[annotations]] === Annotations Annotations are a mechanism to provides extra metadata about Pkl <>, <>, <>, and <>. They provide metadata about the type or member they annotate that can be via link:{uri-stdlib-reflectModule}[reflection] or Pkl's Java APIs. The most common use cases for annotations are to add metadata to influence behavior of xref:pkl-doc:index.adoc[Pkldoc], code generation tools, or <>. Annotations are regular Pkl objects whose class extends link:{uri-stdlib-Annotation}[`Annotation`]. Annotation instances are defined similarly to regular <>, but instead of using the `new` keyword the class name is prefixed with `@`. The object body may be omitted if an annotation's class has no properties or the declared annotation does not override any of the class's default values Multiple annotations may be defined on a member. If the annotated member has a <>, the annotation is defined between the comment and the member. [source%tested,{pkl}] ---- /// Module doc comment @SomeAnnotation // <1> module myModule class SomeAnnotation extends Annotation { // <2> description: String = "some annotation" } /// Module doc comment @SomeAnnotation // <3> class Bird { @SomeAnnotation { description = "some property" } // <4> name: String @SomeAnnotation { description = "some method" } // <5> @Unlisted // <6> function greet(greeting: String): String = "\(greeting), \(name)!" } ---- <1> An annotation applied to a module. The annotation(s) must precede the `module` clause and follow the doc comment. The object body is omitted. <2> The definition of an annotation class. <3> An annotation appied to a class. The object body is omitted. <4> An annotation applied to a property. The object body is included because the `description` property is overridden. <5> An annotation applied to a method. The object body is included because the `description` property is overridden. <6> A second annotation applied to the same method. [[name-resolution]] === Name Resolution Consider this snippet of code buried deep inside a config file: [source%parsed,{pkl}] ---- a = x + 1 ---- The call site's "variable" syntax reveals that `x` refers to a _LAMP_ (let binding, anonymous function parameter, method parameter, or property) definition. But which one? To answer this question, Pkl follows these steps: . Search the lexically enclosing scopes of `x`, starting with the scope in which `x` occurs and continuing outwards up to and including the enclosing module's top-level scope, for a LAMP definition named `x`. If a match is found, this is the answer. . Search the `pkl.base` module for a top-level definition of property `x`. If a match is found, this is the answer. . Search the <> of `this`, from bottom to top, for a definition of property `x`. If a match is found, this is the answer. . Throw a "name `x` not found" error. NOTE: Pkl's LAMP name resolution is inspired by link:{uri-newspeak}[Newspeak]. The goal is for name resolution to be stable with regard to changes in external modules. This is why lexically enclosing scopes are searched before the prototype chain of `this`, and why the prototype chains of lexically enclosing scopes are not searched, which sometimes requires the use of `outer.` or `module.`. For name resolution to fully stabilize, the list of top-level properties defined in `pkl.base` needs to be freezed. This is tentatively planned for Pkl 1.0. Consider this snippet of code buried deep inside a config file: [source%parsed,{pkl}] ---- a = x("foo") + 1 ---- The call site's method call syntax reveals that `x` refers to a method definition. But which one? To answer this question, Pkl follows these steps: . Search the call sites' lexically enclosing scopes, starting with the scope in which the call site occurs and continuing outwards up to and including the enclosing module's top-level scope, for a definition of method `x`. If a match is found, this is the answer. . Search the `pkl.base` module for a top-level definition of method `x`. If a match is found, this is the answer. . Search the class inheritance chain of `this`, starting with the class of `this` and continuing upwards until and including class `Any`, for a method named `x.` If a match is found, this is the answer. . Throw a "method `x` not found" error. NOTE: Pkl does not support arity or type-based method overloading. Hence, the argument list of a method call is irrelevant for method resolution. [[prototype-chain]] ==== Prototype Chain Pkl's object model is based on link:{uri-prototypical-inheritance}[prototypical inheritance]. The prototype chain of objectfootnote:[An instance of `Listing`, `Mapping`, `Dynamic`, or (a subclass of) `Typed`.] `x` contains, from bottom to top: 1. The chain of objects amended to create `x`, ending in `x` itself, in reverse order.footnote:[All objects in this chain are instances of the same class, except when a direct conversion between listing, mapping, dynamic, and typed object has occurred. For example, `Typed.toDynamic()` returns a dynamic object that amends a typed object.] 2. The prototype of the class of the top object in (1). If no amending took place, this is the class of `x`. 3. The prototypes of the superclasses of (2). The prototype of class `X` is an instance of `X` that defines the defaults for properties defined in `X`. Its direct ancestor in the prototype chain is the prototype of the superclass of `X`. The prototype of class `Any` sits at the top of every prototype chain. To reduce the chance of naming collisions, `Any` does not define any property names.footnote:[Method resolution searches the class inheritance rather than prototype chain.] Consider the following code: [source%tested,{pkl}] ---- one = new Dynamic { name = "Pigeon" } two = (one) { lifespan = 8 } ---- The prototype chain of object `two` contains, now listed from top to bottom: . The prototype of class `Any`. . The prototype of class `Dynamic`. . `one` . `two` Consider the following code: [source%tested,{pkl}] ---- abstract class Named { name: String } class Bird extends Named { lifespan: Int = 42 } one = new Bird { name = "Pigeon" } two = (one) { lifespan = 8 } ---- The prototype chain of object `two` contains, listed from top to bottom: . The prototype of class `Any`. . The prototype of class `Typed`. . The prototype of class `Named`. . The prototype of class `Bird`. . `one` . `two` ===== Non-object Values The prototype chain of non-object value `x` contains, from bottom to top: 1. The prototype of the class of `x`. 2. The prototypes of the superclasses of (1). For example, the prototype chain of value `42` contains, now listed from top to bottom: . The prototype of class `Any`. . The prototype of class `Number`. . The prototype of class `Int`. A prototype chain never contains a non-object value, such as `42`. [[reserved-keywords]] === Reserved keywords The following keywords are reserved in the language. They cannot be used as a regular identifier, and currently do not have any meaning. * `protected` * `override` * `record` * `delete` * `case` * `switch` * `vararg` To use these names in an identifier, <>. For a complete list of keywords, consult field `Lexer.KEYWORDS` in {uri-github-PklLexer}[Lexer.java]. [[blank-identifiers]] === Blank Identifiers Blank identifiers can be used in many places to ignore parameters and variables. + `_` is not a valid identifier. To use it as a parameter or variable name, it needs to be enclosed in backticks: +`_`+. ==== Functions and methods [source%tested,{pkl}] ---- birds = List("Robin", "Swallow", "Eagle", "Falcon") indexes = birds.mapIndexed((i, _) -> i) function constantly(_, second) = second ---- ==== For generators [source%tested,{pkl}] ---- birdColors = Map("Robin", "blue", "Eagle", "white", "Falcon", "red") birds = new Listing { for (name, _ in birdColors) { name } } ---- ==== Let bindings [source%tested,{pkl}] ---- name = let (_ = trace("defining name")) "Eagle" ---- ==== Object bodies [source%tested,{pkl}] ---- birds = new Dynamic { default { _ -> species = "Bird" } ["Falcon"] {} ["Eagle"] {} } ---- [[projects]] === Projects A _project_ is a directory of Pkl modules and other resources. It is defined by the presence of a `PklProject` file that amends the standard library module `pkl:Project`. Defining a project serves the following purposes: 1. It allows defining common evaluator settings for Pkl modules within a logical project. 2. It helps with managing <> dependencies for Pkl modules within a logical project. 3. It enables packaging and sharing the contents of the project as a <>. 4. It allows importing packages via dependency notation. [[project-dependencies]] ==== Dependencies A project is useful for managing <> dependencies. Within a PklProject file, dependencies can be defined: .PklProject [source%tested,{pkl}] ---- amends "pkl:Project" dependencies { ["birds"] { // <1> uri = "package://example.com/birds@1.0.0" } } ---- <1> Declare dependency on `package://example.com/birds@1.0.0` with simple name "birds". These dependencies can then be imported by their simple name. This syntax is called _dependency notation_. Example: [source,{pkl}] ---- import "@birds/Bird.pkl" // <1> pigeon: Bird = new { name = "Pigeon" } ---- <1> Dependency notation; imports path `/Bird.pkl` within dependency `package://example.com/birds@1.0.0` NOTE: Internally, Pkl assigns URI scheme `projectpackage` to project dependencies imported using dependency notation. When the project gets published as a _package_, these names and URIs are preserved as the package's dependencies. [[resolving-dependencies]] ==== Resolving Dependencies Dependencies that are declared in a `PklProject` file must be _resolved_ via CLI command xref:pkl-cli:index.adoc#command-project-resolve[`pkl project resolve`]. This builds a single dependency list, resolving all transitive dependencies, and determines the appropriate version for each package. It creates or updates a file called `PklProject.deps.json` in the project's root directory with the list of resolved dependencies. When resolving version conflicts, the CLI will pick the latest link:{uri-semver}[semver] minor version of each package. For example, if the project declares a dependency on package A at `1.2.0`, and a package transitively declares a dependency on package A at `1.3.0`, version `1.3.0` is selected. In short, the algorithm has the following steps: 1. Gather a list of all dependencies, either directly declared or transitive. 2. For each dependency, keep only the newest minor version. The resolve command is idempotent; given a PklProject file, it always produces the same set of resolved dependencies. NOTE: This algorithm is adapted from Go's link:{uri-mvs-build-list}[minimum version selection]. ==== Creating a Package Projects enable the creation of a <>. To create a package, the `package` section of a `PklProject` module must be defined. .PklProject [source,pkl] ---- amends "pkl:Project" package { name = "mypackage" // <1> baseUri = "package://example.com/\(name)" // <2> version = "1.0.0" // <3> packageZipUrl = "https://example.com/\(name)/\(name)@\(version).zip" // <4> } ---- <1> The display name of the package.For display purposes only. <2> The package URI, without the version part. <3> The version of the package. <4> The URL to download the package's ZIP file. The package itself is created by the command xref:pkl-cli:index.adoc#command-project-package[`pkl project package`]. This command only prepares artifacts to be published. Once the artifacts are prepared, they are expected to be uploaded to an HTTPS server such that the ZIP asset can be downloaded at path `packageZipUrl`, and the metadata can be downloaded at `+https://+`. [[local-dependencies]] ==== Local dependencies A project can depend on a local project as a dependency. This can be useful for: * Structuring a monorepo that publishes multiple packages. * Temporarily testing out library changes when used within another project. To specify a local dependency, import its `PklProject` file. The imported `PklProject` _must_ have a package section defined. .birds/PklProject [source,{pkl}] ---- amends "pkl:Project" dependencies { ["fruit"] = import("../fruit/PklProject") // <1> } package { name = "birds" baseUri = "package://example.com/birds" version = "1.8.3" packageZipUrl = "https://example.com/birds@\(version).zip" } ---- <1> Specify relative project `../fruit` as a dependency. .fruit/PklProject [source,{pkl}] ---- amends "pkl:Project" package { name = "fruit" baseUri = "package://example.com/fruit" version = "1.5.0" packageZipUrl = "https://example.com/fruit@\(version).zip" } ---- From the perspective of project `birds`, `fruit` is just another package. It can be imported using dependency notation, i.e. `import "@fruit/Pear.pkl"`. At runtime, it will resolve to relative path `../fruit/Pear.pkl`. When packaging projects with local dependencies, both the project and its dependent project must be passed to the xref:pkl-cli:index.adoc#command-project-package[`pkl project package`] command. [[external-readers]] === External Readers External readers are a mechanism to extend the <> and <> URI schemes that Pkl supports. Readers are implemented as ordinary executables and use Pkl's xref:bindings-specification:message-passing-api.adoc[message passing API] to communicate with the hosting Pkl evaluator. The xref:swift:ROOT:index.adoc[Swift] and xref:go:ROOT:index.adoc[Go] language binding libraries provide an `ExternalReaderRuntime` type to facilitate implementing external readers. External readers are configured separately for modules and resources. They are registered by mapping their URI scheme to the executable to run and additional arguments to pass. This is done on the command line by passing `--external-resource-reader` and `--external-module-reader` flags, which may both be passed multiple times. [source,text] ---- $ pkl eval --external-resource-reader = --external-module-reader =' ' ---- External readers may also be configured in a <> `PklProject` file. [source,{pkl}] ---- evaluatorSettings { externalResourceReaders { [""] { executable = "" } } externalModuleReaders { [""] { executable = "" arguments { ""; "" } } } } ---- Registering an external reader for a scheme automatically adds that scheme to the default allowed modules/resources. As with Pkl's built-in module and resource schemes, setting explicit allowed modules or resources overrides this behavior and appropriate patterns must be specified to allow use of external readers. ==== Example Consider this module: [source,{pkl}] ---- username = "pigeon" email = read("ldap://ds.example.com:389/dc=example,dc=com?mail?sub?(uid=\(username))").text ---- Pkl doesn't implement the `ldap:` resource URI scheme natively, but an external reader can provide it. Assuming a hypothetical `pkl-ldap` executable implementing the external reader protocol and the `ldap:` scheme is in the `$PATH`, this module can be evaluated as: [source,text] ---- $ pkl eval --external-resource-reader ldap=pkl-ldap username = "pigeon" email = "pigeon@example.com" ---- In this example, the external reader may provide both `ldap:` and `ldaps:` schemes. To support both schemes during evaluation, both would need to be registered explicitly: [source,text] ---- $ pkl eval --external-resource-reader ldap=pkl-ldap --external-resource-reader ldaps=pkl-ldap ---- [[mirroring_packages]] === Mirroring packages A package is a shareable archive of modules and resources that are published to the internet. A package's URI tells two things: 1. The name of the package. 2. Where the package is downloaded from. For example, given the package name `package://example.com/mypackage@1.0.0`, Pkl will make an HTTPS request to `\https://example.com/mypackage@1.0.0` to fetch package metadata. In situations where internet access is restricted, a mirror can be set up to allow use of packages that are published to the internet. To direct Pkl to a mirror, the `--http-rewrite` CLI option (and its equivalent options when using Pkl's other evaluator APIs) must be used. For example, `--http-rewrite \https://pkg.pkl-lang.org/=\https://my.internal.mirror/` will tell Pkl to download packages from host `my.internal.mirror`. NOTE: To effectively mirror packages from pkg.pkl-lang.org, there must be two rewrites; one for `\https://pkg.pkl-lang.org/` (where package metadata is downloaded), and one for `\https://github.com/` (where package zip files are downloaded). NOTE: Pkl does not provide any tooling to run a mirror server. To fully set up mirroring, an HTTP(s) server will need be running, and which mirrors the same assets byte-for-byte. ================================================ FILE: docs/modules/language-tutorial/pages/01_basic_config.adoc ================================================ = Basic Configuration include::ROOT:partial$component-attributes.adoc[] In this first part of xref:index.adoc[the Pkl tutorial], you build familiarity with Pkl syntax and basic structure. You also learn different ways to invoke Pkl to produce different formats. == Basic values Consider the following example Pkl file. [source,{pkl}] .intro.pkl ---- name = "Pkl: Configure your Systems in New Ways" attendants = 100 isInteractive = true amountLearned = 13.37 ---- Running Pkl on this file gives [source,shell] ---- $ pkl eval /Users/me/tutorial/intro.pkl name = "Pkl: Configure your Systems in New Ways" attendants = 100 isInteractive = true amountLearned = 13.37 ---- It may seem nothing happened. However, Pkl tells you that it _accepts the input_. In other words, you now know that `intro.pkl` does not contain any errors. You can ask Pkl to print this configuration in a different format, using the `-f` option. For example, JSON: [source,shell] ---- $ pkl eval -f json /Users/me/tutorial/intro.pkl { "name": "Pkl: Configure your Systems in New Ways", "attendants": 100, "isInteractive": true, "amountLearned": 13.37 } ---- Or _PropertyList_ format: [source,shell] ---- $ pkl eval -f plist /Users/me/tutorial/intro.pkl name Pkl: Configure your Systems in New Ways attendants 100 isInteractive amountLearned 13.37 ---- Notice that Pkl generated ``, ``, `` and `` for the values in your configuration. This means it has _both_ correctly derived the types of the literal values _and_ translated those types to the corresponding elements in the PropertyList. xref:03_writing_a_template.adoc[Part III] goes into types in more detail. == Structure: Classes, objects, modules A configuration often requires more than just basic values. Typically, you need some kind of (hierarchical) structure. Pkl provides _immutable objects_ for this. Objects have three kinds of members: properties, elements and entries. First, look at the syntax for objects and their members. === Properties [source,{pkl}] .simpleObjectWithProperties.pkl ---- bird { // <1> name = "Common wood pigeon" // <2> diet = "Seeds" taxonomy { // <3> species = "Columba palumbus" } } ---- <1> This _defines_ `bird` to be an object <2> For primitive values, Pkl has the `=` syntax (more on this later). <3> Just like `bird {`, but to show that objects can be nested. This defines an object called `bird` with three _named properties_: `name`, `diet`, and `taxonomy`. The first two of these are strings, but `taxonomy` is another object. This means properties in an object can have different types and objects can be nested. === Elements Of course, you don't always have names for every individual structure in your configuration. What if you want "just a bunch of things" without knowing how many? Pkl offers _elements_ for this purpose. Elements are object members, just like properties. Where you index properties by their name, you index elements by an integer. You can think of an object that only contains elements as _array_. Much like arrays in many languages, you can use square brackets to access an element, for example, `myObject[42]`. You write an element, by writing only an expression. Pkl derives the index from the number of elements already in the object. For example: [source,{pkl}] .simpleObjectsWithElements.pkl ---- exampleObjectWithJustIntElements { 100 // <1> 42 } exampleObjectWithMixedElements { "Bird Breeder Conference" (2000 + 23) // <2> exampleObjectWithJustIntElements // <3> } ---- <1> When you write only the value (without a name), you describe an _element_. <2> Elements don't have to be literal values; they can be arbitrary _expressions_. <3> Elements can really be _any_ value, not just primitive values. [[entries]] === Entries Objects can have one more kind of member; _entries_. Like a _property_, an _entry_ is "named" (technically _keyed_). Unlike a property, the name does not need to be known at declaration time. Of course, we need a syntax to tell entries apart from properties. You write entry "names" by enclosing them in square brackets ("names" is quoted, because the names do not need to be strings; any value can index entries). [source,{pkl}] .simpleObjectsWithEntries.pkl ---- pigeonShelter { ["bird"] { // <1> name = "Common wood pigeon" diet = "Seeds" taxonomy { species = "Columba palumbus" } } ["address"] = "355 Bird St." // <2> } birdCount { [pigeonShelter] = 42 // <3> } ---- <1> The difference with properties is the notation of the key: `[]`. <2> As with properties, entries can be primitive values or objects. <3> Any object can be used as a key for an entry. === Mixed members In the examples so far, you have seen objects with properties, object with elements and object with entries. These object members can be freely mixed. [source,{pkl}] .mixedObject.pkl ---- mixedObject { name = "Pigeon" lifespan = 8 "wing" "claw" ["wing"] = "Not related to the _element_ \"wing\"" 42 extinct = false [false] { description = "Construed object example" } } ---- Notice, how properties (`name`, `lifespan` and `extinct`), elements (`"wing"`, `"claw"`, `42`) and entries (`"wing"`, `false`) are mixed together in this one object. You don't have to order them by kind, and you don't require (other) special syntax. === Collections This free-for-all mixing of object members can become confusing. Also, target formats are often considerably more restrictive. In the following example, you see what happens when you try to produce JSON from `mixedObject`: [source,shell] ---- $ pkl eval -f json /Users/me/tutorial/mixedObject.pkl –– Pkl Error –– Cannot render object with both properties/entries and elements as JSON. Object: "Pigeon" 89 | text = renderer.renderDocument(value) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at pkl.base#Module.output.text (https://github.com/apple/pkl/blob/0.24.0/stdlib/base.pkl#L90) ---- This is why Pkl has two special types of object, namely _listings_, which contain _exclusively_ elements, and _mappings_, which contain _exclusively_ entries. Both listings and mappings _are_ "just objects," so, they don't require syntax besides that of objects: [source,{pkl}] .collections.pkl ---- birds { // <1> "Pigeon" "Parrot" "Barn owl" "Falcon" } habitats { // <2> ["Pigeon"] = "Streets" ["Parrot"] = "Parks" ["Barn owl"] = "Forests" ["Falcon"] = "Mountains" } ---- <1> A listing containing four elements. <2> A mapping containing four entries. [NOTE] ==== _Technically_, the correct way to define `birds` and `habitats` is by using `new Listing {...}` and `new Mapping {...}` explicitly. You will see what these mean in part xref:03_writing_a_template.adoc[three] of this tutorial. ==== When you render _this_ configuration as JSON, everything works: [source,json] ---- { "birds": [ "Pigeon", "Parrot", "Barn owl", "Falcon" ], "habitats": { "Pigeon": "Streets", "Parrot": "Parks", "Barn owl": "Forests", "Falcon": "Mountains" } } ---- Notice particularly, that you rendered the listing as a JSON _array_. When you index the listing with an integer, you're referring to the element inside the listing at the corresponding position (starting from `0`). For example: [source,{pkl}] .indexedListing.pkl ---- birds { "Pigeon" "Parrot" "Barn owl" "Falcon" } relatedToSnowOwl = birds[2] ---- results in [source,{pkl}] ---- birds { "Pigeon" "Parrot" "Barn owl" "Falcon" } relatedToSnowOwl = "Barn owl" ---- == Exercises 1. Given the following JSON snippet (taken from W3C examples), write the `.pkl` file that produces this JSON: + [source,json] ---- { "name": "Common wood pigeon", "lifespan": 8, "friends": { "bird1": "Parrot", "bird2": "Albatross", "bird3": "Falcon" } } ---- 2. For some reason, we decide we no longer need the birdX names of the different birds; we just need them as an array. Change your solution to the previous question to produce the following JSON result: + [source,json] ---- { "name": "Common wood pigeon", "lifespan": 8, "birds": ["Parrot", "Barn owl", "Falcon"] } ---- ================================================ FILE: docs/modules/language-tutorial/pages/02_filling_out_a_template.adoc ================================================ = Filling out a Template include::ROOT:partial$component-attributes.adoc[] In this second part of xref:index.adoc[the Pkl tutorial], you will learn how to write one (part of a) configuration in terms of another. You will also find and fill out an existing _template_. == Composing configurations === Amending The central mechanism in Pkl for expressing one (part of a) configuration in terms of another is _amending_. Consider the following example. [source,{pkl}] .amendingObjects.pkl ---- bird { name = "Pigeon" diet = "Seeds" taxonomy { kingdom = "Animalia" clade = "Dinosauria" order = "Columbiformes" } } parrot = (bird) { name = "Parrot" diet = "Berries" taxonomy { order = "Psittaciformes" } } ---- Parrot and Pigeon have nearly identical properties. They only differ in their name and taxonomy, so if you have already written out `bird`, you can say that `parrot` is just like `bird` except `name` is `"Parrot"`, diet is `"Berries"` the `taxonomy.order` is `"Psittaciformes"`. When you run this, Pkl expands everything fully. [source,{pkl}] ---- bird { name = "Pigeon" diet = "Seeds" taxonomy { kingdom = "Animalia" clade = "Dinosauria" order = "Columbiformes" } } parrot { name = "Parrot" diet = "Berries" taxonomy { kingdom = "Animalia" clade = "Dinosauria" order = "Psittaciformes" } } ---- [NOTE] ==== So far, you have only worked with link:{uri-stdlib-Dynamic}[Dynamic] objects. Pkl also offers link:{uri-stdlib-Typed}[Typed] objects. _Amending_ allows you to override, amend, and add new properties to a dynamic object. Typed objects will only let you amend or override existing properties, not add entirely new ones. The xref:03_writing_a_template.adoc[next part of the tutorial] discusses types in more detail. There, you see that amending _never changes the type_ of the object. ==== You can also amend nested objects. This allows you to only describe the difference with the outermost object for arbitrarily deeply nested structures. Consider the following example. [source,{pkl}] .nestedAmends.pkl ---- stockPigeon { name = "Stock pigeon" diet = "Seeds" taxonomy { kingdom = "Animalia" clade = "Columbimorphae" order = "Columbiformes" species = "Columba oenas" } } woodPigeon = (stockPigeon) { name = "Common wood pigeon" taxonomy { // <1> species = "Columba palumbus" } } dodo = (woodPigeon) { // <2> name = "Dodo" extinct = true // <3> taxonomy { species = "Raphus cucullatus" } } ---- <1> This amends `species`, _as it occurs in_ `stockPigeon`. <2> Amended objects can, themselves, be amended. <3> New fields can be added to objects when amending. Notice how you only have to change `taxonomy.species`. In this example, `stockPigeon.taxonomy` has `kingdom`, `clade`, `order` and `species`. You are amending `stockPigeon`, to define `woodPigeon`. They have the same `taxonomy`, except for `species`. This notation says that everything in `taxonomy` should be what it is in the object you are amending (`stockPigeon`), except for `species`, which should be `"Columba palumbus"` . For the input above, Pkl produces the following output. [source,{pkl}] ---- stockPigeon { name = "Stock pigeon" diet = "Seeds" taxonomy { kingdom = "Animalia" clade = "Columbimorphae" order = "Columbiformes" species = "Columba oenas" } } woodPigeon { name = "Common wood pigeon" diet = "Seeds" taxonomy { kingdom = "Animalia" clade = "Columbimorphae" order = "Columbiformes" species = "Columba palumbus" } } dodo { name = "Dodo" diet = "Seeds" taxonomy { kingdom = "Animalia" clade = "Columbimorphae" order = "Columbiformes" species = "Raphus cucullatus" } extinct = true } ---- So far, you have only amended _properties_. Since you refer to them by name, it makes sense that you "overwrite" the value from the object you're amending. What if you include _elements_ or _entries_ in an amends expression? [source,{pkl}] .amendElementsAndEntries.pkl ---- favoriteFoods { "red berries" "blue berries" ["Barn owl"] { "mice" } } adultBirdFoods = (favoriteFoods) { [1] = "pebbles" // <1> "worms" // <2> ["Falcon"] { // <3> "insects" "amphibians" } ["Barn owl"] { // <4> "fish" } } ---- <1> Explicitly amending _by index_ replaces the element at that index. <2> Without explicit indices, Pkl can't know which element to overwrite, so, instead, it _adds_ an element to the object you're amending. <3> When you write "new" entries (using a key that does not occur in the object you're amending), Pkl also _adds_ them. <4> When you write an entry using a key that exists, this notation amends its value. Pkl can't know which of the `favoriteFoods` to overwrite only by their _value_. When you want to _replace_ an element, you have to explicitly amend the element at a specific index. This is why a "plain" element in an amends expression is _added_ to the object being amended. Result: [source,{pkl}] ---- favoriteFoods { ["Barn owl"] { "mice" } "red berries" "blue berries" } adultBirdFoods { ["Barn owl"] { "mice" "fish" } "red berries" "pebbles" ["Falcon"] { "insects" "amphibians" } "worms" } ---- === Modules A `.pkl` file describes a _module_. Modules are objects that can be referred to from other modules. Going back to the example above, you can write `pigeon` as a separate module. [source,{pkl}] .pigeon.pkl ---- name = "Common wood pigeon" diet = "Seeds" taxonomy { kingdom = "Animalia" clade = "Dinosauria" species = "Columba palumbus" } ---- You can `import` this module and express `parrot` like you did before. [source,{pkl}] .parrot.pkl ---- import "pigeon.pkl" // <1> parrot = (pigeon) { name = "Great green macaw" diet = "Berries" taxonomy { species = "Ara ambiguus" } } ---- <1> Importing `pigeon.pkl` creates the object `pigeon`, so you can refer to `pigeon` in this code, like you did before. If you run Pkl on both, you will see that it works. Looking at the result, however, you see a (possibly) unexpected difference. [source,{pkl}] ---- $ pkl eval /Users/me/tutorial/pigeon.pkl name = "Common wood pigeon" diet = "Seeds" taxonomy { kingdom = "Animalia" clade = "Dinosauria" species = "Columba palumbus" } $ pkl eval /Users/me/tutorial/parrot.pkl parrot { name = "Great green macaw" diet = "Berries" taxonomy { kingdom = "Animalia" clade = "Dinosauria" species = "Ara ambiguus" } } ---- The object `pigeon` is "spread" in the top-level, while `parrot` is a nested and named object. This is because writing `parrot {...}` defines an object property _in_ the "current" module. In order to say that "this module is an object, amended from the `pigeon` module," you use an _amends clause_. [source,{pkl}] .parrot.pkl ---- amends "pigeon.pkl" // <1> name = "Great green macaw" ---- <1> "This" module is the same as `"pigeon.pkl"`, except for what is in the remainder of the file. [NOTE] ==== As a first intuition, think of "amending a module" as "filling out a form." ==== == Amending templates A Pkl file can be either a _template_ or a _"normal" module_. This terminology describes the _intended use_ of the module and doesn't imply anything about its structure. In other words: just by looking at Pkl code, you can't tell whether it is a template or a "normal" module. [source,{pkl}] .AcmeCICD.pkl ---- module AcmeCICD class Pipeline { name: String(nameRequiresBranchName)? hidden nameRequiresBranchName = (_) -> if (branchName == null) throw("Pipelines that set a 'name' must also set a 'branchName'.") else true branchName: String? } timeout: Int(this >= 3) pipelines: Listing output { renderer = new YamlRenderer {} } ---- Remember that amending is like filling out a form. That's exactly what you're doing here; you're filling out "work order forms". Next, add a time-out of one minute for your job. [source,{pkl}] .cicd.pkl ---- amends "AcmeCICD.pkl" timeout = 1 ---- Unfortunately, Pkl does not accept this configuration and provides a rather elaborate error message: [source,plain] ---- –– Pkl Error –– // <1> Type constraint `this >= 3` violated. // <2> Value: 1 // <3> 225 | timeout: Int(this >= 3)? // <4> ^^^^^^^^^ at AcmeCICD#timeout (file:///Users/me/tutorial/AcmeCICD.pkl, line 8) 3 | timeout = 1 // <5> ^ at cicd#timeout (file:///Users/me/tutorial/cicd.pkl, line 3) 90 | text = renderer.renderDocument(value) // <6> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at pkl.base#Module.output.text (https://github.com/apple/pkl/blob/e4d8c882d/stdlib/base.pkl#L90) ---- <1> Pkl found an error. <2> Which error Pkl found. <3> What the offending value is. <4> Where Pkl found its expectation (line 8 of the amended module). <5> Where Pkl found the offending value (line 3 of the input module). <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 packages imported from elsewhere, if available, Pkl produces `https://` links to their repository. Pkl complains about a _type constraint_. Pkl's type system doesn't just protect you from providing a `String` where you expected an `Int`, it even checks which _values_ are allowed. In this case, the minimum time-out is _three_ minutes. If you change the value to `3`, Pkl accepts your configuration. [source, shell] ---- $ pkl eval cicd.pkl timeout: 3 pipelines: [] ---- You can now define a pipeline. Start off by specifying the name of the pipeline and nothing else. [source,{pkl}] .cicd.pkl ---- amends "AcmeCICD.pkl" timeout = 3 pipelines { new { // <1> name = "prb" } } ---- <1> There is no pipeline object to amend. The `new` keyword gives you an object to amend. So far, you've defined objects the same way you amended them. If the name `foo` didn't occur before, `foo { ... }` _creates_ a property called `foo` and assigns to it the contents in place of the `...` (given that `...` is not valid Pkl, but an abbreviation for illustration purposes). If `foo` is an existing object, this notation is an _amend expression_; resulting in a new _object_ (value), but _not_ a new (named) property. Since `pipelines` is a listing, you can _add_ elements by writing expressions in an amend expression. In this case, though, there is no object to amend. Writing `myNewPipeline { ... }` defines a _property_, but listings may only include _elements_. This is where you can use the keyword `new`. `new` gives you an object to amend. Pkl derives from the context in which `new` is used and what the object to amend should look like. This is called the _default value_ for the context. xref:03_writing_a_template.adoc[The next part] goes into detail about how Pkl does this. Running Pkl on your new configuration produces a verbose error. [source,plain] .cicd.pkl ---- –– Pkl Error –– Pipelines that set a 'name' must also set a 'branchName'. 8 | throw("Pipelines that set a 'name' must also set a 'branchName'.") ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at AcmeCICD#Pipeline.nameRequiresBranchName. (file:///Users/me/tutorial/AcmeCICD.pkl, line 8) 6 | name = "prb" ^^^^^ at cicd#pipelines[#1].name (file:///Users/me/tutorial/cicd.pkl, line 6) 90 | text = renderer.renderDocument(value) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at pkl.base#Module.output.text (https://github.com/apple/pkl/blob/e4d8c882d/stdlib/base.pkl#L90) ---- You have hit another type constraint, like `timeout: Int(this >= 3)` before. In this case, the error message consists of an English language sentence, instead of Pkl code. When constraints are complicated or very application specific, template authors can `throw` a more descriptive error message like this. The message is quite instructive, so you can fix the error by adding a `branchName`. [source,{pkl}] .cicd.pkl ---- amends "AcmeCICD.pkl" timeout = 3 pipelines { new { name = "prb" branchName = "main" } } ---- and indeed [source,{pkl}] ---- $ pkl eval -f yml /Users/me/tutorial/cicd.pkl timeout: 3 pipelines: - name: prb branchName: main ---- ================================================ FILE: docs/modules/language-tutorial/pages/03_writing_a_template.adoc ================================================ = Writing a Template include::ROOT:partial$component-attributes.adoc[] In parts xref:01_basic_config.adoc[one] and xref:02_filling_out_a_template.adoc[two], you saw that Pkl provides _validation_ of our configurations. It checks syntax, types and constraints. As you saw in the `AcmeCICD` example xref:02_filling_out_a_template.adoc#amending-templates[here], the template can provide informative error messages when an amending module violates a type constraint. In this final part, you will see some of Pkl's techniques that are particularly relevant for writing a template. == Basic types Pkl always checks the _syntax_ of its input. As it evaluates your configuration, it also checks _types_. You've seen objects, listings, and mappings already. These provide ways to write structured configuration. Before you can write types for them, you need to know how to write the types for the simplest (unstructured) values. These are all Pkl's _basic_ types: [source,{pkl}] .pklTutorialPart3.pkl ---- name: String = "Writing a Template" part: Int = 3 hasExercises: Boolean = true amountLearned: Float = 13.37 duration: Duration = 30.min bandwidthRequirementPerSecond: DataSize = 52.4288.mb ---- In the above, you've explicitly annotated the code with type signatures. The default output of Pkl is actually `pcf`, which is a subset of Pkl. Since `pcf` does not have type signatures, running Pkl on this example removes them. [source,shell] ---- $ pkl eval pklTutorialPart3.pkl name = "Writing a Template" part = 3 hasExercises = true amountLearned = 13.37 duration = 30.min bandwidthRequirementPerSecond = 52.4288.mb ---- Note how `Duration` and `DataSize` help you prevent https://en.wikipedia.org/wiki/Mars_Climate_Orbiter[unit errors] in these common (for configuration) domains. == Typed objects, properties and amending Having a notation for basic types, you can now write _typed objects_. [source,{pkl}] .simpleClass.pkl ---- class Language { // <1> name: String } bestForConfig: Language = new { // <2> name = "Pkl" } ---- <1> A class definition. <2> A property definition, using the `Language` class. [NOTE] ==== Although not required (or enforced), it's customary to name properties starting with a lower-case letter. Class names, by that same convention, start with an upper-case letter. ==== You can type objects with _classes_. In this example, you define a class called `Language`. You can now be certain that every instance of `Language` has a property `name` with type `String`. Types and values are different things in Pkl. Pkl does not render types in its output,footnote:[Although, some output formats can contain their own form of type annotation. This may be derived from the Pkl type. Type definitions (`class` and `typealias`) themselves are never rendered.] so when you run Pkl on this, you don't see the class _definition_ at all. [source,{pkl}] ---- $ pkl eval simpleClass.pkl bestForConfig { name = "Pkl" } ---- Did you notice that the output doesn't just omit the type signature, but also the `= new`? We will discuss this further in the next section. When your configuration describes a few different parts like this, you can define one instance and amend it for every other instance. For example: [source,{pkl}] .pklTutorialParts.pkl ---- class TutorialPart { name: String part: Int hasExercises: Boolean amountLearned: Float duration: Duration bandwidthRequirementPerSecond: DataSize } pklTutorialPart1: TutorialPart = new { name = "Basic Configuration" part = 1 hasExercises = true amountLearned = 13.37 duration = 30.min bandwidthRequirementPerSecond = 50.mib.toUnit("mb") } pklTutorialPart2: TutorialPart = (pklTutorialPart1) { name = "Filling out a Template" part = 2 } pklTutorialPart3: TutorialPart = (pklTutorialPart1) { name = "Writing a Template" part = 3 } ---- You can read this as saying "``pklTutorialPart2`` & `pklTutorialPart3` are exactly like `pklTutorialPart1`, except for their `name` and `part`." Running Pkl confirms this: [source,shell] ---- $ pkl eval pklTutorialParts.pkl pklTutorialPart1 { name = "Basic Configuration" part = 1 hasExercises = true amountLearned = 13.37 duration = 30.min bandwidthRequirementPerSecond = 52.4288.mb } pklTutorialPart2 { name = "Filling out a Template" part = 2 hasExercises = true amountLearned = 13.37 duration = 30.min bandwidthRequirementPerSecond = 52.4288.mb } pklTutorialPart3 { name = "Writing a Template" part = 3 hasExercises = true amountLearned = 13.37 duration = 30.min bandwidthRequirementPerSecond = 52.4288.mb } ---- Sadly, `pklTutorialParts.pkl` is a _rewrite_ of `pklTutorialPart3.pkl`. It creates a separate `class TutorialPart` and instantiates three properties with it (`pklTutorialPart1`, `pklTutorialPart2` and `pklTutorialPart3`). In doing so, it implicitly moves everything "down" one level (`pklTutorialPart3` is now a property in the module `pklTutorialParts`, whereas above, in `pklTutorialPart3.pkl` it was its own module). This is not very DRY. As a matter of fact, you don't need this rewrite. Any `.pkl` file defines a _module_ in Pkl. Any module is represented by a _module class_, which is an actual Pkl `class`. A module is not quite the same as any other class, because Pkl never renders class definitions on the output. However, when you ran Pkl on `pklTutorialPart3.pkl`, it _did_ produce an output. This is because a module also defines an _instance_ of the module class. The values given to properties in a module (or in any "normal" class) are called _default values_. When you instantiate a class, all the properties for which you _don't_ provide a value are populated from the class' default values. In our examples of tutorial parts, only the `name` and `part` varied across instances. You can express this by adding default values to the (module) class definition. Instead of starting from a particular tutorial part, you can define the module `tutorialPart` as follows: [source,{pkl}] .TutorialPart.pkl ---- name: String // <1> part: Int // <1> hasExercises: Boolean = true // <2> amountLearned: Float = 13.37 // <2> duration: Duration = 30.min // <2> bandwidthRequirementPerSecond: DataSize = 52.4288.mb // <2> ---- <1> No default value given. <2> Default value given. Running this through Pkl gives an error, or course, because of the missing values: [source, shell] ---- $ pkl eval TutorialPart.pkl –– Pkl Error –– Tried to read property `name` but its value is undefined. 1 | name: String ^^^^ ... ---- An individual part now only has to fill in the missing fields, so you can change `pklTutorialPart3.pkl` to amend this: [source,{pkl}] .pklTutorialPart3.pkl ---- amends "TutorialPart.pkl" name = "Writing a Template" part = 3 ---- This results in [source, shell] ---- $ pkl eval pklTutorialPart3.pkl name = "Writing a Template" part = 3 hasExercises = true amountLearned = 13.37 duration = 30.min bandwidthRequirementPerSecond = 52.4288.mb ---- This now behaves exactly like our `pklTutorialPart3: TutorialPart = (pklTutorialPart1) {...` before. `pklTutorialPart3` is now defined as the value we get by amending `tutorialPart` and giving it a `name` and a `part`. [IMPORTANT] ==== Amending anything _never changes its type_. When we amend an object of type `Foo`, the result will always be precisely of type `Foo`. By "precisely" we mean, that amending an object also can't "turn it into" an instance of a sub-class of the class of the object being amended. ==== == A new template Now that you know about types, you can start writing your first template. So far, you've written configurations with Pkl, either without a template, or using the `AcmeCICD` template from xref:02_filling_out_a_template.adoc#amending-templates[Amending templates]. It is often easiest to first write a (typical) configuration for which you want to create a template. Suppose you want to define what a live workshop for this tutorial looks like. Consider this example: [source,{pkl}] .workshop2024.pkl ---- title = "Pkl: Configure your Systems in New Ways" interactive = true seats = 100 occupancy = 0.85 duration = 1.5.h `abstract` = """ With more systems to configure, the software industry is drowning in repetitive and brittle configuration files. YAML and other configuration formats have been turned into programming languages against their will. Unsurprisingly, they don’t live up to the task. Pkl puts you back in control. """ event { name = "Migrating Birds between hemispheres" year = 2024 } instructors { "Kate Sparrow" "Jerome Owl" } sessions { new { date = "2/1/2024" time = 30.min } new { date = "2/1/2024" time = 30.min } } assistants { ["kevin"] = "Kevin Parrot" ["betty"] = "Betty Harrier" } agenda { ["beginners"] { name = "Basic Configuration" part = 1 duration = 45.min } ["intermediates"] { name = "Filling out a Template" part = 2 duration = 45.min } ["experts"] { name = "Writing a Template" part = 3 duration = 45.min } } ---- Call your new template `Workshop.pkl`. Although not required, it's good practice to always name your template with a `module`-clause. Defining the first few properties are like you saw in the previous section: [source,{pkl}] ---- module Workshop title: String interactive: Boolean seats: Int occupancy: Float duration: Duration `abstract`: String ---- Unlike these first few properties, `event` is an object with multiple properties. To be able to type `event`, you need a `class`. You've seen before how to define this: [source,{pkl}] ---- class Event { name: String year: Int } event: Event ---- Next, `instructors` isn't an object with properties, but a list of unnamed values. Pkl offers the `Listing` type for this: [source,{pkl}] ---- instructors: Listing ---- `sessions` is a `Listing` of objects, so you need a `Session` class. [source,{pkl}] ---- class Session { time: Duration date: String } sessions: Listing ---- `assistants` has a structure like an object, in that all the values are named, but the set of names is not fixed for all possible workshops (and some workshops may have more assistants than others). The Pkl type for this is a `Mapping`: [source,{pkl}] ---- assistants: Mapping ---- Finally, for every workshop session, there is an `agenda`, which describes which ``TutorialPart``s are covered. You already defined `TutorialPart.pkl` as its own module, so you should not define a separate class, but rather `import` that module and reuse it here: [source,{pkl}] ---- import "TutorialPart.pkl" // <1> agenda: Mapping ---- <1> This `import` clause brings the name `TutorialPart` into scope, which is the module class as discussed above. Note that import clauses must appear before property definitions. Putting it all together, your `Workshop.pkl` template looks like this: [source,{pkl}] .Workshop.pkl ---- module Workshop import "TutorialPart.pkl" title: String interactive: Boolean seats: Int occupancy: Float duration: Duration `abstract`: String class Event { name: String year: Int } event: Event instructors: Listing class Session { time: Duration date: String } sessions: Listing assistants: Mapping agenda: Mapping ---- ================================================ FILE: docs/modules/language-tutorial/pages/index.adoc ================================================ = Tutorial include::ROOT:partial$component-attributes.adoc[] Welcome to the Pkl tutorial. We will get you up and running quickly! If you are new to Pkl, we recommend that you follow along with the code examples. This tutorial describes interactions with the xref:pkl-cli:index.adoc#repl[REPL]. For an even more interactive experience, follow along using a xref:main:ROOT:tools.adoc[supported editor]. For more comprehensive documentation, see xref:language-reference:index.adoc[Language Reference]. For ready-to-go examples with full source code, see xref:ROOT:examples.adoc[]. For API documentation, see xref:ROOT:standard-library.adoc[Standard Library]. Pick a tutorial by topic: 1. xref:01_basic_config.adoc[Basic Configuration] 2. xref:02_filling_out_a_template.adoc[Filling out a Template] 3. xref:03_writing_a_template.adoc[Writing a Template] ================================================ FILE: docs/modules/pkl-cli/pages/index.adoc ================================================ = CLI include::ROOT:partial$component-attributes.adoc[] :uri-homebrew: https://brew.sh :uri-mise: https://mise.jdx.dev :uri-winget: https://learn.microsoft.com/en-us/windows/package-manager/ :uri-pkl-macos-amd64-download: {uri-sonatype-snapshot-download}&a=pkl-cli-macos-amd64&e=bin :uri-pkl-macos-aarch64-download: {uri-sonatype-snapshot-download}&a=pkl-cli-macos-aarch64&e=bin :uri-pkl-linux-amd64-download: {uri-sonatype-snapshot-download}&a=pkl-cli-linux-amd64&e=bin :uri-pkl-linux-aarch64-download: {uri-sonatype-snapshot-download}&a=pkl-cli-linux-aarch64&e=bin :uri-pkl-alpine-download: {uri-sonatype-snapshot-download}&a=pkl-cli-alpine-linux-amd64&e=bin :uri-pkl-windows-download: {uri-sonatype-snapshot-download}&a=pkl-cli-windows-amd64&e=exe :uri-pkl-java-download: {uri-sonatype-snapshot-download}&a=pkl-cli-java&e=jar ifdef::is-release-version[] :uri-pkl-macos-amd64-download: {github-releases}/pkl-macos-amd64 :uri-pkl-macos-aarch64-download: {github-releases}/pkl-macos-aarch64 :uri-pkl-linux-amd64-download: {github-releases}/pkl-linux-amd64 :uri-pkl-linux-aarch64-download: {github-releases}/pkl-linux-aarch64 :uri-pkl-alpine-download: {github-releases}/pkl-alpine-linux-amd64 :uri-pkl-windows-download: {github-releases}/pkl-windows-amd64.exe :uri-pkl-java-download: {uri-maven-repo}/org/pkl-lang/pkl-cli-java/{pkl-artifact-version}/pkl-cli-java-{pkl-artifact-version}.jar endif::[] :uri-pkl-stdlib-docs-settings: {uri-pkl-stdlib-docs}/settings/ :uri-pkl-cli-main-sources: {uri-github-tree}/pkl-cli/src/main/kotlin/org/pkl/cli :uri-pkl-cli-CliEvaluatorOptions: {uri-pkl-cli-main-sources}/CliEvaluatorOptions.kt :uri-certificates: {uri-github-tree}/pkl-commons-cli/src/main/resources/org/pkl/commons/cli/commands :uri-ci-artifacts: https://s01.oss.sonatype.org/content/groups/public/org/pkl-lang/ The `pkl` command-line interface (CLI) evaluates Pkl modules and writes their output to the console or a file. For interactive development, the CLI includes a Read-Eval-Print Loop (REPL). [[installation]] == Installation The CLI comes in multiple flavors: * Native macOS executable for amd64 (tested on macOS 10.15) * Native Linux executable for amd64 * Native Linux executable for aarch64 * Native Alpine Linux executable for amd64 (cross-compiled and tested on Oracle Linux 8) * Native Windows executable for amd64 (tested on Windows Server 2022) * Java executable (tested with Java 17/21 on macOS and Oracle Linux) On macOS, Linux, and Windows, we recommend using the native executables. They are self-contained, start up instantly, and run complex Pkl code much faster than the Java executable. .What is the Difference Between the Linux and Alpine Linux Executables? [NOTE] ==== The Linux executable is dynamically linked against _glibc_ and _libstdc{plus}{plus}_, whereas, the Alpine Linux executable is statically linked against _musl libc_ and _libstdc{plus}{plus}_. ==== The Java executable works on multiple platforms and has a smaller binary size than the native executables. However, it requires a Java 17 (or higher) runtime on the system path, and has a noticeable startup delay. All flavors are built from the same codebase and undergo the same automated testing. Except where noted otherwise, the rest of this page discusses the native executables. [[homebrew]] === Homebrew On macOS and Linux, release versions can be installed with {uri-homebrew}[Homebrew]. ifdef::is-release-version[] To install Pkl, run: [source,shell] ---- brew install pkl ---- To update Pkl, run: [source,shell] ---- brew update brew upgrade pkl # or just `brew upgrade` ---- endif::[] ifndef::is-release-version[] For instructions, switch to a release version of this page. endif::[] [[mise]] === Mise On macOS, Linux, and Windows, release versions can be installed with {uri-mise}[Mise]. ifdef::is-release-version[] To install Pkl, run: [source,shell] [subs="+attributes"] ---- # Install and activate Pkl globally mise use -g pkl@{pkl-version} # Install and activate Pkl locally mise use pkl@{pkl-version} ---- endif::[] ifndef::is-release-version[] For instructions, switch to a release version of this page. endif::[] [[winget]] === Windows Package Manager On Windows, release versions can be installed with {uri-winget}[Windows Package Manager]. ifdef::is-release-version[] To install Pkl, run: [source,shell] ---- winget install Apple.Pkl ---- To update Pkl, run: [source,shell] ---- winget upgrade Apple.Pkl ---- endif::[] ifndef::is-release-version[] For instructions, switch to a release version of this page. endif::[] [[download]] === Download Development and release versions can be downloaded and installed manually. === macOS Executable On aarch64: [source,shell] [subs="+attributes"] ---- curl -L -o pkl '{uri-pkl-macos-aarch64-download}' chmod +x pkl ./pkl --version ---- On amd64: [source,shell] [subs="+attributes"] ---- curl -L -o pkl '{uri-pkl-macos-amd64-download}' chmod +x pkl ./pkl --version ---- This should print something similar to: [source] [subs="+attributes"] ---- Pkl {pkl-version} (macOS, native) ---- [[linux-executable]] === Linux Executable The Linux executable is dynamically linked against _glibc_ and _libstdc{plus}{plus}_ for the amd64 and aarch64 architectures. For a statically linked executable, see <>. On aarch64: [source,shell] [subs="+attributes"] ---- curl -L -o pkl '{uri-pkl-linux-aarch64-download}' chmod +x pkl ./pkl --version ---- On amd64: [source,shell] [subs="+attributes"] ---- curl -L -o pkl '{uri-pkl-linux-amd64-download}' chmod +x pkl ./pkl --version ---- This should print something similar to: [source] [subs="+attributes"] ---- Pkl {pkl-version} (Linux, native) ---- [[alpine-linux-executable]] === Alpine Linux Executable The Alpine Linux executable is statically linked against _musl libc_ and _libstdc{plus}{plus}_. For a dynamically linked executable, see <>. [source,shell] [subs="+attributes"] ---- curl -L -o pkl '{uri-pkl-alpine-download}' chmod +x pkl ./pkl --version ---- This should print something similar to: [source] [subs="+attributes"] ---- Pkl {pkl-version} (Linux, native) ---- NOTE: We currently do not support the aarch64 architecture for Alpine Linux. [[windows-executable]] === Windows Executable [source,PowerShell] [subs="+attributes"] ---- Invoke-WebRequest '{uri-pkl-windows-download}' -OutFile pkl.exe .\pkl --version ---- This should print something similar to: [source] [subs="+attributes"] ---- Pkl {pkl-version} (Windows 10.0, native) ---- NOTE: We currently do not support the aarch64 architecture for Windows. [[java-executable]] === Java Executable The Java executable is a jar that can be executed directly on macOS, Linux, and Windows. It requires `java` to be installed, and available on `$PATH`. [tabs] ==== macOS/Linux:: + [source,shell] [subs="+attributes"] ---- curl -L -o jpkl '{uri-pkl-java-download}' chmod +x jpkl ./jpkl --version ---- Windows:: + [source,PowerShell] [subs="+attributes"] ---- Invoke-WebRequest '{uri-pkl-java-download}' -OutFile jpkl.bat .\jpkl --version ---- ==== This should print something similar to: [source,shell] [subs="+attributes"] ---- Pkl {pkl-version} (macOS 14.2, Java 17.0.10) ---- NOTE: The Java executable is named `jpkl`. [[usage]] == Usage *Synopsis:* `pkl [] []` For a brief description of available options, run `pkl -h`. NOTE: The Java executable is named `jpkl`. [[command-eval]] === `pkl eval` *Synopsis:* `pkl eval [] []` Evaluate the given Pkl `` and produce their rendering results. :: The absolute or relative URIs of the modules to evaluate. Relative URIs are resolved against the working directory. ==== Options [[format]] .-f, --format [%collapsible] ==== Default: (none) + Example: `yaml` + The output format to generate. The default output renderer for a module supports the following formats: * `json` * `jsonnet` * `pcf` * `plist` * `properties` * `textproto` * `xml` * `yaml` If no format is set, the default renderer chooses `pcf`. ==== [[output-path]] .-o, --output-path [%collapsible] ==== Default: (none) + Example: "config.yaml" + The file path where the output file is placed. Relative paths are resolved against the project directory. // suppress inspection "AsciiDocLinkResolve" This option is mutually exclusive with link:#multiple-file-output-path[`--multiple-file-output-path`]. If neither option is set, each module's `output.text` is written to standard output. If multiple source modules are given, placeholders can be used to map them to different output files. The following placeholders are supported: `%\{moduleDir}`::: The directory path of the module, relative to the working directory. Only available when evaluating file-based modules. `%\{moduleName}`::: The simple module name as inferred from the module URI. For hierarchical URIs such as `+file:///foo/bar/baz.pkl+`, this is the last path segment without file extension. `%\{outputFormat}`::: The requested output format. Only available if `--format` is set. If multiple source modules are mapped to the same output file, their outputs are concatenated. By default, module outputs are separated with `---`, as in a YAML stream. // suppress inspection "AsciiDocLinkResolve" The separator can be customized using the link:#module-output-separator[`--module-output-separator`] option. ==== [[module-output-separator]] .--module-output-separator [%collapsible] ==== Default: `---` (as in a YAML stream) + The separator to use when multiple module outputs are written to the same file, or to standard output. ==== [[multiple-file-output-path]] .-m, --multiple-file-output-path [%collapsible] ==== Default: (none) + Example: "output/" + The directory where a module's output files are placed. Setting this option causes Pkl to evaluate a module's `output.files` property and write the files specified therein. Within `output.files`, a key determines a file's path relative to `--multiple-file-output-path`, and a value determines the file's contents. // suppress inspection "AsciiDocLinkResolve" This option cannot be used together with any of the following: * xref:output-path[`--output-path`] * xref:expression[`--expression`] // suppress inspection "AsciiDocLinkResolve" This option supports the same placeholders as link:#output-path[`--output-path`]. Examples: [source,shell] ---- # Write files to `output/` pkl eval -m output/ myFiles.pkl # Write files to the current working directory pkl eval -m . myFiles.pkl # Write foo.pkl's files to the `foo` directory, and bar.pkl's files # to the `bar` directory pkl eval -m "%{moduleName}" foo.pkl bar.pkl ---- For additional details, see xref:language-reference:index.adoc#multiple-file-output[Multiple File Output] in the language reference. ==== [[expression]] .-x, --expression [%collapsible] ==== Default: (none) + The expression to be evaluated within the module. This option causes Pkl to evaluate the provided expression instead of the module's `output.text` or `output.files` properties. The resulting value is then stringified, and written to either standard out, or the designated output file. For example, consider the following Pkl module: .pigeon.pkl [source%tested,{pkl}] ---- metadata { species = "Pigeon" } ---- The following command prints `Pigeon` to the console: [source,shell] ---- pkl eval -x metadata.species pigeon.pkl # => Pigeon ---- Setting an `--expression` flag can be thought of as substituting the expression in place of a module's `output.text` property. Running the previous command is conceptually the same as if the below module were evaluated without the `--expression` flag: [source,pkl] ---- metadata { species = "Pigeon" } output { text = metadata.species.toString() } ---- ==== [[power-assertions-eval]] .--power-assertions, --no-power-assertions [%collapsible] ==== Default: enabled + Enable or disable power assertions for detailed assertion failure messages. When enabled, type constraint failures will show intermediate values in the assertion expression. Use `--no-power-assertions` to disable this feature if you prefer simpler output or better performance. ==== This command also takes <>. [[command-server]] === `pkl server` *Synopsis:* `pkl server` Run as a server that communicates over standard input/output. This option is used for embedding Pkl in an external client, such as xref:swift:ROOT:index.adoc[pkl-swift] or xref:go:ROOT:index.adoc[pkl-go]. [[command-test]] === `pkl test` *Synopsis:* `pkl test [] []` Evaluate the given `` as _tests_, producing a test report and appropriate exit code. Renderers defined in test files will be ignored by the `test` command. Tests that result in writing `pkl-expected.pcf` files are considered failing tests. If these are the only failures, the command exits with exit code 10. Otherwise, failures result in exit code 1. :: The absolute or relative URIs of the modules to test. The module must extend `pkl:test`. Relative URIs are resolved against the working directory. ==== Options [[junit-reports]] .--junit-reports [%collapsible] ==== Default: (none) + Example: `./build/test-results` + Directory where to store JUnit reports. By default, one file will be created for each test module. This behavior can be changed with `--junit-aggregate-reports`, which will instead create a single JUnit report file with all test results. No JUnit reports will be generated if this option is not present. ==== [[junit-aggregate-reports]] .--junit-aggregate-reports [%collapsible] ==== Aggregate JUnit reports into a single file. By default it will be `pkl-tests.xml` but you can override it using `--junit-aggregate-suite-name` flag. ==== [[junit-aggregate-suite-name]] .--junit-aggregate-suite-name [%collapsible] ==== Default: (none) + Example: `my-tests` + The name of the root JUnit test suite. Used in combination with `--junit-aggregate-reports` flag. ==== [[overwrite]] .--overwrite [%collapsible] ==== Force generation of expected examples. + The old expected files will be deleted if present. ==== [[power-assertions-test]] .--power-assertions, --no-power-assertions [%collapsible] ==== Default: enabled + Enable or disable power assertions for detailed assertion failure messages. When enabled, test failures will show intermediate values in the assertion expression, making it easier to understand why a test failed. Use `--no-power-assertions` to disable this feature if you prefer simpler output. ==== This command also takes <>. [[command-run]] === `pkl run` *Synopsis:* `pkl run [] [] []` Evaluate a <> defined by ``. :: The absolute or relative URIs of the command module to run. The module must extend `pkl:Command`. Relative URIs are resolved against the working directory. :: Additional CLI options and arguments defined by ``. This command also takes <>. [[command-repl]] === `pkl repl` *Synopsis:* `pkl repl []` Start a REPL session. This command takes <>. [[command-project-package]] === `pkl project package` *Synopsis:* `pkl project package ` This command prepares a project to be published as a package. Given a project directory, it creates the following artifacts: * `@` - the package metadata file * `@.sha256` - the dependency metadata file's SHA-256 checksum * `@.zip` - the package archive * `@.zip.sha256` - the package archive's SHA-256 checksum These artifacts are expected to be published to an HTTPS server, such that the metadata and zip files can be fetched at their expected locations. The package ZIP should be available at the `packageZipUrl` location specified in the `PklProject` file The package metadata should be available at the package URI's derived HTTPS URL. For example, given package `package://example.com/mypackage@1.0.0`, the metadata file should be published to `+https://example.com/mypackage@1.0.0+`. During packaging, this command runs these additional steps: 1. Run the package's API tests, if any are defined. 2. Validates that if the package has already been published, that the package's metadata is identical. This step can be skipped using the `--skip-publish-check` flag. Examples: [source,shell] ---- # Search the current working directory for a project, and package it. pkl project package # Package all projects within the `packages/` directory to `.out`, writing each package's artifacts to its own directory. pkl project package --output-path ".out/%{name}@%{version}/" packages/*/ ---- ==== Options .--output-path [%collapsible] ==== Default: `.out` The directory to write artifacts to. Accepts the following placeholders: `%\{name}`:: The name of the package `%\{version}`:: The version of the package ==== .--skip-publish-check [%collapsible] ==== Skips checking whether a package has already been published with different contents. By default, the packager will check whether a package at the same version has already been published. If the package has been published, it validates that the package's metadata is identical to the locally generated metadata. ==== .--junit-reports [%collapsible] ==== Default: (none) + Example: `./build/test-results` + Directory where to store JUnit reports. No JUnit reports will be generated if this option is not present. ==== .--junit-aggregate-reports [%collapsible] ==== Aggregate JUnit reports into a single file. By default it will be `pkl-tests.xml` but you can override it using `--junit-aggregate-suite-name` flag. ==== .--junit-aggregate-suite-name [%collapsible] ==== Default: (none) + Example: `my-tests` + The name of the root JUnit test suite. Used in combination with `--junit-aggregate-reports` flag. ==== .--overwrite [%collapsible] ==== Force generation of expected examples. + The old expected files will be deleted if present. ==== This command also takes <>. [[command-project-resolve]] === `pkl project resolve` *Synopsis:* `pkl project resolve ` This command takes the dependencies of a project, and writes the resolved versions a file at path `PklProject.deps.json`. It builds a dependency list, taking the latest minor version in case of version conflicts. For more details, see the xref:language-reference:index.adoc#resolving-dependencies[resolving dependencies] section of the language reference. Examples: [source,shell] ---- # Search the current working directory for a project, and resolve its dependencies. pkl project resolve # Resolve dependencies for all projects within the `packages/` directory. pkl project resolve packages/*/ ---- ==== Options This command accepts <>. [[command-download-package]] === `pkl download-package` *Synopsis*: `pkl download-package ` This command downloads the specified packages to the cache directory. If the package already exists in the cache directory, this command is a no-op. ==== Options This command accepts <>. [[command-analyze-imports]] === `pkl analyze imports` *Synopsis*: `pkl analyze imports []` This command builds a graph of imports declared in the provided modules. This is a lower level command that is meant to be useful for Pkl-related tooling. For example, this command feeds into the xref:pkl-gradle:index.adoc[] to determine if tasks are considered up-to-date or not. This command produces an object with two properties, `imports` and `resolvedImports`. The `imports` property is a mapping of a module's absolute URI, to the set of imports declared within that module. The `resolvedImports` property is a mapping of a module's absolute URI (as stated in `imports`), to the resolved absolute URI that might be useful for fetching the module's contents. For example, a xref:language-reference:index.adoc#local-dependencies[local dependency] import will have an in-language URI with scheme `projectpackage:`, and may have resolved URI with scheme `file:` (assuming that the project is file-based). Examples: [source,shell] ---- # Analyze the imports of a single module pkl analyze imports myModule.pkl # Same as the previous command, but output in JSON. pkl analyze imports -f json myModule.pkl # Analyze imports of all modules declared within src/ pkl analyze imports src/*.pkl ---- :: The absolute or relative URIs of the modules to analyze. Relative URIs are resolved against the working directory. ==== Options .-f, --format [%collapsible] ==== Same meaning as <> in <>. ==== .-o, --output-path [%collapsible] ==== Same meaning as <> in <>. ==== This command also takes <>. [[command-shell-completion]] === `pkl shell-completion` *Synopsis*: `pkl shell-completion ` Generate shell completion script. Supported shells are: `bash`, `zsh`, `fish`. [source,shell] ---- # Generate shell completion script for bash pkl shell-completion bash # Generate shell completion script for zsh pkl shell-completion zsh ---- [[command-format]] === `pkl format` *Synopsis*: `pkl format []` This command formats or checks formatting of Pkl files. + Exit codes: * 0: No violations found or files were updated. * 1: An unexpected error happened (ex.: IO error) * 11: Violations were found (when running without `--write`). If the path is a directory, recursively looks for files with a `.pkl` extension, or files named `PklProject`. By default, the input files are formatted, and written to standard out. ==== Options .--grammar-version [%collapsible] ==== Default: `2` (latest version) + Select the grammar compatibility version for the formatter. New versions are created for each backward incompatible grammar change. ==== .-s, --silent [%collapsible] ==== Skip writing to standard out. Mutually exclusive with `--diff-name-only`. ==== .-w, --write [%collapsible] ==== Format files in place, overwriting them. Implies `--diff-name-only`. ==== .--diff-name-only [%collapsible] ==== Write the path of files with formatting violations to stdout. ==== [[common-options]] === Common options The <>, <>, <>, <>, <>, <>, <>, and <> commands support the following common options: include::../../pkl-cli/partials/cli-common-options.adoc[] The <>, <>, <>, <>, and <> commands also take the following options: include::../../pkl-cli/partials/cli-project-options.adoc[] [[root-options]] === Root options The root `pkl` command supports the following options: .-v, --version [%collapsible] ==== Display version information. ==== == Evaluating Modules Say we have the following module: [[config.pkl]] .config.pkl [source,{pkl}] ---- bird { species = "Pigeon" diet = "Seeds" } parrot = (bird) { species = "Parrot" diet = "Berries" } ---- To evaluate this module and write its output to standard output, run: [source,shell] ---- pkl eval config.pkl ---- You should see the following output: [source,{pkl}] ---- bird { species = "Pigeon" diet = "Seeds" } parrot { species = "Parrot" diet = "Berries" } ---- To render output as JSON, YAML, XML property list, or Java properties, use `--format json`, `--format yaml`, `--format plist`, or `--format properties`, respectively. To control the output format from within Pkl code, see xref:language-reference:index.adoc#module-output[Module Output]. To read a source module from standard input rather than a file, use `-` as a module name: [source,shell] ---- echo mod2.pkl | pkl eval mod1.pkl - mod3.pkl ---- This is especially useful in environments that don't support `/dev/stdin`. To write output to a file rather than standard output, use `--output-path some/file.ext`. [[batch-evaluation]] === Batch Evaluation Multiple modules can be evaluated at once: [source,shell] ---- pkl eval config1.pkl config2.pkl config3.pkl ---- To write module outputs to separate output files, `--output-path` supports the following placeholders: `%\{moduleDir}`:: the directory path of the source module, relative to the working directory (only available for file based modules) `%\{moduleName}`:: the last path segment of the module URI, without file extension `%\{outputFormat}`:: the target format (only available if `--format` is set) The following run produces three JSON files placed next to the given source modules: [source,shell] ---- pkl eval --format=json --output-path=%{moduleDir}/%{moduleName}.json config1.pkl config2.pkl config3.pkl ---- If multiple module outputs are written to the same file, or to standard output, their outputs are concatenated. By default, module outputs are separated with `---`, as in a YAML stream. The separator can be customized using the `--module-output-separator` option. [[cli-tools]] == Implementing CLI Tools CLI tools can be implemented in Pkl by modules extending the `pkl:Command` module. With `pkl:Command`, you can define a script in Pkl that is executed by your shell, providing a better CLI experience. Regular evaluation requires use of xref:language-reference:index.adoc#resources[resources] like properties and evironment variables to provide parameters: [source,bash] ---- $ pkl eval script.pkl -p username=me -p password=password ---- Commands provide a native, familiar CLI experience: [source,bash] ---- $ pkl run script.pkl --username=admin --password=hunter2 $ ./script.pkl --username=admin --password=hunter2 ---- Pkl commands have a few properties that distinguish them from standard module evaluation: * Users provide input to commands using familiar command line idioms, providing a better experience than deriving inputs from xref:language-reference:index.adoc#resources[resources] like external properties or environment variables. * Commands can dynamically import modules when they are specified as command line options. * Commands may write to standard output (via `output.text` or `output.bytes`) and the filesystem (via `output.files`) in the same evaluation. * Command file output may write to any absolute path (not only relative to the `--multiple-file-output-path` option). ** Relative output paths are written relative to the current working directory (or `--working-dir`, if specified). ** Paths of output file are printed to the command's standard error. IMPORTANT: Users of `pkl run` must be aware of the security implications of this behavior. Using `pkl eval` prevents accidental overwrites by not allowing absolute paths, but `pkl run` does not offer this protection. Commands may write to any path the invoking user has permissions to modify. Commands are implemented as regular modules and declare their supported command line flags and positional arguments using a class with annotated properties. === Defining Commands Commands are defined by creating a module that extends `pkl:Command`: [source,pkl%tested] .my-tool.pkl ---- /// This doc comment becomes part of the command's CLI help! /// Markdown formatting is **allowed!** extends "pkl:Command" options: Options // <1> class Options { // Define CLI flags/arguments... } // Regular module code... ---- <1> Re-declaration of the `options` property's type. Like `pkl eval`, when a command completes without an evaluation error the process exits successfully (exit code 0). Commands can return a failure using `throw` (exit code 1), but otherwise may not control the exit code. Other than the differences listed above, commands behave like any other Pkl module. For example, there is no way to execute other programs or make arbitrary HTTP requests. If additional functionality is desired, xref:language-reference:index.adoc#external-readers[external readers] may be used to extends Pkl's capabilities. === Command Options Each property of a command's options class becomes a command line option. Properties with the `local`, `hidden`, `fixed`, and/or `const` modifiers are not parsed as options A property's doc comment, if present, becomes the corresponding option's CLI help description. Doc comments are interpreted as Markdown text and formatted nicely when displayed to users. Properties must have xref:language-reference:index.adoc#type-annotations[type annotations] to determine how they are parsed. Properties may be xref:language-reference:index.adoc#annotations[annotated] to influence how they behave: * Properties annotated with link:{uri-stdlib-Command-Flag}[`@Flag`] become CLI flags named `--` that accept a value. * `Boolean` properties annotated with link:{uri-stdlib-Command-BooleanFlag}[`@BooleanFlag`] become CLI flags named `--` and `--no-` that result in `true` and `false` values, respectively. * `Int` (and type aliases of `Int`) properties annotated with link:{uri-stdlib-Command-CountedFlag}[`@CountedFlag`] become CLI flags named `--` that produce a value equal to the number of times they are present on the command line. * Properties annotated with link:{uri-stdlib-Command-Argument}[`@Argument`] become positional CLI arguments and are parsed in the order they appear in the class. * Properties with no annotation are treated the same as `@Flag` with no further customization. Flag options may set a `shortName` property to define a single-character abbreviation (`-`). Flag abbreviations may be combined (e.g. `-a -b -v -v -q some-value` is equivalent to `-abvvq some-value`). [CAUTION] ==== Flag names and short names may not conflict with <>. Future versions of Pkl may introduce additional common options and the names of these options will become forbidden for use in `pkl:Command`. Thus, any Pkl release that adds common options may introduce breaking changes for commands. While unfortunate, this behavior eliminates potentially dangerous or misleading ambiguities between Pkl-defined and user-defined options. ==== A `@Flag` or `@Argument` property's type annotation determines how it is converted from the raw string value: |=== |Type |Behavior |`String` |Value is used verbatim. |`Char` |Value is used verbatim but must be exactly one character. |`Boolean` |True values: `true`, `t`, `1`, `yes`, `y`, `on` False values: `false`, `f`, `0`, `no`, `n`, `off` |`Number` |Value is parsed as an `Int` if possible, otherwise parsed as `Float`. |`Float` |Value is parsed as a `Float`. |`Int` |Value is parsed as a `Int`. |`Int8`, `Int16`, `Int32`, `UInt`, `UInt8`, `UInt16`, `UInt32` |Value is parsed as a `Int` and must be within the type's range. |xref:language-reference:index.adoc#union-types[Union] of xref:language-reference:index.adoc#string-literal-types[string literals] |Value is used verbatim but must match a member of the union. |`List`, `Listing`, `Set` |Each occurrence of the option becomes an element of the final value. `Element` values are parsed based on the above primitive types. |`Map`, `Mapping` |Each occurrence of the option becomes an entry of the final value. Values are split on the first `"="` character; the first part is parsed as `Key` and the second as `Value`, both based on the above primitive types. |`Pair` |Value is split on the first `"="` character; the first part is parsed as `First` and the second as `Second`, both based on the above primitive types. |=== If a flag that accepts only a single value is provided multiple times, the last occurrence becomes the final value. Only a single positional argument accepting multiple values is permitted per command. A property with a xref:language-reference:index.adoc#nullable-types[nullable type] is optional and, if not specified on the command line, will have value `null`. Properties with default values are also optional. Type constraints are evaluated when the command is executed, so additional restrictions on option values are enforced at runtime. ==== Custom Option Conversion and Aggregation A property may be annotated with any type if its `@Flag` or `@Argument` annotation sets the `convert` or `transformAll` properties. The `convert` property is a xref:language-reference:index.adoc#anonymous-functions[function] that overrides how _each_ raw option value is interpreted. The `transformAll` property is a function that overrides how _all_ parsed option values become the final property value. The `convert` and `transformAll` functions may return an pkldoc:Import[pkl:Command] value that is replaced during option parsing with the actual value of the module specified by its `uri` property. If `glob` is `true`, the replacement value is a `Mapping`; its keys are the _absolute_ URIs of the matched modules and its values are the actual module values. When specifying glob import options on the command line, it is often necessary to quote the value to avoid it being interpreted by the shell. If the return value of `convert` or `transformAll` is a `List`, `Set`, `Map`, or `Pair`, each contained value (elements and entry keys/values) that are `Import` values are also replaced. [IMPORTANT] ==== If an option has type `Mapping` and should accept a single glob pattern value, the option's annotation must also set `multiple = false` to override the default behavior of `Mapping` options accepting multiple values. Example: [source%parsed,{pkl}] ---- @Flag { convert = (it) -> new Import { uri = it; glob = true } multiple = false } birds: Mapping ---- If multiple glob patterns values should be accepted and merged, `transformAll` may be used to merge every glob-imported `Mapping`: [source%parsed,{pkl}] ---- @Flag { convert = (it) -> new Import { uri = it; glob = true } transformAll = (values) -> values.fold(new Mapping {}, (result, element) -> (result) { ...element } ) } birds: Mapping ---- ==== === Subcommands Like many other command line libraries, `pkl:Command` allows building commands into a hierarchy with a root command and subcommands: [source,pkl%tested] .my-tool.pkl ---- extends "pkl:Command" command { subcommands { import("subcommand1.pkl") import("subcommand2.pkl") for (_, subcommand in import*("./subcommands/*.pkl")) { subcommand } } } ---- [source,pkl%tested] .subcommand1.pkl ---- extends "pkl:Command" import "my-tool.pkl" parent: `my-tool` // <1> // Regular module code... ---- <1> Optional; asserts that this is a subcommand of `my-tool` and simplifies accessing properties and options of the parent command Each element of `subcommands` must have a unique value for `command.name`. === Testing Commands Command modules are normal Pkl modules, so they may be imported and used like any other module. This is particularly helpful when testing commands, as the command's `options` and `parent` properties can be populated by test code. Testing the above command and subcommand might look like this: [source,pkl%tested] ---- amends "pkl:test" import "my-tool.pkl" import "subcommand1.pkl" examples { ["Test my-tool"] { (`my-tool`) { options { // Set my-tool options here... } }.output.text } ["Test subcommand1"] { (subcommand1) { parent { // this amends `my-tool` options { // Set my-tool options here... } } options { // Set subcommand options here... } }.output.text } } ---- [[commands-as-standalone-scripts]] === Commands as standalone scripts On *nix platforms, Pkl commands can be configured to run as standalone tools that can be invoked without the `pkl run` command. To achieve this, the command file must be marked executable (i.e. `chmod +x my-tool.pkl`) and a link:https://en.wikipedia.org/wiki/Shebang_(Unix)[shebang comment] must be added on the first line of the file: [source,pkl%parsed] ---- #!/usr/bin/env -S pkl run ---- NOTE: The `-S` flag for `env` is required on Linux systems due to a limitation of shebang handling in the Linux kernel. While not required on other *nix platforms like macOS, but it should be included for compatibility. ==== Shell Completion Like with Pkl's own CLI, <> can be generated for standalone scripts. [source,shell] ---- # Generate shell completion script for bash ./my-tool.pkl shell-completion bash # Generate shell completion script for zsh ./my-tool.pkl shell-completion zsh ---- ==== Customizing Completion Candidates `@Flag` and `@Argument` annotations may specify the `completionCandidates` to improve generated shell completions. Valid values include: * A `Listing` of literal string values to offer for completion. * The literal string `"path"`, which offers local file paths for completion. Options with a string literal union type implicitly offer the members of the union as completion candidates. === Flag name ambiguities It is possible for commands to define flags with names or short names that collide with Pkl's own command line options. To avoid ambiguity in parsing these options, all flags for Pkl itself (e.g. `--root-dir`) must be placed before the root command module's URI. Command authors are encouraged to avoid overlapping with Pkl's built-in flags, but this may not always be feasible, especially for single-character abbreviated names. This imposes a limitation around <> that prevents users from customizing Pkl evaluator options when they are invoked. There are two recommended workarounds for this limitation: * Use a `PklProject` to define evaluator settings instead of doing so on the command line. * If the command line must be used, switch to invoking via `pkl run [] []`. [[repl]] == Working with the REPL To start a REPL session, run `pkl repl`: [source,shell] [subs="+attributes"] ---- $ pkl repl Welcome to Pkl {pkl-version}. Type an expression to have it evaluated. Type :help or :examples for more information. pkl> ---- === Loading Modules To load <> into the REPL, run: [source,shell] ---- pkl> :load config.pkl ---- To evaluate the `bird.species` property, run: [source,shell] ---- pkl> bird.species "Pigeon" ---- To evaluate the entire module, force-evaluate `this`: [source,shell] ---- pkl> :force this ---- === REPL Commands Commands start with `:` and can be tab-completed: [source,shell] [subs="+attributes,+macros"] ---- pkl> :{empty}kbd:[Tab] clear examples force help load quit reset pkl> :q{empty}kbd:[Tab] pkl> :quit{empty}kbd:[Return] $ ---- Commands can be abbreviated with any unique name prefix: [source,shell] [subs="+attributes,+macros"] ---- pkl> :q{empty}kbd:[Return] $ ---- To learn more about each command, run the `:help` command. Some commands support further command-specific tab completion. For example, the `:load` command supports completing file paths. With commands out of the way, let's move on to evaluating code. === Evaluating Code To evaluate an expression, type the expression and hit kbd:[Return]. [source,shell] ---- pkl> 2 + 4 6 ---- Apart from expressions, the REPL also accepts property, function, and class definitions. (See the xref:language-reference:index.adoc[Language Reference] to learn more about these language concepts.) [source,shell] ---- pkl> hello = "Hello, World!" pkl> hello "Hello, World!" pkl> function double(n) = 2 * n pkl> double(5) 10 pkl> class Bird { species: String } pkl> new Bird { species = "Pigeon" } { name = ? } ---- Top-level expressions are only supported in the REPL. In a regular module, every expression is contained in a definition, and only definitions exist at the top level. === Redefining Members Existing members can be redefined: [source,shell] ---- pkl> species = "Pigeon" pkl> species "Pigeon" pkl> species = "Barn" pkl> species "Barn" pkl> species + " Owl" pkl> species "Barn Owl" ---- Due to Pkl's late binding semantics, redefining a member affects dependent members: [source,shell] ---- pkl> name = "Barn" pkl> species = "\(name) Owl" pkl> species "Barn Owl" pkl> name = "Elf" pkl> species "Elf Owl" ---- Redefining members is only supported in the REPL. Under the hood, it works as follows: * The REPL environment is represented as a synthetic Pkl module. * When a new member is defined, it is added to the current REPL module. * When an existing member is redefined, it is added to a new REPL module that xref:language-reference:index.adoc#module-amend[amends] the previous REPL module. [[settings-file]] == Settings File 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`. 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 [source%parsed,{pkl}] ---- amends "pkl:settings" // <1> editor = Idea // <2> ---- <1> A settings file should amend the `pkl.settings` standard library module. <2> Configures IntelliJ IDEA as the preferred editor. Other supported values are `System`, `GoLand`, `TextMate`, `Sublime`, `Atom`, and `VsCode`. With the above settings file in place, kbd:[Cmd]+Double-clicking a source code link in a stack trace opens the corresponding file in IntelliJ IDEA at the correct location. To learn more about available settings, see link:{uri-pkl-stdlib-docs-settings}[pkl.settings]. [[ca-certs]] == CA Certificates When making TLS requests, Pkl comes with its own set of {uri-certificates}[CA certificates]. These certificates can be overridden via either of the two options: - Set them directly via the CLI option `--ca-certificates `. - Add them to a directory at path `~/.pkl/cacerts/`. Both these options will *replace* the default CA certificates bundled with Pkl. + The CLI option takes precedence over the certificates in `~/.pkl/cacerts/`. + Certificates need to be X.509 certificates in PEM format. [[http-proxy]] == HTTP Proxying When making HTTP(s) requests, Pkl can possibly make use of an HTTP proxy. There are two values that determine proxy settings; the proxy address and list of noproxy rules. When determining proxy settings, Pkl will look at the following locations, in order of precedence (lowest to highest): 1. OS settings (on macOS, Windows, and GNOME environments) 2. <> 3. xref:language-reference:index.adoc#projects[PklProject file] 4. `--http-proxy` and `--http-no-proxy` CLI flags [NOTE] ==== The proxy and noproxy values are individually set. For example, using the `--http-no-proxy` flag but not the `--http-proxy` flag will cause Pkl to look at the PklProject file, then the settings file, then system settings for the proxy address. One exception to this rule is that setting a proxy address will cause Pkl to ignore any noproxy values set at the OS level. ==== Pkl only supports HTTP proxies, so neither HTTPS nor SOCKS proxies are supported. Pkl does not support authentication with a proxy. When specifying a proxy address, it must have scheme `http`, and may not contain anything other than a host and port. === Proxy exclusions Pkl can be configured to bypass the proxy for specific requests via a proxy exclusion rule (i.e. the `--http-no-proxy` flag). It may be provided either as a hostname, an IP address, or an IP range via https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation[CIDR notation]. When determining whether a proxy should be excluded, hostnames do not get resolved to their IP address. For example, a request to `localhost` will not match `--http-no-proxy=127.0.0.1`. For individual hosts (not CIDRs), ports can be specified. When no port is specified for a given host, connections to all ports bypass the proxy. A hostname will match all of its subdomains. For example, `example.com` will match `foo.example.com`, and `bar.foo.example.com`, but not `fooexample.com`. The value `*` is a special wildcard that matches all hosts. A port can optionally be specified. If omitted, it matches all ports. NOTE: Pkl follows the rules described in https://about.gitlab.com/blog/2021/01/27/we-need-to-talk-no-proxy/#standardizing-no_proxy[Standardizing `no_proxy`], except it does not look at environment variables. ================================================ FILE: docs/modules/pkl-cli/partials/cli-common-options.adoc ================================================ [[allowed-modules]] .--allowed-modules [%collapsible] ==== Default: `pkl:,file:,modulepath:,https:,repl:,package:,projectpackage:` + Comma-separated list of URI patterns that determine which modules can be loaded and evaluated. Patterns are matched against the beginning of module URIs. (File paths have been converted to `file:` URLs at this stage.) At least one pattern needs to match for a module to be loadable. ==== [[allowed-resources]] .--allowed-resources [%collapsible] ==== Default: `env:,prop:,package:,projectpackage:` + Comma-separated list of URI patterns that determine which external resources can be read. Patterns are matched against the beginning of resource URIs. At least one pattern needs to match for a resource to be readable. ==== [[color]] .--color [%collapsible] ==== Default: `auto` + When to format messages with ANSI color codes. Possible values: - `"never"`: Never format - `"auto"`: Format if `stdin`, `stdout`, or `stderr` are connected to a console. - `"always"`: Always format ==== [[cache-dir]] .--cache-dir [%collapsible] ==== Default: `~/.pkl/cache` + Example: `/path/to/module/cache/` + The cache directory for storing packages. ==== .--no-cache [%collapsible] ==== Disable caching of packages. ==== .-e, --env-var [%collapsible] ==== Default: OS environment variables for the current process + Example: `MY_VAR=myValue` + Sets an environment variable that can be read by Pkl code with `read("env:")`. Repeat this option to set multiple environment variables. ==== .-h, --help [%collapsible] ==== Display help information. ==== .--module-path [%collapsible] ==== Default: (empty) + Example: `dir1:zip1.zip:jar1.jar` + Directories, ZIP archives, or JAR archives to search when resolving `modulepath:` URIs. Paths are separated by the platform-specific path separator (`:` on *nix, `;` on Windows). Relative paths are resolved against the working directory. ==== .-p, --property [%collapsible] ==== Default: (none) + Example: `myProp=myValue` + Sets an external property that can be read by Pkl code with `read("prop:")`. Repeat this option to set multiple external properties. ==== .--root-dir [%collapsible] ==== Default: (none) + Example: `/some/path` + Root directory for `file:` modules and resources. If set, access to file-based modules and resources is restricted to those located under the specified root directory. Any symlinks are resolved before this check is performed. ==== .--settings [%collapsible] ==== 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. ==== .-t, --timeout [%collapsible] ==== Default: (none) + Example: `30` + Duration, in seconds, after which evaluation of a source module will be timed out. Note that a timeout is treated the same as a program error in that any subsequent source modules will not be evaluated. ==== .-w, --working-dir [%collapsible] ==== Base path that relative module paths passed as command-line arguments are resolved against. Defaults to the current working directory. ==== .--ca-certificates [%collapsible] ==== Default: (none) + Example: `/some/path/certificates.pem` + Path to a file containing CA certificates to be used for TLS connections. Setting this option replaces the existing set of CA certificates bundled into the CLI. Certificates need to be X.509 certificates in PEM format. For other methods of configuring certificates, see xref:pkl-cli:index.adoc#ca-certs[CA Certificates]. ==== .--http-proxy [%collapsible] ==== Default: (none) + Example: `\http://proxy.example.com:1234` + Configures HTTP connections to connect to the provided proxy address. The URI must have scheme `http`, and may not contain anything other than a host and port. ==== .--http-no-proxy [%collapsible] ==== Default: (none) + Example: `example.com,169.254.0.0/16` + Comma separated list of hosts to which all connections should bypass the proxy. Hosts can be specified by name, IP address, or IP range using https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation[CIDR notation]. ==== .--http-rewrite [%collapsible] ==== Default: (none) + Example: `\https://pkg.pkl-lang.org/=https://my.internal.mirror/` + Replace outbound HTTP(S) requests from one URL with another URL. The left-hand side describes the source prefix, and the right-hand describes the target prefix. This option is commonly used to enable package mirroring. The above example will rewrite URL `\https://pkg.pkl-lang.org/pkl-k8s/k8s@1.0.0` to `\https://my.internal.mirror/pkl-k8s/k8s@1.0.0`. ==== .--trace-mode [%collapsible] ==== Default: `compact` + Specifies how `trace()` output is formatted. Possible options are `compact` and `pretty`. ==== ================================================ FILE: docs/modules/pkl-cli/partials/cli-project-options.adoc ================================================ [[project-dir]] .--project-dir [%collapsible] ==== Default: (none) + Example: `/some/path` + Directory where the project lives. A project is a directory that contains a `PklProject` file, which is used to declare package dependencies, as well as common evaluator settings to be applied in the project. If omitted, this is determined by searching up from the working directory for a directory that contains a `PklProject` file, until `--root-dir` or the file system root is reached. ==== [[omit-project-settings]] .--omit-project-settings [%collapsible] ==== Disables loading evaluator settings from the PklProject file. ==== [[no-project]] .--no-project [%collapsible] ==== Disables all behavior related to projects. ==== ================================================ FILE: docs/modules/pkl-core/examples/CoreEvaluatorExample.java ================================================ import org.pkl.core.Evaluator; import org.pkl.core.ModuleSource; import java.util.List; import org.pkl.core.PModule; import org.pkl.core.PObject; import org.junit.jupiter.api.Test; // the pkl-jvm-examples repo has a similar example @SuppressWarnings({"unchecked", "unused", "ConstantConditions", "NewClassNamingConvention"}) public class CoreEvaluatorExample { @Test public void usage() { // tag::usage[] PModule module; try (var evaluator = Evaluator.preconfigured()) { // <1> module = evaluator.evaluate( ModuleSource.text("pigeon { age = 30; hobbies = List(\"swimming\", \"surfing\") }")); // <2> } var pigeon = (PObject) module.get("pigeon"); // <3> var className = pigeon.getClassInfo().getQualifiedName(); // <4> var hobbies = (List) pigeon.get("hobbies"); // <5> // end::usage[] } } ================================================ FILE: docs/modules/pkl-core/pages/index.adoc ================================================ = pkl-core Library include::ROOT:partial$component-attributes.adoc[] :uri-pkl-core-maven-module: {uri-maven-docsite}/artifact/org.pkl-lang/pkl-core :uri-pkl-core-main-sources: {uri-github-tree}/pkl-core/src/main/java/org/pkl/core :uri-pkl-core-test-sources: {uri-github-tree}/pkl-core/src/test/kotlin/org/pkl/core :uri-pkl-core-Evaluator: {uri-pkl-core-main-sources}/Evaluator.java :uri-pkl-core-PModule: {uri-pkl-core-main-sources}/PModule.java :uri-pkl-core-PklException: {uri-pkl-core-main-sources}/PklException.java :uri-pkl-core-ValueVisitor: {uri-pkl-core-main-sources}/ValueVisitor.java :uri-pkl-core-JsonRenderer: {uri-pkl-core-main-sources}/JsonRenderer.java :uri-pkl-core-PcfRenderer: {uri-pkl-core-main-sources}/PcfRenderer.java :uri-pkl-core-PListRenderer: {uri-pkl-core-main-sources}/PListRenderer.java :uri-pkl-core-YamlRenderer: {uri-pkl-core-main-sources}/YamlRenderer.java :uri-pkl-core-SecurityManagers: {uri-pkl-core-main-sources}/SecurityManagers.java :uri-pkl-core-ModuleKeyFactories: {uri-pkl-core-main-sources}/module/ModuleKeyFactories.java The _pkl-core_ library contains the Pkl parser, evaluator, REPL server, and xref:ROOT:standard-library.adoc[Standard Library]. It is the foundation for most of Pkl's other libraries and tools. The library can also be used to embed Pkl in Java libraries and applications. [[pkl-core-installation]] == Installation The _pkl-core_ library is available {uri-pkl-core-maven-module}[from Maven Central]. It requires Java 17 or higher. === Gradle To use the library in a Gradle project, declare the following dependency: [tabs] ==== Kotlin:: + .build.gradle.kts [source,kotlin,subs="+attributes"] ---- dependencies { implementation("org.pkl-lang:pkl-core:{pkl-artifact-version}") } repositories { ifdef::is-release-version[] mavenCentral() endif::[] ifndef::is-release-version[] maven(url = "{uri-sonatype}") endif::[] } ---- Groovy:: + .build.gradle [source,groovy,subs="+attributes"] ---- dependencies { implementation "org.pkl-lang:pkl-core:{pkl-artifact-version}" } repositories { ifdef::is-release-version[] mavenCentral() endif::[] ifndef::is-release-version[] maven { url "{uri-sonatype}" } endif::[] } ---- ==== === Maven To use the library in a Maven project, declare the following dependency: .pom.xml [source,xml,subs="+attributes"] ---- org.pkl-lang pkl-core {pkl-artifact-version} ifndef::is-release-version[] sonatype-s01 Sonatype S01 {uri-sonatype} endif::[] ---- == Usage {uri-pkl-core-Evaluator}[`Evaluator`] is the core evaluator that exposes multiple methods of evaluation. The main evaluation method is `evaluate`, which returns a Java representation of the Pkl module object. If evaluation succeeds, a {uri-pkl-core-PModule}[`PModule`] object representing the fully evaluated module is returned. Otherwise, an {uri-pkl-core-PklException}[`PklException`] with error details is thrown. Let's look at an example: [[config-evaluator-core-example]] [source,java,indent=0] ---- include::{examplesdir}/CoreEvaluatorExample.java[tags=usage] ---- <1> Build an `Evaluator` with default configuration. The evaluator should be closed once it is no longer needed. In this example, this is done with a try-with-resources statement. Note that objects returned by the evaluator remain valid after calling `close()`. <2> Build a `ModuleSource` using the given text as the module's contents. Evaluate the given module source. Alternatively, it's possible to build a `ModuleSource` from a file, path, uri, and other sources. <3> Get the module's `"pigeon"` property, which is represented as `PObject` in Java. <4> Get the class name for this object. In this example, the class name is `pkl.base#Dynamic`. <5> Get pigeon's `"diet"` property, which is represented as `List` in Java. [[value-visitor]] Often, {uri-pkl-core-ValueVisitor}[`ValueVisitor`] is a better way to process a module. See {uri-pkl-core-PcfRenderer}[`PcfRenderer`], {uri-pkl-core-JsonRenderer}[`JsonRenderer`], {uri-pkl-core-YamlRenderer}[`YamlRenderer`] and {uri-pkl-core-PListRenderer}[`PListRenderer`] for examples. [[security-manager-spi]] The (Pkl, not Java) security manager can be configured and customized using {uri-pkl-core-SecurityManagers}[`SecurityManagers`] and related classes. [[module-loader-spi]] Module loaders can be configured and customized using {uri-pkl-core-ModuleKeyFactories}[`ModuleKeyFactories`] and related classes. == Further Information Refer to the Javadoc and sources published with the library, or browse the library's {uri-pkl-core-main-sources}[main] and {uri-pkl-core-test-sources}[test] sources. ================================================ FILE: docs/modules/pkl-doc/pages/index.adoc ================================================ = Pkldoc include::ROOT:partial$component-attributes.adoc[] :uri-pkl-doc-maven: {uri-maven-docsite}/artifact/org.pkl-lang/pkl-doc :uri-DocsiteInfo: {uri-pkl-stdlib-docs}/DocsiteInfo/ :uri-DocPackageInfo: {uri-pkl-stdlib-docs}/DocPackageInfo/ :uri-CliDocGenerator: {uri-pkl-doc-main-sources}/CliDocGenerator.kt :uri-DocGenerator: {uri-pkl-doc-main-sources}/DocGenerator.kt :uri-pkldoc-macos-amd64-download: {uri-sonatype-snapshot-download}&a=pkldoc-macos-amd64&e=bin :uri-pkldoc-macos-aarch64-download: {uri-sonatype-snapshot-download}&a=pkldoc-macos-aarch64&e=bin :uri-pkldoc-linux-amd64-download: {uri-sonatype-snapshot-download}&a=pkldoc-linux-amd64&e=bin :uri-pkldoc-linux-aarch64-download: {uri-sonatype-snapshot-download}&a=pkldoc-linux-aarch64&e=bin :uri-pkldoc-alpine-download: {uri-sonatype-snapshot-download}&a=pkldoc-alpine-linux-amd64&e=bin :uri-pkldoc-windows-download: {uri-sonatype-snapshot-download}&a=pkldoc-windows-amd64&e=exe :uri-pkldoc-java-download: {uri-sonatype-snapshot-download}&a=pkldoc-java&e=jar ifdef::is-release-version[] :uri-pkldoc-macos-amd64-download: {github-releases}/pkldoc-macos-amd64 :uri-pkldoc-macos-aarch64-download: {github-releases}/pkldoc-macos-aarch64 :uri-pkldoc-linux-amd64-download: {github-releases}/pkldoc-linux-amd64 :uri-pkldoc-linux-aarch64-download: {github-releases}/pkldoc-linux-aarch64 :uri-pkldoc-alpine-download: {github-releases}/pkldoc-alpine-linux-amd64 :uri-pkldoc-windows-download: {github-releases}/pkldoc-windows-amd64.exe :uri-pkldoc-java-download: {github-releases}/jpkldoc endif::[] _Pkldoc_ is a documentation website generator that produces navigable and searchable API documentation for Pkl modules. Pkldoc's look and feel is inspired by Scaladoc. To get a first impression, browse the link:{uri-pkl-stdlib-docs-index}[Standard Library API Docs]. == Features Pkldoc offers the following features: Code navigation:: Easily navigate between hyperlinked modules, classes, functions, and properties. Member search:: Search the entire documentation by member name. See the next section for details. Comment folding:: Expand and collapse multi-paragraph doc comments. Markdown support:: Write doc comments in Markdown. See xref:language-reference:index.adoc#doc-comments[Doc Comments] for details. Member links:: Link to other members from your doc comments. See xref:language-reference:index.adoc#member-links[Member Links] for details. Member anchors:: Get a member's deep link by clicking its anchor symbol and copying the URL in the address bar. Cross-site links:: Enable cross-site member links simply by providing the URLs of other Pkldoc websites such as the standard library docs. [[member-search]] === Member Search To get a first impression of Pkldoc's member search, let's try and find property `MinFiniteFloat` in the link:{uri-pkl-stdlib-docs-index}[standard library docs]: image::pkldoc-search.gif[title="Searching the standard library docs."] To start a search, press kbd:[s]. Search results are displayed as you type. To limit the search to a particular kind of member, prefix the search term with _m:_ for modules, _c:_ for classes, _f:_ for functions, or _p:_ for properties. For example, search term _p:min_ finds property `MinFiniteFloat` but not function `min()`. Camel case matching is always enabled and does not require capitalizing the search term. For example, search term _mff_ matches properties `MinFiniteFloat` and `MaxFiniteFloat`. Both search terms and member names may contain non-ASCII Unicode characters. As characters are normalized to their base form, search term _res_ matches `Réseau`. The `@AlsoKnownAs` annotation, defined and used throughout the _pkl.base_ module, documents alternative names for a member used in other programming languages or earlier versions of a module. Pkldoc's search takes these alternative names into account. For example, searching the standard library docs for _count_ or _size_ finds property `String.length`. Feel free to use `@AlsoKnownAs` in your own modules. Search results are categorized into _exact_ and _other_ (partial) matches. On module and class pages, additional categories show matches in the same module and class. Within a category, results are ranked by similarity with the search term. To navigate to a search result, either click the result or select it with the up/down arrow keys and press kbd:[Enter]. == Installation Pkldoc is offered as Gradle plugin, Java library, and CLI. === Gradle Plugin See xref:pkl-gradle:index.adoc#installation[Installation] in the _Gradle Plugin_ chapter. [[install-library]] === Java Library The `pkl-doc` library is available {uri-pkl-doc-maven}[from Maven Central]. It requires Java 17 or higher. ifndef::is-release-version[] NOTE: Snapshots are published to repository `{uri-snapshot-repo}`. endif::[] ==== Gradle To use the library in a Gradle project, declare the following dependency: [tabs] ==== Kotlin:: + .build.gradle.kts [source,kotlin,subs="+attributes"] ---- dependencies { implementation("org.pkl-lang:pkl-doc:{pkl-artifact-version}") } repositories { ifdef::is-release-version[] mavenCentral() endif::[] ifndef::is-release-version[] maven(url = "{uri-sonatype}") endif::[] } ---- Groovy:: + .build.gradle [source,groovy,subs="+attributes"] ---- dependencies { implementation "org.pkl-lang:pkl-doc:{pkl-artifact-version}" } repositories { ifdef::is-release-version[] mavenCentral() endif::[] ifndef::is-release-version[] maven { url "{uri-sonatype}" } endif::[] } ---- ==== ==== Maven To use the library in a Maven project, declare the following dependency: .pom.xml [source,xml,subs="+attributes"] ---- org.pkl-lang pkl-doc {pkl-artifact-version} ifndef::is-release-version[] sonatype-s01 Sonatype S01 {uri-sonatype} endif::[] ---- [[install-cli]] === CLI The CLI comes in multiple flavors: * Native macOS executable for amd64 (tested on macOS 10.15) * Native Linux executable for amd64 * Native Linux executable for aarch64 * Native Alpine Linux executable for amd64 (cross-compiled and tested on Oracle Linux 8) * Native Windows executable for amd64 (tested on Windows Server 2022) * Java executable (tested with Java 17/21 on macOS and Oracle Linux) .What is the Difference Between the Linux and Alpine Linux Executables? [NOTE] ==== The Linux executable is dynamically linked against _glibc_ and _libstdc{plus}{plus}_, whereas, the Alpine Linux executable is statically linked against _musl libc_ and _libstdc{plus}{plus}_. ==== The Java executable works on multiple platforms and has a smaller binary size than the native executables. However, it requires a Java 17 (or higher) runtime on the system path, and has a noticeable startup delay. Download links: * macOS aarch64: {uri-pkldoc-macos-aarch64-download} * macOS amd64: {uri-pkldoc-macos-amd64-download} * Linux aarch64: {uri-pkldoc-linux-aarch64-download} * Linux amd64: {uri-pkldoc-linux-amd64-download} * Alpine Linux amd64: {uri-pkldoc-alpine-download} * Windows amd64: {uri-pkldoc-windows-download} [[usage]] == Usage The Pkldoc tool is offered as Gradle plugin, Java library, and CLI. It can generate documentation either for modules directly, or generate documentation for _package uris_. The tool requires an argument of a module named _docsite-info.pkl_, that amends link:{uri-DocsiteInfo}[pkl.DocsiteInfo]. [discrete] ==== Generating documentation for modules directly Modules can be passed directly to Pkldoc for documentation generation. When generating documentation for these modules, there must also be a module named _doc-package-info.pkl_ that amends link:{uri-DocPackageInfo}[pkl.DocPackageInfo]. The _doc-package-info.pkl_ module defines a _doc package_, which describes how modules are grouped and versioned together. When generating documentation for modules, each such module must declare a module name that starts with a package name declared in a _doc-package-info.pkl_ module. For example, the following are valid module declarations for package _com.example_: * `module com.example.Birds` * `module com.example.Birds.Parrot` The part of the module name that comes after the package name must match the module's relative path in its source code repository. For example, module _com.example.Bird.Parrot_ is expected to be found at _$sourceCode/Bird/Parrot.pkl_, where _sourceCode_ is configured in _doc-package-info.pkl_. [discrete] ==== Generating documentation for a _package_ Pkldoc can alternatively generate documentation for a _package_. When generating documentation for a package, the URI of the package must be passed as an argument to Pkldoc. These packages must already be published and downloadable. When generating documentation for packages, modules within a package must declare a module name that is prefixed by the package's name declared in the `Package.name` property of its `PklProject` file. For example, the following are valid module declarations for package `com.example`: * `module com.example.Birds` * `module com.example.Birds.Parrot` The part of the module name that comes after the package name must match the module's relative path in its source code repository. For example, module _com.example.Bird.Parrot_ is expected to be found at _$sourceCode/Bird/Parrot.pkl_, where _sourceCode_ is configured in the `Package.sourceCode` property of its `PklProject` file. === Gradle Plugin See xref:pkl-gradle:index.adoc#pkldoc-generation[Pkldoc Generation] in the _Gradle Plugin_ chapter. === Java Library The Java library offers two APIs: * A high-level link:{uri-CliDocGenerator}[CliDocGenerator] API whose feature set corresponds to the CLI. * A low-level link:{uri-DocGenerator}[DocGenerator] API that offers additional features and control. For more information, refer to the Javadoc documentation. === CLI *Synopsis:* `pkldoc [] ` ``:: The absolute or relative URIs of docsite descriptors, package descriptors, and the modules for which to generate documentation. Relative URIs are resolved against the working directory. ==== Options .-o, --output-dir [%collapsible] ==== Default: (none) + Example: `pkldoc` + The directory where generated documentation is placed. ==== .--no-symlinks [%collapsible] ==== Create copies of files and directories instead of symbolic links. In particular, this affects how the "current" directories containing documentation content for the last generated version should be created. By default, a symbolic link is created pointing to the last generated version. If symlinks are disabled, a full copy of the last generated version is created. ==== Common CLI options: include::../../pkl-cli/partials/cli-common-options.adoc[] [[full-example]] == Full Example For a ready-to-go example with full source code and detailed walkthrough, see link:{uri-pkldoc-example}[pkldoc] in the _pkl-jvm-examples_ repository. ================================================ FILE: docs/modules/pkl-gradle/pages/index.adoc ================================================ = Gradle Plugin include::ROOT:partial$component-attributes.adoc[] :uri-pkl-gradle-maven-module: {uri-maven-docsite}/artifact/org.pkl-lang/org.pkl-lang.gradle.plugin :uri-pkl-gradle-main-sources: {uri-github-tree}/pkl-gradle/src/main/java/org/pkl/gradle :uri-pkl-gradle-Eval: {uri-pkl-gradle-main-sources}/Eval.java :uri-pkl-gradle-JavaCodeGen: {uri-pkl-gradle-main-sources}/JavaCodeGen.java :uri-pkl-gradle-KotlinCodeGen: {uri-pkl-gradle-main-sources}/KotlinCodeGen.java :uri-pkl-gradle-Pkldoc: {uri-pkl-gradle-main-sources}/Pkldoc.java The Gradle plugin offers the following features: * <> * <> * <> * <> Plugin versions coincide with Pkl versions. That is, plugin version `x.y.z` uses Pkl version `x.y.z`. [[installation]] == Installation The Gradle plugin is available {uri-pkl-gradle-maven-module}[from Maven Central]. It requires Java 17 or higher and Gradle 8.1 or higher. Earlier Gradle versions are not supported. ifndef::is-release-version[] NOTE: Snapshots are published to repository `{uri-snapshot-repo}`. endif::[] The plugin is applied as follows: [tabs] ==== Kotlin:: + .build.gradle.kts [source,kotlin,subs="+attributes"] ---- plugins { id("org.pkl-lang") version "{pkl-artifact-version}" } ---- + .settings.gradle.kts [source,kotlin,subs="+attributes"] ---- pluginManagement { repositories { ifdef::is-release-version[] mavenCentral() endif::[] ifndef::is-release-version[] maven(url = "{uri-sonatype}") endif::[] } } ---- Groovy:: + .build.gradle [source,groovy,subs="+attributes"] ---- plugins { id "org.pkl-lang" version "{pkl-artifact-version}" } ---- + .settings.gradle [source,groovy,subs="+attributes"] ---- pluginManagement { repositories { ifdef::is-release-version[] mavenCentral() endif::[] ifndef::is-release-version[] maven { url "{uri-sonatype}" } endif::[] } } ---- ==== [[module-evaluation]] == Module Evaluation This feature integrates the xref:pkl-cli:index.adoc[Pkl evaluator] into Gradle builds. === Usage To add an evaluator to the build, add a named configuration block inside `pkl.evaluators`: [tabs] ==== build.gradle:: + [source,groovy] ---- pkl { evaluators { evalPkl { sourceModules.add(file("module1.pkl")) outputFile = layout.buildDirectory.file("module1.yaml") outputFormat = "yaml" } } } ---- build.gradle.kts:: + [source,kotlin] ---- pkl { evaluators { register("evalPkl") { sourceModules.add(file("module1.pkl")) outputFile.set(layout.buildDirectory.file("module1.yaml")) outputFormat.set("yaml") } } } ---- ==== For each declared evaluator, the Pkl plugin creates an equally named task. Hence the above evaluator can be run with: [source,shell script] ---- $ ./gradlew evalPkl ---- For a ready-to-go example with full source code, see link:{uri-build-eval-example}[codegen-java] in the _pkl-jvm-examples_ repository. === Configuration Options [[output-format]] .outputFormat: Property [%collapsible] ==== Default: `"pcf"` + Example: `outputFormat = "yaml"` + The output format to generate. The default output renderer for a module supports the following formats: * `"json"` * `"jsonnet"` * `"pcf"` * `"plist"` * `"properties"` * `"textproto"` * `"xml"` * `"yaml"` ==== [[output-file]] .outputFile: RegularFileProperty [%collapsible] ==== Default: `file("%\{moduleDir}/%\{moduleName}.%\{outputFormat}")` (places output files next to the source modules) + Example: `outputFile = layout.projectDirectory.file("config.yaml")` + The file path where the output file is placed. Relative paths are resolved against the project directory. If multiple source modules are given, placeholders can be used to map them to different output files. The following placeholders are supported: `%\{moduleDir}`::: The directory path of the module, relative to the working directory. Only available when evaluating file-based modules. `%\{moduleName}`::: The simple module name as inferred from the module URI. For hierarchical module URIs such as `+file:///foo/bar/baz.pkl+`, this is the last path segment without file extension. `%\{outputFormat}`::: The requested output format. Only available if `outputFormat` is set. If multiple sources modules are mapped to the same output file, their outputs are concatenated. By default, module outputs are separated with `---`, as in a YAML stream. // suppress inspection "AsciiDocLinkResolve" The separator can be customized using the link:#module-output-separator[`moduleOutputSeparator`] option. ==== [[multiple-file-output-dir]] .multipleFileOutputDir: DirectoryProperty [%collapsible] ==== Example 1: `multipleFileOutputDir = layout.projectDirectory.dir("output")` + Example 2: `+multipleFileOutputDir = layout.projectDirectory.file("%{moduleDir}/output")+` The directory where a module's output files are placed. Setting this option causes Pkl to evaluate a module's `output.files` property and write the files specified therein. Within `output.files`, a key determines a file's path relative to `multipleFileOutputDir`, and a value determines the file's contents. This option cannot be used together with any of the following: * xref:output-file[outputFile] * xref:expression[expression] This option supports the same placeholders as xref:output-file[outputFile]. For additional details, see xref:language-reference:index.adoc#multiple-file-output[Multiple File Output] in the language reference. ==== [[module-output-separator]] .moduleOutputSeparator: Property [%collapsible] ==== Default: `"---"` (as in a YAML stream) + The separator to use when multiple module outputs are written to the same file. ==== [[expression]] .expression: Property [%collapsible] ==== Default: (none) + Example: `expression = "topLevelProperty.subValue"` + The expression to be evaluated within the module. This option causes Pkl to evaluate the provided expression instead of the module's `output.text` or `output.files` properties. The resulting value is then stringified, and written to the designated output file. For example, consider the following Pkl module: .my-pod.pkl [source%tested,{pkl}] ---- metadata { name = "my-pod" } ---- The expression `metadata.name` evaluates to text `my-pod`. ==== Common properties: include::../partials/gradle-modules-properties.adoc[] [[tests]] == Tests This feature integrates the xref:pkl-cli:index.adoc#usage[Pkl test evaluator] into Gradle builds. === Usage To add tests to the build, add a named configuration block inside `pkl.tests`: [tabs] ==== build.gradle:: + [source,groovy] ---- pkl { tests { testPkl { sourceModules.add(files("module1_test.pkl", "module2_test.pkl")) junitReportsDir = layout.buildDirectory.dir("reports") overwrite = false } } } ---- build.gradle.kts:: + [source,kotlin] ---- pkl { tests { register("testPkl") { sourceModules.addAll(files("module1_test.pkl", "module2_test.pkl")) junitReportsDir.set(layout.buildDirectory.dir("reports")) overwrite.set(false) } } } ---- ==== [[junit-reports-path]] .junitReportsDir: DirectoryProperty [%collapsible] ==== Default: `null` + Example: `junitReportsDir = layout.buildDirectory.dir("reports")` + Whether and where to generate JUnit XML reports. ==== [[junit-aggregate-reports]] .junitAggregateReports: Property [%collapsible] ==== Default: `false` + Aggregate JUnit reports into a single file. ==== [[junit-aggregate-suite-name]] .junitAggregateSuiteName: Property [%collapsible] ==== Default: `null` + The name of the root JUnit test suite. ==== [[overwrite]] .overwrite: Property [%collapsible] ==== Default: `false` + Whether to ignore expected example files and generate them again. ==== [[power-assertions-test]] .powerAssertions: Property [%collapsible] ==== Default: `true` (for test tasks) + Example: `powerAssertions = false` + Enable or disable power assertions for detailed assertion failure messages. When enabled, test failures will show intermediate values in the assertion expression, making it easier to understand why a test failed. ==== Common properties: include::../partials/gradle-modules-properties.adoc[] [[java-code-gen]] == Java Code Generation This feature integrates the xref:java-binding:codegen.adoc[Java code generator] into Gradle builds. === Usage To add a Java code generator to the build, add a named configuration block inside `pkl.javaCodeGenerators`: [tabs] ==== build.gradle:: + [source,groovy] ---- pkl { javaCodeGenerators { genJava { sourceModules.addAll(files("Template1.pkl", "Template2.pkl")) } } } ---- build.gradle.kts:: + [source,kotlin] ---- pkl { javaCodeGenerators { register("genJava") { sourceModules.addAll(files("Template1.pkl", "Template2.pkl")) } } } ---- ==== To compile generated classes together with test code rather than main code, use `sourceSet = sourceSets.test`. To generate getter methods instead of public final fields, use `generateGetters = true`. For each declared Java code generator, the Pkl plugin creates an equally named task. Hence, the above generator can be run with: [source,shell script] ---- $ ./gradlew genJava ---- For a ready-to-go example with full source code, see link:{uri-codegen-java-example}[codegen-java] in the _pkl-jvm-examples_ repository. === Configuration Options .generateGetters: Property [%collapsible] ==== Default: `false` + Example: `generateGetters = true` + Whether to generate private final fields and public getter methods rather than public final fields. ==== .paramsAnnotation: Property [%collapsible] ==== Default: `null` if `generateSpringBootConfig` is `true`, `"org.pkl.config.java.mapper.Named"` otherwise+ Example: `paramsAnnotation = "org.project.MyAnnotation"` + Fully qualified name of the annotation type to use for annotating constructor parameters with their name. + The specified annotation type must have a `value` parameter of type `String` or the generated code may not compile. If set to `null`, constructor parameters are not annotated. Whether and how constructor parameters should be annotated depends on the library that instantiates the generated classes. For Spring Boot applications, and for users of `pkl-config-java` compiling the generated classes with `-parameters`, no annotation is required. ==== .nonNullAnnotation: Property [%collapsible] ==== Default: `"org.pkl.config.java.mapper.NonNull"` + Example: `nonNullAnnotation = "org.project.MyAnnotation"` + Fully qualified name of the annotation type to use for annotating non-null types. + The specified annotation type must be annotated with `@java.lang.annotation.Target(ElementType.TYPE_USE)` or the generated code may not compile. ==== Common code generation properties: include::../partials/gradle-codegen-properties.adoc[] Common properties: include::../partials/gradle-modules-properties.adoc[] [[kotlin-code-gen]] == Kotlin Code Generation This feature integrates the xref:kotlin-binding:codegen.adoc[Kotlin code generator] into Gradle builds. === Usage To add a Kotlin code generator to the build, add a named configuration block inside `pkl.kotlinCodeGenerators`: [tabs] ==== build.gradle:: + [source,groovy] ---- pkl { kotlinCodeGenerators { genKotlin { sourceModules.addAll(files("Template1.pkl", "Template2.pkl")) } } } ---- build.gradle.kts:: + [source,kotlin] ---- pkl { kotlinCodeGenerators { register("genKotlin") { sourceModules.addAll(files("Template1.pkl", "Template2.pkl")) } } } ---- ==== To compile generated classes together with test code rather than main code, use `sourceSet = sourceSets.test`. For each declared Kotlin code generator, the Pkl plugin creates an equally named task. Hence the above generator can be run with: [source,shell script] ---- $ ./gradlew genKotlin ---- For a ready-to-go example with full source code, see link:{uri-codegen-kotlin-example}[codegen-kotlin] in the _pkl-jvm-examples_ repository. === Configuration Options === Configuration Options .generateKdoc: Property [%collapsible] ==== Default: `false` + Example: `generateKdoc = true` + Whether to preserve Pkl doc comments by generating corresponding KDoc comments. ==== Common code generation properties: include::../partials/gradle-codegen-properties.adoc[] Common properties: include::../partials/gradle-modules-properties.adoc[] [[pkldoc-generation]] == Pkldoc generation This features integrates the xref:pkl-doc:index.adoc[Pkldoc] generator into Gradle builds. === Usage To add a Pkldoc generator to the build, add a named configuration block inside `pkl.pkldocGenerators`: [tabs] ==== build.gradle:: + [source,groovy] ---- pkl { pkldocGenerators { pkldoc { sourceModules.addAll(files("doc-package-info.pkl", "Template1.pkl", "Template2.pkl")) } } } ---- build.gradle.kts:: + [source,kotlin] ---- pkl { pkldocGenerators { register("pkldoc") { sourceModules.addAll(files("doc-package-info.pkl", "Template1.pkl", "Template2.pkl")) } } } ---- ==== For each declared Pkldoc generator, the Pkl plugin creates an equally named task. Hence, the above generator can be run with: [source,shell script] ---- $ ./gradlew pkldoc ---- For a ready-to-go example with full source code, see link:{uri-pkldoc-example}[pkldoc] in the _pkl-jvm-examples_ repository. === Configuration Options The following properties can be configured inside a Pkldoc generator's configuration block: .outputDir: DirectoryProperty [%collapsible] ==== Default: `layout.buildDirectory.dir("pkldoc/")` + Example: `outputDir = layout.projectDirectory.dir("pkl-docs")` + The directory where generated documentation is placed. ==== .noSymlinks: Property [%collapsible] ==== Default: `false` + Example: `noSymlinks = true` + Create copies of files and directories instead of symbolic links. In particular, this affects how the "current" directories containing documentation content for the last generated version should be created. By default, a symbolic link is created pointing to the last generated version. If symlinks are disabled, a full copy of the last generated version is created. ==== Common properties: include::../partials/gradle-modules-properties.adoc[] [[project-package]] == Project packaging This feature is the Gradle analogy for the xref:pkl-cli:index.adoc#command-project-package[project package] command in the CLI. It prepares package assets to be published from a project. There are two differences between this feature and the CLI: * Input project directories are required (the CLI determines a project from the current working directory if arguments are omitted). * Output directory defaults to a path within the build directory. === Usage [tabs] ==== build.gradle:: + [source,groovy] ---- pkl { project { packagers { makePackages { projectDirectories.from(file("pkl-config/")) } } } } ---- build.gradle.kts:: + [source,kotlin] ---- pkl { project { packagers { register("makePackages") { projectDirectories.from(file("pkl-config/")) } } } } ---- ==== For each declared packager, the Pkl plugin creates an equally named task. Hence, the above packager can be run with: [source,shell] ---- $ ./gradlew makePackages ---- === Configuration Options .projectDirectories: ConfigurableFileCollection [%collapsible] ==== Default: (none) + Example: `projectDirectories.from(file("pkl-config/""))` + The project directories to create packages for. ==== .skipPublishCheck: Property [%collapsible] ==== Default: (false) + Example: `skipPublishCheck.set(true)` Skips checking whether a package has already been published with different contents. By default, the packager will check whether a package at the same version has already been published. If the package has been published, it validates that the package's metadata is identical to the locally generated metadata. ==== .outputPath: DirectoryProperty [%collapsible] ==== Default: `project.getLayout().getBuildDirectory().dir("generated/pkl/packages")` The directory to write artifacts to. Accepts the following placeholders: `%\{name}`:: The name of the package `%\{version}`:: The version of the package ==== .junitReportsDir: DirectoryProperty [%collapsible] ==== Default: `null` + Example: `junitReportsDir = layout.buildDirectory.dir("reports")` + Whether and where to generate JUnit XML reports. ==== .overwrite: Property [%collapsible] ==== Default: `false` + Whether to ignore expected example files and generate them again. ==== Common properties: include::../partials/gradle-common-properties.adoc[] == Project Resolving This feature is the Gradle analogy for the xref:pkl-cli:index.adoc#command-project-resolve[project resolve] command in the CLI. It takes the dependencies of a project, and writes the resolved versions a file at path `PklProject.deps.json`, within the root directory of the project. === Usage [tabs] ==== build.gradle:: + [source,groovy] ---- pkl { project { resolvers { resolvePklDeps { projectDirectories.from(file("pkl-config/")) } } } } ---- build.gradle.kts:: + [source,kotlin] ---- pkl { project { resolvers { register("resolvePklDeps") { projectDirectories.from(file("pkl-config/")) } } } } ---- ==== For each declared resolver, the Pkl plugin creates an equally named task. Hence, the above resolver can be run with: [source,shell] ---- $ ./gradlew resolvePklDeps ---- === Configuration Options .projectDirectories: ConfigurableFileCollection [%collapsible] ==== Default: (none) + Example: `projectDirectories.from(file("pkl-config/""))` + The project directories to create packages for. ==== Common properties: include::../partials/gradle-common-properties.adoc[] [[analyze-imports]] == Analyze Imports This feature is the Gradle analogy for the xref:pkl-cli:index.adoc#command-analyze-imports[analyze imports] command in the CLI. It builds a graph of imports of the provided source modules. === Usage [tabs] ==== build.gradle:: + [source,groovy] ---- pkl { analyzers { imports { appConfig { sourceModules.add(file("src/main/resources/appConfig.pkl")) } } } } ---- build.gradle.kts:: + [source,kotlin] ---- pkl { analyzers { imports { register("appConfig") { sourceModules.add(file("src/main/resources/appConfig.pkl")) } } } } ---- ==== === Configuration Options .outputFormat: Property [%collapsible] ==== Same meaning as <> in <>. ==== .outputFile: RegularFileProperty [%collapsible] ==== Same meaning as <> in <>. ==== Common properties: include::../partials/gradle-modules-properties.adoc[] ================================================ FILE: docs/modules/pkl-gradle/partials/gradle-codegen-properties.adoc ================================================ .indent: Property [%collapsible] ==== Default: `" "` (two spaces) + Example: `indent = "\t"` (one tab) + The characters to use for indenting generated source code. ==== .outputDir: DirectoryProperty [%collapsible] ==== Default: `layout.buildDirectory.dir("generated/pkl/")` + Example: `outputDir = layout.projectDirectory.dir("src/main/pkl")` + The directory where generated classes are placed. The default places generated sources within the build directory of the project, to avoid sources from being committed into the repository on accident. ==== .sourceSet: Property [%collapsible] ==== Default: `sourceSets.main` (if it exists; no default otherwise) + Example: `sourceSet = sourceSets.test` + The Gradle source set that generated code is compiled together with. For the codegen tasks, the `modulePath` property defaults to the compilation classpath of this source set, as well as all of the source directories of the `resource` source directory set of this source set. This setup makes it possible to rely on modules defined in classpath dependencies of your project or in the resources of your project. For projects which apply the `idea` plugin and are opened in IntelliJ IDEA, this option determines whether generated sources are marked as test sources (if the source set's name contains the word "test") or regular sources (otherwise). ==== .generateSpringBootConfig: Property [%collapsible] ==== Default: `false` + Example: `generateSpringBootConfig = true` + Whether to generate config classes for use with Spring Boot. ==== .implementSerializable: Property [%collapsible] ==== Default: `false` + Example: `implementSerializable = true` + Whether to generate classes that implement `java.io.Serializable`. ==== .addGeneratedAnnotation: Property [%collapsible] ==== Default: `false` + Example: `addGeneratedAnnotation = true` + Whether to add the `org.pkl.config.java.Generated` annotation to generated types. ==== .renames: MapProperty [%collapsible] ==== Default: `[:]` + Example: `renames = ["foo.": "com.example.foo.", "bar.Config": "com.example.bar.Config"]` + Allows to change default class and package names (derived from Pkl module names) in the generated code. When you need the generated class or package names to be different from the default names derived from the Pkl module names, you can define a rename mapping, where the key is the original Pkl module name prefix, and the value is its replacement. When you do, the generated code's `package` declarations, class names, as well as file locations, will be modified according to this mapping. The prefixes are replaced literally, which means that dots at the end are important. If you want to rename packages only, in most cases, you must ensure that you have an ending dot on both sides of a mapping (except for an empty mapping, if you use it), otherwise you may get unexpected results: ---- // Assuming the following mapping configuration: renames = [ "com.foo.": "x", // Dot on the left only "org.bar": "y.", // Dot on the right only "net.baz": "z" // No dots ] // The following renames will be made: "com.foo.bar" -> "xbar" // Target prefix merged into the suffix "org.bar.baz" -> "y..baz" // Double dot, invalid name "net.baz.qux" -> "z.qux" // Looks okay, but... "net.bazqux" -> "zqux" // ...may cut the name in the middle. ---- When computing the appropriate target name, the longest matching prefix is used: ---- // Assuming the following mapping configuration: renames = [ "com.foo.Main": "w.Main", "com.foo.": "x.", "com.": "y.", "": "z." ] // The following renames will be made: com.foo.Main -> w.Main com.foo.bar -> x.bar com.baz.qux -> y.baz.qux org.foo.bar -> z.org.foo.bar ---- Keys in this mapping can be arbitrary strings, including an empty string. Values must be valid dot-separated fully qualifed class name prefixes, possibly terminated by a dot. ==== ================================================ FILE: docs/modules/pkl-gradle/partials/gradle-common-properties.adoc ================================================ .allowedModules: ListProperty [%collapsible] ==== Default: `["pkl:", "file:", "modulepath:", "https:", "repl:", "package:", "projectpackage:"]` + Example: `allowedModules = ["file:"]` + URI patterns that determine which modules can be loaded and evaluated. Patterns are matched against the beginning of module URIs. (File paths have been converted to `file:` URLs at this stage.) At least one pattern needs to match for a module to be loadable. ==== .allowedResources: ListProperty [%collapsible] ==== Default: `["env:", "prop:", "modulepath:", "https:", "file:", "package:", "projectpackage:"]` + Example: `allowedResources = ["env:", "prop:"]` + URL patterns that determine which external resources can be read. Patterns are matched against the beginning of resource URLs. At least one pattern needs to match for a resource to be readable. ==== .environmentVariables: MapProperty [%collapsible] ==== Default: `[:]` (note that Gradle default differs from CLI default) + Example 1: `environmentVariables = ["MY_VAR_1": "myValue1", "MY_VAR_2": "myValue2"]` + Example 2: `environmentVariables = System.getenv()` + Environment variables that can be read by Pkl code with `read("env:")`. ==== .evalRootDir: DirectoryProperty [%collapsible] ==== Default: `rootProject.layout.projectDirectory` + Example 1: `evalRootDir = layout.projectDirectory.dir("pkl-modules")` + Example 2: `evalRootDir.fileValue file("/some/absolute/path")` + Root directory for `file:` modules and resources. If non-null, access to file-based modules and resources is restricted to those located under the root directory. Any symlinks are resolved before this check is performed. ==== .evalTimeout: Property [%collapsible] ==== Default: `null` + Example: `evalTimeout = Duration.ofSeconds(10)` + Duration after which evaluation of a source module will be timed out. Note that a timeout is treated the same as a program error in that any subsequent source modules will not be evaluated. ==== .externalProperties: MapProperty [%collapsible] ==== Default: `[:]` + Example: `externalProperties = ["myProp1": "myValue1", "myProp2": "myValue2"]` + External properties that can be read by Pkl code with `read("prop:")`. ==== .moduleCacheDir: DirectoryProperty [%collapsible] ==== 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`. ==== .color: Property [%collapsible] ==== Default: `false` + Format messages using ANSI color. ==== .noCache: Property [%collapsible] ==== Default: `false` + Disable caching of packages. ==== .modulePath: ConfigurableFileCollection [%collapsible] ==== Default: `files()` (empty collection) + Example: `modulePath.from files("dir1", "zip1.zip", "jar1.jar")` + The directories, ZIP archives, or JAR archives to search when resolving `modulepath:` URIs. Relative paths are resolved against the project directory. ==== .proxy: Property [%collapsible] ==== Default: `null` + Example: `proxy = uri("http://proxy.example.com:1234")` + Configures HTTP connections to connect to the provided proxy address. The URI must have scheme `http`, and may not contain anything other than a host and port. ==== .noProxy: ListProperty [%collapsible] ==== Default: `null` + Example: `noProxy = ["example.com", "169.254.0.0/16"]` + Hosts to which all connections should bypass the proxy. Hosts can be specified by name, IP address, or IP range using https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation[CIDR notation]. ==== .httpRewrites: MapProperty [%collapsible] ==== Default: `null` + Example: `httpRewrites = [uri("https://pkg.pkl-lang.org/"): uri("https://my.internal.mirror/")]` + Replace outbound HTTP(S) requests from one URL with another URL. The left-hand side describes the source prefix, and the right-hand describes the target prefix. This option is commonly used to enable package mirroring. The above example will rewrite URL `\https://pkg.pkl-lang.org/pkl-k8s/k8s@1.0.0` to `\https://my.internal.mirror/pkl-k8s/k8s@1.0.0`. ==== .powerAssertions: Property [%collapsible] ==== Default: `false` (except for test tasks, which default to `true`) + Example: `powerAssertions = true` + Enable power assertions for detailed assertion failure messages. When enabled, type constraint failures will show intermediate values in the assertion expression. This option has a performance impact when constraint failures occur. ==== ================================================ FILE: docs/modules/pkl-gradle/partials/gradle-modules-properties.adoc ================================================ .sourceModules: ListProperty [%collapsible] ==== Default: `[]` + Example 1: `sourceModules = ["module1.pkl", "module2.pkl"]` + Example 2: `+sourceModules = fileTree("config").include("**/*.pkl")+` + List of Pkl modules which are used for this operation. This property accepts the following types to represent a module: * `java.net.URI` * `java.io.File` * `java.nio.file.Path` * `java.net.URL` * `java.lang.CharSequence` - if the represented string looks like a URI (it contains a scheme), the input is treated as a URI. Otherwise, it is treated as a path. Relative paths are resolved against the project directory. * `org.gradle.api.file.FileSystemLocation` ==== .transitiveModules: ConfigurableFileCollection [%collapsible] ==== Default: [computed by pkl-gradle] + Example 1: `transitiveModules.from files("module1.pkl", "module2.pkl")` + Example 2: `+transitiveModules.from fileTree("config").include("**/*.pkl")+` + File paths of modules that are directly or indirectly used by source modules. This property, along with `sourceModules`, is the set of input files used to determine whether this task is up-to-date or not. By default, Pkl computes this property by analyzing the imports of the source modules. Setting this property explicitly causes Pkl to skip the analyze imports step. Including source modules in `transitiveModules` is permitted but not required. Relative paths are resolved against the project directory. ==== .projectDir: DirectoryProperty [%collapsible] ==== Default: `null` + Example 1: `projectDir = layout.projectDirectory.dir("pkl")` + Example 2: `projectDir.fileValue file("/some/absolute/path")` Directory where the project lives. A project is a directory that contains a `PklProject` file, which is used to declare package dependencies, as well as common evaluator settings to be applied in the project. If `null`, this is determined by searching up from the working directory for a directory that contains a `PklProject` file, until `evalRootDir` or the file system root is reached. ==== .omitProjectSettings: Property [%collapsible] ==== Disables loading evaluator settings from the PklProject file. ==== .noProject: Property [%collapsible] ==== Disables all behavior related to projects. ==== .settingsModule: Property [%collapsible] ==== Default: `null` + 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. ==== include::../partials/gradle-common-properties.adoc[] ================================================ FILE: docs/modules/release-notes/pages/0.25.adoc ================================================ = Pkl 0.25 Release Notes :version: 0.25 :version-minor: 0.25.1 :release-date: February 1st, 2024 Pkl {version} was released on {release-date}. + [.small]#The latest bugfix release is {version-minor}. (xref:changelog.adoc[All Versions])# This is the initial release of Pkl, a programming language for creating configuration. To learn more about Pkl, refer to our xref:blog:ROOT:introducing-pkl.adoc[blog post]. ================================================ FILE: docs/modules/release-notes/pages/0.26.adoc ================================================ = Pkl 0.26 Release Notes :version: 0.26 :version-minor: 0.26.3 :release-date: June 17th, 2024 include::ROOT:partial$component-attributes.adoc[] Pkl {version} was released on {release-date}. + [.small]#The latest bugfix release is {version-minor}. (xref:changelog.adoc[All Versions])# This release brings Windows support, improvements to controlling how Pkl talks over HTTP, and also fixes a scoping bug around typealiases. The next release (0.27) is scheduled for October 10th, 2024. Please send feedback and questions to https://github.com/apple/pkl/discussions[GitHub Discussions], or submit an issue on https://github.com/apple/pkl/issues/new[GitHub]. + [small]#Pkl is hosted on https://github.com/apple/pkl[GitHub]. To get started, follow xref:pkl-cli:index.adoc#installation[Installation].# == Highlights [small]#💖# News you don't want to miss. [[windows-support]] === Windows Support Pkl is now available on Windows! (https://github.com/apple/pkl/pull/492[#492]) In addition to macOS and Linux, Pkl is now available for download on Windows operating systems. For installation instructions, consult xref:pkl-cli:index.adoc#windows-executable[Windows Executable] in the CLI reference. ==== Writing cross-platform Pkl programs On Windows, the in-language filepath separator is still `/`. This means that most existing Pkl code should still continue working as-is. A small portion of programs will need to be updated to handle Windows support correctly. One example is the various conversion scripts that read input files as CLI flags. For example, here is a snippet from module https://github.com/apple/pkl-pantry/blob/d9571cdce407bb7f1687f81b945a4bfd24ce017d/packages/k8s.contrib/convert.pkl#L100C1-L103C74[k8s.contrib.convert]: .convert.pkl#L100-L103 [source,pkl] ---- local inputUri = if (input.startsWith(Regex(#"\w+:"#))) input // absolute URI else if (input.startsWith("/")) "file://\(input)" // absolute file path else "file://\(read("env:PWD"))/\(input)" // relative file path ---- On Windows, the env var `PWD` is a Windows-style path, like `C:\Foo\Bar\Baz`. With the above logic, `inputUri` can expand to a value like `"\file://C:\Foo\Bar\Baz/input.yaml"`. This would an invalid URI; the correct URI should be `"\file:///C:/Foo/Bar/Baz/input.yaml"`. Here is a possible fix for this code: [source,pkl] ---- import "pkl:platform" local inputUri = if (input.startsWith(Regex(#"\w+:/"#))) input else if (input.startsWith("/")) "file://\(input)" else let (pwd = read("env:PWD")) let ( path = if (platform.current.operatingSystem.name == "Windows") "/\(pwd)/\(input)".replaceAll("\\", "/") else "\(pwd)/\(input)" ) "file://\(path)" ---- To learn more about this design, consult https://github.com/apple/pkl-evolution/blob/main/spices/SPICE-0006-import-paths-on-windows.adoc[SPICE-0006]. [[http-proxying]] === HTTP proxying Pkl now supports proxying HTTP(S) requests (https://github.com/apple/pkl/pull/506[#506]). For users of the CLI, Pkl will by default look for proxy settings configured within the OS when on macOS, Windows, and GNOME environments. This can be changed by adding settings in the following locations: 1. xref:pkl-cli:index.adoc#settings-file[Settings file] 2. xref:language-reference:index.adoc#projects[PklProject file] 3. `--http-proxy` and `--http-no-proxy` CLI flags As part of this, some changes have been made to the standard library. For details, see <>. Users of the Java/Kotlin bindings can specify proxy settings when building an <>. HTTP proxy settings are also added as new fields in the message-passing-api. Authors of external Pkl clients are encouraged to support these new fields to provide proxy support in their libraries. For details, see <>. To read more about the design of HTTP proxying, consult https://github.com/apple/pkl-evolution/blob/main/spices/SPICE-0004-HTTP-Proxy-Support.adoc[SPICE-0004], and also the xref:pkl-cli:index.adoc#http-proxy[documentation]. [[scheme-agnostic-project-dependencies]] === Scheme-agnostic project dependencies Improvements have been made to project dependencies (https://github.com/apple/pkl/pull/486[#486]). A project dependency is a way to manage `package`-based modules. It provides two uses: 1. A package can be imported through xref:language-reference:index.adoc#dependency-notation[dependency notation]. 2. Conflicting versions in the transitive dependency graph are resolved. Currently, dependencies can only be used with file-based modules. This can be limiting in some circumstances: 1. JVM library users typically interact with Pkl by bundling Pkl files a classpath resources, and load them using `modulepath:` URIs. 2. Go library users often embed Pkl code using the https://pkg.go.dev/embed#hdr-Directives[`go:embed` directive], and load them using https://pkg.go.dev/github.com/apple/pkl-go/pkl#WithFs[`WithFs`]. This means that these users miss out on the benefits of project dependencies. Pkl 0.26 adds support for using dependencies in any hierarchical module scheme. This means that `modulepath:` users in Java can now declare projects and dependencies. This also means that other custom methods of embedding modules can also use dependency notation. [NOTE] ==== A _hierarchical_ URI is a URI whose path part starts with `/`, and in which `/` delimits path segments. The URI `myscheme:/foo.pkl` is hierarchical, whereas the URI `myscheme:foo.pkl` is not. ==== To read more about this design, consult https://github.com/apple/pkl-evolution/tree/main/spices/SPICE-0005-scheme-agnostic-projects.adoc[SPICE-0005]. [[typealias-scoping-fix-and-change]] === Typealias scoping fix and change Improvements have been made to `typealias`es (https://github.com/apple/pkl/pull/144[#144], https://github.com/apple/pkl/pull/373[#373], https://github.com/apple/pkl/pull/516[#516]). Currently, a typealias that references a value on its enclosing module will break if used in another module. .myModule.pkl [source,pkl] ---- typealias StartsWithFoo = String(startsWith(foo)) foo = "foo" ---- [source,pkl] ---- import "myModule.pkl" myStr: StartsWithFoo = "fooey" ---- This results in error: [source] ---- –– Pkl Error –– Cannot find property `foo`. 1 | typealias StartsWithFoo = String(startsWith(foo)) ---- This is a language bug that has been fixed. In the process of fixing this bug, we realized that the rules around variable resolution within typealiases were problematic. Typealiases are meant to be statically defined, and shouldn't be able to see properties that can be late-bound. Thus, a new rule is introduced: a typealias can only reference properties/methods that are `const`. This is a breaking change. To read more about this breakage and remediation steps, reference <>. == Noteworthy [small]#🎶# Ready when you need them. [[pkldoc-improvements]] === Pkldoc improvements The following improvements have been made to xref:pkl-doc:index.adoc[Pkldoc]: 1. The documentation for the standard library no longer shows dependent packages, because every package implicitly depends on the standard library (https://github.com/apple/pkl/pull/503[#503]). 2. If the standard library exists in the package list, it is shown first (https://github.com/apple/pkl/pull/165[#165]). 3. Fewer characters are percent-encoded. For example, the `(` and `)` characters are no longer percent-encoded (https://github.com/apple/pkl/pull/489[#489]). [[standard-library-changes]] === Standard library changes To support <>, several changes have been made to the standard library (https://github.com/apple/pkl/pull/506[#506]). * Module `pkl.settings` has new property `http`. * class `EvaluatorSettings` originally in `pkl.Project` has been moved to its own module. For backwards compatibility, `pkl.Project` now has a `typealias EvaluatorSettings` pointing to the new module, but it is marked `@Deprecated`. === Java API Changes [[http-java-client]] ==== HTTP Java Client A new Java API is introduced, named `org.pkl.core.http.HttpClient` (https://github.com/apple/pkl/pull/217[#217], https://github.com/apple/pkl/pull/295[#295], https://github.com/apple/pkl/pull/506[#506], https://github.com/apple/pkl/pull/518[#518]). This interface provides the ability to manage how Pkl makes HTTP(S) calls, including how it deals with CA certificates, as well as proxies. This client can be passed to Pkl's evaluator using `EvaluatorBuilder` for users of pkl-core, and `ConfigEvaluatorBuilder` for users of pkl-config-java or pkl-config-kotlin. [[http-module-key-factory]] ==== New HTTP ModuleKeyFactory A new module key factory for HTTP(S) modules has been added (https://github.com/apple/pkl/pull/495[#495]), and can be built with `org.pkl.core.ModuleKeyFactories#http`. The preconfigured evaluator (`org.pkl.core.EvaluatorBuilder#preconfigured`) includes this module key. Users that build their own evaluator from scratch should add this module key factory if HTTP(S) modules are needed. Example: [source,diff] ---- import org.pkl.core.EvaluatorBuilder; import org.pkl.core.module.ModuleKeyFactories; var evaluator = EvaluatorBuilder.unconfigured() + .addModuleKeyFactory(ModuleKeyFactories.http) // <1> .build(); ---- <1> Add `ModuleKeyFactories.http` to the set of module key factories used by this evaluator. If this module key factory is not added, Pkl may still make HTTP(S) requests if `ModuleKeyFactories.genericUrl` is included. However, this bypasses proxy and CA certificate settings. [[pkl-executor-changes]] ==== `pkl-executor` changes A new set of parameters are now available to `org.pkl.executor.Executor` (https://github.com/apple/pkl/pull/217[#217], https://github.com/apple/pkl/pull/518[#518]). These new parameters are exposed by `org.pkl.executor.spi.v1.ExecutorSpiOptions2`. The new parameters are: * `certificateFiles`: A set of CA certificate files to trust when making HTTPS requests. * `certificateBytes`: A set of PEM-encoded CA certificate bytes to trust when making HTTPS requests. * `testPort`: An option that is used for internal testing only. These options are ignored when using a Pkl distribution whose version is lower than 0.26.0. [[message-passing-api-changes]] === Message passing API changes A new property, `http`, is added to xref:bindings-specification:message-passing-api.adoc#create-evaluator-request[Create Evaluator Request] (https://github.com/apple/pkl/pull/506[#506], https://github.com/apple/pkl/pull/518[#518]). This allows for the configuration of Pkl's HTTP proxy, as well as CA certificates. [[rename-codegen-classes]] === Ability to rename classes produced by Java/Kotlin code generators The Java and Kotlin code generators have a new option that allows users to change the name of Java/Kotlin classes that get produced during code generation (https://github.com/apple/pkl/pull/499[#499]). The CLIs have a new flag, `--rename`, and the Gradle plugin receives a similarly named property called `renames`. This option accepts a map from an old prefix to a new prefix, where longer prefixes have higher precedence than shorter prefixes. For example, when generating `module foo.Bar`, specifying `--rename foo.=com.foo.` will cause the Java/Kotlin code generators to emit package `com.foo`, and class `Bar`. == Breaking Changes [small]#💔# Things to watch out for when upgrading. [[typealiases-are-treated-as-const]] === Typealiases are treated as `const` A breaking change has been made to typealiases (https://github.com/apple/pkl/pull/516[#516]). Typealiases are types that can stand in for another type declaration. The aliased type can have constraints, where these constraints can reference values defined on the enclosing module. .baseModule.pkl [source,pkl] ---- typealias MyValue = Any(isValid) // <1> isValid = true ---- <1> `isValid` is defined on the enclosing module. One problem with this is that typealiases are meant to be statically defined. Like classes, typealiases should not be changed by amending its enclosing module. [source,pkl] ---- amends "baseModule.pkl" isValid = false // <1> ---- <1> Despite this amended value, `typealias MyValue` should still stand for `Any(true)`. To have clearer semantics, a new rule is introduced: referenced members on the enclosing module must be `const`. Effectively, a typealias is treated as if it is also a `const` member. To fix the above typealias, the `const` modifier should be added to `isValid`. [source,diff] ---- typealias MyValue = Any(isValid) -isValid = true +const isValid = true ---- It might not always be valid to add the `const` modifier. For example, this property may be overwritten in a downstream module, so adding the `const` modifier this would break that module. In these cases, another fix is to self-import the enclosing module. This works because import declarations introduce values that are implicitly marked `const`. .baseModule.pkl [source,diff] ---- +import "baseModule.pkl" // <1> + -typealias MyValue = Any(isValid) +typealias MyValue = Any(baseModule.isValid) isValid = true ---- <1> Self import This change aligns with the behavior of class and annotation bodies. To read more about the rationale behind this change, consult https://github.com/apple/pkl-evolution/blob/main/spices/SPICE-0007-const-checks-in-typealiases.adoc[SPICE-0007]. [[expanded-dependency-notation-uris]] === Expanded dependency notation URIs The parsing of relative path imports has changed (https://github.com/apple/pkl/pull/486[#486]). Currently, the declaration `import "@foo/bar.pkl"` is treated as the import of a dependency named `foo`, but _only_ when declared within file-based and package-based modules. In an HTTP-based module, for example, the above import is treated as "bar.pkl inside directory @foo". To <>, such declarations are treated as dependency notation in all modules, and will fail with error "Cannot find dependency". Any import/reads that are intentionally relative-path imports will need to be updated to be prefixed with `./`. Example: [source,diff] ---- -import "@bar/foo.pkl" +import "./@bar/foo.pkl" ---- [[minimum-java-version-bump]] === Minimum Java version bumped to Java 17 The minimum Java version for Pkl has been bumped to Java 17 (https://github.com/apple/pkl/pull/439[#439]). This means that when running the xref:pkl-cli:index.adoc#java-executable[jpkl] CLI, the installed `java` is expected to be Java 17 or higher. This also means that users of the JVM libraries need to be on at least Java 17 or higher. [[minimum-gradle-version-bump]] === Gradle plugin minimum version bump The minimum Gradle version for the xref:main:pkl-gradle:index.adoc[Gradle plugin] is now 8.1 (https://github.com/apple/pkl/pull/454[#454]). [[path-encoding-changes]] === Path encoding changes In order to support Windows, the output of some Pkl tools have unsafe characters encoded in a special format (https://github.com/apple/pkl/pull/489[#489]). On Windows, the characters `<`, `>`, `:`, `"`, `\`, `|`, `?`, and `*` are reserved and cannot exist in a filename. Additionally, the ASCII control character code points `0x0` through `0x1f` are also illegal. These characters are encoded by wrapping their hexadecimal code point value in parentheses. For example, the character `:` is encoded as `(3a)`. In some scenarios, files that get written to disk will be encoded. These are: * Files generated by Pkldoc. * `*.kt` files produced by the Kotlin code generator. * Packages written to the cache directory. To learn more about this design, consult https://github.com/apple/pkl-evolution/blob/main/spices/SPICE-0003-windows-safe-paths.adoc[SPICE-0003]. ==== Pkldoc links changes The links generated by Pkldoc have changed. For example, a module called `module `foo/package-1` to `/package-2`. If Pkl's dependencies are vendored within a repository, these dependencies will need to be re-vendored. === Java API Breaking Changes The following Java APIs have breaking changes: |=== |Class/method | Breaking change |`org.pkl.cli.commands.CliDownloadPackageCommand` |Renamed to `org.pkl.cli.commands.CliPackageDownloader` |`org.pkl.cli.commands.CliAbstractProjectCommand` |Renamed to `org.pkl.cli.commands.CliProjectCommand` |`org.pkl.config.java.InvalidMappingException` |Fields `pklName` and `javaName` are now `private`. |`org.pkl.core.packages.PackageResolver#getInstance` |New parameter `httpClient` added |`org.pkl.core.repl.ReplServer.ReplServer` |New parameter `httpClient` added |`org.pkl.commons.cli.CliBaseOptions` |New parameters `testPort`, `httpProxy`, `httpNoProxy` added |=== [[standard-library-breaking-changes]] === Standard library breaking changes Class `pkl.Project.EvaluatorSettings` has been removed. A new (deprecated) typealias is added that points to new module `pkl.EvaluatorSettings`. For both of these, see https://github.com/apple/pkl/pull/506[#506]. [[type-checked-settings-file]] === Type-checked settings file The loading of the xref:pkl-cli:index.adoc#settings-file[settings file] has changed (https://github.com/apple/pkl/pull/477[#477]). The settings file is a way to control the behavior of the Pkl CLI. This module is expected to `amends "pkl:settings"`, but this behavior was not checked. In 0.26, it is an error if the settings module neither amends `"pkl:settings"`, nor set its `output.value` to an instance of the settings module. [[empty-directories-excluded-from-packaging]] === Empty directories excluded from packaging Currently, the `pkl project package` command will bundle empty directories into the resulting ZIP file. In version 0.26, this has been changed to exclude these directories (https://github.com/apple/pkl/pull/330[#330]). This means that the packages can produce a _different_ checksum if there are any empty directories. This does not break the usage of existing packages, and does not affect compatibility of new packages used with older Pkl 0.25. However, this can break certain workflows. The `pkl project package` command runs a publish check, to determine if a package has already been published but with a different checksum. When upgrading, users might encounter an error message like the following: [source] ---- -- Pkl Error -- Package `package://example.com/foo@1.0.0` was already published with different contents. ---- To mitigate, the package's version needs to be bumped, even if package contents have not changed. == Miscellaneous [small]#🐸# The following changes have been made that are neither new features nor breaking changes. * Pkl's user-agent header for HTTP requests has been tweaked to add a semicolon (https://github.com/apple/pkl/pull/221[#221]). Here is an example difference: + Before: `Pkl/0.26 (macOS native)` + After: `Pkl/0.26 (macOS; native)` * Documentation improvements (https://github.com/apple/pkl/pull/120[#120], https://github.com/apple/pkl/pull/121[#121], https://github.com/apple/pkl/pull/142[#142], https://github.com/apple/pkl/pull/121[#121], https://github.com/apple/pkl/pull/337[#337], https://github.com/apple/pkl/pull/341[#341], https://github.com/apple/pkl/pull/372[#372], https://github.com/apple/pkl/pull/386[#386], https://github.com/apple/pkl/pull/391[#391], https://github.com/apple/pkl/pull/397[#397], https://github.com/apple/pkl/pull/422[#422], https://github.com/apple/pkl/pull/436[#436], https://github.com/apple/pkl/pull/469[#469], https://github.com/apple/pkl/pull/484[#484], https://github.com/apple/pkl/pull/485[#485], https://github.com/apple/pkl/pull/[#491]). * Optimization: `const` access is checked when variables are resolved, instead of every time the variable is accessed (https://github.com/apple/pkl/pull/438[#438]). * Optimization: use logical AND instead of bitwise AND when comparing numbers (https://github.com/apple/pkl/pull/102[#102]). * Mark Java classes `final` (https://github.com/apple/pkl/pull/458[#458]). * Migrate code to newer JDK17 features, and clean up existing Java code (https://github.com/apple/pkl/pull/451[#451], https://github.com/apple/pkl/pull/458[#458], https://github.com/apple/pkl/pull/512[#512]). * Improve rendering of string values within generated `.pkl-expected.pcf` files when running `pkl test` (https://github.com/apple/pkl/pull/416[#416]). * Improve performance of loading Pkl's built-in CA certificates (https://github.com/apple/pkl/pull/518[#518]). == Bug Fixes [small]#🐜# The following bugs have been fixed. * `Collection#sortWith` produces un-sorted output (https://github.com/apple/pkl/pull/394[#394]). * Property `typedType` in module `pkl.reflect` reflects upon `Type`, instead of `Typed` (https://github.com/apple/pkl/pull/426[#426]). * `const` members can be assigned to when via an object spread (https://github.com/apple/pkl/pull/428[#428]). * Relative globbed reads resolve to the same value in different modules (https://github.com/apple/pkl/pull/449[#449]). * Performance bug: globbed imports and globbed reads expand to an unbounded number of root nodes (https://github.com/apple/pkl/pull/449[#449]). * Relative globbed imports within a package match no modules (https://github.com/apple/pkl/pull/496[#496]). * Constraints within typealiases resolve to the wrong values (https://github.com/apple/pkl/pull/144[#144]). * Members of `pkl.reflect` cannot be rendered (https://github.com/apple/pkl/pull/170[#170], https://github.com/apple/pkl/pull/470[#470]). * Throws `NullPointerException` if "List Resources Response" or "List Modules Response" messages contain both null `pathElements` and `error` (https://github.com/apple/pkl/pull/480[#480]). * Classes of `com.oracle.truffle` are not shaded in pkl-config-java-all and pkl-tools (https://github.com/apple/pkl/pull/238[#238]). * Throws `PklBugException` when running publish check on an invalid URL (https://github.com/apple/pkl/pull/441[#441]). == Contributors [small]#🙏# We would like to thank the contributors to this release (in alphabetical order): * https://github.com/flyinprogrammer[@flyinprogrammer] * https://github.com/fumiya-kume[@fumiya-kume] * https://github.com/garysassano[@garysassano] * https://github.com/HT154[@HT154] * https://github.com/hoxell[@hoxell] * https://github.com/jw-y[@jw-y] * https://github.com/KushalP[@KushalP] * https://github.com/lilyball[@lilyball] * https://github.com/luuvish[@luuvish] * https://github.com/Malix-off[@Malix-off] * https://github.com/manuelsblanco[@manuelsblanco] * https://github.com/MarkSRobinson[@MarkSRobinson] * https://github.com/mitchcapper[@mitchcapper] * https://github.com/mrs1669[@mrs1669] * https://github.com/netvl[@netvl] * https://github.com/nirinchev[@nirinchev] * https://github.com/raj-j-shah[@raj-j-shah] * https://github.com/sgammon[@sgammon] * https://github.com/StefMa[@StefMa] * https://github.com/stefanobaghino[@stefanobaghino] * https://github.com/TimeTravelPenguin[@TimeTravelPenguin] * https://github.com/TheFruxz[@TheFruxz] * https://github.com/translatenix[@translatenix] * https://github.com/zihluwang[@zihluwang] A special thank-you goes out to https://github.com/translatenix[@translatenix]! They submitted multiple bug fixes, improved the quality of the codebase, and provided HTTP improvements. ================================================ FILE: docs/modules/release-notes/pages/0.27.adoc ================================================ = Pkl 0.27 Release Notes :version: 0.27 :version-minor: 0.27.2 :release-date: November 5th, 2024 include::ROOT:partial$component-attributes.adoc[] Pkl {version} was released on {release-date}. + [.small]#The latest bugfix release is {version-minor}. (xref:changelog.adoc[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 https://github.com/apple/pkl/discussions[GitHub Discussions], or submit an issue on https://github.com/apple/pkl/issues/new[GitHub]. + [small]#Pkl is hosted on https://github.com/apple/pkl[GitHub]. To get started, follow xref:pkl-cli:index.adoc#installation[Installation].# == Highlights [small]#💖# News you don't want to miss. [#typecheck-improvements] === Improved typechecking of `Listing` and `Mapping` types Typechecking of `Listing` and `Mapping` types has been improved (https://github.com/apple/pkl/pull/628[#628], https://github.com/apple/pkl/pull/725[#725], https://github.com/apple/pkl/pull/740[#740], https://github.com/apple/pkl/pull/752[#752], https://github.com/apple/pkl/pull/778[#778], https://github.com/apple/pkl/pull/781[#781]). Today, the typecheck `listing: Listing` immediately evaluates all listing elements to check that they have type `E`. Likewise, the typecheck `mapping: Mapping` immediately evaluates all mapping values to check that they have type `V`. For example, the typecheck `listing: Listing` proceeds as follows: 1. Check that `listing` has type `Listing` 2. 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: [source,pkl] ---- 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. NOTE: Mapping _keys_ are still eagerly checked. This change causes some previously failing programs to evaluate successfully: [source,pkl] ---- local myNumbers: Listing = 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: [source,pkl] ---- local allBirds: Mapping = 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 https://github.com/apple/pkl-evolution/blob/main/spices/SPICE-0010-overhauled-mapping-listing-typechecks.adoc[SPICE-0010]. === External readers A new feature has been added to allow Pkl to spawn external processes to read resources and modules (https://github.com/apple/pkl/pull/660[#660], https://github.com/apple/pkl/pull/762[#762], https://github.com/apple/pkl/pull/766[#766], https://github.com/apple/pkl/pull/770[#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. [source,pkl] ---- 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 xref:bindings-specification:message-passing-api.adoc[message passing]. To learn more about this feature, consult https://github.com/apple/pkl-evolution/blob/main/spices/SPICE-0009-external-readers.adoc[SPICE-0009]. Thanks to https://github.com/HT154[@HT154] for contributing the feature! [[import-analysis]] === Import analysis API A new API has been added to analyze the import graph of Pkl modules (https://github.com/apple/pkl/pull/695[#695]). This API comes in four forms: 1. A standard library module: `pkl:analyze` 2. A CLI command: `pkl analyze imports` 3. A Java API: `org.pkl.core.Analyzer` 4. 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, xref:pkl-gradle:index.adoc[] now by default <> for many of its tasks. Here is an example of the CLI in use: [source,shell] ---- pkl analyze imports -f json myModule.pkl # <1> ---- <1> `-f` means: produce output in JSON. Produces: [source,json] ---- { "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 xref:language-reference:index.adoc#local-dependencies[local dependency]). To learn more about this feature, consult https://github.com/apple/pkl-evolution/blob/main/spices/SPICE-0001-import-graph-analyzer-api.adoc[SPICE-0001]. == Noteworthy [small]#🎶# Ready when you need them. === Colored output The Pkl CLI will now emit some messages in color (https://github.com/apple/pkl/pull/552[#552], https://github.com/apple/pkl/pull/746[#746], https://github.com/apple/pkl/pull/771[#771], https://github.com/apple/pkl/pull/779[#779]). Here is a sneak peek of colored error messages in action. image::error_sample.png[syntax highlighted output] Thanks to https://github.com/thomaspurchas[@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` (https://github.com/apple/pkl/pull/678[#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: [source,pkl] ---- 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 https://github.com/apple/pkl-evolution/blob/main/spices/SPICE-0011-const-object-members.adoc[SPICE-0011]. === Pkl CLI changes ==== New CLI Flags Some new common flags have been added to the CLI (https://github.com/apple/pkl/pull/660[#660], https://github.com/apple/pkl/pull/746[#746]). |=== |Flag |Description |`--color` |Format messages with ANSI color codes |`--external-module-reader` |Shell out to a process to read certain modules. |`--external-resource-reader` |Shell out to a process to read certain resources. |=== ==== New command: `pkl analyze imports` As part of the set of APIs added for <>, a new subcommand has been added called `analyze imports` (https://github.com/apple/pkl/pull/695[#695]). For details, consult the xref:pkl-cli:index.adoc#command-analyze-imports[CLI documentation]. === Kotlin/Java code generator improvements Various improvements have been made to the Kotlin and Java code generators (https://github.com/apple/pkl/pull/705[#705], https://github.com/apple/pkl/pull/710[#710], https://github.com/apple/pkl/pull/714[#714], https://github.com/apple/pkl/pull/721[#721], https://github.com/apple/pkl/pull/729[#729]). ==== Java codegen improvements * Only generate `hashCode()`, `equals()`, and `toString()` 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 https://github.com/translatenix[@translatenix] for contributing these improvements! === Gradle Plugin changes ==== New `AnalyzeImportsTask` A new task called `org.pkl.gradle.task.AnalyzeImportsTask` is introduced (https://github.com/apple/pkl/pull/695[#695]). This task is the Gradle analogy to `pkl analyze imports`. Example: [tabs] ==== build.gradle:: + [source,groovy] ---- pkl { analyzers { imports { appConfig { sourceModules.add(file("src/main/resources/appConfig.pkl")) } } } } ---- build.gradle.kts:: + [source,kotlin] ---- pkl { analyzers { imports { register("appConfig") { sourceModules.add(file("src/main/resources/appConfig.pkl")) } } } } ---- ==== For more details, consult the xref:pkl-gradle:index.adoc#analyze-imports[documentation]. ==== Tracked file outputs Tasks created by the Pkl plugin now declare tracked output files (https://github.com/apple/pkl/pull/403[#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`: .build.gradle.kts [source,diff] ---- val myGradleTask by tasks.registering { inputs.files(evalPkl) - dependsOn(evalPkl) // <1> } ---- <1> No longer necessary to declare this dependency. [[transitive-modules-computed-by-default]] ==== `transitiveModules` computed by default The `transitiveModules` property of a task is now computed by building the import graph of the source modules (https://github.com/apple/pkl/pull/695[#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. NOTE: This adds latency to each task. To opt out of this behavior, set `transitiveModules` explicitly. === Standard library changes ==== Additions to `pkl:base` New properties and methods have been added to the classes of `pkl:base` (https://github.com/apple/pkl/pull/666[#666], https://github.com/apple/pkl/pull/683[#683]). New properties and methods have been added to {uri-stdlib-Listing}[`Listing`] and {uri-stdlib-Mapping}[`Mapping`]. 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: * {uri-stdlib-Listing}#first[`Listing.first`] * {uri-stdlib-Listing}#firstOrNull[`Listing.firstOrNull`] * {uri-stdlib-Listing}#last[`Listing.last`] * {uri-stdlib-Listing}#lastOrNull[`Listing.lastOrNull`] * {uri-stdlib-Listing}#single[`Listing.single`] * {uri-stdlib-Listing}#singleOrNull[`Listing.singleOrNull`] * {uri-stdlib-Listing}#every()[`Listing.every()`] * {uri-stdlib-Listing}#any()[`Listing.any()`] * {uri-stdlib-Listing}#contains()[`Listing.contains()`] * {uri-stdlib-Mapping}#containsValue()[`Mapping.containsValue()`] * {uri-stdlib-Mapping}#every()[`Mapping.every()`] * {uri-stdlib-Mapping}#any()[`Mapping.any()`] With this, the following change can be made to existing constraints: [source,diff] ---- -ipAddresses: Listing(toList().contains("127.0.0.1")) +ipAddresses: Listing(contains("127.0.0.1")) ---- Additionally, a new method is added to `String`, called {uri-stdlib-String}#splitLimit()[`String.splitLimit()`]. ==== Additions to `pkl:EvaluatorSettings` New properties have been added to `pkl:EvaluatorSettings` (https://github.com/apple/pkl/pull/660[#660], https://github.com/apple/pkl/pull/746[#746]). These are: * {uri-stdlib-evaluatorSettingsModule}/#color[`color`] * {uri-stdlib-evaluatorSettingsModule}/#externalModuleReaders[`externalModuleReaders`] * {uri-stdlib-evaluatorSettingsModule}/#externalResourceReaders[`externalResourceReaders`] ==== `String` to `Number` conversion improvements. The `String` to `Number` converter methods, for example, {uri-stdlib-StringToInt}[`String.toInt()`], can now handle underscore separators (https://github.com/apple/pkl/pull/578[#578], https://github.com/apple/pkl/pull/580[#580]). This better aligns with the source code representation of xref:language-reference:index.adoc#integers[number literals]. [source,pkl] ---- myNum = "1_000".toInt() // <1> ---- <1> Result: `1000` ==== New module: `pkl:analyze` As part of <>, a new standard library module is added called `pkl:analyze` (https://github.com/apple/pkl/pull/695[#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: [source,pkl] ---- import "pkl:analyze" importGraph: analyze.ImportGraph = analyze.importGraph(Set("file:///path/to/my/module.pkl")) ---- For details, see the {uri-stdlib-analyzeModule}[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: .PklProject [source,pkl] ---- @Deprecated amends "pkl:Project" ---- === Test report improvements The report generated from `pkl test` has been overhauled and improved (https://github.com/apple/pkl/pull/498[#498], https://github.com/apple/pkl/pull/628[#682], https://github.com/apple/pkl/pull/738[#738], https://github.com/apple/pkl/pull/771[#771]). * Tests are grouped into either the `facts` or `examples` 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. image::test_sample.png[] Thanks to https://github.com/jjmaestro[@jjmaestro] for contributing to this improvement! === Java API additions ==== New API: `org.pkl.core.Analyzer` As part of <>, a new Java API called `org.pkl.core.Analyzer` is introduced (https://github.com/apple/pkl/pull/695[#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 [small]#💔# Things to watch out for when upgrading. === Java API breaking changes The following Java APIs have breaking changes: |=== |Class/method | Breaking change |`org.pkl.core.runtime.TestResults` |Moved to `org.pkl.core.TestResults`, turned into a record, and largely restructured. |`org.pkl.core.module.ModuleKeyFactories.closeQuietly` |Deprecated for removal in favor of `org.pkl.core.Closeables.closeQuietly`. |=== === Spring Boot 2 support dropped The Java code generator no longer supports Spring Boot 2.x (https://github.com/apple/pkl/pull/729[#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` (https://github.com/apple/pkl/pull/738[#738]). Additionally, the junit reports will consider the test as failing. === `Listing` and `Mapping` typecheck changes Due to changes to <>, some previously failing programs now succeed. This can happen when erroneous code is never evaluated because it does not affect program output: [source,pkl] ---- local myNumbers: Listing = 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 `1`. == Miscellaneous [small]#🐸# * Make pkl-doc top box include a preview of the rest of module-level documentation (https://github.com/apple/pkl/pull/570[#570]). * Module `pkl-core` now has a dependency on `org.msgpack:msgpack-core` (https://github.com/apple/pkl/pull/660[#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 (https://github.com/apple/pkl/pull/664[#644]). * Fix invalid syntax of code examples in stdlib doc comments (https://github.com/apple/pkl/pull/703[#703]). * Update Java dependencies (https://github.com/apple/pkl/pull/689[#689], https://github.com/apple/pkl/pull/767[#767]). * Add jbang catalog support (https://github.com/apple/pkl/pull/655[#655]). * Documentation improvements (https://github.com/apple/pkl/pull/623[#623], https://github.com/apple/pkl/pull/680[#680], https://github.com/apple/pkl/pull/682[#682], https://github.com/apple/pkl/pull/685[#685], https://github.com/apple/pkl/pull/687[#687], https://github.com/apple/pkl/pull/703[#703], https://github.com/apple/pkl/pull/704[#704], https://github.com/apple/pkl/pull/715[#715], https://github.com/apple/pkl/pull/730[#730], https://github.com/apple/pkl/pull/753[#753]). == Bug Fixes [small]#🐜# The following bugs have been fixed. * CLI `--property` flags containing `=` in property values are not parsed correctly (https://github.com/apple/pkl/issues/630[#630]). * Thrown `PklBugException` when reading assets from a local project dependency (https://github.com/apple/pkl/issues/641[#641]). * Thrown `PklBugException` when PklProjects have a cyclical local dependency (https://github.com/apple/pkl/pull/731[#731]). * Thrown exception when sending `ReadResourceResponse` or `ReadModuleResponse` where both `contents` and `error` are `null` (https://github.com/apple/pkl/pull/657[#657]). * Double unary minus is evaluated as single unary minus (https://github.com/apple/pkl/pull/697[#697]). * Kotlin compiler fails to compiler generated classes with "copy overrides nothing" for subclasses of abstract classes (https://github.com/apple/pkl/issues/569[#569]). * "Unexpected error" thrown when `Module.output` has its type overridden (https://github.com/apple/pkl/issues/709[#709]). * "Unexpected error" thrown when loading files with non-ASCII characters (https://github.com/apple/pkl/issues/653[#653]). * Type parameters in `new Mapping`/`new Listing ` are not checked (https://github.com/apple/pkl/issues/405[#405]). * Comparison methods of `pkl:semver` are incorrect (https://github.com/apple/pkl/issues/772[#772]). == Contributors [small]#🙏# We would like to thank the contributors to this release (in alphabetical order): * https://github.com/djarnis73[@djarnis73] * https://github.com/kasugamirai[@kasugamirai] * https://github.com/KushalP[@KushalP] * https://github.com/jjmaestro[@jjmaestro] * https://github.com/HT154[@HT154] * https://github.com/lamtrinhdev[@lamtrinhdev] * https://github.com/ManuW[@ManuW] * https://github.com/maxandersen[@maxandersen] * https://github.com/netvl[@netvl] * https://github.com/StefMa[@StefMa] * https://github.com/taichi-ishitani[@taichi-ishitani] * https://github.com/thomaspurchas[@thomaspurchas] * https://github.com/translatenix[@translatenix] ================================================ FILE: docs/modules/release-notes/pages/0.28.adoc ================================================ = Pkl 0.28 Release Notes :version: 0.28 :version-minor: 0.28.2 :release-date: February 26th, 2025 include::ROOT:partial$component-attributes.adoc[] :uri-snippet-tests: {uri-github-tree}/pkl-core/src/test/files/LanguageSnippetTests/input :uri-standard-library: {uri-github-tree}/stdlib/ :uri-antlr: https://www.antlr.org :wbr: pass:[] Pkl {version} was released on {release-date}. + [.small]#The latest bugfix release is {version-minor}. (xref:changelog.adoc[All Versions])# The next release (0.29) is scheduled for June 2025. To see what's coming in the future, follow the {uri-pkl-roadmap}[Pkl Roadmap]. Please send feedback and questions to https://github.com/apple/pkl/discussions[GitHub Discussions], or submit an issue on https://github.com/apple/pkl/issues/new[GitHub]. + [small]#Pkl is hosted on https://github.com/apple/pkl[GitHub]. To get started, follow xref:pkl-cli:index.adoc#installation[Installation].# == Highlights [small]#💖# News you don't want to miss. === New parser Pkl has new parser (https://github.com/apple/pkl/pull/917[#917], https://github.com/apple/pkl/pull/957[#957], https://github.com/apple/pkl/pull/962[#962])! The first step to evaluating a program is to parse its source code into a parse tree. Currently, Pkl uses link:{uri-antlr}[ANTLR] to generate parser code from grammar files. Using this approach has allowed for rapidly iterating and changing the language's grammar. However, Pkl's grammar has matured; it's not as likely that the grammar will change from one version to the next. Additionally, the current parser's performance has become more painful as projects written in Pkl grow in size. In many cases, parsing can take up most of the time spent during evaluation. In 0.28, the ANTLR-generated parser is being replaced by a handwritten parser. In benchmarking tests, this has shown to improve parsing speed by around two orders of magnitude. Here are some quick comparisons between the two parsers, run on a MacBook Pro M4 Max machine, with 5 warmup iterations, and run for 10 iterations: |=== ||Old parser |New parser |link:{uri-snippet-tests}[Language snippet tests] |440.8ms |5.9ms |link:{uri-standard-library}[Standard library] |136.7msfootnote:[In the `pkl` CLI, the standard library is parsed during compilation rather than during evaluation. As a result, there is no parsing overhead.] |0.8ms |All https://github.com/apple/pkl-pantry[pkl-pantry] modules |1424.2ms |7.0ms |=== == Noteworthy [small]#🎶# Ready when you need them. === CLI improvements ==== Colored help messages The `pkl`, `pkldoc`, `pkl-codegen-java`, and `pkl-codegen-kotlin` CLIs now emits help text with colors (https://github.com/apple/pkl/pull/947[#947]). Here is a sneak peek: image::pkl-cli-help-new.png[new pkl cli help output] Thanks to https://github.com/gordonbondon[@gordonbondon] for their contribution! ==== `jpkl` support on Windows The `jpkl` executable is now supported on Windows (https://github.com/apple/pkl/pull/872[#872]). `jpkl` is a fat jar that can be executed directly when on macOS and Linux, but current users of Windows are required to execute it with `java -jar `. In 0.28, Windows users can also call execute this command directly. To do so, the filename should be called `jpkl.bat`, and placed in `PATH`. === Type constraint changes Type annotations that are executed as a result of calling a constraint expressions now perform an eager typecheck (https://github.com/apple/pkl/pull/964[#964]). In Pkl 0.27, we changed how xref:0.27.adoc#typecheck-improvements[typechecks are executed for `Listing` and `Mapping`] types. However, this had two unintended side effects: For one, error messages from certain failing constraints show `?` in place of values that are failing. The following snippet: [source%parsed,pkl] ---- class Bird { name: String } bird: Listing(firstOneIsQuail) = new { new { name = "Pigeon" } new { name = "Quail" } } local firstOneIsQuail = (it: Listing) -> it[0].name == "Quail" ---- Produces the following error message: [source] ---- –– Pkl Error –– Type constraint `firstOneIsQuail` violated. Value: new Listing { ?; ? } 3 | bird: Listing(firstOneIsQuail) = new { ^^^^^^^^^^^^^^^ ---- :fn-typecheck-details: footnote:[See https://github.com/apple/pkl-evolution/blob/main/spices/SPICE-0010-overhauled-mapping-listing-typechecks.adoc#delegating-objects[delegating objects] in SPICE-0010 for more details on this behavior.] This is happening because lazy typechecks of mappings and listings will return a _new_ listing. Because the lambda's argument is defined as ``it: Listing``, the value being passed into the function body is this new listing{fn-typecheck-details}. Secondly, some constraints became less strict, allowing more values to pass. The following snippet fails in Pkl 0.26, but passes in Pkl 0.27: [source%parsed,pkl] ---- class Bird { name: String } local nonEmpty = (it: Listing) -> !it.isEmpty birds: Listing(nonEmpty) = new { 1; 2; 3 } ---- This is because the function parameter `it: Listing` does not actually check members of the argument until they are accessed, and the function body `!it.isEmpty` does not access any members. To address both of these issues, the rule for executing type constraints has changed. When executing a type annotation, mapping and listing typechecks are always _eager_. This means that Pkl will check that each member of the mapping/listing conforms to the type parameter. === Java 22+ support Pkl's Java libraries now support Java 22 and higher (https://github.com/apple/pkl/pull/876[#876]). Thanks to https://github.com/sgammon[@sgammon] for their contribution! === New standard library method The `pkl:math` standard library module has a new method, called {uri-stdlib-mathModule}/index.html#atan2()[atan2] (https://github.com/apple/pkl/pull/819[#819]). It computes the https://en.wikipedia.org/wiki/Atan2[2-argument arctangent]. Thanks to https://github.com/gordonbondon[@gordonbondon] for their contribution! === Overhauled for-generator implementation The implementation of xref:language-reference:index.adoc#for-generators[for-generators] has been overhauled (https://github.com/apple/pkl/pull/844[#844]). This fixes some known bugs with the current for-generator implementation, and also improves code health by reducing complexity and removing workarounds and band-aids. Thanks to https://github.com/odenix[@odenix] for their contribution! === Java code generator improvements Improvements have been made to the Java code generator (https://github.com/apple/pkl/pull/792[#792]). In pkl-codegen-java, the `--params-annotation` flag now accepts `none` as a value, which causes the code generator to skip adding annotations to a constructor's parameters. Additionally, the Kotlin API `org.pkl.codegen.java.JavaCodeGeneratorOptions.paramsAnnotation` is now nullable. If null, this also skips adding annotations. By default, this annotation is set to `org.pkl.config.java.mapper.Named`, or none if the `--generate-spring-boot-config` flag is set. This is useful for those compiling Java code with the `-parameters` flag set, because the pkl-config-java library is able to map into these classes without any extra annotations. Thanks to https://github.com/odenix[@odenix] for their contribution! === Kotlin code generator improvements Improvements have been made to the Kotlin code generator (https://github.com/apple/pkl/pull/793[#793]). Currently, the output of `toString()` on a generated Kotlin class differs depending on if it was generated as a data class or as a regular class. In Pkl 0.28, this method produces the same format, and matches the format used by data classes. Thanks to https://github.com/odenix[@odenix] for their contribution! === pkldoc improvements pkldoc has a new flag to write real files instead of symlinks (https://github.com/apple/pkl/pull/824[#824]). Currently, pkldoc creates a symlink called "current", which points to the latest non-prerelease version of a package. However, these symlinks can be problematic for some systems. A new flag, `--no-symlinks`, and a similarly named Gradle property, `noSymlinks`, instructs pkldoc to write real files instead of symlinks. Thanks to https://github.com/netvl[@netvl] for their contributions! === `jar:nested:` URIs are accepted by default To improve integration with Spring Boot, the Pkl evaluator by default now accepts module URIs starting with `jar:nested:` (https://github.com/apple/pkl/pull/895[#895]). The `--allowed-modules` flag (and the equally named option in other APIs) can be used to configure the set of allowed modules. == Breaking Changes [small]#💔# Things to watch out for when upgrading. === Stricter grammar As a result of implementing a new parser, the following previously accepted grammar is no longer valid (https://github.com/apple/pkl/pull/917[#917]). ==== Inline object entries When declaring multiple xref:language-tutorial:01_basic_config.adoc#entries[entries] on the same line, a semicolon is now required to separate them. The following code snippet is currently valid, and becomes a syntax error in 0.28. [source,pkl] ---- obj { ["one"] = 1 ["two"] = 2 } ---- To fix, insert a semicolon between the two entries: [source,diff] ---- -obj { ["one"] = 1 ["two"] = 2 } +obj { ["one"] = 1; ["two"] = 2 } ---- ==== String escapes When using xref:language-reference:index.adoc#custom-string-delimiters[custom string delimiters], the escape sequence becomes backslash (`\`) plus the number of pounds used to delimit the string. Due to a parser bug, any extra pounds after this escape sequence are also permitted. In Pkl 0.28, this becomes a syntax error. [source,pkl] ---- foo = "foo \#(bar)" // Invalid character escape sequence `\#`. ---- ==== Whitespace between generator members Inline object spreads, for generators, and when generators all now require either whitespace or a semicolon separator. [source,pkl] ---- foo { ...bar...baz } // Syntax error ---- === Minimum Gradle version bump The minimum Gradle version for the xref:main:pkl-gradle:index.adoc[Gradle Plugin] has been bumped to 8.2 (https://github.com/apple/pkl/pull/901[#901]). === Minimum Kotlin version bump For users of Pkl's Kotlin libraries, the minimum Kotlin version has been bumped to 2.0 (https://github.com/apple/pkl/pull/900[#900]). === Java/Kotlin API breaking changes Breaking changes have been made to the Java/Kotlin APIs (https://github.com/apple/pkl/pull/748[#748], https://github.com/apple/pkl/pull/749[#749], https://github.com/apple/pkl/pull/776[#776], https://github.com/apple/pkl/pull/793[#793], https://github.com/apple/pkl/pull/808[#808], https://github.com/apple/pkl/pull/810[#810]). |=== |API |Breakage | org.pkl.core.resource{wbr}.Resource | Converted into a record; previous getter methods are deprecated. | org.pkl.core.project{wbr}.Package | Converted into a record; previous getter methods are deprecated. | org.pkl.core.Member{wbr}.SourceLocation | Converted into a record; previous getter methods are deprecated. | org.pkl.core{wbr}.Release | Converted into a record; previous getter methods are deprecated. | org.pkl.core.Release{wbr}.SourceCode | Converted into a record; previous getter methods are deprecated. | org.pkl.core.Release{wbr}.Documentation | Converted into a record; previous getter methods are deprecated. | org.pkl.core.Release{wbr}.StandardLibrary | Converted into a record; previous getter methods are deprecated. | org.pkl.core.project{wbr}.DeclaredDependencies | Converted into a record; previous getter methods are deprecated. | org.pkl.core.evaluatorSettings{wbr}.PklEvaluatorSettings{wbr}.Proxy{wbr}.create | Deprecated; use the constructor instead. | org.pkl.core.module{wbr}.ExternalModuleResolver | Moved into package org.pkl.core.externalreader | org.pkl.core.resource{wbr}.ExternalResourceResolver | Moved into package org.pkl.core.externalreader | org.pkl.core.messaging{wbr}.MessageTransportModuleResolver | Renamed to ExternalModuleResolverImpl, made package-private | org.pkl.core.messaging{wbr}.MessageTransportResourceResolver | Renamed to ExternalResourceResolverImpl, made package-private | org.pkl.core.module{wbr}.ExternalModuleResolver{wbr}.Spec | Replaced with record org.pkl.core.externalreader.ModuleReaderSpec | org.pkl.core.resource{wbr}.ExternalResourceResolver{wbr}.Spec | Replaced with record org.pkl.core.externalreader.ResourceReaderSpec | org.pkl.core.messaging{wbr}.CreateEvaluatorRequest | Changed properties `allowedModules` and `allowedResources` to be of type `List` instead of `List` | org.pkl.codegen.kotlin{wbr}.KotlinCodegenOptions | Renamed to org.pkl.codegen.kotlin.KotlinCodeGeneratorOptions | org.pkl.codegen.kotlin{wbr}.CliKotlinCodeGeneratorOptions{wbr}.toKotlinCodegenOptions | Deprecated without replacement | org.pkl.codegen.java{wbr}.JavaCodegenOptions | Renamed to org.pkl.codegen.java.JavaCodeGeneratorOptions | org.pkl.codegen.java{wbr}.CliJavaCodeGeneratorOptions{wbr}.toJavaCodegenOptions() | Deprecated without replacement |=== === Fat jars no longer shade Truffle Pkl publishes two fat jars to Maven Central: https://central.sonatype.com/artifact/org.pkl-lang/pkl-tools[pkl-tools], and https://central.sonatype.com/artifact/org.pkl-lang/pkl-config-java-all[pkl-config-java-all]. Due a version bump in the Truffle library, the package `com.oracle.truffle` can no longer be shaded. For users that use both Pkl and other Truffle languages, this means that their version of Truffle should match Pkl's version. == Miscellaneous [small]#🐸# * Documentation improvements (https://github.com/apple/pkl/pull/792[#792], https://github.com/apple/pkl/pull/846[#846], https://github.com/apple/pkl/pull/860[#860], https://github.com/apple/pkl/pull/892[#892], https://github.com/apple/pkl/pull/921[#921], https://github.com/apple/pkl/pull/937[#937], https://github.com/apple/pkl/pull/943[#943], https://github.com/apple/pkl/pull/944[#944], https://github.com/apple/pkl/pull/955[#955], https://github.com/apple/pkl/pull/956[#956], https://github.com/apple/pkl/pull/973[#973]). * The repository now requires Java 21 or higher to build (https://github.com/apple/pkl/pull/876[#876]). * Enable multi-jdk testing via `-DmultiJdkTesting=true` flag when building Pkl (https://github.com/apple/pkl/pull/876[#876]). * Allow setting commit id via `-DcommitId` flag when building Pkl (https://github.com/apple/pkl/pull/954[#954]). == Bugs fixed [small]#🐜# The following bugs have been fixed. [smaller] * Optimization: `const` members should be cached for all children in the prototype chain (https://github.com/apple/pkl/issues/508[#508]) * codegen-kotlin: Use same toString() representation for data classes and regular classes (https://github.com/apple/pkl/issues/717[#717]) * Late-bound values of iteratees within nested for/spread fail to resolve for-generator variables (https://github.com/apple/pkl/issues/741[#741]) * codegen-java/kotlin: Fix generation of equals/hashCode methods (https://github.com/apple/pkl/pull/802[#802]) * Not possible to render mapping with Int keys in YAML (https://github.com/apple/pkl/issues/878[#878]) * Parser accepts wrong string escapes (https://github.com/apple/pkl/issues/888[#888]) * Downstream native-image embedders are broken (https://github.com/apple/pkl/issues/907[#907]) * Failed type constraint error messages do not show forced members (https://github.com/apple/pkl/issues/918[#918]) * ANTLR incompatibilities (https://github.com/apple/pkl/issues/927[#927]) * Doc comments with interleaving comments result in an error (https://github.com/apple/pkl/issues/931[#931]) * Spread elements inside an object body don't need separators (https://github.com/apple/pkl/issues/932[#932]) * Correctly set allowed modules/resoures when external reader scheme contain regex control characters (https://github.com/apple/pkl/pull/941[#941]) * Bad import analysis fails with "None (cause has no message)" (https://github.com/apple/pkl/issues/949[#949]) == Contributors [small]#🙏# We would like to thank the contributors to this release (in alphabetical order): * https://github.com/gordonbondon[@gordonbondon] * https://github.com/HT154[@HT154] * https://github.com/jsoref[@jsoref] * https://github.com/KushalP[@KushalP] * https://github.com/netvl[@netvl] * https://github.com/odenix[@odenix] * https://github.com/romacafe[@romacafe] * https://github.com/sgammon[@sgammon] * https://github.com/sorcix[@sorcix] * https://github.com/stanleyycheung[@stanleyycheung] ================================================ FILE: docs/modules/release-notes/pages/0.29.adoc ================================================ = Pkl 0.29 Release Notes :version: 0.29 :version-minor: 0.29.1 :release-date: July 24th, 2025 include::ROOT:partial$component-attributes.adoc[] Pkl {version} was released on {release-date}. + [.small]#The latest bugfix release is {version-minor}. (xref:changelog.adoc[All Versions])# This release brings support for working with binary data, and also a new setting to control HTTP rewriting. The next release (0.30) is scheduled for October 2025. To see what's coming in the future, follow the {uri-pkl-roadmap}[Pkl Roadmap]. Please send feedback and questions to https://github.com/apple/pkl/discussions[GitHub Discussions], or submit an issue on https://github.com/apple/pkl/issues/new[GitHub]. + [small]#Pkl is hosted on https://github.com/apple/pkl[GitHub]. To get started, follow xref:pkl-cli:index.adoc#installation[Installation].# == Highlights [small]#💖# [[bytes-standard-library-class]] === `Bytes` standard library class A new standard library class is introduced, called `Bytes` (https://github.com/apple/pkl/pull/1019[#1019]). Currently, Pkl does not have a built-in way to describe binary data. In situations where binary data is needed, the common pattern is to use a Base64 string. This is sufficient in simple cases, but still introduces many shortcomings: 1. `pkl eval` can only produce UTF-8 encoded strings. 2. It is not possible to compute the checksum of binary content (except in the case of `read()`). 3. It is hard to interop with configuration formats that allow binary data. 4. It is hard to do transformations on binary data. To address these shortcomings, the `Bytes` class is introduced. This is a data type representing a sequence of bytes. [source,pkl] ---- myBytes = Bytes(0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x21) // <1> ---- <1> ASCII bytes for the string "Hello, World!" To learn more about this feature, consult https://github.com/apple/pkl-evolution/blob/main/spices/SPICE-0013-bytes-standard-library.adoc[SPICE-0013]. ==== Emitting binary output A new property called `bytes` is added to `FileOutput`. The CLI has been changed to evaluate this property, and write its contents to the designated output location. This change means that Pkl modules can now output binary content. For example, here is a simple module that outputs bytes: [source,pkl] ---- output { bytes = Bytes(1, 2, 3, 4) // <1> } ---- <1> Write bytes 1, 2, 3, 4 The same change applies when evaluating in multiple-file output mode. [source,pkl] ---- output { files { ["foo.bin"] { bytes = Bytes(1, 2, 3, 4) // <1> } } } ---- <1> Write bytes 1, 2, 3, 4 to a file called `foo.bin`. ==== Rendering `Bytes` Out of the box, only the `plist` and `pcf` formats are able to render `Bytes`. For other formats, a renderer needs to be defined. [source,pkl] ---- output { renderer = new JsonRenderer { [Bytes] = (it) -> it.base64 // <1> } } ---- <1> Render bytes as a base64 string in JSON. ==== Using `Bytes` from language bindings Users of Pkl's language bindings also benefit from the new type. When using code generation, the `Bytes` data type will turn into the following types: |=== |Language |Type |Java |`byte[]` |Kotlin |`ByteArray` |Go |`[]byte` |Swift |`[UInt8]` |=== Maintainers of other language bindings are encouraged to map `Bytes` into the corresponding binary type in their language. ==== `Bytes` versus `List` Conceptually, `Bytes` is very similar to `List`, because both are data types that describe sequences of `UInt8` values. However, they have different performance characteristics. A `List` is a https://en.wikipedia.org/wiki/Persistent_data_structure[persistent data structure]. This means that creating modified copies of lists is very cheap. However, lists have more overhead per element. A `List` with 1000 elements takes up about 5.4 kilobytes of heap space, whereas the same data in `Bytes` takes roughly 1 kilobyte. === HTTP Rewrites and Package Mirroring A new evaluator setting is introduced, which rewrites URLs before making outbound HTTP calls (https://github.com/apple/pkl/pull/1062[#1062], https://github.com/apple/pkl/pull/1127[#1127], https://github.com/apple/pkl/pull/1133[#1133]). This setting can be configured via CLI flag `--http-rewrite`, and also in other ways: * A builder option in `org.pkl.core.EvaluatorBuilder` * A builder option in `org.pkl.executor.ExecutorOptions` * A new property in `CreateEvaluatorRequest` in the Message Passing API * A new property in the Gradle plugin * A new property in `pkl.EvaluatorSettings#Http` The main use-case for this setting is to enable package mirroring. For example, let's assume that the following mirrors exist: |=== |Original |Mirror |\https://pkg.pkl-lang.org |\https://my.internal.mirror/pkg-pkl-lang |\https://github.com |\https://my.internal.mirror/github |=== A user of the CLI can use these mirrors with the following settings. .~/.pkl/settings.pkl [source,pkl] ---- amends "pkl:settings" http { rewrites { ["https://pkg.pkl-lang.org/"] = "https://my.internal.mirror/pkg-pkl-lang/" ["https://github.com/"] = "https://my.internal.mirror/github/" } } ---- To learn more about this feature, consult https://github.com/apple/pkl-evolution/blob/main/spices/SPICE-0016-http-rewrites.adoc[SPICE-0016]. == Noteworthy [small]#🎶# Ready when you need them. === pkldoc, pkl-codegen-java, pkl-codegen-kotlin executables The pkldoc, pkl-config-java and pkl-config-kotlin CLIs are published as their own executables (https://github.com/apple/pkl/pull/1023[#1023]). Currently, these tools have excellent support when called from within the pkl-gradle plugin. However, the story for using these as CLIs is much clunkier. Users must call Java by including their published `.jar` files, as well as all of their dependencies via the `-classpath` argument. In 0.29, they are all being published as executables. In particular, pkldoc is being published as both a Java executable and a native executable. On the other hand, pkl-codegen-java and pkl-codegen-kotlin are published as Java executables only. For more information, see their download instructions: * xref:pkl-doc:index.adoc#install-cli[pkldoc] * xref:java-binding:codegen.adoc#install-cli[pkl-codegen-java] * xref:kotlin-binding:codegen.adoc#install-cli[pkl-codegen-kotlin] === Java API changes ==== Resource Readers SPI added to preconfigured evaluators A change was made to the preconfigured evaluators (https://github.com/apple/pkl/pull/1094[#1094]). Currently, they add module key factories from service providers, but not resource readers. This means that users of pkl-spring, for example, cannot add custom resource readers. This is also inconsistent (in the preconfigured evaluator, import can use custom schemes, but not read). In Pkl 0.29, any resource reader registered via the Service Provider Interface will be added to the preconfigured evalutors. ==== New methods New methods are introduced to the Java API. * `org.pkl.core.Evaluator.evaluateOutputBytes` * `org.pkl.core.http.HttpClient.Builder.setRewrites` * `org.pkl.core.http.HttpClient.Builder.addRewrite` * `org.pkl.executor.ExecutorOptions.httpRewrites` * `org.pkl.config.java.ConfigEvaluatorBuilder.getHttpClient` * `org.pkl.config.java.ConfigEvaluatorBuilder.setHttpClient` === Standard Library changes New properties, methods, classes and typealiases have been added to the standard library (https://github.com/apple/pkl/pull/1019[#1019], https://github.com/apple/pkl/pull/1053[#1053], https://github.com/apple/pkl/pull/1063[#1063], https://github.com/apple/pkl/pull/1144[#1144]). ==== Additions to `pkl:base` * {uri-stdlib-String}#isBase64[`String.isBase64`] * {uri-stdlib-String}#base64DecodedBytes[`String.base64DecodedBytes`] * {uri-stdlib-String}#encodeToBytes()[`String.encodeToBytes()`] * {uri-stdlib-List}#mapNonNullIndexed()[`List.mapNonNullIndexed()`] * {uri-stdlib-List}#toBytes()[`List.toBytes()`] * {uri-stdlib-Set}#mapNonNullIndexed()[`Set.mapNonNullIndexed()`] * {uri-stdlib-FileOutput}#bytes[`FileOutput.bytes`] * {uri-stdlib-Resource}#bytes[`Resource.bytes`] * {uri-stdlib-Mapping}#getOrDefault()[`Mapping.getOrDefault()`] * {uri-stdlib-Listing}#getOrDefault()[`Listing.getOrDefault()`] * {uri-stdlib-baseModule}#Charset[`Charset`] * {uri-stdlib-Bytes}[`Bytes`] ==== Additions to `pkl:EvaluatorSettings` * {uri-stdlib-evaluatorSettingsHttpClass}#rewrites[`Http.rewrites`] ==== Additions to `pkl:reflect` * {uri-stdlib-reflectModule}#bytesType[`bytesType`] === `pkl` CLI changes New features are added to the `pkl` CLI (https://github.com/apple/pkl/pull/1052[#1052], https://github.com/apple/pkl/pull/1056[#1056]). ==== Aggregated JUnit reports A new set of CLI flags are introduced: `--junit-aggregate-reports`, and `--junit-aggregate-suite-name`. Collectively, these flags tell Pkl to combine the JUnit reports into a single file, instead of creating a file per Pkl module. Thanks to https://github.com/gordonbondon[@gordonbondon] for contributing to this feature! ==== shell-completion subcommand A new subcommand called `shell-completion` has been added to the Pkl CLI. This command produces autocompletion scripts for a given shell. The following example installs shell completions for https://fishshell.com[fish shell]: [source,shellscript] ---- pkl shell-completion fish > "~/.config/fish/completions/pkl.fish" ---- Thanks to https://github.com/gordonbondon[@gordonbondon] for contributing to this feature! === `@Generated` annotation for Java/Kotlin codegen Classes generated by the Java and Kotlin code generator can optionally receive new annotation called `Generated` (https://github.com/apple/pkl/pull/1075[#1075], https://github.com/apple/pkl/pull/1115[#1115]). This behavior is toggled with the `--generated-annotation` CLI flag, or the similarly named Gradle property. When enabled, classes are annotated with `org.pkl.config.java.Generated`. This allows users of JaCoCo to exclude Pkl-generated classes from coverage reports. Thanks to https://github.com/arouel[@arouel] for their contributions here! == Breaking Changes [small]#💔# Things to watch out for when upgrading. === Standard library deprecations The following methods have been deprecated in the standard library: |=== |Method name |Details |`Resource.md5` |Replaced with `Resource.bytes.md5` |`Resource.sha1` |Replaced with `Resource.bytes.sha1` |`Resource.sha256` |Replaced with `Resource.bytes.sha256` |`Resource.sha256Int` |Replaced with `Resource.bytes.sha256Int` |=== === Grammar changes New rules have been introduced to the parser (https://github.com/apple/pkl/pull/1022[#1022], https://github.com/apple/pkl/pull/1126[#1126]). ==== Block comment nesting is removed Currently, block comments can be nested. For example, the comment `/* /* my comment */ */` is two block comments; one outer comment and one inner comment. However, this has some drawbacks. 1. Block comments can be possibly be closed incorrectly, like `/* /* my comment */`. However, this is a valid block comment in most languages. 2. Pkl's syntax highlighters do not support block comment nesting, leading to highlighting that is inconsistent with Pkl's parser. To improve user experience, block comment nesting is removed. As a result, block comments are always terminated upon the first `*/`. ==== Shebang line can only appear at the start of a file The grammar around shebang comments has been made stricter. Pkl files allow for a https://en.wikipedia.org/wiki/Shebang_(Unix)[shebang comment]. Currently, this comment can appear anywhere in a file. In 0.29, this comment must appear at the start of a file. === Opaque `file:` URIs are invalid A new rule is introduced to treat opaque `file:` URIs as errors (https://github.com/apple/pkl/pull/1087[#1087]). Opaque file URIs are URIs whose scheme-specific part does not start with `/`. For example, `file:foo/bar.txt` is an opaque URI. Currently, this has the unintentional behavior of: look for file `foo/bar.txt` from the process working directory. These are effectively dynamics imports; from a single import, we can't statically determine what file is actually being imported. According to https://datatracker.ietf.org/doc/html/rfc8089#section-2[RFC-8089], `file` URIs must have paths that start with /. So, these are actually not valid URIs. In Pkl 0.29, it is an error to load a module or resource with an opaque `file:` URI. NOTE: To import or read a relative path, omit `file:` from the import string. For example, `import("foo/bar.txt")` instead of `import("file:foo/bar.txt")`. [[new-base-module-names]] === New base module names: `Bytes` and `Charset` Two new names are introduced to the base module: `Bytes` and `Charset`. That means that any code that currently references these names on implicit `this` will break (https://github.com/apple/pkl/pull/1019[#1019]). The following snippet demonstrates this breaking behavior. In 0.28 and below, `obj2.prop` resolves to string "my bytes". In 0.29, it resolves to class `Bytes` in the base module. [source,pkl] ---- obj1 { Bytes = "my bytes" } obj2 = (obj1) { prop = Bytes } ---- To make this code continue to have the same meaning, an explicit `this` reference is required. [source,diff] ---- obj1 { Bytes = "my bytes" } obj2 = (obj1) { - prop = Bytes + prop = this.Bytes } ---- This only affects code that references these names off of implicit this. Code that references the name from the lexical scope will continue to work as-is. To learn more about name resolution, consult the xref:language-reference:index.adoc#name-resolution[language reference]. === jpkl is not published to Maven Central Due to a breakage in release pipeline, the `jpkl` executable is not published to Maven Central (https://github.com/apple/pkl/pull/1147[#1147]). It is still available to download as a GitHub release asset. == Miscellaneous [small]#🐸# * Documentation improvements (https://github.com/apple/pkl/pull/1065[#1065], https://github.com/apple/pkl/pull/1127[#1127]). * Dependency updates (https://github.com/apple/pkl/pull/1088[#1088], https://github.com/apple/pkl/pull/1128[#1128], https://github.com/apple/pkl/pull/1139[#1139]). * Use `javac -release` and `kotlinc -Xjdk-release` compiler flags for improved bytecode compatibilty (https://github.com/apple/pkl/pull/1080[#1080]). * Parser improvements (https://github.com/apple/pkl/pull/1066[#1066]). == Bugs fixed [small]#🐜# The following bugs have been fixed. * New parser fails on nested multi line comments (https://github.com/apple/pkl/issues/1014[#1014]) * Fix package dependency links when generating pkldoc (https://github.com/apple/pkl/pull/1078[#1078]) * Don't show 100% when number of failures is rounded up (https://github.com/apple/pkl/pull/1110[#1110]) * Quoting the module name crashes pkl (https://github.com/apple/pkl/issues/1111[#1111]) * Shebang comment parsing is too lenient (https://github.com/apple/pkl/issues/1125[#1125]) * CLI: noProxy option in settings.pkl and PklProject are ignored (https://github.com/apple/pkl/issues/1142[#1142]) == Contributors [small]#🙏# We would like to thank the contributors to this release (in alphabetical order): * https://github.com/arouel[@arouel] * https://github.com/gordonbondon[@gordonbondon] * https://github.com/HT154[@HT154] * https://github.com/KushalP[@KushalP] * https://github.com/madrob[@madrob] * https://github.com/MikeSchulze[@MikeSchulze] * https://github.com/sitaktif[@sitaktif] * https://github.com/vlsi[@vlsi] ================================================ FILE: docs/modules/release-notes/pages/0.30.adoc ================================================ = Pkl 0.30 Release Notes :version: 0.30 :version-minor: 0.30.2 :release-date: November 3rd, 2025 :yaml-binary-scalar: https://yaml.org/type/binary.html include::ROOT:partial$component-attributes.adoc[] Pkl {version} was released on {release-date}. + [.small]#The latest bugfix release is {version-minor}. (xref:changelog.adoc[All Versions])# This release introduces a code formatter, and an in-language API for producing `pkl-binary` output. The next release (0.31) is scheduled for February 2026. To see what's coming in the future, follow the {uri-pkl-roadmap}[Pkl Roadmap]. Please send feedback and questions to https://github.com/apple/pkl/discussions[GitHub Discussions], or submit an issue on https://github.com/apple/pkl/issues/new[GitHub]. + [small]#Pkl is hosted on https://github.com/apple/pkl[GitHub]. To get started, follow xref:pkl-cli:index.adoc#installation[Installation].# == Highlights [small]#💖# [[formatter]] === Formatter Pkl now has a formatter (https://github.com/apple/pkl/pull/1107[#1107], https://github.com/apple/pkl/pull/1208[#1208], https://github.com/apple/pkl/pull/1211[#1211], https://github.com/apple/pkl/pull/1215[#1215], https://github.com/apple/pkl/pull/1217[#1217], https://github.com/apple/pkl/pull/1235[#1235], https://github.com/apple/pkl/pull/1246[#1246], https://github.com/apple/pkl/pull/1247[#1247], https://github.com/apple/pkl/pull/1252[#1252], https://github.com/apple/pkl/pull/1256[#1256], https://github.com/apple/pkl/pull/1259[#1259], https://github.com/apple/pkl/pull/1260[#1260], https://github.com/apple/pkl/pull/1263[#1263], https://github.com/apple/pkl/pull/1265[#1265], https://github.com/apple/pkl/pull/1266[#1266], https://github.com/apple/pkl/pull/1267[#1267], https://github.com/apple/pkl/pull/1268[#1268], https://github.com/apple/pkl/pull/1270[#1270], https://github.com/apple/pkl/pull/1271[#1271], https://github.com/apple/pkl/pull/1272[#1272], https://github.com/apple/pkl/pull/1273[#1273], https://github.com/apple/pkl/pull/1274[#1274], https://github.com/apple/pkl/pull/1280[#1280], https://github.com/apple/pkl/pull/1283[#1283], https://github.com/apple/pkl/pull/1289[#1289], https://github.com/apple/pkl/pull/1290[#1290])! Pkl's formatter is _canonical_, which means that it has a single set of formatting rules, with (almost) no configuration options. The goal is to eliminate the possibility of formatting debates, which can lead to churn and bike-shedding. The formatter is available both as a CLI subcommand and as a Java API. To learn more about this feature, consult https://github.com/apple/pkl-evolution/blob/main/spices/SPICE-0014-canonical-formatter.adoc[SPICE-0014]. ==== Using the CLI The Pkl formatter is available under the `pkl format` subcommand. By default, the command will concatenate and write all formatted content to standard out. To simply check for formatting violations, getting formatted output on stdout is likely too verbose. The `--silent` flag can be used to omit any output, and the <> can be used to determine if there are formatting violations. [source,shell] ---- pkl format --silent . || echo "Formatting violations were found!" ---- Alternatively, the `--names` flag will print out the names of any files that have formatting violations. [source,shell] ---- pkl format --diff-name-only . ---- To apply formatting, use the `--write` (`-w`) flag. This also implies the `--diff-name-only` flag. [source,shell] ---- pkl format -w . ---- [[formatting-exit-codes]] ==== Exit codes The formatter will exit with the following codes: |=== |Code |Description |0 |No formatting violations were found. |1 |Non-formatting errors occurred. |11 |Formatting violations were found. |=== ==== Grammar version The formatter can be configured with a _grammar version_, which maps to a Pkl version range. |=== |Grammar version |Pkl versions |1 |0.25 - 0.29 |2 |0.30+ |=== Grammar version 2 uses the newly introduced <> feature, and therefore is not compatible with existing versions of Pkl. To ensure compatibility, use the `--grammar-version 1` CLI flag. [[binary-renderer-parser]] === `pkl-binary` in-language Renderer A new in-language API has been added to render values into xref:bindings-specification:binary-encoding.adoc[pkl-binary encoding] (https://github.com/apple/pkl/pull/1203[#1203], https://github.com/apple/pkl/pull/1250[#1250], https://github.com/apple/pkl/pull/1275[#1275]). It's sometimes useful to separate Pkl evaluation from data consumption when used as application or service configuration. This is possible with the `pkl-binary` format, which is a lossless encoding of Pkl data. In this approach, Pkl is first rendered into `pkl-binary` during build time, and then deserialized into classes and structs at startup time. This means that the Pkl evaluator does not need to be shipped with the application, which improves code portability and eliminates runtime overhead. However, currently, the API for getting this binary format is somewhat cumbersome. Only the host languages have access to this API (for example, https://swiftpackageindex.com/apple/pkl-swift/0.6.0/documentation/pklswift/evaluator/evaluateexpressionraw(source:expression:)[`evaluateExpressionRaw`] in pkl-swift). This means that one-off logic must be written to render this format in the host language. In 0.30, this renderer is added as an in-language API, through the {uri-stdlib-pklbinaryModule}[`pkl:pklbinary`] standard library module. Additionally, it is available through CLI flag `--format pkl-binary`. For example: // this is parsed instead of tested because DocSnippetTests only handles textual output (via ReplServer) [source%parsed,pkl] ---- import "pkl:pklbinary" output { renderer = new pklbinary.Renderer {} } ---- To learn more about this feature, consult https://github.com/apple/pkl-evolution/blob/main/spices/SPICE-0021-binary-renderer-and-parser.adoc[SPICE-0021]. ==== New renderer type: `BytesRenderer` To enable the `pkl-binary` renderer feature, Pkl now supports renderers that produce `Bytes` output (https://github.com/apple/pkl/pull/1203[#1203]). The existing `ValueRenderer` class now extends new class `BaseValueRenderer`, and a new `BytesRenderer` class is introduced. Setting a module's output renderer to a `BytesRenderer` will control its resulting `output.bytes`. This affects usage scenarios where a module's `output.bytes` is evaluated, for example, when using `pkl eval`. ==== Using `pkl-binary` data Users of Pkl's language binding libraries can decode `pkl-binary` data into instances of code-generated types. [tabs] ==== Java:: + [source,java] ---- var encodedData = fetchEncodedData(); // some byte[] or InputStream var config = Config.fromPklBinary(encodedData); var appConfig = config.as(AppConfig.class); ---- Kotlin:: + [source,kotlin] ---- val encodedData = fetchEncodedData() // some ByteArray or InputStream val config = Config.fromPklBinary(encodedData, ValueMapper.preconfigured().forKotlin()) val appConfig = config.to() ---- Go:: + [source,go] ---- encodedData := fetchEncodedData() // some []byte var appConfig AppConfig if err := pkl.Unmarshal(encodedData, &appConfig); err != nil { // handle error } ---- Swift:: + [source,swift] ---- let encodedData = fetchEncodedData() // some [UInt8] let appConfig = try PklDecoder.decode(AppConfig.self, from: encodedData) ---- ==== == Noteworthy [small]#🎶# [[trailing-commas]] === Trailing Commas Pkl's grammar has been updated to allow including a comma following the final item of comma-separated syntax elements (https://github.com/apple/pkl/pull/1137[#1137]). These syntax elements are affected: [source%tested,pkl] ---- function add( bar, baz, // <1> ) = bar + baz foo = add( 1, 2, // <2> ) typealias MyMapping< Key, Value, // <3> > = Mapping bar: Mapping< String, Int, // <4> > baz: Mapping( !containsKey("forbidden key"), !containsKey("forbidden value"), // <5> ) qux: ( String, Int, // <6> ) -> Dynamic = (paramA, paramB) -> new Dynamic { quux = "\(paramA): \(paramB)" } corge = (qux) { paramA, paramB, // <7> -> grault = paramA.length * paramB } ---- <1> Function parameter lists in method definitions. <2> Argument lists in method calls. <3> Type parameter lists in generic type definitions. <4> Type argument lists in type annotations and casts. <5> Type constraint lists. <6> Function type parameter lists in function type annotations. <7> Object body parameter lists. To learn more about this change, consult https://github.com/apple/pkl-evolution/blob/main/spices/SPICE-0019-trailing-commas.adoc[SPICE-0019]. === Pretty-printed traces A new evaluator option is added to enable pretty-printed traces (https://github.com/apple/pkl/pull/1100[#1100], https://github.com/apple/pkl/pull/1227[#1227], https://github.com/apple/pkl/pull/1230[#1230]). Currently, the `trace()` operator will render values as a single line, and trims the output after 100 characters. This can obscure information when debugging. In 0.30, a new evaluator option is added to control how traces are emitted. Two trace modes are introduced: * `compact` - the current output mode (default). * `pretty` - multiline, with no limit on output size. For example, users of the CLI can specify `--trace-mode` as a flag. [source,shell] ---- pkl eval --trace-mode pretty myModule.pkl ---- Thanks to https://github.com/ssalevan[@ssalevan] for their contribution to this feature! === Better support for `Bytes` when rendering YAML Previously, attempting to render a `Bytes` value using {uri-stdlib-YamlRenderer}[`YamlRenderer`] required the use of a link:{uri-stdlib-PcfRenderer-converters}[converter]. Now, Pkl can natively render YAML containing link:{yaml-binary-scalar}[binary scalars] (https://github.com/apple/pkl/pull/1276[#1276]). [source,pkl%tested] ---- foo { bar = Bytes(1, 2, 3) } rendered = new YamlRenderer {}.renderValue(foo) // <1> ---- <1> Result: `bar: !!binary AQID` Similarly, link:{uri-stdlib-YamlParser}[`yaml.Parser`] now parses binary YAML values as Pkl link:{uri-stdlib-Bytes}[`Bytes`] values (https://github.com/apple/pkl/pull/1277[#1277]). This is a breaking change; previously these values were parsed as link:{uri-stdlib-String}[`String`] value containing the base64-encoded binary data. [source,pkl%tested] ---- import "pkl:yaml" yamlData = """ bytes: !!binary AQID """ parsed = new yaml.Parser {}.parse(yamlData).bytes // <1> ---- <1> Result: `Bytes(1, 2, 3)` [[pkldoc-perf-improvements]] === `pkldoc` performance improvements The `pkldoc` documentation generator has been overhauled (https://github.com/apple/pkl/pull/1169[#1169], https://github.com/apple/pkl/pull/1224[#1224], https://github.com/apple/pkl/pull/1241[#1241], https://github.com/apple/pkl/pull/1242[#1242]). Currently, the `pkldoc` tool needs to consume much of an existing documentation website whenever generating new documentation. This adds significant I/O overhead as a pkldoc documentation website grows. The generator has been overhauled to minimize the amount of data needed to be read from the current site. To read more about this change, consult https://github.com/apple/pkl-evolution/blob/main/spices/SPICE-0018-pkldoc-io-improvements.adoc[SPICE-0018]. [[pkldoc-migration]] ==== Migration The new pkldoc website introduces breaking changes to the data model. Because of this, existing sites must be migrated before using the `0.30` version of pkldoc. To migrate, run the one-time command `pkldoc --migrate`. === Java API changes ==== New classes New classes are introduced to the Java API. * `org.pkl.core.PklBinaryDecoder` ==== New methods New methods are introduced to the Java API. * `org.pkl.core.Evaluator.evaluateExpressionPklBinary` * `org.pkl.core.EvaluatorBuilder.setTraceMode` * `org.pkl.core.EvaluatorBuilder.getTraceMode` * `org.pkl.config.java.Config.fromPklBinary` === Standard Library changes New modules, properties, methods, classes and typealiases have been added to the standard library (https://github.com/apple/pkl/pull/1106[#1106]). ==== Changes to `pkl:base` Additions: * New class: {uri-stdlib-BaseValueRenderer}[`BaseValueRenderer`] * {uri-stdlib-ValueRenderer}[`ValueRenderer`] (now a subclass of {uri-stdlib-BaseValueRenderer}[`BaseValueRenderer`]) * {uri-stdlib-BytesRenderer}[`BytesRenderer`] * {uri-stdlib-baseModule}/RenderDirective#bytes[`RenderDirective.bytes`] ==== Additions to `pkl:EvaluatorSettings` * {uri-stdlib-evaluatorSettingsModule}#traceMode[`traceMode`] ==== Additions to `pkl:reflect` * {uri-stdlib-reflectModule}/Class#allMethods[`Class.allMethods`] * {uri-stdlib-reflectModule}/Class#allProperties[`Class.allProperties`] * {uri-stdlib-reflectModule}/Property#allAnnotations[`Propterty.allAnnotations`] * {uri-stdlib-reflectModule}/Propterty#allModifiers[`Propterty.allModifiers`] ==== New module: `pkl:pklbinary` The `pkl:pklbinary` standard library module is added. === `pkl repl` improvements The REPL now handles interrupts (Ctrl-C) in a more user-friendly way (https://github.com/apple/pkl/pull/1188[#1188]). Instead of exiting immediately, it behaves like other REPLs: * If the line is non-empty or in a continuation, the buffer is cleared. * If the line is empty, print a message with instructions on exiting the REPL. ** If another interrupt is sent immediately after, exit. == Breaking Changes [small]#💔# Things to watch out for when upgrading. === Binary data handling `yaml.Parser` link:{yaml-binary-scalar}[YAML binary scalars] are now parsed as link:{uri-stdlib-Bytes}[`Bytes`] values. Prior versions of Pkl parsed binary scalars as link:{uri-stdlib-String}[`String`] values containing the base64-encoded binary data. === Minimum Kotlin version bump For users of Pkl's Kotlin libraries, the minimum Kotlin version has been bumped to 2.1 (https://github.com/apple/pkl/pull/1232[#1232]). === New base module names: `BaseValueRenderer`, `BytesRenderer` Two new names are introduced to the base module: `BaseValueRenderer`, and `BytesRenderer`. That means that any code that currently references these names on implicit `this` will break (https://github.com/apple/pkl/pull/1203[#1203]). To learn more about how this breaks code and how to fix it, see xref:0.29.adoc#new-base-module-names[New base module names] in 0.29's release notes. === `pkldoc` sites need to be migrated Due to breaking changes made in pkldoc's data model, existing pkldoc websites must be migrated. See <> for more details. == Miscellaneous [small]#🐸# * Dependency updates (https://github.com/apple/pkl/pull/1184[#1184], https://github.com/apple/pkl/pull/1225[#1225], https://github.com/apple/pkl/pull/1226[#1226], https://github.com/apple/pkl/pull/1228[#1228]). * Enforce Pkl formatting of stdlib (https://github.com/apple/pkl/pull/1236[#1236], https://github.com/apple/pkl/pull/1253[#1253], https://github.com/apple/pkl/pull/1258[#1258], https://github.com/apple/pkl/pull/1278[#1278], https://github.com/apple/pkl/pull/1279[#1279]). * Add internal IntelliJ plugin that's meant to assist with development of the Pkl codebase itself (https://github.com/apple/pkl/pull/1248[#1248]). * Update CircleCI macOS instance type and Xcode version (https://github.com/apple/pkl/pull/1243[#1243], https://github.com/apple/pkl/pull/1244[#1244]). * Disable multi-jdk testing when running on Windows ARM (https://github.com/apple/pkl/pull/1223[#1223]). * Refine documentation for class `Any` (https://github.com/apple/pkl/pull/1194[#1194]). == Bugs fixed [small]#🐜# The following bugs have been fixed. * Incorrect error message when refusing to read past root dir (https://github.com/apple/pkl/pull/1234[#1233]). * Unicode U+7FFF character (翿) incorrectly parsed as EOF (https://github.com/apple/pkl/pull/1251[#1251]). * Fallback certificates do not work in certain classloader setups (https://github.com/apple/pkl/pull/1198[#1199]). == Contributors [small]#🙏# We would like to thank the contributors to this release (in alphabetical order): * https://github.com/bioball[@bioball] * https://github.com/HT154[@HT154] * https://github.com/netvl[@netvl] * https://github.com/spyoungtech[@spyoungtech] * https://github.com/srueg[@srueg] * https://github.com/ssalevan[@ssalevan] * https://github.com/stackoverflow[@stackoverflow] ================================================ FILE: docs/modules/release-notes/pages/0.31.adoc ================================================ = Pkl 0.31 Release Notes :version: 0.31 :version-minor: 0.31.0 :release-date: February 26th, 2026 :version-next: 0.32 :version-next-date: July 2026 include::partial$intro.adoc[] == Highlights [small]#💖# [[power-assertions]] === Power Assertions Pkl's test output and error output has been improved with power assertions (pr:https://github.com/apple/pkl/pull/1384[], pr:https://github.com/apple/pkl/pull/1419[])! Pkl has two places that are effectively assertions. These are: * Type constraint expressions * Test facts Currently, when these assertions fail, the error message prints the assertion's source section. However, this information can sometimes be lacking. It tells you what the mechanics of the assertion is, but doesn't tell you _why_ the assertion is failing. For example, here is the current error output of a failing typecheck. [source,text] ---- –– Pkl Error –– Type constraint `name.endsWith(lastName)` violated. Value: new Person { name = "Bub Johnson" } 7 | passenger: Person(name.endsWith(lastName)) = new { name = "Bub Johnson" } ---- Just from this error message, we don't know what the last name is supposed to be. What is `name` supposed to end with? We just know it's some property called `lastName` but, we don't know what it _is_. In Pkl 0.31, the error output becomes: [source,text] ---- –– Pkl Error –– Type constraint `name.endsWith(lastName)` violated. Value: new Person { name = "Bub Johnson" } name.endsWith(lastName) │ │ │ │ false "Smith" "Bub Johnson" 7 | passenger: Person(name.endsWith(lastName)) = new { name = "Bub Johnson" } ---- Now, we know what the expectation is. This type of diagram is also added to test facts. When tests fail, Pkl emits a diagram of the expression, and the values produced. For example, given the following test: .math.pkl [source,pkl] ---- amends "pkl:test" facts { local function add(a: Int, b: Int) = a * b local function divide(a: Int, b: Int) = a % b ["math"] { add(3, 4) == 7 divide(8, 2) == 4 } } ---- The error output now includes a power assertion diagram, which helps explain why the test failed. [source,text] ---- module math facts ✘ math add(3, 4) == 7 (math.pkl:9) │ │ 12 false divide(8, 2) == 4 (math.pkl:10) │ │ 0 false 0.0% tests pass [1/1 failed], 0.0% asserts pass [2/2 failed] ---- To learn more about this feature, consult https://github.com/apple/pkl-evolution/blob/main/spices/SPICE-0026-power-assertions.adoc[SPICE-0026]. [[cli-framework]] === CLI Framework Pkl 0.31 introduces a new framework for implementing CLI tools in Pkl (pr:https://github.com/apple/pkl/pull/1367[], pr:https://github.com/apple/pkl/pull/1431[], pr:https://github.com/apple/pkl/pull/1432[], pr:https://github.com/apple/pkl/pull/1436[], pr:https://github.com/apple/pkl/pull/1440[], pr:https://github.com/apple/pkl/pull/1444[]). The framework provides a way to build command line tools with user experience idioms that will be immediately familiar to users. CLI tools implemented in Pkl have largely the same capabilities as normal Pkl evaluation (i.e. writing to standard output and files), but this may be extended using xref:language-reference:index.adoc#external-readers[external readers]. Commands are defined by extending the pkldoc:#[pkl:Command] module: .bird-generator.pkl [source,pkl] ---- extends "pkl:Command" options: Options class Options { /// Mapping of = pairs defining the list of birds. @Argument birds: Mapping /// Aggregation function to apply to all bird ages. aggregate: *"sum" | "mean" | "count" } class Bird { /// Name of the bird. name: String /// Age of the bird in years. age: Number } birds: Listing = new { for (_name, _age in options.birds) { new { name = _name; age = _age } } } result: Number = if (options.aggregate == "sum") birds.toList().fold(0.0, (result, bird) -> result + bird.age) else if (options.aggregate == "mean") birds.toList().fold(0.0, (result, bird) -> result + bird.age) / birds.length else birds.length ---- Commands are executed using the `pkl run` CLI subcommand: [source,bash] ---- $ pkl run bird-generator.pkl pigeon --aggregate=mean Pigeon=1 Hawk=8 Eagle=3 birds { new { name = "Pigeon" age = 1 } new { name = "Hawk" age = 8 } new { name = "Eagle" age = 3 } } result = 4.0 ---- Automatic CLI help is provided: [source,bash] ---- $ pkl run test.pkl -h Usage: test.pkl [] []... []... Options: --aggregate= Aggregation function to apply to all bird ages. -h, --help Show this message and exit Arguments: Mapping of = pairs defining the list of birds. ---- To learn more about this feature, consult xref:pkl-cli:index.adoc#cli-tools[the documentation] and https://github.com/apple/pkl-evolution/blob/main/spices/SPICE-0025-pkl-run-cli-framework.adoc[SPICE-0025]. == Noteworthy [small]#🎶# === Syntax Highlighting The Pkl CLI displays Pkl code in several locations: stack frames within errors messages, <>, and in the xref:pkl-cli:index.adoc#repl[REPL]. This code is now syntax highlighted to improve readability (pr:https://github.com/apple/pkl/pull/1385[], pr:https://github.com/apple/pkl/pull/1409[]): image::syntax-highlight-error.png[syntax highlighted output] [[cli-dependency-notation]] === CLI Support for Dependency Notation The Pkl CLI now supports specifying modules using xref:language-reference:index.adoc#project-dependencies[dependency notation] (pr:https://github.com/apple/pkl/pull/1434[], pr:https://github.com/apple/pkl/pull/1439[]). This is especially helpful for <> defined in Packages: [source,bash] ---- $ pkl run @my-tool/cmd.pkl ... ---- This enhancement applies to the `pkl eval`, `pkl run`, `pkl test`, and `pkl analyze imports` commands. It also applies to the `pkldoc`, `pkl-codegen-java`, and `pkl-codegen-kotlin` tools. [NOTE] Dependency notation only works for remote package dependencies. xref:language-reference:index.adoc#local-dependencies[Local dependencies] are not supported. === Property Converter Annotations Pkl provides the pkldoc:BaseValueRenderers#converters[] mechanism for transforming values during rendering. Converters are flexible, but their design makes some transformations awkward. In particular, modifying property names during rendering required a lot of extra code. The new pkldoc:ConvertProperty[] annotation adds a way to express parameterized per-property name and value transformations (pr:https://github.com/apple/pkl/pull/1333[]). To learn more about this feature, consult https://github.com/apple/pkl-evolution/blob/main/spices/SPICE-0024-annotation-converters.adoc[SPICE-0024]. Additional new Pkl APIs for per-format property renaming will be added for many built-in renderers: .fmt.pkl [source,pkl] ---- import "pkl:json" import "pkl:yaml" @json.Property { name = "foo_bar" } @yaml.Property { name = "foo-bar" } fooBar: String = "hello world" ---- [source,terminaloutput] ---- $ pkl eval fmt.pkl -f json { "foo_bar": "hello world" } $ pkl eval fmt.pkl -f yaml foo-bar: hello world ---- === Java API changes ==== New classes New classes are introduced to the Java API. * `org.pkl.core.CommandSpec` ==== New methods New methods are introduced to the Java API. * `org.pkl.core.Evaluator.evaluateCommand` * `org.pkl.core.EvaluatorBuilder.setPowerAssertionsEnabled` * `org.pkl.core.EvaluatorBuilder.getPowerAssertionsEnabled` * `org.pkl.core.SecurityManager.resolveSecurePath` * `org.pkl.config.java.ConfigEvaluator.evaluateOutputValue` * `org.pkl.coznfig.java.ConfigEvaluator.evaluateExpression` === Standard Library changes New properties have been added to the standard library (pr:https://github.com/apple/pkl/pull/1396[]). ==== Additions to `pkl:base` * pkldoc:List#isNotEmpty[] * pkldoc:Map#isNotEmpty[] * pkldoc:Set#isNotEmpty[] * pkldoc:Listing#isNotEmpty[] * pkldoc:Mapping#isNotEmpty[] * pkldoc:String#isNotEmpty[] * pkldoc:String#isNotBlank[] * pkldoc:ConvertProperty[] * pkldoc:BaseValueRenderer#convertPropertyTransformers[] ==== New module `pkl:Command` The pkldoc:#[pkl:Command] standard library module is added. ==== Additions to `pkl:json` * pkldoc:Property[pkl:json] ==== Additions to `pkl:jsonnet` * pkldoc:Property[pkl:jsonnet] ==== Additions to `pkl:protobuf` * pkldoc:Property[pkl:protobuf] ==== Additions to `pkl:xml` * pkldoc:Property[pkl:xml] ==== Additions to `pkl:yaml` * pkldoc:Property[pkl:yaml] == Breaking Changes [small]#💔# Things to watch out for when upgrading. === Removal of `@argfile` support Prior versions of Pkl had an undocumented feature allowing inclusion of CLI arguments from files using `@path/to/file`. In order to support <> on the CLI, `@argfile` support has been removed from Pkl. === Removal of `Collection#transpose()` Prior versions of Pkl defined a `transpose()` method on the `Collection` class. This method was never implemented and threw an error when called. As it was never functional, it has been removed entirely without a deprecation warning (pr:https://github.com/apple/pkl/pull/1437[]). == Miscellaneous [small]#🐸# * Improve formatting of imports to keep surrounding comments (pr:https://github.com/apple/pkl/pull/1424[]). * Add support for evaluating module output and expressions to `ConfigEvaluator` (pr:https://github.com/apple/pkl/pull/1297[]). * The `pkl format --write` command now exits successfully when formatting violations are found and updated (pr:https://github.com/apple/pkl/pull/1340[]). * Add `pkl-bom` module to aid in aligning Pkl Java dependencies (pr:https://github.com/apple/pkl/pull/1390[]). * Improve error message when writing `PklProject.deps.json` fails (pr:https://github.com/apple/pkl/pull/1405[]). * Add information about pkldoc:Annotation[]s to the language reference (pr:https://github.com/apple/pkl/pull/1427[]). * Improved usability for the `org.pkl.formatter.Formatter` Java API (pr:https://github.com/apple/pkl/pull/1428[]). == Bugs fixed [small]#🐜# The following bugs have been fixed. * `Function.toString()` returns incorrect result (pr:https://github.com/apple/pkl/pull/1411[]). * Failure when `--multiple-file-output-path` is a symlink (pr:https://github.com/apple/pkl/pull/1389[]). * The `module` type in a non-final module has default value of type `Dynamic` (pr:https://github.com/apple/pkl/pull/1392[]). * The `module` type is cached incorrectly in some cases (pr:https://github.com/apple/pkl/pull/1393[]). * A possible race condition involving symlinks could bypass `--root-dir` during module and resource reading (pr:https://github.com/apple/pkl/pull/1426[]). * `pkl format` produces internal stack traces when lexing fails (pr:https://github.com/apple/pkl/pull/1430[]). * `super` access expressions are parsed incorrectly inside the spread operator (pr:https://github.com/apple/pkl/pull/1364[]). * Modules and resources with `jar:file:` URIs were not properly sandboxed by `--root-dir` (pr:https://github.com/apple/pkl/pull/1442[]). == Contributors [small]#🙏# We would like to thank the contributors to this release (in alphabetical order): * https://github.com/bioball[@bioball] * https://github.com/eddie4941[@eddie4941] * https://github.com/HT154[@HT154] * https://github.com/stackoverflow[@stackoverflow] * https://github.com/StefMa[@StefMa] ================================================ FILE: docs/modules/release-notes/pages/0.32.adoc ================================================ = Pkl 0.32.0 Release Notes :version: 0.32 :version-minor: 0.32.0 :release-date: TBD :version-next: 0.33 :version-next-date: TBD include::partial$intro.adoc[] == Highlights [small]#💖# News you don't want to miss. .XXX [%collapsible] ==== XXX ==== == Noteworthy [small]#🎶# Ready when you need them. .XXX [%collapsible] ==== XXX ==== == Breaking Changes [small]#💔# Things to watch out for when upgrading. .XXX [%collapsible] ==== XXX ==== == Work In Progress [small]#🚆# They missed the train but deserve a mention. .XXX [%collapsible] ==== XXX ==== == Contributors [small]#🙏# We would like to thank the contributors to this release (in alphabetical order): * XXX ================================================ FILE: docs/modules/release-notes/pages/changelog.adoc ================================================ = Changelog include::ROOT:partial$component-attributes.adoc[] [[release-0.32.0]] == 0.32.0 (UNRELEASED) [[release-0.31.0]] == 0.31.0 (2026-02-26) xref:0.31.adoc[Release Notes] [[release-0.30.2]] == 0.30.2 (2025-12-15) === Fixes * Fixes formatting of blank files (https://github.com/apple/pkl/pull/1351[#1351]). * Fixes an issue where the `pkl format` CLI command adds an extra newline when writing formatted content to standard output (https://github.com/apple/pkl/issues/1346[#1346]). * Make linux executables link to glibc 2.17 (https://github.com/apple/pkl/pull/1352[#1352]). * Fix incorrect parsing of super expressions (https://github.com/apple/pkl/pull/1364[#1364]). === Contributors ❤️ Thank you to all the contributors for this release! * https://github.com/bioball[@bioball] * https://github.com/HT154[@HT154] * https://github.com/stackoverflow[@stackoverflow] [[release-0.30.1]] == 0.30.1 (2025-12-03) === Fixes * Fixes formatting of `Map` constructors with line comments (https://github.com/apple/pkl/pull/1312[#1312]). * Fixes a crash when parsing empty parenthesized types (https://github.com/apple/pkl/pull/1323[#1323]). * Fixes a parser issue allowing too many newlines between tokens (https://github.com/apple/pkl/pull/1328[#1328]). * Fixes parsing of URIs with schemes containing `+`, `-`, or `.` (https://github.com/apple/pkl/pull/1335[#1335]). * Fixes a crash when creating very large `List` instances (https://github.com/apple/pkl/pull/1337[#1337]). === Contributors ❤️ Thank you to all the contributors for this release! * https://github.com/bioball[@bioball] * https://github.com/HT154[@HT154] * https://github.com/spyoungtech[@spyoungtech] * https://github.com/stackoverflow[@stackoverflow] [[release-0.30.0]] == 0.30.0 (2025-10-30) xref:0.30.adoc[Release notes] [[release-0.29.1]] == 0.29.1 (2025-08-27) === Fixes * Fixes an issue where autocompletion in Bash and ZSH do noes not suggest filenames (https://github.com/apple/pkl/pull/1161[#1161]). * Fixes an issue where `pkldoc` throws a runtime error about failing to load class path resources (https://github.com/apple/pkl/issues/1174[#1174]). * Fixes an issue where `pkldoc` always runs with `testMode` set to true. * Fixes an issue where evaluating a module that ends with an unmatched backtick throws `ArrayIndexOutOfBoundsException` (https://github.com/apple/pkl/issues/1182[#1182]). * Fixes the formatting of YAML strings when emitting backslash characters within quoted strings (https://github.com/apple/pkl/pull/1165[#1165]). * Fixes an issue where `local` members inside `Mapping` objects are incorrectly encoded into binary format (https://github.com/apple/pkl/issues/1151[#1151]). === Contributors ❤️ Thank you to all the contributors for this release! * https://github.com/bioball[@bioball] * https://github.com/gordonbondon[@gordonbondon] * https://github.com/HT154[@HT154] [[release-0.29.0]] == 0.29.0 (2025-07-24) xref:0.29.adoc[Release notes] [[release-0.28.2]] == 0.28.2 (2025-04-17) === Fixes * Adds an optimization when object bodies with generators produce no members (https://github.com/apple/pkl/pull/1013[#1013]). * Fixes a runtime crash when `--output-path` points to a directory (https://github.com/apple/pkl/pull/1038[#1038]). * Fixes a bug that sometimes causes pkl-doc to crash (https://github.com/apple/pkl/pull/1028[#1028]). === Miscellaneous * Documentation improvements (https://github.com/apple/pkl/pull/982[#982], https://github.com/apple/pkl/pull/1010[#1010], https://github.com/apple/pkl/pull/1031[#1031]). * CI improvements (https://github.com/apple/pkl/pull/1020[#1020]). === Contributors ❤️ Thank you to all the contributors for this release! * https://github.com/JeroenSoeters[@JeroenSoeters] * https://github.com/KushalP[@KushalP] * https://github.com/mbvissers[@mbvissers] * https://github.com/pepicrft[@pepicrft] [[release-0.28.1]] == 0.28.1 (2025-03-03) === Fixes * Fixes an issue where Pkl Gradle tasks can possibly fail with `java.lang.UnsatisfiedLinkError` (https://github.com/apple/pkl/pull/995[#995]). * Fixes an issue where the artifacts pkl-tools and pkl-config-java-all fail with `java.lang.ClassFormatError` (https://github.com/apple/pkl/pull/998[#998]). === Changes * Adds the ability to configure `native-image` build with Gradle system properties (https://github.com/apple/pkl/pull/1001[#1001]). + Now, any system property starting with `"pkl.native"` will have that prefix stripped, and the rest passed as CLI arguments to `native-image`. For example, the native-image resource cache can be configured by passing Gradle flag `-Dpkl.native-Dpolyglot.engine.userResourceCache=/my/cache/dir`. [[release-0.28.0]] == 0.28.0 (2025-02-26) xref:0.28.adoc[Release notes] [[release-0.27.2]] == 0.27.2 (2025-01-22) === Fixes * Fixes issues where server mode message decoding might result in null pointer exceptions (https://github.com/apple/pkl/pull/853[#853], https://github.com/apple/pkl/pull/882[#882]). * Fixes an issue where the test report outputs decimal numbers using local-specific decimals (https://github.com/apple/pkl/pull/868[#868]). * Fixes an issue where the native executables might not run on some environments, resulting in an error like "Fatal error: Failed to create the main Isolate" (https://github.com/apple/pkl/pull/875[#875]). === Contributors ❤️ Thank you to all the contributors for this release! * link:https://github.com/HT154[@HT154] * link:https://github.com/StefMa[@StefMa] [[release-0.27.1]] == 0.27.1 (2024-12-06) === Fixes - Fixes a broken "number literals" link in the 0.27 release notes (https://github.com/apple/pkl/pull/784[#784]). - Fixes a possible deadlock during external reader process close (https://github.com/apple/pkl/pull/786[#786]). - Fixes counting elements with computed indices multiple times in length computation of listings (https://github.com/apple/pkl/pull/797[#797]). - Fixes non Pkl modules being reported in GatherImports task, leading to plugin failures (https://github.com/apple/pkl/pull/821[#821]). - Fixes a problem where the delegate chain of type casts for Listing/Mapping get unreasonably big, even though the type nodes are the same, which may lead to a stack overflow or performance degradation (https://github.com/apple/pkl/pull/826[#826]). - Fixes incorrect scoping of type variables in lazy Listing/Mapping type checking in cross-module typealiases (https://github.com/apple/pkl/pull/789[#789]). - Fixes regression in type checking logic for Listing/Mapping (https://github.com/apple/pkl/pull/789[#789]). === Contributors ❤️ Thank you to all the contributors for this release! * link:https://github.com/GUI[@GUI] * link:https://github.com/HT154[@HT154] * link:https://github.com/odenix[@odenix] (formerly @translatenix) [[release-0.27.0]] == 0.27.0 (2024-11-05) xref:0.27.adoc[Release notes] [[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]] == 0.26.0 (2024-06-17) xref:0.26.adoc[Release notes] [[release-0.25.3]] == 0.25.3 (2024-03-26) === Fixes * Fixes some issues with generated pkldoc websites (link:https://github.com/apple/pkl/pull/357[#357], link:https://github.com/apple/pkl/pull/362[#362]) * Fixes a bug where amending a module that defines an abstract class might cause a Java `AssertionError` (link:https://github.com/apple/pkl/pull/319[#319]) * Fixes a bug where a for/when generator within a lambda declared with `new {}` syntax might not resolve variables correctly (link:https://github.com/apple/pkl/pull/297[#297]) * Fixes a bug where `const` and `local` modifiers are not exported when obtaining a class's mirror in `pkl:reflect` (link:https://github.com/apple/pkl/pull/265[#265]). === Miscellaneous * Documentation improvements (link:https://github.com/apple/pkl/pull/93[#93], link:https://github.com/apple/pkl/pull/106[#106], link:https://github.com/apple/pkl/pull/143[#143], link:https://github.com/apple/pkl/pull/205[#205], link:https://github.com/apple/pkl/pull/214[#214], link:https://github.com/apple/pkl/pull/224[#224], link:https://github.com/apple/pkl/pull/257[#257], link:https://github.com/apple/pkl/pull/270[#270], link:https://github.com/apple/pkl/pull/282[#282], link:https://github.com/apple/pkl/pull/283[#283], link:https://github.com/apple/pkl/pull/299[#299], link:https://github.com/apple/pkl/pull/337[#337], link:https://github.com/apple/pkl/pull/340[#340]) * Build script improvements (link:https://github.com/apple/pkl/pull/253[#253], link:https://github.com/apple/pkl/pull/314[#314], link:https://github.com/apple/pkl/pull/333[#333], link:https://github.com/apple/pkl/pull/338[#338]) === Changes * Add `jpkl` to the set of artifacts released to GitHub (link:https://github.com/apple/pkl/pull/314[#314]) === Contributors ❤️ Thank you to all the contributors for this release! * link:https://github.com/r1ft1[@r1ft1] * link:https://github.com/WardsParadox[@WardsParadox] * link:https://github.com/grantabbott[@grantabbott] * link:https://github.com/mshakhmaykin[@mshakhmaykin] * link:https://github.com/d4wae89d498[@d4wae89d498] * link:https://github.com/KushalP[@KushalP] * link:https://github.com/zihluwang[@zihluwang] * link:https://github.com/Malix-off[@Malix-off] [[release-0.25.2]] == 0.25.2 (2024-02-08) === Fixes * Fixes some issues with generated pkldoc websites (link:https://github.com/apple/pkl/pull/70[#70], link:https://github.com/apple/pkl/pull/81[#81], link:https://github.com/apple/pkl/pull/96[#96]) * Fixes an issue where a PklBugException produces an incorrect URL to file issues (link:https://github.com/apple/pkl/pull/73[#73]) === Miscellaneous * Documentation fixes (link:https://github.com/apple/pkl/pull/21[#21], link:https://github.com/apple/pkl/pull/29[#29], link:https://github.com/apple/pkl/pull/41[#41], link:https://github.com/apple/pkl/pull/44[#44], link:https://github.com/apple/pkl/pull/51[#51], link:https://github.com/apple/pkl/pull/57[#57], link:https://github.com/apple/pkl/pull/60[#60], link:https://github.com/apple/pkl/pull/64[#64], link:https://github.com/apple/pkl/pull/68[#68], link:https://github.com/apple/pkl/pull/74[#74], link:https://github.com/apple/pkl/pull/77[#77], link:https://github.com/apple/pkl/pull/78[#78], link:https://github.com/apple/pkl/pull/82[#82], link:https://github.com/apple/pkl/pull/84[#84], link:https://github.com/apple/pkl/pull/86[#86], link:https://github.com/apple/pkl/pull/89[#89], link:https://github.com/apple/pkl/pull/91[#91], link:https://github.com/apple/pkl/pull/104[#104]) * Build script improvements (https://github.com/apple/pkl/pull/53[#53], link:https://github.com/apple/pkl/pull/83[#83]) === Contributors ❤️ Thank you to all the contributors for this release! * link:https://github.com/uhooi[@uhooi] * link:https://github.com/mnin[@mnin] * link:https://github.com/TinkoLiu[@TinkoLiu] * link:https://github.com/shufanhao[@shufanhao] * link:https://github.com/pfeilbr[@pfeilbr] * link:https://github.com/KadoBOT[@KadoBOT] * link:https://github.com/KushalP[@KushalP] * link:https://github.com/translatenix[@translatenix] * link:https://github.com/Edward-Knight[@Edward-Knight] * link:https://github.com/jamesward[@jamesward] * link:https://github.com/shubhgng[@shubhgng] * link:https://github.com/hayashikun[@hayashikun] * link:https://github.com/lanzafame[@lanzafame] * link:https://github.com/igmrrf[@igmrrf] * link:https://github.com/kelvinelove[@kelvinelove] * link:https://github.com/lilyball[@lilyball] * link:https://github.com/maiph[@maiph] [[release-0.25.1]] == 0.25.1 (2024-02-01) === Fixes * Fixes an issue where pkl-tools fat jar is empty. [[release-0.25.0]] == 0.25.0 (2024-02-01) xref:0.25.adoc[Release notes] ================================================ FILE: docs/modules/release-notes/pages/index.adoc ================================================ = Release Notes The Pkl team aims to release a new version of Pkl in February, June, and October of each year. * xref:0.32.adoc[0.32 Release Notes] * xref:0.31.adoc[0.31 Release Notes] * xref:0.30.adoc[0.30 Release Notes] * xref:0.29.adoc[0.29 Release Notes] * xref:0.28.adoc[0.28 Release Notes] * xref:0.27.adoc[0.27 Release Notes] * xref:0.26.adoc[0.26 Release Notes] * xref:0.25.adoc[0.25 Release Notes] * xref:changelog.adoc[Changelog] ================================================ FILE: docs/modules/release-notes/partials/intro.adoc ================================================ include::ROOT:partial$component-attributes.adoc[] Pkl {version} was released on {release-date}. + [.small]#The latest bugfix release is {version-minor}. (xref:changelog.adoc[All Versions])# The next release ({version-next}) is scheduled for {version-next-date}. To see what's coming in the future, follow the {uri-pkl-roadmap}[Pkl Roadmap]. Please send feedback and questions to https://github.com/apple/pkl/discussions[GitHub Discussions], or submit an issue on https://github.com/apple/pkl/issues/new[GitHub]. + [small]#Pkl is hosted on https://github.com/apple/pkl[GitHub]. To get started, follow xref:pkl-cli:index.adoc#installation[Installation].# ================================================ FILE: docs/modules/release-notes/template.adoc ================================================ = Pkl XXX Release Notes :version: XXX (e.g., 0.9) :version-minor: XXX (e.g., 0.9.0) :release-date: XXX (e.g., July 11, 2018) :version-next: XXX (e.g., 0.10) :version-next-date: XXX (e.g., July 2018) include::partial$intro.adoc[] == Highlights [small]#💖# News you don't want to miss. .XXX [%collapsible] ==== XXX ==== == Noteworthy [small]#🎶# Ready when you need them. .XXX [%collapsible] ==== XXX ==== == Breaking Changes [small]#💔# Things to watch out for when upgrading. .XXX [%collapsible] ==== XXX ==== == Work In Progress [small]#🚆# They missed the train but deserve a mention. .XXX [%collapsible] ==== XXX ==== == Contributors [small]#🙏# We would like to thank the contributors to this release (in alphabetical order): * XXX ================================================ FILE: docs/modules/style-guide/pages/index.adoc ================================================ = Pkl Style Guide :icons: font :source-highlighter: highlight.js :pkl-expr: pkl expression :pkl: pkl :sectnums: This document serves as the Pkl team's recommended coding standard for the Pkl configuration language. == Files === Filename Use the `.pkl` extension for all files. Follow these rules for casing the file's name: [cols="1,3,1"] |=== | Casing | Description | Example | PascalCase | It is designed to be used as a template, or used as a class (i.e. imported and instantiated). | `K8sResource.pkl` | camelCase | It is designed to be used as a value. | `myDeployment.pkl` | kebab-case | It is designed to be used as a CLI tool. | `do-convert.pkl` |=== *Exception*: If a file is meant to render into a static configuration file, the filename should match the target file's name without the extension. For example, `config.pkl` turns into `config.yml`. *Exception*: The `PklProject` file cannot have any extension. === File Encoding Encode all files using UTF-8. == Module Structure === Header Separate each section of the module header by one blank line. A module header consists of the following clauses, each of which is optional: - Module clause - `amends` or `extends` clause - Import clauses .module.pkl [source%parsed,{pkl}] ---- module com.example.Foo // <1> extends "Bar.pkl" // <2> import "baz.pkl" // <3> import "Buz.pkl" // <3> ---- <1> Module clause <2> `extends` clause <3> Import clause ==== Module name Match the name of the module with the name of the file. .MyModule.pkl [source%tested,{pkl}] ---- module MyModule ---- If a module is meant to be published, add a module clause, `@ModuleInfo` annotation, and doc comments. Modules that do not get published anywhere may omit a module clause. .MyModule.pkl [source%tested,{pkl}] ---- /// Used for some type of purpose. <1> @ModuleInfo { minPklVersion = "0.24.0" } // <2> module MyModule // <3> ---- <1> Doc comments <2> `@ModuleInfo` annotation <3> Module clause ==== `amends` vs. `extends` clause A module that doesn't add new properties shouldn't use the `extends` clause. ==== Imports Sort imports sections using https://en.wikipedia.org/wiki/Natural_sort_order[natural sorting] by their module URI. Relative path and package imports should be in their own section, separated by a newline. There should be no unused imports. [source%parsed,{pkl}] ---- import "modulepath:/foo.pkl" import "package://example.com/mypackage@1.0.0#/foo.pkl" import "@mypackage/baz.pkl" import ".../my/file/bar2.pkl" import ".../my/file/bar11.pkl" ---- === Module body Within a module body, define members in this order: 1. Properties 2. Methods 3. Classes and type aliases 4. The amended xref:language-reference:index.adoc#in-language[output] property. *Exception*: local members can be close to their usage. *Exception*: functions meant to be a class constructor can be next to the class declaration. .constructor.pkl [source%tested,{pkl}] ---- function MyClass(_name: String): MyClass = new { name = _name } class MyClass { name: String } ---- === Module URIs If possible, use xref:language-reference:index.adoc#triple-dot-module-uris[triple-dot Module URIs] to reference ancestor modules instead of multiple `../`. .good.pkl [source%parsed,{pkl}] ---- amends ".../ancestor.pkl" import ".../ancestor2.pkl" ---- .bad.pkl [source%parsed,{pkl}] ---- amends "../../../ancestor.pkl" import "../../../ancestor2.pkl" ---- == Objects === Member spacing Object members (properties, elements, and entries) should be separated by at most one blank line. .good.pkl [source%tested,{pkl}] ---- foo = "bar" baz = "buz" ---- .good.pkl [source%tested,{pkl}] ---- foo = "bar" baz = "buz" ---- .bad.pkl [source%tested,{pkl}] ---- foo = "bar" baz = "buz" ---- Too many lines separate `foo` and `baz`. === Overridden properties Properties that override an existing property shouldn't have doc comments nor type annotations, unless the type is intentionally overridden via `extends`. [source%tested,{pkl}] ---- amends "myOtherModule.pkl" foo = "bar" ---- === New property definitions Each property definition should have a type annotation and <>. Successive definitions should be separated by a blank line. .good.pkl [source%parsed,{pkl}] ---- /// Denotes something. myFoo: String /// Something else myOtherFoo: String ---- .bad.pkl [source%parsed,{pkl}] ---- /// Denotes something. myFoo: String /// Something else myOtherFoo: String ---- === Objects with `new` When initializing a `Typed` object using `new`, omit the type. For example, use `new {}` instead of `new Foo {}`. This rule does not apply when initializing a property to a subtype of the property's declared type. .good.pkl [source%parsed,{pkl}] ---- myFoo: Foo = new { foo = "bar" } ---- .good.pkl [source%parsed,{pkl}] ---- open class Foo {} class Bar extends Foo {} foo: Foo = new Bar {} ---- This is okay because this is meaning to initialize `Bar` instead of `Foo`. .bad.pkl [source%parsed,{pkl}] ---- myFoo1: Foo = new Foo { foo = "bar" } // <1> myFoo2 = new Foo { foo = "bar" } // <2> ---- <1> Unnecessary `new Foo { ... }` <2> Unless amending/extending a module where `myFoo2` is already defined, `myFoo2` is effectively the `unknown` type, i.e. `myFoo2: unknown`. == Comments Use doc comments to convey information to users of a module. Use line comments or block comments to convey implementation concerns to authors of a module, or to comment out code. [[doc-comment]] === Doc comments Doc comments should start with a one sentence summary paragraph, followed by additional paragraphs if necessary. Start new sentences on their own line. [source%parsed,{pkl}] ---- /// The time allotted for eating lunch. /// /// Note: /// * Hamburgers typically take longer to eat than salad. /// * Pizza gets prepared per-order. /// /// Orders must be placed on-prem. /// See for more details. lunchHours: Duration ---- === Line comments If a comment relates to a property definition, place it after the property's doc comments. Add a single space after `//`. .good.pkl [source%parsed,{pkl}] ---- /// Designates whether it is zebra party time. // TODO: Add constraints here? partyTime: Boolean ---- A line comment may also be placed at the end of a line, as long as the line doesn't exceed 100 characters. .good.pkl [source%tested,{pkl}] ---- /// Designates whether it is zebra party time. partyTime: Boolean // TODO: Add constraints here? ---- === Block comments A single-line block comment should have a single space after `+++/*+++` and before `+++*/+++`. .good.pkl [source%tested,{pkl}] ---- /* Let's have a zebra party */ ---- .bad.pkl [source%tested,{pkl}] ---- /*Let's have a zebra party*/ ---- == Classes === Class names Name classes in PascalCase. .good.pkl [source%tested,{pkl}] ---- class ZebraParty {} ---- .bad.pkl [source%tested,{pkl}] ---- class zebraParty {} ---- == Strings === Custom String Delimiters Use xref:language-reference:index.adoc#custom-string-delimiters[custom string delimiters] to avoid the need for string escaping. .good.pkl [source%tested,{pkl}] ---- myString = #"foo \ bar \ baz"# ---- .bad.pkl [source%tested,{pkl}] ---- myString = "foo \\ bar \\ baz" ---- NOTE: Sometimes, using custom string delimiters makes source code harder to read. For example, the `+\#+` literal reads better using escapes (`"\\#"`) than using custom string delimiters (`+##"\#"##+`). === Interpolation Prefer interpolation to string concatenation. .good.pkl [source%parsed,{pkl}] ---- greeting = "Hello, \(name)" ---- .bad.pkl [source%parsed,{pkl}] ---- greeting = "Hello, " + name ---- == Formatting === Line width Lines shouldn't exceed 100 characters. *Exceptions:* 1. String literals 2. Code snippets within doc comments === Indentation Use two spaces per indentation level. ==== Members within braces Members within braces should be indented one level deeper than their parents. [source%tested,{pkl}] ---- foo { bar { baz = "hi" } } ---- ==== Assignment operator (`=`) An assignee that starts after a newline should be indented. .good.pkl [source%tested,{pkl}] ---- foo = "foo" bar = new { baz = "baz" biz = "biz" } ---- .bad.pkl [source%tested,{pkl}] ---- foo = "foo" bar = new { baz = "baz" biz = "biz" } ---- An assignee that starts on the same line should not be indented. .good.pkl [source%tested,{pkl}] ---- foo = new { baz = "baz" biz = "biz" } ---- .bad.pkl [source%tested,{pkl}] ---- foo = new { baz = "baz" biz = "biz" } ---- ==== `if` and `let` expressions `if` and `let` bodies that start on their own line should be indented. Child bodies may also be inline, and the `else` branch of `if` expressions may be inline of `if`. .good.pkl [source%parsed,{pkl-expr}] ---- if (bar) bar else foo ---- .good.pkl [source%parsed,{pkl-expr}] ---- if (bar) bar else foo ---- .good.pkl [source%parsed,{pkl-expr}] ---- let (foo = "bar") foo.toUpperCase() ---- .good.pkl [source%parsed,{pkl-expr}] ---- let (foo = "bar") foo.toUpperCase() ---- .bad.pkl [source%parsed,{pkl-expr}] ---- if (bar) bar else foo ---- .bad.pkl [source%parsed,{pkl-expr}] ---- let (foo = "bar") foo.toUpperCase() ---- *Exception*: A nested `if` expression within the `else` branch should have the same indentation level as its parent, and start on the same line as the parent `else` keyword. .good.pkl [source%parsed,{pkl-expr}] ---- if (bar) bar else if (baz) baz else foo ---- .bad.pkl [source%parsed,{pkl-expr}] ---- if (bar) bar else if (baz) baz else foo ---- ==== Multiline chained method calls Indent successive multiline chained method calls. [source%parsed,{pkl-expr}] ---- foo() .bar() .baz() .biz() ---- ==== Multiline binary operators Place operators after the newline, and indent successive lines to the same level. .good.pkl [source%parsed,{pkl}] ---- foo = bar |> baz |> biz myNum = 1 + 2 + 3 + 4 ---- .bad.pkl [source%parsed,{pkl}] ---- foo = bar |> baz |> biz myNum = 1 + 2 + 3 + 4 ---- .bad.pkl [source%tested,{pkl}] ---- foo = bar |> baz |> biz ---- .bad.pkl [source%tested,{pkl}] ---- foo = bar |> baz |> biz ---- *Exception*: the minus operator must come before the newline, because otherwise it is parsed as a unary minus. .good.pkl [source%tested,{pkl}] ---- myNum = 1 - 2 - 3 - 4 ---- .bad.pkl [source%tested,{pkl}] ---- myNum = 1 - 2 - 3 - 4 ---- === Spaces Add a space: [source%parsed,{pkl}] ---- amends "Foo.pkl" // <1> res1 { "foo" } // <2> res2 = 1 + 2 // <3> res3 = res2 as Number // <3> res4 = List(1, 2, 3) // <4> res5 = if (foo) bar else baz // <5> typealias Foo = "foo" | "bar" | "baz" // <6> ---- <1> After keywords <2> Before and after braces <3> Around infix operators <4> After a comma <5> Before opening parentheses in control operators (`if`, `for`, `when` are control operators) <6> Before and after the pipe symbol (`|`) === Object bodies ==== Single line An object body may be a single line if it only consists of primitive elements, or if it contains two or fewer members. Otherwise, split them into multiple lines. Separate each member of a single line object with a semicolon and a space. .good.pkl [source%tested,{pkl}] ---- res1 = new { bar = "bar"; baz = "baz" } res2 = new { 1; 2; 3; 4; 5; 6 } ---- .bad.pkl [source%parsed,{pkl}] ---- res1 = new { bar = "bar"; baz = "baz"; biz = "biz"; } // <1> res2 = new { 1 2 3 4 5 6 } // <2> ---- <1> Too many members and trailing `;` <2> No semicolon ==== Multiline Multiline objects should have their members separated by at least one line break and at most one blank line. .good.pkl [source%tested,{pkl}] ---- res { foo = "foo" bar = "bar" } res2 { ["foo"] = "foo" ["bar"] = "bar" } res3 { "foo" "bar" } ---- .good.pkl [source%tested,{pkl}] ---- res { foo = "foo" bar = "bar" } res2 { ["foo"] = "foo" ["bar"] = "bar" } res3 { "foo" "bar" } ---- .bad.pkl [source%tested,{pkl}] ---- res { foo = "foo" bar = "bar" // <1> } res2 { ["foo"] = "foo" ["bar"] = "bar" // <1> } res3 { "foo" "bar" // <1> } res4 { foo = "foo"; bar = "bar" // <2> } ---- <1> Too many blank lines between members <2> No line break separating members Put the opening brace on the same line. .good.pkl [source%tested,{pkl}] ---- res { foo = "foo" bar = "bar" } ---- .bad.pkl [source%tested,{pkl}] ---- res { foo = "foo" bar = "bar" } ---- == Programming Practices === Prefer `for` generators When programmatically creating elements and entries, prefer xref:language-reference:index.adoc#for-generators[for generators] over using the collection API. Using for generators preserves xref:language-reference:index.adoc#late-binding[late binding]. .good.pkl [source%tested,{pkl}] ---- numbers { 1 2 3 4 } squares { for (num in numbers) { num ** 2 } } ---- .bad.pkl [source%tested,{pkl}] ---- numbers { 1 2 3 4 } squares = numbers.toList().map((num) -> num ** 2).toListing() ---- ================================================ FILE: docs/nav.adoc ================================================ * xref:introduction:index.adoc[Introduction] ** xref:introduction:use-cases.adoc[Use Cases] ** xref:introduction:concepts.adoc[Concepts] ** xref:introduction:comparison.adoc[Comparison] * xref:pkl-cli:index.adoc#installation[Installation] * xref:language-tutorial:index.adoc[Tutorial] ** xref:language-tutorial:01_basic_config.adoc[Basic Configuration] ** xref:language-tutorial:02_filling_out_a_template.adoc[Filling out a Template] ** xref:language-tutorial:03_writing_a_template.adoc[Writing a Template] * xref:ROOT:language.adoc[Language] ** xref:language-reference:index.adoc[Language Reference] ** xref:ROOT:standard-library.adoc[Standard Library] ** xref:ROOT:language-bindings.adoc[Language Bindings] *** xref:java-binding:index.adoc[Java] **** xref:java-binding:codegen.adoc[Code Generator] **** xref:pkl-core:index.adoc[pkl-core Library] **** xref:java-binding:pkl-config-java.adoc[pkl-config-java Library] *** xref:kotlin-binding:index.adoc[Kotlin] **** xref:kotlin-binding:codegen.adoc[Code Generator] **** xref:kotlin-binding:pkl-config-kotlin.adoc[pkl-config-kotlin Library] *** xref:swift:ROOT:index.adoc[Swift] *** xref:go:ROOT:index.adoc[Go] *** xref:bindings-specification:index.adoc[Specification] **** xref:bindings-specification:message-passing-api.adoc[Message Passing API] **** xref:bindings-specification:binary-encoding.adoc[Pkl Binary Encoding] * xref:ROOT:tools.adoc[Tools] ** xref:pkl-cli:index.adoc[CLI] ** xref:pkl-doc:index.adoc[Pkldoc] ** xref:pkl-gradle:index.adoc[Gradle Plugin] ** Editor support *** xref:intellij:ROOT:index.adoc[IntelliJ] *** xref:vscode:ROOT:index.adoc[VSCode] *** xref:neovim:ROOT:index.adoc[Neovim] * xref:ROOT:examples.adoc[Examples] * xref:ROOT:evolution-and-roadmap.adoc[Evolution and Roadmap] * xref:release-notes:index.adoc[Release Notes] ** xref:release-notes:0.32.adoc[0.32 Release Notes] ** xref:release-notes:0.31.adoc[0.31 Release Notes] ** xref:release-notes:0.30.adoc[0.30 Release Notes] ** xref:release-notes:0.29.adoc[0.29 Release Notes] ** xref:release-notes:0.28.adoc[0.28 Release Notes] ** xref:release-notes:0.27.adoc[0.27 Release Notes] ** xref:release-notes:0.26.adoc[0.26 Release Notes] ** xref:release-notes:0.25.adoc[0.25 Release Notes] ** xref:release-notes:changelog.adoc[Changelog] ================================================ FILE: docs/src/test/kotlin/DocSnippetTests.kt ================================================ import org.junit.platform.commons.annotation.Testable import org.junit.platform.engine.* import org.junit.platform.engine.TestDescriptor.Type import org.junit.platform.engine.discovery.ClassSelector import org.junit.platform.engine.discovery.MethodSelector import org.junit.platform.engine.discovery.PackageSelector import org.junit.platform.engine.discovery.UniqueIdSelector import org.junit.platform.engine.support.descriptor.* import org.junit.platform.engine.support.hierarchical.EngineExecutionContext import org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine import org.junit.platform.engine.support.hierarchical.Node import org.junit.platform.engine.support.hierarchical.Node.DynamicTestExecutor import org.opentest4j.MultipleFailuresError import org.pkl.commons.test.FileTestUtils.rootProjectDir import org.pkl.core.Loggers import org.pkl.core.SecurityManagers import org.pkl.core.StackFrameTransformers import org.pkl.core.evaluatorSettings.TraceMode import org.pkl.core.module.ModuleKeyFactories import org.pkl.core.repl.ReplRequest import org.pkl.core.repl.ReplResponse import org.pkl.core.repl.ReplServer import org.pkl.core.util.IoUtils import org.pkl.core.http.HttpClient import org.pkl.parser.Parser import org.pkl.parser.ParserError import org.pkl.parser.syntax.ClassProperty import org.pkl.core.resource.ResourceReaders import java.nio.file.Files import kotlin.io.path.isDirectory import kotlin.io.path.isRegularFile import kotlin.io.path.useDirectoryEntries @Testable class DocSnippetTests class DocSnippetTestsEngine : HierarchicalTestEngine() { private val projectDir = rootProjectDir.resolve("docs") private val docsDir = projectDir.resolve("modules") private companion object { val headingRegex = Regex("""(?u)^\s*(=++)\s*(.+)""") val collapsibleBlockRegex = Regex("""(?u)^\s*\[%collapsible""") val codeBlockRegex = Regex("""(?u)^\s*\[source(?:%(tested|parsed)(%error)?)?(?:,\{?([a-zA-Z-_]+)}?)?""") val codeBlockNameRegex = Regex("""(?u)^\s*\.(.+)""") val codeBlockDelimiterRegex = Regex("""(?u)^\s*----""") val graphicsRegex = Regex("\\[small]#.+#") } override fun getId() = "pkl-doc-tests" override fun discover( discoveryRequest: EngineDiscoveryRequest, uniqueId: UniqueId ): TestDescriptor { val packageSelectors = discoveryRequest.getSelectorsByType(PackageSelector::class.java) val classSelectors = discoveryRequest.getSelectorsByType(ClassSelector::class.java) val methodSelectors = discoveryRequest.getSelectorsByType(MethodSelector::class.java) val uniqueIdSelectors = discoveryRequest.getSelectorsByType(UniqueIdSelector::class.java) val testClass = DocSnippetTests::class.java val packageName = testClass.`package`.name val className = testClass.name if (methodSelectors.isEmpty() && (packageSelectors.isEmpty() || packageSelectors.any { it.packageName == packageName }) && (classSelectors.isEmpty() || classSelectors.any { it.className == className }) ) { val rootDescriptor = Descriptor.Path(uniqueId, docsDir.fileName.toString(), ClassSource.from(testClass), docsDir) doDiscover(rootDescriptor, uniqueIdSelectors) return rootDescriptor } // return empty descriptor w/o children return EngineDescriptor(uniqueId, javaClass.simpleName) } override fun createExecutionContext(request: ExecutionRequest): ExecutionContext { val replServer = ReplServer( SecurityManagers.defaultManager, HttpClient.dummyClient(), Loggers.stdErr(), listOf( ModuleKeyFactories.standardLibrary, ModuleKeyFactories.classPath(DocSnippetTests::class.java.classLoader), ModuleKeyFactories.file ), listOf( ResourceReaders.environmentVariable(), ResourceReaders.externalProperty() ), System.getenv(), emptyMap(), null, null, null, IoUtils.getCurrentWorkingDir(), StackFrameTransformers.defaultTransformer, false, TraceMode.COMPACT, ) return ExecutionContext(replServer) } private fun doDiscover(rootDescriptor: TestDescriptor, selectors: List) { fun isMatch(other: UniqueId) = selectors.isEmpty() || selectors.any { it.uniqueId.hasPrefix(other) || other.hasPrefix(it.uniqueId) } docsDir.useDirectoryEntries { docsDirEntries -> for (docsDirEntry in docsDirEntries) { if (!docsDirEntry.isDirectory()) continue val docsDirEntryName = docsDirEntry.fileName.toString() val docsDirEntryId = rootDescriptor.uniqueId.append("dir", docsDirEntryName) if (!isMatch(docsDirEntryId)) continue val docsDirEntryDescriptor = Descriptor.Path( docsDirEntryId, docsDirEntryName, DirectorySource.from(docsDirEntry.toFile()), docsDirEntry ) rootDescriptor.addChild(docsDirEntryDescriptor) val pagesDir = docsDirEntry.resolve("pages") if (!pagesDir.isDirectory()) continue pagesDir.useDirectoryEntries { pagesDirEntries -> for (pagesDirEntry in pagesDirEntries) { val pagesDirEntryName = pagesDirEntry.fileName.toString() val pagesDirEntryId = docsDirEntryId.append("file", pagesDirEntryName) if (!pagesDirEntry.isRegularFile() || !pagesDirEntryName.endsWith(".adoc") || !isMatch(pagesDirEntryId) ) continue val pagesDirEntryDescriptor = Descriptor.Path( pagesDirEntryId, pagesDirEntryName, FileSource.from(pagesDirEntry.toFile()), pagesDirEntry ) docsDirEntryDescriptor.addChild(pagesDirEntryDescriptor) parseAsciidoc(pagesDirEntryDescriptor, selectors) } } } } } @Suppress("DEPRECATION") private fun parseAsciidoc(docDescriptor: Descriptor.Path, selectors: List) { var line = "" var prevLine = "" var lineNum = 0 var codeBlockNum = 0 val sections = ArrayDeque() Files.lines(docDescriptor.path).use { linesStream -> val linesIterator = linesStream.iterator() fun advance() { prevLine = line line = linesIterator.next() lineNum += 1 } fun addSection(title: String, newLevel: Int) { while (sections.isNotEmpty() && sections.first().level >= newLevel) { sections.removeFirst() } val parent = sections.firstOrNull() ?: docDescriptor val normalizedTitle = title .replace("", "") .replace("", "") .replace(graphicsRegex, "") .trim() val childSection = Descriptor.Section( parent.uniqueId.append("section", normalizedTitle), normalizedTitle, FileSource.from(docDescriptor.path.toFile(), FilePosition.from(lineNum)), newLevel ) sections.addFirst(childSection) parent.addChild(childSection) codeBlockNum = 0 } nextLine@ while (linesIterator.hasNext()) { advance() val headingMatch = headingRegex.find(line) if (headingMatch != null) { val (markup, title) = headingMatch.destructured val newLevel = markup.length // ignore level 1 heading (we already have a test node for the file) if (newLevel == 1) continue addSection(title, newLevel) continue } val collapsibleBlockMatch = collapsibleBlockRegex.find(line) if (collapsibleBlockMatch != null) { val blockName = codeBlockNameRegex.find(prevLine)?.groupValues?.get(1) ?: "Details" val newLevel = 999 addSection(blockName, newLevel) continue } val codeBlockMatch = codeBlockRegex.find(line) if (codeBlockMatch != null) { codeBlockNum += 1 val (testMode, error, language) = codeBlockMatch.destructured if (testMode.isNotEmpty()) { val blockName = codeBlockNameRegex.find(prevLine)?.groupValues?.get(1) ?: "snippet$codeBlockNum" while (linesIterator.hasNext()) { advance() val startDelimiterMatch = codeBlockDelimiterRegex.find(line) if (startDelimiterMatch != null) { val jumpToLineNum = lineNum + 1 val builder = StringBuilder() while (linesIterator.hasNext()) { advance() val endDelimiterMatch = codeBlockDelimiterRegex.find(line) if (endDelimiterMatch != null) { val section = sections.first() val snippetId = section.uniqueId.append("snippet", blockName) if (selectors.isEmpty() || selectors.any { snippetId.hasPrefix(it.uniqueId) }) { section.addChild( Descriptor.Snippet( snippetId, blockName, language, FileSource.from(docDescriptor.path.toFile(), FilePosition.from(jumpToLineNum)), builder.toString(), testMode == "parsed", error.isNotEmpty() ) ) } continue@nextLine } builder.appendLine(line) } } } } } } } } class ExecutionContext(val replServer: ReplServer) : EngineExecutionContext, AutoCloseable { override fun close() { replServer.close() } } private sealed class Descriptor( uniqueId: UniqueId, displayName: String, source: TestSource ) : AbstractTestDescriptor(uniqueId, displayName, source), Node { class Path( uniqueId: UniqueId, displayName: String, source: TestSource, val path: java.nio.file.Path ) : Descriptor(uniqueId, displayName, source) { override fun getType() = Type.CONTAINER } class Section( uniqueId: UniqueId, displayName: String, source: TestSource, val level: Int ) : Descriptor(uniqueId, displayName, source) { override fun getType() = Type.CONTAINER override fun before(context: ExecutionContext): ExecutionContext { context.replServer.handleRequest(ReplRequest.Reset("reset")) return context } } class Snippet( uniqueId: UniqueId, displayName: String, private val language: String, source: TestSource, private val code: String, private val parseOnly: Boolean, private val expectError: Boolean ) : Descriptor(uniqueId, displayName, source) { override fun getType() = Type.TEST private val parsed: org.pkl.parser.syntax.Node by lazy { when (language) { "pkl" -> Parser().parseModule(code) "pkl-expr" -> Parser().parseExpressionInput(code) else -> throw(Exception("Unrecognized language: $language")) } } override fun execute(context: ExecutionContext, executor: DynamicTestExecutor): ExecutionContext { if (parseOnly) { try { parsed if (expectError) { throw AssertionError("Expected a parse error, but got none.") } } catch (e: ParserError) { if (!expectError) { throw AssertionError(e.message) } } return context } context.replServer.handleRequest( ReplRequest.Eval( "snippet", code, !expectError, !expectError ) ) val properties = parsed.children()?.filterIsInstance() ?: emptyList() val responses = mutableListOf() // force each property for (prop in properties) { responses.addAll(context.replServer.handleRequest( ReplRequest.Eval( "snippet", prop.name.value, false, true ) )) } if (expectError) { if (responses.dropLast(1).any { it !is ReplResponse.EvalSuccess } || responses.last() !is ReplResponse.EvalError) { throw AssertionError( "Expected %error snippet to fail at the end, but got the following REPL responses:\n\n" + responses.joinToString("\n\n") { it.message }) } return context } val badResponses = responses.filter { it !is ReplResponse.EvalSuccess } if (badResponses.isNotEmpty()) { throw MultipleFailuresError(null, badResponses.map { AssertionError(it.message) }) } return context } } } } ================================================ FILE: docs/src/test/resources/META-INF/services/org.junit.platform.engine.TestEngine ================================================ DocSnippetTestsEngine ================================================ FILE: gradle/libs.versions.toml ================================================ [versions] # ordered alphabetically assertj = "3.+" checksumPlugin = "1.4.0" clikt = "5.+" commonMark = "0.+" downloadTaskPlugin = "5.6.0" geantyref = "1.+" googleJavaFormat = "1.25.2" # must not use `+` because used in download URL # 23.1.x requires JDK 20+ graalVm = "25.0.0" #noinspection UnusedVersionCatalogEntry graalVmJdkVersion = "25.0.0" # slightly hacky but convenient place so we remember to update the checksum #noinspection UnusedVersionCatalogEntry graalVmSha256-macos-x64 = "04278cf867d040e29dc71dd7727793f0ea67eb72adce8a35d04b87b57906778d" #noinspection UnusedVersionCatalogEntry graalVmSha256-macos-aarch64 = "c446d5aaeda98660a4c14049d299e9fba72105a007df89f19d27cf3979d37158" #noinspection UnusedVersionCatalogEntry graalVmSha256-linux-x64 = "1862f2ce97387a303cae4c512cb21baf36fafd2457c3cbbc10d87db94b89d3dd" #noinspection UnusedVersionCatalogEntry graalVmSha256-linux-aarch64 = "6c3c8b7617006c5d174d9cf7d357ccfb4bae77a4df1294ee28084fcb6eea8921" #noinspection UnusedVersionCatalogEntry graalVmSha256-windows-x64 = "33ef1d186b5c1e95465fcc97e637bc26e72d5f2250a8615b9c5d667ed5c17fd0" ideaExtPlugin = "1.1.9" intellijPlugin = "2.10.1" intellij = "2025.2.3" javaPoet = "0.+" javaxInject = "1" jansi = "2.+" jimfs = "1.+" # later versions don't work with native image # (at least not without additional configuration; tested with 3.25.1 and 3.27.1) jline = "3.23.0" jmh = "1.+" jmhPlugin = "0.7.2" jsr305 = "3.+" junit = "5.+" junitPlatform = "1.+" kotlin = "2.2.20" # 1.7+ generates much more verbose code kotlinPoet = "1.6.+" kotlinxHtml = "0.11.0" kotlinxSerialization = "1.8.0" kotlinxCoroutines = "1.+" ktfmt = "0.53" # replaces nuValidator's log4j dependency # something related to log4j-1.2-api is apparently broken in 2.17.2 log4j = "2.17.1" msgpack = "0.9.8" nexusPublishPlugin = "2.0.0" nuValidator = "20.+" paguro = "3.+" shadowPlugin = "9.+" slf4j = "1.+" snakeYaml = "2.+" spotlessPlugin = "6.25.0" wiremock = "3.+" [libraries] # ordered alphabetically assertj = { group = "org.assertj", name = "assertj-core", version.ref = "assertj" } clikt = { group = "com.github.ajalt.clikt", name = "clikt", version.ref = "clikt" } cliktMarkdown = { group = "com.github.ajalt.clikt", name = "clikt-markdown", version.ref = "clikt" } commonMark = { group = "org.commonmark", name = "commonmark", version.ref = "commonMark" } commonMarkTables = { group = "org.commonmark", name = "commonmark-ext-gfm-tables", version.ref = "commonMark" } downloadTaskPlugin = { group = "de.undercouch", name = "gradle-download-task", version.ref = "downloadTaskPlugin" } geantyref = { group = "io.leangen.geantyref", name = "geantyref", version.ref = "geantyref" } graalCompiler = { group = "org.graalvm.compiler", name = "compiler", version.ref = "graalVm" } graalSdk = { group = "org.graalvm.sdk", name = "graal-sdk", version.ref = "graalVm" } graalJs = { group = "org.graalvm.js", name = "js", version.ref = "graalVm" } #noinspection UnusedVersionCatalogEntry intellij = { group = "com.jetbrains.intellij.idea", name = "ideaIC", version.ref = "intellij" } javaPoet = { group = "com.palantir.javapoet", name = "javapoet", version.ref = "javaPoet" } javaxInject = { group = "javax.inject", name = "javax.inject", version.ref = "javaxInject" } jansi = { group = "org.fusesource.jansi", name = "jansi", version.ref = "jansi" } jimfs = { group = "com.google.jimfs", name = "jimfs", version.ref = "jimfs" } jlineReader = { group = "org.jline", name = "jline-reader", version.ref = "jline" } jlineTerminal = { group = "org.jline", name = "jline-terminal", version.ref = "jline" } jlineTerminalJansi = { group = "org.jline", name = "jline-terminal-jansi", version.ref = "jline" } jsr305 = { group = "com.google.code.findbugs", name = "jsr305", version.ref = "jsr305" } junitApi = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junit" } junitEngine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "junit" } junitParams = { group = "org.junit.jupiter", name = "junit-jupiter-params", version.ref = "junit" } junitLauncher = { group = "org.junit.platform", name = "junit-platform-launcher", version.ref = "junitPlatform" } kotlinPlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } kotlinPoet = { group = "com.squareup", name = "kotlinpoet", version.ref = "kotlinPoet" } kotlinReflect = { group = "org.jetbrains.kotlin", name = "kotlin-reflect", version.ref = "kotlin" } kotlinScripting = { group = "org.jetbrains.kotlin", name = "kotlin-scripting-jsr223", version.ref = "kotlin" } kotlinStdLib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib-jdk8", version.ref = "kotlin" } kotlinxHtml = { group = "org.jetbrains.kotlinx", name = "kotlinx-html-jvm", version.ref = "kotlinxHtml" } kotlinxSerializationJson = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerialization" } kotlinxCoroutinesCore = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" } #noinspection UnusedVersionCatalogEntry log4j12Api = { group = "org.apache.logging.log4j", name = "log4j-1.2-api", version.ref = "log4j" } msgpack = { group = "org.msgpack", name = "msgpack-core", version.ref = "msgpack" } #noinspection UnusedVersionCatalogEntry nuValidator = { group = "nu.validator", name = "validator", version.ref = "nuValidator" } # to be replaced with https://github.com/usethesource/capsule or https://github.com/lacuna/bifurcan paguro = { group = "org.organicdesign", name = "Paguro", version.ref = "paguro" } pklConfigJavaAll025 = { group = "org.pkl-lang", name = "pkl-config-java-all", version = "0.25.0" } shadowPlugin = { group = "com.gradleup.shadow", name = "com.gradleup.shadow.gradle.plugin", version.ref = "shadowPlugin" } slf4jApi = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" } slf4jSimple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } snakeYaml = { group = "org.snakeyaml", name = "snakeyaml-engine", version.ref = "snakeYaml" } spotlessPlugin = { group = "com.diffplug.spotless", name = "spotless-plugin-gradle", version.ref = "spotlessPlugin"} svm = { group = "org.graalvm.nativeimage", name = "svm", version.ref = "graalVm" } truffleApi = { group = "org.graalvm.truffle", name = "truffle-api", version.ref = "graalVm" } truffleDslProcessor = { group = "org.graalvm.truffle", name = "truffle-dsl-processor", version.ref = "graalVm" } truffleSvm = { group = "org.graalvm.nativeimage", name = "truffle-runtime-svm", version.ref = "graalVm" } truffleRuntime = { group = "org.graalvm.truffle", name = "truffle-runtime", version.ref = "graalVm" } wiremock = { group = "org.wiremock", name = "wiremock", version.ref = "wiremock" } [plugins] # ordered alphabetically checksum = { id = "org.gradle.crypto.checksum", version.ref = "checksumPlugin" } ideaExt = { id = "org.jetbrains.gradle.plugin.idea-ext", version.ref = "ideaExtPlugin" } jmh = { id = "me.champeau.jmh", version.ref = "jmhPlugin" } kotlinxSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } nexusPublish = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "nexusPublishPlugin" } shadow = { id = "com.gradleup.shadow", version.ref = "shadowPlugin" } intellij = { id = "org.jetbrains.intellij.platform", version.ref = "intellijPlugin" } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionSha256Sum=a17ddd85a26b6a7f5ddb71ff8b05fc5104c0202c6e64782429790c933686c806 distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: gradle.properties ================================================ # suppress inspection "UnusedProperty" for whole file group=org.pkl-lang version=0.32.0 # google-java-format requires jdk.compiler exports org.gradle.jvmargs= \ -XX:+UseParallelGC \ -Dfile.encoding=UTF-8 \ --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \ --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \ --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \ --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED org.gradle.parallel=true org.gradle.caching=true kotlin.stdlib.default.dependency=false kotlin.daemon.jvmargs=-XX:+UseParallelGC #org.gradle.workers.max=1 ================================================ FILE: gradlew ================================================ #!/bin/sh # # Copyright © 2015-2021 the original authors. # # 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. # # SPDX-License-Identifier: Apache-2.0 # ############################################################################## # # Gradle start up script for POSIX generated by Gradle. # # Important for running: # # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is # noncompliant, but you have some other compliant shell such as ksh or # bash, then to run this script, type that shell name before the whole # command line, like: # # ksh Gradle # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», # «${var#prefix}», «${var%suffix}», and «$( cmd )»; # * compound commands having a testable exit status, especially «case»; # * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # # (2) This script targets any POSIX shell, so it avoids extensions provided # by Bash, Ksh, etc; in particular arrays are avoided. # # The "traditional" practice of packing multiple parameters into a # space-separated string is a well documented source of bugs and security # problems, so this is (mostly) avoided, by progressively accumulating # options in "$@", and eventually passing that to Java. # # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; # see the in-line comments for details. # # There are tweaks for specific operating systems such as AIX, CygWin, # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. # ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link app_path=$0 # Need this for daisy-chained symlinks. while APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path [ -h "$app_path" ] do ls=$( ls -ld "$app_path" ) link=${ls#*' -> '} case $link in #( /*) app_path=$link ;; #( *) app_path=$APP_HOME$link ;; esac done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum warn () { echo "$*" } >&2 die () { echo echo "$*" echo exit 1 } >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "$( uname )" in #( CYGWIN* ) cygwin=true ;; #( Darwin* ) darwin=true ;; #( MSYS* | MINGW* ) msys=true ;; #( NONSTOP* ) nonstop=true ;; esac CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD=$JAVA_HOME/jre/sh/java else JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD=java if ! command -v java >/dev/null 2>&1 then die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi # Collect all arguments for the java command, stacking in reverse order: # * args from the command line # * the main class name # * -classpath # * -D...appname settings # * --module-path (only if needed) # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) # Now convert the arguments - kludge to limit ourselves to /bin/sh for arg do if case $arg in #( -*) false ;; # don't mess with options #( /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath [ -e "$t" ] ;; #( *) false ;; esac then arg=$( cygpath --path --ignore --mixed "$arg" ) fi # Roll the args list around exactly as many times as the number of # args, so each arg winds up back in the position where it started, but # possibly modified. # # NB: a `for` loop captures its iteration list before it begins, so # changing the positional parameters here affects neither the number of # iterations, nor the values presented in `arg`. shift # remove old arg set -- "$@" "$arg" # push replacement arg done fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. if ! command -v xargs >/dev/null 2>&1 then die "xargs is not available" fi # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. # # In Bash we could simply go: # # readarray ARGS < <( xargs -n1 <<<"$var" ) && # set -- "${ARGS[@]}" "$@" # # but POSIX shell has neither arrays nor command substitution, so instead we # post-process each arg (as a line of input to sed) to backslash-escape any # character that might be a shell metacharacter, then use eval to reverse # that process (while maintaining the separation between arguments), and wrap # the whole thing up as a single "set" statement. # # This will of course break if any of these variables contains a newline or # an unmatched quote. # eval "set -- $( printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | xargs -n1 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | tr '\n' ' ' )" '"$@"' exec "$JAVACMD" "$@" ================================================ FILE: gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @rem SPDX-License-Identifier: Apache-2.0 @rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute echo. 1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. 1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line set CLASSPATH= @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! set EXIT_CODE=%ERRORLEVEL% if %EXIT_CODE% equ 0 set EXIT_CODE=1 if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: jbang-catalog.json ================================================ { "catalogs": {}, "aliases": { "pkl": { "script-ref": "org.pkl-lang:pkl-cli-java:0.31.0", "java-agents": [] }, "pkl-codegen-java": { "script-ref": "org.pkl-lang:pkl-codegen-java:0.31.0", "java-agents": [] }, "pkl-codegen-kotlin": { "script-ref": "org.pkl-lang:pkl-codegen-kotlin:0.31.0", "java-agents": [] } }, "templates": {} } ================================================ FILE: pkl-bom/pkl-bom.gradle.kts ================================================ /* * Copyright © 2024-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. */ plugins { pklAllProjects `java-platform` `maven-publish` signing } description = "Pkl BOM that includes all modules" dependencies { constraints { api(projects.pklCli) api(projects.pklCodegenJava) api(projects.pklCodegenKotlin) // Use explicit coordinates for pkl-config-java to avoid ambiguity with fatJar publication api("${project.group}:pkl-config-java:${project.version}") api("${project.group}:pkl-config-java-all:${project.version}") api(projects.pklConfigKotlin) api(projects.pklCore) api(projects.pklDoc) api(projects.pklExecutor) api(projects.pklFormatter) api(projects.pklGradle) api(projects.pklParser) api(projects.pklTools) } } publishing { publications { create("library") { from(components["javaPlatform"]) pom { url.set("https://github.com/apple/pkl/tree/main/pkl-bom") description.set("Bill of Materials for managing Pkl dependency versions") } } } } configurePklPomMetadata() configurePomValidation() configurePklSigning() ================================================ FILE: pkl-cli/README.adoc ================================================ Command-line interface for Pkl. The CLI provides the following tools: * Batch evaluator * REPL ================================================ FILE: pkl-cli/gradle.lockfile ================================================ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. com.ethlo.time:itu:1.10.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.fasterxml.jackson.core:jackson-annotations:2.19.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.fasterxml.jackson.core:jackson-core:2.19.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.fasterxml.jackson.core:jackson-databind:2.19.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.19.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.19.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.fasterxml.jackson:jackson-bom:2.19.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.github.ajalt.clikt:clikt-core-jvm:5.0.3=compileClasspath,nativeImageClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.clikt:clikt-core:5.0.3=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,nativeImageClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.github.ajalt.clikt:clikt-jvm:5.0.3=compileClasspath,nativeImageClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.clikt:clikt-markdown-jvm:5.0.3=nativeImageClasspath,runtimeClasspath,testRuntimeClasspath com.github.ajalt.clikt:clikt-markdown:5.0.3=nativeImageClasspath,runtimeClasspath,testRuntimeClasspath com.github.ajalt.clikt:clikt:5.0.3=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,nativeImageClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.github.ajalt.colormath:colormath-jvm:3.6.0=compileClasspath,nativeImageClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.colormath:colormath:3.6.0=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,nativeImageClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.github.ajalt.mordant:mordant-core-jvm:3.0.1=compileClasspath,nativeImageClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-core:3.0.1=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,nativeImageClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm-ffm-jvm:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm-ffm:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm-graal-ffi-jvm:3.0.1=nativeImageClasspath,runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm-graal-ffi:3.0.1=nativeImageClasspath,runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm-jna-jvm:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm-jna:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm:3.0.1=compileClasspath,nativeImageClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-markdown-jvm:3.0.1=nativeImageClasspath,runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-markdown:3.0.1=nativeImageClasspath,runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant:3.0.1=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,nativeImageClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.github.ben-manes.caffeine:caffeine:2.9.3=swiftExportClasspathResolvable com.github.jknack:handlebars-helpers:4.3.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.github.jknack:handlebars:4.3.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.google.errorprone:error_prone_annotations:2.28.0=swiftExportClasspathResolvable com.google.errorprone:error_prone_annotations:2.36.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.google.guava:failureaccess:1.0.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.google.guava:guava:33.4.8-jre=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.google.j2objc:j2objc-annotations:3.0.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.jayway.jsonpath:json-path:2.9.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.networknt:json-schema-validator:1.5.7=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath commons-fileupload:commons-fileupload:1.6.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath commons-io:commons-io:2.19.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath io.github.java-diff-utils:java-diff-utils:4.12=kotlinInternalAbiValidation io.opentelemetry:opentelemetry-api:1.41.0=swiftExportClasspathResolvable io.opentelemetry:opentelemetry-context:1.41.0=swiftExportClasspathResolvable net.bytebuddy:byte-buddy:1.17.7=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.java.dev.jna:jna:5.14.0=runtimeClasspath,testRuntimeClasspath net.javacrumbs.json-unit:json-unit-core:2.40.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.minidev:accessors-smart:2.5.0=testRuntimeClasspath net.minidev:json-smart:2.5.0=testRuntimeClasspath net.sf.jopt-simple:jopt-simple:5.0.4=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.apache.httpcomponents.client5:httpclient5:5.5=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.apache.httpcomponents.core5:httpcore5-h2:5.3.4=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.apache.httpcomponents.core5:httpcore5:5.3.4=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata org.assertj:assertj-core:3.27.6=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.bouncycastle:bcpg-jdk18on:1.80=kotlinBouncyCastleConfiguration org.bouncycastle:bcpkix-jdk18on:1.80=kotlinBouncyCastleConfiguration org.bouncycastle:bcprov-jdk18on:1.80=kotlinBouncyCastleConfiguration org.bouncycastle:bcutil-jdk18on:1.80=kotlinBouncyCastleConfiguration org.checkerframework:checker-qual:3.43.0=swiftExportClasspathResolvable org.eclipse.jetty.http2:http2-common:11.0.24=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.eclipse.jetty.http2:http2-hpack:11.0.24=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.eclipse.jetty.http2:http2-server:11.0.24=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.eclipse.jetty.toolchain:jetty-jakarta-servlet-api:5.0.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.eclipse.jetty:jetty-alpn-client:11.0.24=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.eclipse.jetty:jetty-alpn-java-client:11.0.24=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.eclipse.jetty:jetty-alpn-java-server:11.0.24=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.eclipse.jetty:jetty-alpn-server:11.0.24=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.eclipse.jetty:jetty-bom:11.0.24=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.eclipse.jetty:jetty-client:11.0.24=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.eclipse.jetty:jetty-http:11.0.24=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.eclipse.jetty:jetty-io:11.0.24=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.eclipse.jetty:jetty-proxy:11.0.24=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.eclipse.jetty:jetty-security:11.0.24=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.eclipse.jetty:jetty-server:11.0.24=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.eclipse.jetty:jetty-servlet:11.0.24=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.eclipse.jetty:jetty-servlets:11.0.24=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.eclipse.jetty:jetty-util:11.0.24=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.eclipse.jetty:jetty-webapp:11.0.24=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.eclipse.jetty:jetty-xml:11.0.24=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.fusesource.jansi:jansi:2.4.2=compileClasspath,implementationDependenciesMetadata,nativeImageClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.graalvm.polyglot:polyglot:25.0.0=compileClasspath,compileOnlyDependenciesMetadata,implementationDependenciesMetadata,nativeImageClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.graalvm.sdk:collections:25.0.0=compileClasspath,compileOnlyDependenciesMetadata,implementationDependenciesMetadata,nativeImageClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.graalvm.sdk:graal-sdk:25.0.0=compileClasspath,compileOnlyDependenciesMetadata,nativeImageClasspath,runtimeClasspath,testRuntimeClasspath org.graalvm.sdk:jniutils:25.0.0=compileClasspath,implementationDependenciesMetadata,nativeImageClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.graalvm.sdk:nativeimage:25.0.0=compileClasspath,compileOnlyDependenciesMetadata,implementationDependenciesMetadata,nativeImageClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.graalvm.sdk:word:25.0.0=compileClasspath,compileOnlyDependenciesMetadata,implementationDependenciesMetadata,nativeImageClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.graalvm.truffle:truffle-api:25.0.0=compileClasspath,implementationDependenciesMetadata,nativeImageClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.graalvm.truffle:truffle-compiler:25.0.0=compileClasspath,implementationDependenciesMetadata,nativeImageClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.graalvm.truffle:truffle-runtime:25.0.0=compileClasspath,implementationDependenciesMetadata,nativeImageClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.hamcrest:hamcrest-core:2.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.hamcrest:hamcrest:2.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.kotlin:abi-tools-api:2.2.20=kotlinInternalAbiValidation org.jetbrains.kotlin:abi-tools:2.2.20=kotlinInternalAbiValidation org.jetbrains.kotlin:kotlin-build-tools-api:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-build-tools-impl:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-compiler-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlin:kotlin-compiler-runner:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-daemon-client:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-daemon-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:2.2.20=kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-metadata-jvm:2.2.20=kotlinInternalAbiValidation org.jetbrains.kotlin:kotlin-native-prebuilt:2.0.21=kotlinNativeBundleConfiguration org.jetbrains.kotlin:kotlin-reflect:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlin:kotlin-script-runtime:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlin:kotlin-scripting-common:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-jvm:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.2.20=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,nativeImageClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.20=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,nativeImageClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib:2.2.20=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,nativeImageClasspath,runtimeClasspath,swiftExportClasspathResolvable,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.kotlin:swift-export-embeddable:2.2.20=swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-serialization-bom:1.7.3=swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.7.3=swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3=swiftExportClasspathResolvable org.jetbrains:annotations:13.0=compileClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,nativeImageClasspath,runtimeClasspath,swiftExportClasspathResolvable,testCompileClasspath,testRuntimeClasspath org.jetbrains:markdown-jvm:0.7.3=nativeImageClasspath,runtimeClasspath,testRuntimeClasspath org.jetbrains:markdown:0.7.3=nativeImageClasspath,runtimeClasspath,testRuntimeClasspath org.jline:jline-native:3.23.0=compileClasspath,implementationDependenciesMetadata,nativeImageClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jline:jline-reader:3.23.0=compileClasspath,implementationDependenciesMetadata,nativeImageClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jline:jline-terminal-jansi:3.23.0=compileClasspath,implementationDependenciesMetadata,nativeImageClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jline:jline-terminal:3.23.0=compileClasspath,implementationDependenciesMetadata,nativeImageClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jspecify:jspecify:1.0.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.jupiter:junit-jupiter-api:5.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.jupiter:junit-jupiter-engine:5.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.jupiter:junit-jupiter-params:5.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.platform:junit-platform-commons:1.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.platform:junit-platform-engine:1.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.platform:junit-platform-launcher:1.14.0=testRuntimeClasspath org.junit:junit-bom:5.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.msgpack:msgpack-core:0.9.8=nativeImageClasspath,runtimeClasspath,testRuntimeClasspath org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.organicdesign:Paguro:3.10.3=nativeImageClasspath,runtimeClasspath,testRuntimeClasspath org.slf4j:slf4j-api:2.0.17=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.snakeyaml:snakeyaml-engine:2.10=nativeImageClasspath,runtimeClasspath,testRuntimeClasspath org.wiremock:wiremock:3.13.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.xmlunit:xmlunit-core:2.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.xmlunit:xmlunit-legacy:2.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.xmlunit:xmlunit-placeholders:2.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.yaml:snakeyaml:2.4=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath empty=annotationProcessor,intransitiveDependenciesMetadata,javaExecutable,kotlinCompilerPluginClasspath,kotlinNativeCompilerPluginClasspath,kotlinScriptDefExtensions,shadow,signatures,sourcesJar,stagedAlpineLinuxAmd64Executable,stagedLinuxAarch64Executable,stagedLinuxAmd64Executable,stagedMacAarch64Executable,stagedMacAmd64Executable,stagedWindowsAmd64Executable,testAnnotationProcessor,testApiDependenciesMetadata,testCompileOnlyDependenciesMetadata,testIntransitiveDependenciesMetadata,testKotlinScriptDefExtensions ================================================ FILE: pkl-cli/pkl-cli.gradle.kts ================================================ /* * Copyright © 2024-2025 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. */ import java.io.ByteArrayOutputStream import java.io.OutputStream import org.gradle.kotlin.dsl.support.serviceOf plugins { pklAllProjects pklKotlinLibrary pklPublishLibrary pklJavaExecutable pklNativeExecutable `maven-publish` // already on build script class path (see buildSrc/build.gradle.kts), // hence must only specify plugin ID here id(libs.plugins.shadow.get().pluginId) alias(libs.plugins.checksum) } // make Java executable available to other subprojects val javaExecutableConfiguration: Configuration = configurations.create("javaExecutable") publishing { publications { named("library") { pom { url.set("https://github.com/apple/pkl/tree/main/pkl-cli") description.set("Pkl CLI Java library.") } } } } dependencies { implementation(libs.truffleRuntime) compileOnly(libs.graalSdk) // CliEvaluator exposes PClass api(projects.pklCore) // CliEvaluatorOptions exposes CliBaseOptions api(projects.pklCommonsCli) implementation(projects.pklCommons) implementation(libs.jansi) implementation(libs.jlineReader) implementation(libs.jlineTerminal) implementation(libs.jlineTerminalJansi) implementation(projects.pklServer) implementation(projects.pklFormatter) implementation(libs.clikt) testImplementation(projects.pklCommonsTest) testImplementation(libs.wiremock) } tasks.jar { manifest.attributes += mapOf("Main-Class" to "org.pkl.cli.Main", "Add-Exports" to buildInfo.jpmsExportsForJarManifest) } tasks.javadoc { enabled = false } tasks.shadowJar { archiveFileName.set("jpkl") exclude("META-INF/maven/**") exclude("META-INF/upgrade/**") exclude("module-info.*") } val testJavaExecutable by tasks.registering(Test::class) { testClassesDirs = tasks.test.get().testClassesDirs classpath = // compiled test classes sourceSets.test.get().output + // java executable tasks.javaExecutable.get().outputs.files + // test-only dependencies // (test dependencies that are also main dependencies must already be contained in java // executable; // to verify that, we don't want to include them here) (configurations.testRuntimeClasspath.get() - configurations.runtimeClasspath.get()) } // Setup `testJavaExecutable` tasks for multi-JDK testing. val testJavaExecutableOnOtherJdks = buildInfo.multiJdkTestingWith(testJavaExecutable) // Prepare a run of the fat JAR, optionally with a specific Java launcher. private fun setupJavaExecutableRun( name: String, args: Array, launcher: Provider? = null, configurator: Exec.() -> Unit = {}, ) = tasks.register(name, Exec::class) { dependsOn(tasks.javaExecutable) val outputFile = layout.buildDirectory.file(name) // dummy output to satisfy up-to-date check outputs.file(outputFile) executable = when (launcher) { null -> "java" else -> launcher.get().executablePath.asFile.absolutePath } standardOutput = OutputStream.nullOutputStream() args("-jar", tasks.javaExecutable.get().outputs.files.singleFile.toString(), *args) doFirst { outputFile.get().asFile.delete() } doLast { outputFile.get().asFile.writeText("OK") } configurator() } val evalTestFlags = arrayOf("eval", "-x", "1 + 1", "pkl:base") fun Exec.useRootDirAndSuppressOutput() { workingDir = rootProject.layout.projectDirectory.asFile standardOutput = ByteArrayOutputStream() // we only care that this exec doesn't fail } // 0.28 Preparing for JDK21 toolchains revealed that `testStartJavaExecutable` may pass, even though // the evaluator fails. To catch this, we eval a simple expression using the fat jar. val testEvalJavaExecutable by setupJavaExecutableRun("testEvalJavaExecutable", evalTestFlags) { useRootDirAndSuppressOutput() } // Run the same evaluator tests on all configured JDK test versions. val testEvalJavaExecutableOnOtherJdks = buildInfo.jdkTestRange.map { jdkTarget -> setupJavaExecutableRun( "testEvalJavaExecutableJdk${jdkTarget.asInt()}", evalTestFlags, serviceOf().launcherFor { languageVersion = jdkTarget }, ) { useRootDirAndSuppressOutput() } } tasks.check { dependsOn( testJavaExecutable, testJavaExecutableOnOtherJdks, testEvalJavaExecutable, testEvalJavaExecutableOnOtherJdks, ) } tasks.checkNative { dependsOn(":pkl-core:checkNative") dependsOn(":pkl-server:checkNative") } executable { name = "pkl" javaName = "jpkl" documentationName = "Pkl CLI" publicationName = "pkl-cli" javaPublicationName = "pkl-cli-java" mainClass = "org.pkl.cli.Main" website = "https://pkl-lang.org/main/current/pkl-cli/index.html" } // make Java executable available to other subprojects // (we don't do the same for native executables because we don't want tasks assemble/build to build // them) artifacts { add("javaExecutable", tasks.javaExecutable.map { it.outputs.files.singleFile }) { name = "pkl-cli-java" classifier = null extension = "jar" builtBy(tasks.javaExecutable) } } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/CliCommandRunner.kt ================================================ /* * Copyright © 2025-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.cli import com.github.ajalt.clikt.completion.CompletionCandidates import com.github.ajalt.clikt.completion.CompletionCommand import com.github.ajalt.clikt.core.* import com.github.ajalt.clikt.parameters.arguments.* import com.github.ajalt.clikt.parameters.options.* import com.github.ajalt.clikt.parameters.types.int import java.io.OutputStream import java.net.URI import java.nio.file.Path import kotlin.io.path.createParentDirectories import kotlin.io.path.exists import kotlin.io.path.isDirectory import kotlin.io.path.writeBytes import org.pkl.commons.cli.CliBaseOptions import org.pkl.commons.cli.CliCommand import org.pkl.commons.cli.CliException import org.pkl.commons.cli.commands.installCommonOptions import org.pkl.commons.currentWorkingDir import org.pkl.core.Closeables import org.pkl.core.CommandSpec import org.pkl.core.EvaluatorBuilder import org.pkl.core.FileOutput import org.pkl.core.ModuleSource.uri import org.pkl.core.PklBugException import org.pkl.core.PklException import org.pkl.core.util.IoUtils class CliCommandRunner @JvmOverloads constructor( private val options: CliBaseOptions, private val reservedFlagNames: Set, private val reservedFlagShortNames: Set, private val args: List, private val outputStream: OutputStream = System.out, private val errStream: OutputStream = System.err, ) : CliCommand(options) { override fun doRun() { val builder = evaluatorBuilder() try { evalCmd(builder) } finally { Closeables.closeQuietly(builder.moduleKeyFactories) Closeables.closeQuietly(builder.resourceReaders) } } private fun evalCmd(builder: EvaluatorBuilder) { val evaluator = builder.build() evaluator.use { evaluator.evaluateCommand( uri(resolvedSourceModules.first()), reservedFlagNames, reservedFlagShortNames, ) { spec -> try { val root = SynthesizedRunCommand(spec, this, options.sourceModules.first().toString()) root.installCommonOptions(includeVersion = false) root.subcommands( CompletionCommand( name = "shell-completion", help = "Generate a completion script for the given shell", ) ) root.parse(args) } catch (e: PklException) { throw e } catch (e: Exception) { throw e.message?.let { PklException(it, e) } ?: PklException(e) } } } } /** Renders the comand's `output.bytes`, writing it to the standard output stream. */ fun writeOutput(outputBytes: ByteArray) { if (outputBytes.isEmpty()) return outputStream.write(outputBytes) outputStream.flush() } /** * Renders the command's `output.files`, writing each entry as a file. * * File paths are written to the standard error stream. * * Unlike CliEvaluator, command outputs write relative to --working-dir and may write files * anywhere in the filesystem. This is intentionally less sandboxed than `pkl eval` and directly * targets the capabilities of CLI tools written in general purpose languages. Pkl commands should * therefore be treated as untrusted code the way that any other CLI tool would be. */ fun writeMultipleFileOutput(outputFiles: Map) { if (outputFiles.isEmpty()) return val writtenFiles = mutableMapOf() val outputDir = options.normalizedWorkingDir if (outputDir.exists() && !outputDir.isDirectory()) { throw CliException("Output path `$outputDir` exists and is not a directory.") } for ((pathSpec, fileOutput) in outputFiles) { checkPathSpec(pathSpec) val resolvedPath = outputDir.resolve(pathSpec).normalize() val realPath = if (resolvedPath.exists()) resolvedPath.toRealPath() else resolvedPath val previousOutput = writtenFiles[realPath] if (previousOutput != null) { throw CliException( "Output file conflict: `output.files` entries `\"${previousOutput}\"` and `\"$pathSpec\"` resolve to the same file path `$realPath`." ) } if (realPath.isDirectory()) { throw CliException( "Output file conflict: `output.files` entry `\"$pathSpec\"` resolves to file path `$realPath`, which is a directory." ) } writtenFiles[realPath] = pathSpec realPath.createParentDirectories() realPath.writeBytes(fileOutput.bytes) val displayPath = if (Path.of(pathSpec).isAbsolute) pathSpec else IoUtils.relativize(resolvedPath, currentWorkingDir).toString() errStream.writeText(displayPath + IoUtils.getLineSeparator()) errStream.flush() } } class SynthesizedRunCommand( private val spec: CommandSpec, private val runner: CliCommandRunner, name: String? = null, ) : CliktCommand(name = name ?: spec.name) { init { spec.options.forEach { opt -> when (opt) { is CommandSpec.Flag -> registerOption( option( names = opt.names, help = opt.helpText ?: "", metavar = opt.metavar, hidden = opt.hidden, completionCandidates = opt.completionCandidates?.toClikt(), ) .convert { try { opt.transformEach.apply(it, workingDirUri) } catch (e: CommandSpec.Option.BadValue) { fail(e.message!!) } catch (_: CommandSpec.Option.MissingOption) { throw MissingOption(option) } } .transformAll(opt.defaultValue, opt.showAsRequired) { try { opt.transformAll.apply(it, workingDirUri) } catch (e: CommandSpec.Option.BadValue) { fail(e.message!!) } catch (_: CommandSpec.Option.MissingOption) { throw MissingOption(option) } } ) is CommandSpec.BooleanFlag -> registerOption( if (opt.defaultValue != null) option(names = opt.names, help = opt.helpText ?: "", hidden = opt.hidden) .flag("--no-${opt.name}", default = opt.defaultValue!!) else option(names = opt.names, help = opt.helpText ?: "", hidden = opt.hidden) .nullableFlag("--no-${opt.name}") ) is CommandSpec.CountedFlag -> registerOption( option(names = opt.names, help = opt.helpText ?: "", hidden = opt.hidden) .int() .transformValues(0..0) { it.lastOrNull() ?: 1 } .transformAll { it.sum().toLong() } ) is CommandSpec.Argument -> registerArgument( argument( opt.name, opt.helpText ?: "", completionCandidates = opt.completionCandidates?.toClikt(), ) .convert { try { opt.transformEach.apply(it, workingDirUri) } catch (e: CommandSpec.Option.BadValue) { fail(e.message!!) } catch (_: CommandSpec.Option.MissingOption) { throw MissingArgument(argument) } } .transformAll(if (opt.repeated) -1 else 1, !opt.repeated) { try { opt.transformAll.apply(it, workingDirUri) } catch (e: CommandSpec.Option.BadValue) { fail(e.message!!) } catch (_: CommandSpec.Option.MissingOption) { throw MissingArgument(argument) } } ) } } spec.subcommands.forEach { subcommands(SynthesizedRunCommand(it, runner)) } } val workingDirUri: URI by lazy { runner.options.normalizedWorkingDir.toUri() } override val invokeWithoutSubcommand = true override val hiddenFromHelp: Boolean = spec.hidden override fun help(context: Context): String = spec.helpText ?: "" override fun run() { if (currentContext.invokedSubcommand is CompletionCommand) return val opts = registeredOptions() .mapNotNull { val opt = it as? OptionWithValues<*, *, *> ?: return@mapNotNull null return@mapNotNull if (it.names.contains("--help")) null else it.names.last().trimStart('-') to opt.value } .toMap() + registeredArguments() .mapNotNull { it as? ArgumentDelegate<*> } .associateBy({ it.name }, { it.value }) val state = spec.apply.apply(opts, currentContext.obj as CommandSpec.State?) currentContext.obj = state if (currentContext.invokedSubcommand != null) return if (spec.subcommands.isNotEmpty() && spec.noOp) { throw PrintHelpMessage(currentContext, true, 1) } val result = state.evaluate() runner.writeOutput(result.outputBytes) runner.writeMultipleFileOutput(result.outputFiles) } } } fun CommandSpec.CompletionCandidates.toClikt(): CompletionCandidates = when (this) { CommandSpec.CompletionCandidates.PATH -> CompletionCandidates.Path is CommandSpec.CompletionCandidates.Fixed -> CompletionCandidates.Fixed(values) else -> throw PklBugException.unreachableCode() } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/CliEvaluator.kt ================================================ /* * Copyright © 2024-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.cli import java.io.File import java.io.InputStream import java.io.OutputStream import java.net.URI import java.nio.charset.StandardCharsets import java.nio.file.Files import java.nio.file.Path import java.nio.file.StandardOpenOption import kotlin.io.path.createParentDirectories import kotlin.io.path.exists import kotlin.io.path.isDirectory import kotlin.io.path.writeBytes import org.pkl.commons.cli.CliCommand import org.pkl.commons.cli.CliException import org.pkl.commons.currentWorkingDir import org.pkl.commons.writeString import org.pkl.core.Closeables import org.pkl.core.Evaluator import org.pkl.core.EvaluatorBuilder import org.pkl.core.ModuleSource import org.pkl.core.PklException import org.pkl.core.module.ModulePathResolver import org.pkl.core.runtime.ModuleResolver import org.pkl.core.runtime.VmException import org.pkl.core.runtime.VmUtils import org.pkl.core.util.IoUtils private data class OutputFile(val pathSpec: String, val moduleUri: URI) /** API equivalent of the Pkl command-line evaluator. */ class CliEvaluator @JvmOverloads constructor( private val options: CliEvaluatorOptions, // use System.{in,out}() rather than System.console() // because the latter returns null when output is sent through a unix pipe private val inputStream: InputStream = System.`in`, private val outputStream: OutputStream = System.out, ) : CliCommand(options.base) { /** * Output files for the modules to be evaluated. Returns `null` if `options.outputPath` is `null` * or if `options.multipleFileOutputPath` is not `null`. Multiple modules may be mapped to the * same output file, in which case their outputs are concatenated with * [CliEvaluatorOptions.moduleOutputSeparator]. */ @Suppress("MemberVisibilityCanBePrivate") val outputFiles: Set? by lazy { fileOutputPaths?.values?.mapTo(mutableSetOf(), Path::toFile) } /** * Output directories for the modules to be evaluated. Returns `null` if * `options.multipleFileOutputPath` is `null`. */ @Suppress("MemberVisibilityCanBePrivate") val outputDirectories: Set? by lazy { directoryOutputPaths?.values?.mapTo(mutableSetOf(), Path::toFile) } /** The file output path */ val fileOutputPaths: Map? by lazy { if (options.multipleFileOutputPath != null) return@lazy null options.outputPath?.let { resolveOutputPaths(it) } } private val directoryOutputPaths: Map? by lazy { options.multipleFileOutputPath?.let { resolveOutputPaths(it) } } /** * Evaluates source modules according to [options]. * * If [CliEvaluatorOptions.outputPath] is set, each module's `output.bytes` is written to the * module's [output file][outputFiles]. * * If [CliEvaluatorOptions.multipleFileOutputPath] is set, each module's `output.files` are * written to the module's [output directory][outputDirectories]. Otherwise, each module's * `output.bytes` is written to [outputStream] (which defaults to standard out). * * Throws [CliException] in case of an error. */ override fun doRun() { val builder = evaluatorBuilder() try { if (options.multipleFileOutputPath != null) { writeMultipleFileOutput(builder) } else { writeOutput(builder) } } finally { Closeables.closeQuietly(builder.moduleKeyFactories) Closeables.closeQuietly(builder.resourceReaders) } } private fun resolveOutputPaths(pathStr: String): Map { val workingDir = options.base.normalizedWorkingDir // used just to resolve the `%{moduleName}` placeholder val moduleResolver = ModuleResolver(moduleKeyFactories(ModulePathResolver.empty())) return resolvedSourceModules.associateWith { uri -> val moduleDir: String? = IoUtils.toPath(uri)?.let { IoUtils.relativize(it.parent, workingDir).toString().ifEmpty { "." } } val moduleKey = try { moduleResolver.resolve(uri) } catch (e: VmException) { throw e.toPklException(stackFrameTransformer, options.base.color?.hasColor() ?: false) } val substituted = pathStr .replace("%{moduleName}", IoUtils.inferModuleName(moduleKey)) .replace("%{outputFormat}", options.outputFormat ?: "%{outputFormat}") .replace("%{moduleDir}", moduleDir ?: "%{moduleDir}") if (substituted.contains("%{moduleDir}")) { throw PklException( "Cannot substitute output path placeholder `%{moduleDir}` " + "because module `$uri` does not have a file system path." ) } val absolutePath = workingDir.resolve(substituted).normalize() absolutePath } } private fun Evaluator.evalOutput(moduleSource: ModuleSource): ByteArray { if (options.expression == null) { return evaluateOutputBytes(moduleSource) } return evaluateExpressionString(moduleSource, options.expression) .toByteArray(StandardCharsets.UTF_8) } /** Renders each module's `output.bytes`, writing it to the specified output file. */ private fun writeOutput(builder: EvaluatorBuilder) { val evaluator = builder.setOutputFormat(options.outputFormat).build() evaluator.use { ev -> val outputFiles = fileOutputPaths if (outputFiles != null) { // files that we've written non-empty output to // YamlRenderer produces empty output if `isStream` is true and `output.value` is empty // collection val writtenFiles = mutableSetOf() for ((moduleUri, outputFile) in outputFiles) { val moduleSource = toModuleSource(moduleUri, inputStream) if (Files.isDirectory(outputFile)) { throw CliException( "Output file `$outputFile` is a directory. " + "Did you mean `--multiple-file-output-path`?" ) } val output = ev.evalOutput(moduleSource) outputFile.createParentDirectories() if (!writtenFiles.contains(outputFile)) { // write file even if output is empty to overwrite output from previous runs outputFile.writeBytes(output) if (output.isNotEmpty()) { writtenFiles.add(outputFile) } } else { if (output.isNotEmpty()) { outputFile.writeString( options.moduleOutputSeparator + '\n', Charsets.UTF_8, StandardOpenOption.WRITE, StandardOpenOption.APPEND, ) outputFile.writeBytes(output, StandardOpenOption.WRITE, StandardOpenOption.APPEND) } } } } else { var outputWritten = false for (moduleUri in resolvedSourceModules) { val moduleSource = toModuleSource(moduleUri, inputStream) if (options.expression != null) { val output = evaluator.evaluateExpressionString(moduleSource, options.expression) if (output.isNotEmpty()) { if (outputWritten) outputStream.writeLine(options.moduleOutputSeparator) outputStream.writeText(output) outputStream.flush() outputWritten = true } } else { val outputBytes = evaluator.evaluateOutputBytes(moduleSource) if (outputBytes.isNotEmpty()) { if (outputWritten) outputStream.writeLine(options.moduleOutputSeparator) outputStream.write(outputBytes) outputStream.flush() outputWritten = true } } } } } } private fun toModuleSource(uri: URI, reader: InputStream) = if (uri == VmUtils.REPL_TEXT_URI) { ModuleSource.create(uri, reader.readAllBytes().toString(StandardCharsets.UTF_8)) } else { ModuleSource.uri(uri) } /** * Renders each module's `output.files`, writing each entry as a file into the specified output * directory. */ private fun writeMultipleFileOutput(builder: EvaluatorBuilder) { val outputDirs = directoryOutputPaths!! val writtenFiles = mutableMapOf() for ((moduleUri, outputDir) in outputDirs) { val evaluator = builder.setOutputFormat(options.outputFormat).build() if (outputDir.exists() && !outputDir.isDirectory()) { throw CliException("Output path `$outputDir` exists and is not a directory.") } val moduleSource = toModuleSource(moduleUri, inputStream) val output = evaluator.evaluateOutputFiles(moduleSource) val realOutputDir = if (outputDir.exists()) outputDir.toRealPath() else outputDir for ((pathSpec, fileOutput) in output) { checkPathSpec(pathSpec) val resolvedPath = realOutputDir.resolve(pathSpec).normalize() val realPath = if (resolvedPath.exists()) resolvedPath.toRealPath() else resolvedPath if (!realPath.startsWith(realOutputDir)) { throw CliException( "Output file conflict: `output.files` entry `\"$pathSpec\"` in module `$moduleUri` resolves to file path `$realPath`, which is outside output directory `$realOutputDir`." ) } val previousOutput = writtenFiles[realPath] if (previousOutput != null) { throw CliException( "Output file conflict: `output.files` entries `\"${previousOutput.pathSpec}\"` in module `${previousOutput.moduleUri}` and `\"$pathSpec\"` in module `$moduleUri` resolve to the same file path `$realPath`." ) } if (realPath.isDirectory()) { throw CliException( "Output file conflict: `output.files` entry `\"$pathSpec\"` in module `$moduleUri` resolves to file path `$realPath`, which is a directory." ) } writtenFiles[realPath] = OutputFile(pathSpec, moduleUri) realPath.createParentDirectories() realPath.writeBytes(fileOutput.bytes) outputStream.writeText( IoUtils.relativize(resolvedPath, currentWorkingDir).toString() + IoUtils.getLineSeparator() ) outputStream.flush() } } } } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/CliEvaluatorOptions.kt ================================================ /* * Copyright © 2024-2025 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.cli import org.pkl.commons.cli.CliBaseOptions /** Configuration options for [CliEvaluator]. */ data class CliEvaluatorOptions( /** Base options shared between CLI commands. */ val base: CliBaseOptions, /** * The file path where the output file is placed. If multiple source modules are given, * placeholders can be used to map them to different output files. If multiple modules are mapped * to the same output file, their outputs are concatenated. Currently, the only available * concatenation strategy is to separate outputs with `---`, as in a YAML stream. * * The following placeholders are supported: * - `%{moduleDir}` The directory path of the module, relative to the working directory. Only * available when evaluating file-based modules. * - `%{moduleName}` The simple module name as inferred from the module URI. For hierarchical * URIs, this is the last path segment without file extension. * - `%{outputFormat}` The requested output format. Only available if `outputFormat` is non-null. * * If [CliBaseOptions.workingDir] corresponds to a file system path, relative output paths are * resolved against that path. Otherwise, relative output paths are not allowed. * * If `null`, output is written to the console. */ val outputPath: String? = null, /** * The output format to generate. * * The default output renderer for a module supports the following formats: * - `"json"` * - `"jsonnet"` * - `"pcf"` (default) * - `"plist"` * - `"properties"` * - `"textproto"` * - `"xml"` * - `"yaml"` */ val outputFormat: String? = null, /** The separator to use when multiple module outputs are written to the same location. */ val moduleOutputSeparator: String = "---", /** * The directory where a module's output files are placed. * * Setting this option causes Pkl to evaluate `output.files` instead of `output.text`, and write * files using each entry's key as the file path relative to [multipleFileOutputPath], and each * value's `text` property as the file's contents. */ val multipleFileOutputPath: String? = null, /** * The expression to evaluate within the module. * * If set, the said expression is evaluated under the context of the enclosing module. * * If unset, the module's `output.bytes` property is evaluated. */ val expression: String? = null, ) { companion object { val defaults: CliEvaluatorOptions = CliEvaluatorOptions(CliBaseOptions()) } } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/CliFormatterCommand.kt ================================================ /* * Copyright © 2025-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.cli import java.io.IOException import java.io.Writer import java.nio.file.Files import java.nio.file.Path import java.util.stream.Stream import kotlin.io.path.extension import kotlin.io.path.isDirectory import kotlin.io.path.name import kotlin.io.path.writeText import kotlin.math.max import org.pkl.commons.cli.CliBaseOptions import org.pkl.commons.cli.CliCommand import org.pkl.commons.cli.CliException import org.pkl.commons.cli.CliTestException import org.pkl.core.util.IoUtils import org.pkl.formatter.Formatter import org.pkl.formatter.GrammarVersion import org.pkl.parser.GenericParserError import org.pkl.parser.ParserError class CliFormatterCommand @JvmOverloads constructor( private val paths: List, private val grammarVersion: GrammarVersion, private val overwrite: Boolean, private val diffNameOnly: Boolean, private val silent: Boolean, private val consoleWriter: Writer = System.out.writer(), private val errWriter: Writer = System.err.writer(), ) : CliCommand(CliBaseOptions()) { private fun format(contents: String): String { return Formatter().format(contents, grammarVersion) } private fun writeErrLine(error: String) { errWriter.write(error) errWriter.appendLine() errWriter.flush() } private fun writeLine(message: String) { if (silent) return consoleWriter.write(message) consoleWriter.appendLine() consoleWriter.flush() } private fun allPaths(): Stream { return paths.distinct().stream().flatMap { path -> when { path.toString() == "-" -> Stream.of(path) path.isDirectory() -> Files.walk(path) .filter { it.extension == "pkl" || it.name == "PklProject" } .map { it.normalize() } else -> Stream.of(path.normalize()) } } } override fun doRun() { val status = Status(SUCCESS) handlePaths(status) when (status.status) { FORMATTING_VIOLATION -> { // using CliTestException instead of CliException because we want full control on how to // print errors throw CliTestException("", status.status) } ERROR -> { if (!silent) { writeErrLine("An error occurred during formatting.") } throw CliTestException("", status.status) } } } private fun handlePaths(status: Status) { for (path in allPaths()) { val pathStr = path.toString() try { val contents = when { pathStr == "-" -> IoUtils.readString(System.`in`) else -> Files.readString(path) } if (pathStr == "-" && overwrite) { throw CliException("Cannot write to stdin", ERROR) } val formatted = format(contents) if (contents != formatted) { if (diffNameOnly || overwrite) { // if `--diff-name-only` or `-w` is specified, only write file names writeLine(pathStr) } if (overwrite) { path.writeText(formatted, Charsets.UTF_8) } else { // only exit on violation for "check" operations, not when overwriting status.update(FORMATTING_VIOLATION) } } if (!diffNameOnly && !overwrite) { consoleWriter.write(formatted) consoleWriter.flush() } } catch (pe: ParserError) { // thrown by the lexer writeErrLine("Could not format `$pathStr`: $pe") status.update(ERROR) } catch (pe: GenericParserError) { // thrown by the generic parser writeErrLine("Could not format `$pathStr`: $pe") status.update(ERROR) } catch (e: IOException) { writeErrLine("IO error while reading `$pathStr`: ${e.message}") status.update(ERROR) } } } companion object { private const val SUCCESS = 0 private const val FORMATTING_VIOLATION = 11 private const val ERROR = 1 private class Status(var status: Int) { fun update(newStatus: Int) { status = when { status == ERROR -> status newStatus == ERROR -> newStatus else -> max(status, newStatus) } } } } } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/CliImportAnalyzer.kt ================================================ /* * Copyright © 2024-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.cli import java.io.Writer import kotlin.io.path.createParentDirectories import org.pkl.commons.cli.CliCommand import org.pkl.commons.writeString import org.pkl.core.Closeables import org.pkl.core.ModuleSource class CliImportAnalyzer @JvmOverloads constructor( private val options: CliImportAnalyzerOptions, private val consoleWriter: Writer = System.out.writer(), ) : CliCommand(options.base) { override fun doRun() { val rendered = render() if (options.outputPath != null) { options.outputPath.createParentDirectories() options.outputPath.writeString(rendered) } else { consoleWriter.write(rendered) consoleWriter.flush() } } // language=pkl private val sourceModule = ModuleSource.text( """ import "pkl:analyze" local importStrings = read*("prop:pkl.analyzeImports.**").toMap().values.toSet() output { value = analyze.importGraph(importStrings) renderer { converters { [Map] = (it) -> it.toMapping() [Set] = (it) -> it.toListing() } } } """ .trimIndent() ) private fun render(): String { val builder = evaluatorBuilder().setOutputFormat(options.outputFormat) try { return builder .apply { for ((idx, sourceModule) in resolvedSourceModules.withIndex()) { addExternalProperty("pkl.analyzeImports.$idx", sourceModule.toString()) } } .build() .use { it.evaluateOutputText(sourceModule) } } finally { Closeables.closeQuietly(builder.moduleKeyFactories) Closeables.closeQuietly(builder.resourceReaders) } } } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/CliImportAnalyzerOptions.kt ================================================ /* * Copyright © 2024 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.cli import java.nio.file.Path import org.pkl.commons.cli.CliBaseOptions data class CliImportAnalyzerOptions( /** Base options shared between CLI commands. */ val base: CliBaseOptions, /** The file path where the output file is placed. */ val outputPath: Path? = null, /** * The output format to generate. * * These accept the same options as [CliEvaluatorOptions.outputFormat]. */ val outputFormat: String? = null, ) ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/CliPackageDownloader.kt ================================================ /* * Copyright © 2024-2025 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.cli import org.pkl.commons.cli.CliBaseOptions import org.pkl.commons.cli.CliCommand import org.pkl.commons.cli.CliException import org.pkl.core.packages.PackageResolver import org.pkl.core.packages.PackageUri class CliPackageDownloader( baseOptions: CliBaseOptions, private val packageUris: List, private val noTransitive: Boolean, ) : CliCommand(baseOptions) { override fun doRun() { if (moduleCacheDir == null) { throw CliException("Cannot download packages because no cache directory is specified.") } val packageResolver = PackageResolver.getInstance(securityManager, httpClient, moduleCacheDir) val errors = mutableMapOf() for (pkg in packageUris) { try { packageResolver.downloadPackage(pkg, pkg.checksums, noTransitive) } catch (e: Throwable) { errors[pkg] = e } } when (errors.size) { 0 -> return 1 -> throw CliException( errors.values.single().message ?: ("An unexpected error occurred: " + errors.values.single()) ) else -> throw CliException( buildString { appendLine("Failed to download some packages.") for ((uri, error) in errors) { appendLine() appendLine("Failed to download $uri because:") appendLine("${error.message}") } } ) } } } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/CliProjectCommand.kt ================================================ /* * Copyright © 2024 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.cli import java.nio.file.Files import java.nio.file.Path import org.pkl.commons.cli.CliBaseOptions import org.pkl.commons.cli.CliBaseOptions.Companion.getProjectFile import org.pkl.commons.cli.CliCommand import org.pkl.commons.cli.CliException import org.pkl.core.module.ProjectDependenciesManager.PKL_PROJECT_FILENAME abstract class CliProjectCommand(cliOptions: CliBaseOptions, private val projectDirs: List) : CliCommand(cliOptions) { protected val normalizedProjectFiles: List by lazy { if (projectDirs.isEmpty()) { val projectFile = cliOptions.normalizedWorkingDir.getProjectFile(cliOptions.normalizedRootDir) ?: throw CliException( "No project visible to the working directory. Ensure there is a PklProject file in the workspace, or provide an explicit project directory as an argument." ) return@lazy listOf(projectFile.normalize()) } projectDirs.map(cliOptions.normalizedWorkingDir::resolve).map { dir -> val projectFile = dir.resolve(PKL_PROJECT_FILENAME) if (!Files.exists(projectFile)) { throw CliException("Directory $dir does not contain a PklProject file.") } projectFile.normalize() } } } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/CliProjectPackager.kt ================================================ /* * Copyright © 2024-2025 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.cli import java.io.Writer import java.nio.file.Path import org.pkl.commons.cli.CliBaseOptions import org.pkl.commons.cli.CliException import org.pkl.commons.cli.CliTestException import org.pkl.commons.cli.CliTestOptions import org.pkl.core.project.Project import org.pkl.core.project.ProjectPackager import org.pkl.core.util.ErrorMessages class CliProjectPackager( baseOptions: CliBaseOptions, projectDirs: List, private val testOptions: CliTestOptions, private val outputPath: String, private val skipPublishCheck: Boolean, private val consoleWriter: Writer = System.out.writer(), private val errWriter: Writer = System.err.writer(), ) : CliProjectCommand(baseOptions, projectDirs) { private fun runApiTests(project: Project) { val apiTests = project.`package`!!.apiTests() if (apiTests.isEmpty()) return val normalizeApiTests = apiTests.map { project.projectDir.resolve(it).toUri() } val testRunner = CliTestRunner( cliOptions.copy(sourceModules = normalizeApiTests, projectDir = project.projectDir), testOptions = testOptions, consoleWriter = consoleWriter, errWriter = errWriter, ) try { testRunner.run() } catch (e: CliTestException) { throw CliException(ErrorMessages.create("packageTestsFailed", project.`package`!!.uri())) } } override fun doRun() { val projects = buildList { for (projectFile in normalizedProjectFiles) { val project = loadProject(projectFile) project.`package` ?: throw CliException( ErrorMessages.create("noPackageDefinedByProject", project.projectFileUri) ) runApiTests(project) add(project) } } // Require that all local projects are included projects.forEach { proj -> proj.dependencies.localDependencies().values.forEach { localDep -> val projectDir = Path.of(localDep.projectFileUri()).parent if (projects.none { it.projectDir == projectDir }) { throw CliException( ErrorMessages.create("missingProjectInPackageCommand", proj.projectDir, projectDir) ) } } } ProjectPackager( projects, cliOptions.normalizedWorkingDir, outputPath, stackFrameTransformer, cliOptions.color?.hasColor() ?: false, securityManager, httpClient, skipPublishCheck, consoleWriter, ) .createPackages() } } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/CliProjectResolver.kt ================================================ /* * Copyright © 2024-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.cli import java.io.IOException import java.io.Writer import java.nio.file.Path import org.pkl.commons.cli.CliBaseOptions import org.pkl.core.PklException import org.pkl.core.SecurityManagers import org.pkl.core.module.ProjectDependenciesManager import org.pkl.core.packages.PackageResolver import org.pkl.core.project.ProjectDependenciesResolver class CliProjectResolver( baseOptions: CliBaseOptions, projectDirs: List, private val consoleWriter: Writer = System.out.writer(), private val errWriter: Writer = System.err.writer(), ) : CliProjectCommand(baseOptions, projectDirs) { override fun doRun() { for (projectFile in normalizedProjectFiles) { val project = loadProject(projectFile) val packageResolver = PackageResolver.getInstance( SecurityManagers.standard( allowedModules, allowedResources, SecurityManagers.defaultTrustLevels, rootDir, ), httpClient, moduleCacheDir, ) val dependencies = ProjectDependenciesResolver(project, packageResolver, errWriter).resolve() val depsFile = projectFile.parent.resolve(ProjectDependenciesManager.PKL_PROJECT_DEPS_FILENAME).toFile() try { depsFile.outputStream().use { dependencies.writeTo(it) } consoleWriter.appendLine(depsFile.toString()) consoleWriter.flush() } catch (e: IOException) { throw PklException("Failed to write to $depsFile: ${e.message}", e) } } } } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/CliRepl.kt ================================================ /* * Copyright © 2024-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.cli import org.pkl.cli.repl.Repl import org.pkl.commons.cli.CliCommand import org.pkl.core.Loggers import org.pkl.core.SecurityManagers import org.pkl.core.evaluatorSettings.TraceMode import org.pkl.core.module.ModuleKeyFactories import org.pkl.core.module.ModulePathResolver import org.pkl.core.repl.ReplServer import org.pkl.core.resource.ResourceReaders internal class CliRepl(private val options: CliEvaluatorOptions) : CliCommand(options.base) { override fun doRun() { ModulePathResolver(modulePath).use { modulePathResolver -> // TODO: send options as command val server = ReplServer( SecurityManagers.standard( allowedModules, allowedResources, SecurityManagers.defaultTrustLevels, rootDir, ), httpClient, Loggers.stdErr(), listOf( ModuleKeyFactories.standardLibrary, ModuleKeyFactories.modulePath(modulePathResolver), ) + ModuleKeyFactories.fromServiceProviders() + listOf( ModuleKeyFactories.file, ModuleKeyFactories.http, ModuleKeyFactories.pkg, ModuleKeyFactories.projectpackage, ModuleKeyFactories.genericUrl, ), listOf( ResourceReaders.environmentVariable(), ResourceReaders.externalProperty(), ResourceReaders.modulePath(modulePathResolver), ResourceReaders.file(), ResourceReaders.http(), ResourceReaders.https(), ResourceReaders.pkg(), ResourceReaders.projectpackage(), ), environmentVariables, externalProperties, moduleCacheDir, project?.dependencies, options.outputFormat, options.base.normalizedWorkingDir, stackFrameTransformer, options.base.color?.hasColor() ?: false, options.base.traceMode ?: TraceMode.COMPACT, ) Repl(options.base.normalizedWorkingDir, server, options.base.color?.hasColor() ?: false).run() } } } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/CliServer.kt ================================================ /* * Copyright © 2024-2025 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.cli import org.pkl.commons.cli.CliBaseOptions import org.pkl.commons.cli.CliCommand import org.pkl.commons.cli.CliException import org.pkl.core.messaging.ProtocolException import org.pkl.server.Server class CliServer(options: CliBaseOptions) : CliCommand(options) { override fun doRun(): Unit = try { val server = Server.stream(System.`in`, System.out) server.use { it.start() } } catch (e: ProtocolException) { throw CliException(e.message!!) } } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/CliTestRunner.kt ================================================ /* * Copyright © 2024-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.cli import java.io.Writer import org.pkl.commons.cli.* import org.pkl.core.Closeables import org.pkl.core.EvaluatorBuilder import org.pkl.core.ModuleSource.uri import org.pkl.core.PklException import org.pkl.core.TestResults import org.pkl.core.stdlib.test.report.JUnitReport import org.pkl.core.stdlib.test.report.SimpleReport import org.pkl.core.util.ErrorMessages class CliTestRunner @JvmOverloads constructor( private val options: CliBaseOptions, private val testOptions: CliTestOptions, private val consoleWriter: Writer = System.out.writer(), private val errWriter: Writer = System.err.writer(), ) : CliCommand(options) { override fun doRun() { val builder = evaluatorBuilder() try { evalTest(builder) } finally { Closeables.closeQuietly(builder.moduleKeyFactories) Closeables.closeQuietly(builder.resourceReaders) } } private fun evalTest(builder: EvaluatorBuilder) { val sources = resolvedSourceModules.ifEmpty { project?.tests?.map { it.toUri() } } ?: // keep in sync with error message thrown by clikt throw CliException( """ Usage: pkl test [] ... Error: missing argument """ .trimIndent() ) val evaluator = builder.build() evaluator.use { var failed = false var isExampleWrittenFailure = true val moduleNames = mutableSetOf() val reporter = SimpleReport(useColor) val allTestResults = mutableListOf() val junitDir = testOptions.junitDir if (junitDir != null) { junitDir.toFile().mkdirs() } for ((idx, moduleUri) in sources.withIndex()) { try { val results = evaluator.evaluateTest(uri(moduleUri), testOptions.overwrite) allTestResults.add(results) if (!failed) { failed = results.failed() isExampleWrittenFailure = results.isExampleWrittenFailure.and(isExampleWrittenFailure) } reporter.report(results, consoleWriter) if (sources.size > 1 && idx != sources.size - 1) { consoleWriter.append('\n') } consoleWriter.flush() if (junitDir != null) { val moduleName = "${results.moduleName}.xml" if (moduleName in moduleNames) { throw RuntimeException( """ Cannot generate JUnit report for $moduleUri. A report with the same name was already generated. To fix, provide a different name for this module by adding a module header. """ .trimIndent() ) } moduleNames += moduleName if (!testOptions.junitAggregateReports) { JUnitReport().reportToPath(results, junitDir.resolve(moduleName)) } } } catch (ex: Exception) { errWriter.appendLine("Error evaluating module ${moduleUri.path}:") errWriter.write(ex.message ?: "") if (ex !is PklException) { errWriter.write(ex.stackTraceToString()) } if (moduleUri != sources.last()) { errWriter.appendLine() } errWriter.flush() failed = true } } if (testOptions.junitAggregateReports && junitDir != null) { val fileName = "${testOptions.junitAggregateSuiteName}.xml" JUnitReport(testOptions.junitAggregateSuiteName) .summarizeToPath(allTestResults, junitDir.resolve(fileName)) } consoleWriter.append('\n') reporter.summarize(allTestResults, consoleWriter) consoleWriter.flush() if (failed) { val exitCode = if (isExampleWrittenFailure) 10 else 1 throw CliTestException(ErrorMessages.create("testsFailed"), exitCode) } } } } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/Main.kt ================================================ /* * Copyright © 2024-2025 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. */ @file:JvmName("Main") package org.pkl.cli import com.github.ajalt.clikt.core.main import org.pkl.cli.commands.* import org.pkl.commons.cli.cliMain /** Main method of the Pkl CLI (command-line evaluator and REPL). */ internal fun main(args: Array) { cliMain { RootCommand().main(args) } } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/OutputUtils.kt ================================================ /* * Copyright © 2025-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.cli import java.io.OutputStream import org.pkl.commons.cli.CliException import org.pkl.core.util.IoUtils fun checkPathSpec(pathSpec: String) { val illegal = pathSpec.indexOfFirst { IoUtils.isReservedFilenameChar(it) && it != '/' } if (illegal == -1) { return } throw CliException("Path spec `$pathSpec` contains illegal character `${pathSpec[illegal]}`.") } fun OutputStream.writeText(text: String) = write(text.toByteArray()) fun OutputStream.writeLine(text: String) { writeText(text) writeText("\n") } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/commands/AnalyzeCommand.kt ================================================ /* * Copyright © 2024-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.cli.commands import com.github.ajalt.clikt.core.Context import com.github.ajalt.clikt.core.subcommands import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.types.path import java.nio.file.Path import org.pkl.cli.CliImportAnalyzer import org.pkl.cli.CliImportAnalyzerOptions import org.pkl.commons.cli.commands.ModulesCommand import org.pkl.commons.cli.commands.NoOpCommand import org.pkl.commons.cli.commands.single class AnalyzeCommand : NoOpCommand(name = "analyze") { override fun help(context: Context) = "Commands related to static analysis" override fun helpEpilog(context: Context) = "For more information, visit $helpLink" init { subcommands(AnalyzeImportsCommand()) } } class AnalyzeImportsCommand : ModulesCommand(name = "imports", helpLink = helpLink) { override val helpString = "Prints the graph of modules imported by the input module(s)." private val outputPath: Path? by option( names = arrayOf("-o", "--output-path"), metavar = "path", help = "File path where the output file is placed.", ) .path() .single() override fun run() { val options = CliImportAnalyzerOptions( base = baseOptions.baseOptions(modules, projectOptions), outputFormat = baseOptions.format, outputPath = outputPath, ) CliImportAnalyzer(options).run() } } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/commands/DownloadPackageCommand.kt ================================================ /* * Copyright © 2024-2025 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.cli.commands import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.arguments.convert import com.github.ajalt.clikt.parameters.arguments.multiple import com.github.ajalt.clikt.parameters.groups.provideDelegate import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.option import org.pkl.cli.CliPackageDownloader import org.pkl.commons.cli.commands.BaseCommand import org.pkl.commons.cli.commands.ProjectOptions import org.pkl.commons.cli.commands.single import org.pkl.core.packages.PackageUri class DownloadPackageCommand : BaseCommand(name = "download-package", helpLink = helpLink) { override val helpString = """ Download package(s) This command downloads the specified packages to the cache directory. If the package already exists in the cache directory, this command is a no-op. Examples: ``` # Download two packages $ pkl download-package package://example.com/package1@1.0.0 package://example.com/package2@1.0.0 ``` """ .trimIndent() private val projectOptions by ProjectOptions() private val packageUris: List by argument("package", "The package URIs to download") .convert { PackageUri(it) } .multiple(required = true) private val noTransitive: Boolean by option( names = arrayOf("--no-transitive"), help = "Skip downloading transitive dependencies of a package", ) .single() .flag() override fun run() { CliPackageDownloader( baseOptions.baseOptions(emptyList(), projectOptions), packageUris, noTransitive, ) .run() } } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/commands/EvalCommand.kt ================================================ /* * Copyright © 2024-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.cli.commands import com.github.ajalt.clikt.completion.CompletionCandidates import com.github.ajalt.clikt.parameters.options.default import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.options.validate import org.pkl.cli.CliEvaluator import org.pkl.cli.CliEvaluatorOptions import org.pkl.commons.cli.commands.ModulesCommand import org.pkl.commons.cli.commands.single class EvalCommand : ModulesCommand(name = "eval", helpLink = helpLink) { override val helpString = "Render pkl module(s)" private val outputPath: String? by option( names = arrayOf("-o", "--output-path"), metavar = "path", help = "File path where the output file is placed.", completionCandidates = CompletionCandidates.Path, ) .single() private val moduleOutputSeparator: String by option( names = arrayOf("--module-output-separator"), metavar = "string", help = "Separator to use when multiple module outputs are written to the same file. (default: ---)", ) .single() .default("---") private val expression: String? by option( names = arrayOf("-x", "--expression"), metavar = "expression", help = "Expression to be evaluated within the module.", ) .single() private val multipleFileOutputPath: String? by option( names = arrayOf("-m", "--multiple-file-output-path"), metavar = "path", help = "Directory where a module's multiple file output is placed.", completionCandidates = CompletionCandidates.Path, ) .single() .validate { if (outputPath != null || expression != null) { fail("Option is mutually exclusive with -o, --output-path and -x, --expression.") } } // hidden option used by the native tests private val testMode: Boolean by option(names = arrayOf("--test-mode"), help = "Internal test mode", hidden = true).flag() private val powerAssertionsEnabled: Boolean by option( names = arrayOf("--power-assertions"), help = "Enable power assertions for detailed assertion failure messages.", ) .flag("--no-power-assertions", default = true, defaultForHelp = "enabled") override fun run() { val options = CliEvaluatorOptions( base = baseOptions.baseOptions( modules, projectOptions, testMode = testMode, powerAssertionsEnabled = powerAssertionsEnabled, ), outputPath = outputPath, outputFormat = baseOptions.format, moduleOutputSeparator = moduleOutputSeparator, multipleFileOutputPath = multipleFileOutputPath, expression = expression ?: CliEvaluatorOptions.defaults.expression, ) CliEvaluator(options).run() } } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/commands/FormatterCommand.kt ================================================ /* * Copyright © 2025 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.cli.commands import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.core.Context import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.arguments.multiple import com.github.ajalt.clikt.parameters.options.default import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.types.enum import com.github.ajalt.clikt.parameters.types.path import java.nio.file.Path import org.pkl.cli.CliFormatterCommand import org.pkl.formatter.GrammarVersion class FormatterCommand : CliktCommand(name = "format") { override fun help(context: Context) = """ Format or check formatting of Pkl files. Examples: ``` # Overwrite all Pkl files inside `my/folder/`, recursively. $ pkl format -w my/folder/ # Check formatting of all files, printing filenames with formatting violations to stdout. # Exit with exit code `11` if formatting violations were found. $ pkl format --diff-name-only my/folder/ # Format Pkl code from stdin. $ echo "foo = 1" | pkl format - ``` """ .trimIndent() override fun helpEpilog(context: Context) = "For more information, visit $helpLink" val paths: List by argument(name = "paths", help = "Files or directory to check. Use `-` to read from stdin.") .path(mustExist = false, canBeDir = true) .multiple() val grammarVersion: GrammarVersion by option( names = arrayOf("--grammar-version"), help = """ The grammar compatibility version to use.$NEWLINE ${GrammarVersion.entries.joinToString("$NEWLINE", prefix = " ") { val default = if (it == GrammarVersion.latest()) " `(default)`" else "" "`${it.version}`: ${it.versionSpan}$default" }} """ .trimIndent(), ) .enum { "${it.version}" } .default(GrammarVersion.latest()) val overwrite: Boolean by option( names = arrayOf("-w", "--write"), help = "Format files in place, overwriting them. Implies `---diff-name-only`.", ) .flag(default = false) val diffNameOnly: Boolean by option( names = arrayOf("--diff-name-only"), help = "Write the path of files with formatting violations to stdout.", ) .flag(default = false) val silent: Boolean by option( names = arrayOf("-s", "--silent"), help = "Don't write to stdout or stderr. Mutually exclusive with `--diff-name-only`.", ) .flag(default = false) override fun run() { CliFormatterCommand(paths, grammarVersion, overwrite, diffNameOnly, silent).run() } } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/commands/ProjectCommand.kt ================================================ /* * Copyright © 2024-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.cli.commands import com.github.ajalt.clikt.completion.CompletionCandidates import com.github.ajalt.clikt.core.Context import com.github.ajalt.clikt.core.subcommands import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.arguments.multiple import com.github.ajalt.clikt.parameters.groups.provideDelegate import com.github.ajalt.clikt.parameters.options.default import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.types.path import java.nio.file.Path import org.pkl.cli.CliProjectPackager import org.pkl.cli.CliProjectResolver import org.pkl.commons.cli.commands.BaseCommand import org.pkl.commons.cli.commands.NoOpCommand import org.pkl.commons.cli.commands.TestOptions import org.pkl.commons.cli.commands.single class ProjectCommand : NoOpCommand(name = "project") { override fun help(context: Context) = "Run commands related to projects" override fun helpEpilog(context: Context) = "For more information, visit $helpLink" init { subcommands(ResolveCommand(), PackageCommand()) } } class ResolveCommand : BaseCommand(name = "resolve", helpLink = helpLink) { override val helpString = """ Resolve dependencies for project(s) This command takes the `dependencies` of `PklProject`s, and writes the resolved versions to `PklProject.deps.json` files. Examples: ``` # Search the current working directory for a project, and resolve its dependencies. $ pkl project resolve # Resolve dependencies for all projects within the `packages/` directory. $ pkl project resolve packages/*/ ``` """ private val projectDirs: List by argument("dir", "The project directories to resolve dependencies for").path().multiple() override fun run() { CliProjectResolver(baseOptions.baseOptions(emptyList()), projectDirs).run() } } class PackageCommand : BaseCommand(name = "package", helpLink = helpLink) { override val helpString = """ Verify package(s), and prepare package artifacts to be published. This command runs a project's api tests, as defined by `apiTests` in `PklProject`. Additionally, it verifies that all imports resolve to paths that are local to the project. Finally, this command writes the following artifacts into the output directory specified by the output path. - `name@version` - dependency metadata$NEWLINE - `name@version.sha256` - dependency metadata's SHA-256 checksum$NEWLINE - `name@version.zip` - package archive$NEWLINE - `name@version.zip.sha256` - package archive's SHA-256 checksum The output path option accepts the following placeholders: - %{name}: The display name of the package$NEWLINE - %{version}: The version of the package If a project has local project dependencies, the depended upon project directories must also be included as arguments to this command. Examples: ``` # Search the current working directory for a project, and package it. $ pkl project package # Package all projects within the `packages/` directory. $ pkl project package packages/*/ ``` """ .trimIndent() private val testOptions by TestOptions() private val projectDirs: List by argument("dir", "The project directories to package").path().multiple() private val outputPath: String by option( names = arrayOf("--output-path"), help = "The directory to write artifacts to", metavar = "path", completionCandidates = CompletionCandidates.Path, ) .single() .default(".out/%{name}@%{version}") private val skipPublishCheck: Boolean by option( names = arrayOf("--skip-publish-check"), help = "Skip checking if a package has already been published with different contents", ) .single() .flag() override fun run() { CliProjectPackager( baseOptions.baseOptions(emptyList()), projectDirs, testOptions.cliTestOptions, outputPath, skipPublishCheck, ) .run() } } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/commands/ReplCommand.kt ================================================ /* * Copyright © 2024-2025 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.cli.commands import com.github.ajalt.clikt.parameters.groups.provideDelegate import org.pkl.cli.CliEvaluatorOptions import org.pkl.cli.CliRepl import org.pkl.commons.cli.commands.BaseCommand import org.pkl.commons.cli.commands.ProjectOptions class ReplCommand : BaseCommand(name = "repl", helpLink = helpLink) { override val helpString = "Start a REPL session" private val projectOptions by ProjectOptions() override fun run() { val options = CliEvaluatorOptions(base = baseOptions.baseOptions(emptyList(), projectOptions)) CliRepl(options).run() } } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/commands/RootCommand.kt ================================================ /* * Copyright © 2024-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.cli.commands import com.github.ajalt.clikt.completion.CompletionCommand import com.github.ajalt.clikt.core.Context import com.github.ajalt.clikt.core.context import com.github.ajalt.clikt.core.subcommands import org.pkl.commons.cli.commands.NoOpCommand import org.pkl.commons.cli.commands.installCommonOptions import org.pkl.core.Release internal val helpLink = "${Release.current().documentation.homepage}pkl-cli/index.html#usage" class RootCommand : NoOpCommand(name = "pkl") { override val printHelpOnEmptyArgs = true override fun helpEpilog(context: Context) = "For more information, visit $helpLink" init { context { readArgumentFile = null suggestTypoCorrection = { given, possible -> if (!given.startsWith("-")) { registeredSubcommands().map { it.commandName } } else possible } } installCommonOptions() subcommands( EvalCommand(), ReplCommand(), ServerCommand(), TestCommand(), ProjectCommand(), DownloadPackageCommand(), AnalyzeCommand(), FormatterCommand(), RunCommand(), CompletionCommand( name = "shell-completion", help = "Generate a completion script for the given shell", epilog = "For more information, visit $helpLink", ), ) } } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/commands/RunCommand.kt ================================================ /* * Copyright © 2025-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.cli.commands import com.github.ajalt.clikt.completion.CompletionCandidates import com.github.ajalt.clikt.core.MissingArgument import com.github.ajalt.clikt.core.PrintHelpMessage import com.github.ajalt.clikt.core.context import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.arguments.convert import com.github.ajalt.clikt.parameters.arguments.multiple import com.github.ajalt.clikt.parameters.arguments.optional import com.github.ajalt.clikt.parameters.groups.provideDelegate import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.option import java.net.URI import org.pkl.cli.CliCommandRunner import org.pkl.commons.cli.commands.BaseCommand import org.pkl.commons.cli.commands.BaseOptions import org.pkl.commons.cli.commands.ProjectOptions class RunCommand : BaseCommand(name = "run", helpLink = helpLink) { override val helpString = "Run a Pkl pkl:Command CLI tool" override val treatUnknownOptionsAsArgs = true init { context { // override clikt's built-in help behavior // the built-in --help is eager so any --help/-h would force printing of pkl run's help // which is not desired when a command module (or any of its subcommands) are present // since that would mean command-defined help is gated behind a non-obvious `-- --help` helpOptionNames = emptySet() } } private val showHelp by option("-h", "--help", help = "Show this message and exit").flag() val module: URI? by argument( name = "module", help = "Root pkl:Command module to invoke.", completionCandidates = CompletionCandidates.Path, ) .convert { BaseOptions.parseModuleName(it) } .optional() val args: List by argument(name = "args").multiple() private val projectOptions by ProjectOptions() override fun run() { // if no module is specified but --help is show help, otherwise error becuase module is missing if (module == null) if (showHelp) throw PrintHelpMessage(currentContext) else throw MissingArgument(registeredArguments().find { it.name == "module" }!!) val reservedFlagNames = mutableSetOf("help") val reservedFlagShortNames = mutableSetOf("h") registeredOptions().forEach { opt -> (opt.names + opt.secondaryNames).forEach { if (it.startsWith("--")) reservedFlagNames.add(it.trimStart('-')) else reservedFlagShortNames.add(it.trimStart('-')) } } CliCommandRunner( baseOptions.baseOptions(listOf(module!!), projectOptions), reservedFlagNames, reservedFlagShortNames, if (showHelp) args + listOf("--help") else args, ) .run() } } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/commands/ServerCommand.kt ================================================ /* * Copyright © 2024-2025 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.cli.commands import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.core.Context import org.pkl.cli.CliServer import org.pkl.commons.cli.CliBaseOptions class ServerCommand : CliktCommand(name = "server") { override fun help(context: Context) = "Run as a server that communicates over standard input/output" override fun helpEpilog(context: Context) = "For more information, visit $helpLink" override fun run() { CliServer(CliBaseOptions()).run() } } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/commands/TestCommand.kt ================================================ /* * Copyright © 2024-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.cli.commands import com.github.ajalt.clikt.completion.CompletionCandidates import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.arguments.convert import com.github.ajalt.clikt.parameters.arguments.multiple import com.github.ajalt.clikt.parameters.groups.provideDelegate import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.option import java.net.URI import org.pkl.cli.CliTestRunner import org.pkl.commons.cli.commands.BaseCommand import org.pkl.commons.cli.commands.BaseOptions import org.pkl.commons.cli.commands.ProjectOptions import org.pkl.commons.cli.commands.TestOptions class TestCommand : BaseCommand(name = "test", helpLink = helpLink) { override val helpString = "Run tests within the given module(s)" val modules: List by argument( name = "modules", help = "Module paths or URIs to evaluate.", completionCandidates = CompletionCandidates.Path, ) .convert { BaseOptions.parseModuleName(it) } .multiple() private val projectOptions by ProjectOptions() private val testOptions by TestOptions() private val powerAssertionsEnabled: Boolean by option( names = arrayOf("--power-assertions"), help = "Enable power assertions for detailed assertion failure messages.", ) .flag("--no-power-assertions", default = true, defaultForHelp = "enabled") override fun run() { CliTestRunner( options = baseOptions.baseOptions( modules, projectOptions, powerAssertionsEnabled = powerAssertionsEnabled, ), testOptions = testOptions.cliTestOptions, ) .run() } } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/commands/Utils.kt ================================================ /* * Copyright © 2025 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.cli.commands internal const val NEWLINE = '\u0085' ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/repl/Repl.kt ================================================ /* * Copyright © 2024-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.cli.repl import java.io.IOException import java.net.URI import java.nio.file.Path import java.util.regex.Pattern import kotlin.io.path.deleteIfExists import org.fusesource.jansi.Ansi import org.jline.reader.EndOfFileException import org.jline.reader.Highlighter import org.jline.reader.LineReader import org.jline.reader.LineReader.Option import org.jline.reader.LineReaderBuilder import org.jline.reader.UserInterruptException import org.jline.reader.impl.completer.AggregateCompleter import org.jline.reader.impl.history.DefaultHistory import org.jline.terminal.TerminalBuilder import org.jline.utils.AttributedString import org.jline.utils.InfoCmp import org.pkl.core.repl.ReplRequest import org.pkl.core.repl.ReplResponse import org.pkl.core.repl.ReplServer import org.pkl.core.util.AnsiStringBuilder import org.pkl.core.util.AnsiStringBuilder.AnsiCode import org.pkl.core.util.IoUtils import org.pkl.core.util.SyntaxHighlighter class PklHighlighter : Highlighter { override fun highlight(reader: LineReader, buffer: String): AttributedString { val ansi = AnsiStringBuilder(true).apply { SyntaxHighlighter.writeTo(this, buffer) }.toString() return AttributedString.fromAnsi(ansi) } override fun setErrorPattern(pattern: Pattern) {} override fun setErrorIndex(idx: Int) {} } internal class Repl(workingDir: Path, private val server: ReplServer, private val color: Boolean) { private val terminal = TerminalBuilder.builder().apply { jansi(true) }.build() private val history = DefaultHistory() private val reader = LineReaderBuilder.builder() .apply { history(history) terminal(terminal) if (color) { highlighter(PklHighlighter()) } completer(AggregateCompleter(CommandCompleter, FileCompleter(workingDir))) option(Option.DISABLE_EVENT_EXPANSION, true) variable(LineReader.HISTORY_FILE, (IoUtils.getPklHomeDir().resolve("repl-history"))) } .build() private var continuation = false private var quit = false private var maybeQuit = false private var nextRequestId = 0 private fun String.faint(): String { val sb = AnsiStringBuilder(color) sb.append(AnsiCode.FAINT, this) return sb.toString() } fun run() { // JLine 2 history file is incompatible with JLine 3 IoUtils.getPklHomeDir().resolve("repl-history.bin").deleteIfExists() println(ReplMessages.welcome) println() var inputBuffer = "" try { while (!quit) { val line = try { if (continuation) { nextRequestId -= 1 reader.readLine(" ".repeat("pkl$nextRequestId> ".length).faint()) } else { reader.readLine("pkl$nextRequestId> ".faint()) } } catch (_: UserInterruptException) { if (!continuation && reader.buffer.length() == 0) { if (maybeQuit) quit() else { maybeQuit = true println("(To exit, press ^C again or ^D or type :quit)") } } else { maybeQuit = false } inputBuffer = "" continuation = false continue } catch (_: EndOfFileException) { ":quit" } maybeQuit = false val input = line.trim() if (input.isEmpty()) continue if (continuation) { inputBuffer = (inputBuffer + "\n" + input).trim() continuation = false } else { inputBuffer = input } if (inputBuffer.startsWith(":")) { executeCommand(inputBuffer) } else { evaluate(inputBuffer) } } } finally { try { history.save() } catch (_: IOException) {} try { terminal.close() } catch (_: IOException) {} } } private fun executeCommand(inputBuffer: String) { val candidates = getMatchingCommands(inputBuffer) when { candidates.isEmpty() -> { println("Unknown command: `${inputBuffer.drop(1)}`") } candidates.size > 1 -> { print("Which of the following did you mean? ") println(candidates.joinToString(separator = " ") { "`:${it.type}`" }) } else -> { doExecuteCommand(candidates.single()) } } } private fun doExecuteCommand(command: ParsedCommand) { when (command.type) { Command.Clear -> clear() Command.Examples -> examples() Command.Force -> force(command) Command.Help -> help() Command.Load -> load(command) Command.Quit -> quit() Command.Reset -> reset() } } private fun clear() { terminal.puts(InfoCmp.Capability.clear_screen) terminal.flush() } private fun examples() { println(ReplMessages.examples) } private fun help() { println(ReplMessages.help) } private fun quit() { quit = true } private fun reset() { server.handleRequest(ReplRequest.Reset(nextRequestId())) clear() nextRequestId = 0 } private fun evaluate(inputBuffer: String) { handleEvalRequest(ReplRequest.Eval(nextRequestId(), inputBuffer, false, false)) } private fun loadModule(uri: URI) { handleEvalRequest(ReplRequest.Load(nextRequestId(), uri)) } private fun force(command: ParsedCommand) { handleEvalRequest(ReplRequest.Eval(nextRequestId(), command.arg, false, true)) } private fun load(command: ParsedCommand) { loadModule(IoUtils.toUri(command.arg)) } private fun handleEvalRequest(request: ReplRequest) { val responses = server.handleRequest(request) for (response in responses) { when (response) { is ReplResponse.EvalSuccess -> { println(response.result) } is ReplResponse.EvalError -> { println(response.message) } is ReplResponse.InternalError -> { throw response.cause } is ReplResponse.IncompleteInput -> { assert(responses.size == 1) continuation = true } else -> throw IllegalStateException("Unexpected response: $response") } } } private fun nextRequestId(): String = "pkl$nextRequestId".apply { nextRequestId += 1 } private fun print(msg: String) { terminal.writer().print(highlight(msg)) } private fun println(msg: String = "") { terminal.writer().println(highlight(msg)) } private fun highlight(str: String): String { val ansi = Ansi.ansi() var normal = true for (part in str.split("`", "```")) { ansi.a(part) normal = !normal if (!normal) ansi.bold() else ansi.boldOff() } ansi.reset() return ansi.toString() } } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/repl/ReplCommands.kt ================================================ /* * Copyright © 2024-2025 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.cli.repl private val cmdRegex = Regex(":(\\p{Alpha}*)(\\p{Space}*)(.*)", RegexOption.DOT_MATCHES_ALL) internal fun getMatchingCommands(input: String): List { val match = cmdRegex.matchEntire(input) ?: return listOf() val (cmd, ws, arg) = match.destructured return Command.entries .filter { it.toString().lowercase().startsWith(cmd) } .map { ParsedCommand(it, cmd, ws, arg) } } internal data class ParsedCommand( val type: Command, val cmd: String, val ws: String, val arg: String, ) internal enum class Command { Clear, Examples, Force, Help, Load, Quit, Reset; override fun toString() = name.lowercase() } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/repl/ReplCompleters.kt ================================================ /* * Copyright © 2024-2025 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.cli.repl import java.io.IOException import java.nio.file.Files import java.nio.file.Path import org.jline.reader.Candidate import org.jline.reader.Completer import org.jline.reader.LineReader import org.jline.reader.ParsedLine import org.jline.terminal.Terminal import org.jline.utils.AttributedStringBuilder import org.jline.utils.OSUtils import org.jline.utils.StyleResolver /** * Originally copied from: * https://github.com/jline/jline3/blob/jline-parent-3.21.0/builtins/src/main/java/org/jline/builtins/Completers.java * * Reasons for copying this class instead of adding jline-builtins dependency: * - Adding the dependency breaks native-image build (at least when using build-time initialization, * might work with some config). * - Completers.FileNameCompleter is the only class we currently use. */ internal abstract class JLineFileNameCompleter : Completer { override fun complete( reader: LineReader, commandLine: ParsedLine, candidates: MutableList, ) { val buffer = commandLine.word().substring(0, commandLine.wordCursor()) val current: Path val curBuf: String val sep = getSeparator(reader.isSet(LineReader.Option.USE_FORWARD_SLASH)) val lastSep = buffer.lastIndexOf(sep) try { if (lastSep >= 0) { curBuf = buffer.substring(0, lastSep + 1) current = if (curBuf.startsWith("~")) { if (curBuf.startsWith("~$sep")) { userHome.resolve(curBuf.substring(2)) } else { userHome.parent.resolve(curBuf.substring(1)) } } else { userDir.resolve(curBuf) } } else { curBuf = "" current = userDir } try { Files.newDirectoryStream(current) { accept(it) } .use { directory -> directory.forEach { path -> val value = curBuf + path.fileName.toString() if (Files.isDirectory(path)) { candidates.add( Candidate( value + if (reader.isSet(LineReader.Option.AUTO_PARAM_SLASH)) sep else "", getDisplay(reader.terminal, path, resolver, sep), null, null, if (reader.isSet(LineReader.Option.AUTO_REMOVE_SLASH)) sep else null, null, false, ) ) } else { candidates.add( Candidate( value, getDisplay(reader.terminal, path, resolver, sep), null, null, null, null, true, ) ) } } } } catch (ignored: IOException) {} } catch (ignored: Exception) {} } protected open fun accept(path: Path): Boolean { return try { !Files.isHidden(path) } catch (e: IOException) { false } } protected open val userDir: Path get() = Path.of(System.getProperty("user.dir")) private val userHome: Path get() = Path.of(System.getProperty("user.home")) private fun getSeparator(useForwardSlash: Boolean): String { return if (useForwardSlash) "/" else userDir.fileSystem.separator } private fun getDisplay( terminal: Terminal, path: Path, resolver: StyleResolver, separator: String, ): String { val builder = AttributedStringBuilder() val name = path.fileName.toString() val index = name.lastIndexOf(".") val type = if (index != -1) ".*" + name.substring(index) else null if (Files.isSymbolicLink(path)) { builder.styled(resolver.resolve(".ln"), name).append("@") } else if (Files.isDirectory(path)) { builder.styled(resolver.resolve(".di"), name).append(separator) } else if (Files.isExecutable(path) && !OSUtils.IS_WINDOWS) { builder.styled(resolver.resolve(".ex"), name).append("*") } else if (type != null && resolver.resolve(type).style != 0L) { builder.styled(resolver.resolve(type), name) } else if (Files.isRegularFile(path)) { builder.styled(resolver.resolve(".fi"), name) } else { builder.append(name) } return builder.toAnsi(terminal) } companion object { private val resolver = StyleResolver { name -> when (name) { // imitate org.jline.builtins.Styles.DEFAULT_LS_COLORS "di" -> "1;91" "ex" -> "1;92" "ln" -> "1;96" "fi" -> null else -> null } } } } internal class FileCompleter(override val userDir: Path) : JLineFileNameCompleter() { override fun complete( reader: LineReader, commandLine: ParsedLine, candidates: MutableList, ) { val loadCmd = getMatchingCommands(commandLine.line()).find { it.type == Command.Load && it.ws.isNotEmpty() } if (loadCmd != null) { super.complete(reader, commandLine, candidates) } } } internal object CommandCompleter : Completer { private val commandCandidates: List = Command.entries.map { Candidate(":" + it.toString().lowercase()) } override fun complete(reader: LineReader, line: ParsedLine, candidates: MutableList) { if (line.wordIndex() == 0) candidates.addAll(commandCandidates) } } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/repl/ReplMessages.kt ================================================ /* * Copyright © 2024 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.cli.repl import org.pkl.core.Release internal object ReplMessages { val welcome = """ Welcome to Pkl ${Release.current().version()}. Type an expression to have it evaluated. Type `:help` or `:examples` for more information. """ .trimIndent() val help = """ `` Evaluate and print the result. `1 + 3` ` = ` Evaluate and assign the result to property . `msg = "howdy"` `:clear` Clear the screen. `:examples` Show code examples (use copy and paste to run them). `:force ` Force eager evaluation of a value. `:help` Show this help. `:load ` Load from local file system. `:load path/to/config.pkl` `:quit` Quit this program. `:reset` Reset the environment to its initial state. Tips: * Commands can be abbreviated. `:h` * Commands can be completed. `:` * File paths can be completed. `:load ` * Expressions can be completed. `"hello".re` * Multiple declarations and expressions can be evaluated at once. `a = 1; b = a + 2` * Incomplete input will be continued on the next line. * Multi-line programs can be copy-pasted into the REPL. """ .trimIndent() val examples: String = """ Expressions: `2 + 3 * 4` Strings: `"Hello, " + "World!"` Properties: `timeout = 5.min; timeout` Objects: ```pigeon { name = "Pigeon" fullName = "\(name) Bird" age = 42 address { street = "Landers St." } } pigeon.fullName hobbies { "Swimming" "Dancing" "Surfing" } hobbies[1] prices { ["Apple"] = 1.5 ["Orange"] = 5 ["Banana"] = 2 } prices["Banana"]``` Inheritance: ```parrot = (pigeon) { name = "Parrot" age = 41 } :force parrot``` For more examples, see the Language Reference${if (isMacOs()) " (Command+Double-click the link below)" else ""}: ${Release.current().documentation().homepage()}language-reference/ """ .trimIndent() private fun isMacOs() = System.getProperty("os.name").equals("Mac OS X", ignoreCase = true) } ================================================ FILE: pkl-cli/src/main/kotlin/org/pkl/cli/repl/package-info.java ================================================ /** * This package contains source code from: * *

https://github.com/jline/jline3 * *

Original license: * *

Copyright (c) 2002-2018, the original author or authors. All rights reserved. * *

https://opensource.org/licenses/BSD-3-Clause * *

Redistribution and use in source and binary forms, with or without modification, are permitted * provided that the following conditions are met: * *

Redistributions of source code must retain the above copyright notice, this list of conditions * and the following disclaimer. * *

Redistributions in binary form must reproduce the above copyright notice, this list of * conditions and the following disclaimer in the documentation and/or other materials provided with * the distribution. * *

Neither the name of JLine nor the names of its contributors may be used to endorse or promote * products derived from this software without specific prior written permission. * *

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.pkl.cli.repl; ================================================ FILE: pkl-cli/src/test/files/projects/project1/PklProject ================================================ amends "pkl:Project" dependencies { ["birds"] { uri = "package://localhost:0/birds@0.5.0" } } ================================================ FILE: pkl-cli/src/test/kotlin/org/pkl/cli/CliCommandRunnerTest.kt ================================================ /* * Copyright © 2025-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.cli import com.github.ajalt.clikt.core.CliktError import com.github.ajalt.clikt.core.MissingOption import com.github.ajalt.clikt.core.PrintCompletionMessage import com.github.tomakehurst.wiremock.junit5.WireMockTest import java.io.ByteArrayOutputStream import java.net.URI import java.nio.charset.StandardCharsets import java.nio.file.Files import java.nio.file.Path import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.createDirectories import kotlin.io.path.createParentDirectories import kotlin.io.path.deleteRecursively import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.pkl.commons.cli.CliBaseOptions import org.pkl.commons.cli.CliException import org.pkl.commons.test.FileTestUtils import org.pkl.commons.test.PackageServer import org.pkl.commons.writeString @OptIn(ExperimentalPathApi::class) @WireMockTest(httpsEnabled = true, proxyMode = true) class CliCommandRunnerTest { private val renderOptions = """ extends "pkl:Command" options: Options output { value = options } """ .trimIndent() companion object { private val packageServer = PackageServer() @AfterAll @JvmStatic fun afterAll() { packageServer.close() } } // use manually constructed temp dir instead of @TempDir to work around // https://forums.developer.apple.com/thread/118358 private val tempDir: Path = run { val baseDir = FileTestUtils.rootProjectDir.resolve("pkl-cli/build/tmp/CliCommandRunnerTest") baseDir.createDirectories() Files.createTempDirectory(baseDir, null) } @AfterEach fun afterEach() { tempDir.deleteRecursively() } private fun writePklFile(fileName: String, contents: String): URI { tempDir.resolve(fileName).createParentDirectories() return tempDir.resolve(fileName).writeString(contents).toUri() } private fun runToStdout(options: CliBaseOptions, args: List): String { val outWriter = ByteArrayOutputStream() CliCommandRunner( options, setOf("root-dir"), emptySet(), args, outWriter, ByteArrayOutputStream(), ) .run() return outWriter.toString(StandardCharsets.UTF_8) } @Test fun `missing required flag`() { val moduleUri = writePklFile( "cmd.pkl", renderOptions + """ class Options { foo: String } """ .trimIndent(), ) val exc = assertThrows { runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri), testMode = true), listOf()) } assertThat(exc.paramName).isEqualTo("--foo") } @Test fun `primitive flags`() { val moduleUri = writePklFile( "cmd.pkl", renderOptions + """ class Options { `number-as-int`: Number `number-as-float`: Number `number-nullable`: Number? `number-default`: Number = 100.0 `number-default-overridden`: Number = 100.0 float: Float `float-without-decimals`: Float `float-nullable`: Float? `float-default`: Float = 100.0 `float-default-overridden`: Float = 100.0 int: Int `int-nullable`: Int? `int-default`: Int = 100 `int-default-overridden`: Int = 100 int8: Int8 int16: Int16 int32: Int32 uint: UInt uint8: UInt8 uint16: UInt16 uint32: UInt32 boolean: Boolean `boolean-nullable`: Boolean? `boolean-default`: Boolean = true `boolean-default-overridden`: Boolean = false string: String `string-nullable`: String? `string-default`: String = "default" `string-default-overridden`: String = "default" char: Char `char-nullable`: Char? `char-default`: Char = "a" `char-default-overridden`: Char = "b" } """ .trimIndent(), ) val output = runToStdout( CliBaseOptions(sourceModules = listOf(moduleUri)), listOf( "--number-as-int=123", "--number-as-float=123.0", "--number-default-overridden=-200.0", "--float=123.456", "--float-without-decimals=789", "--float-default-overridden=-200", "--int=123", "--int-default-overridden=-200", "--int8=127", "--int16=32767", "--int32=2147483647", "--uint=0", "--uint8=255", "--uint16=65535", "--uint32=4294967295", "--boolean=n", "--boolean-default-overridden=1", "--string=foobar", "--string-default-overridden=non-default", "--char=X", "--char-default-overridden=c", ), ) assertThat(output) .isEqualTo( """ `number-as-int` = 123 `number-as-float` = 123.0 `number-nullable` = null `number-default` = 100.0 `number-default-overridden` = -200.0 float = 123.456 `float-without-decimals` = 789.0 `float-nullable` = null `float-default` = 100.0 `float-default-overridden` = -200.0 int = 123 `int-nullable` = null `int-default` = 100 `int-default-overridden` = -200 int8 = 127 int16 = 32767 int32 = 2147483647 uint = 0 uint8 = 255 uint16 = 65535 uint32 = 4294967295 boolean = false `boolean-nullable` = null `boolean-default` = true `boolean-default-overridden` = true string = "foobar" `string-nullable` = null `string-default` = "default" `string-default-overridden` = "non-default" char = "X" `char-nullable` = null `char-default` = "a" `char-default-overridden` = "c" """ .trimIndent() ) } @Test fun `primitive arguments`() { val moduleUri = writePklFile( "cmd.pkl", renderOptions + """ class Options { @Argument `number-as-int`: Number @Argument `number-as-float`: Number @Argument float: Float @Argument `float-without-decimals`: Float @Argument int: Int @Argument int8: Int8 @Argument int16: Int16 @Argument int32: Int32 @Argument uint: UInt @Argument uint8: UInt8 @Argument uint16: UInt16 @Argument uint32: UInt32 @Argument boolean: Boolean @Argument string: String @Argument char: Char } """ .trimIndent(), ) val output = runToStdout( CliBaseOptions(sourceModules = listOf(moduleUri)), listOf( "123", "123.0", "123.456", "789", "123", "127", "32767", "2147483647", "0", "255", "65535", "4294967295", "n", "foobar", "X", ), ) assertThat(output) .isEqualTo( """ `number-as-int` = 123 `number-as-float` = 123.0 float = 123.456 `float-without-decimals` = 789.0 int = 123 int8 = 127 int16 = 32767 int32 = 2147483647 uint = 0 uint8 = 255 uint16 = 65535 uint32 = 4294967295 boolean = false string = "foobar" char = "X" """ .trimIndent() ) } @Test fun `enum flags`() { val moduleUri = writePklFile( "cmd.pkl", renderOptions + """ typealias MyEnum = "d" | "e" | *"f" class Options { enum: "a" | "b" | "c" `enum-default`: "a" | *"b" | "c" `enum-explicit-default`: "a" | "b" | "c" = "c" `enum-alias-default`: MyEnum `enum-alias-explicit-default`: MyEnum = "e" `enum-alias-default-overridden`: MyEnum `enum-single`: "x" `enum-single-nullable`: "x"? `enum-single-explicit-default`: "x" = "x" `enum-single-overridden`: "x" } """ .trimIndent(), ) val output = runToStdout( CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("--enum=a", "--enum-alias-default-overridden=d", "--enum-single-overridden=x"), ) assertThat(output) .isEqualTo( """ enum = "a" `enum-default` = "b" `enum-explicit-default` = "c" `enum-alias-default` = "f" `enum-alias-explicit-default` = "e" `enum-alias-default-overridden` = "d" `enum-single` = "x" `enum-single-nullable` = null `enum-single-explicit-default` = "x" `enum-single-overridden` = "x" """ .trimIndent() ) } @Test fun `enum args`() { val moduleUri = writePklFile( "cmd.pkl", renderOptions + """ typealias MyEnum = "d" | "e" | *"f" class Options { @Argument enum: "a" | "b" | "c" @Argument `enum-default`: "a" | *"b" | "c" @Argument `enum-alias-default`: MyEnum } """ .trimIndent(), ) val output = runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("a", "c", "d")) assertThat(output) .isEqualTo( """ enum = "a" `enum-default` = "c" `enum-alias-default` = "d" """ .trimIndent() ) } @Test fun `collection flags`() { val moduleUri = writePklFile( "cmd.pkl", renderOptions + """ class Options { list: List `list-nullable`: List? `list-default`: List = List(1, 2, 300.0) set: Set `set-nullable`: Set? `set-default`: Set = Set(1, 2, 300.0, 2) `enum-list`: List<"a" | "b" | *"c"> `enum-set`: Set<"a" | "b" | *"c"> } """ .trimIndent(), ) val output = runToStdout( CliBaseOptions(sourceModules = listOf(moduleUri)), listOf( "--list=1", "--list=0", "--list=0.0", "--list=1", "--set=1", "--set=0", "--set=0.0", "--set=1", "--enum-list=a", "--enum-list=a", "--enum-list=b", "--enum-set=a", "--enum-set=a", "--enum-set=b", ), ) assertThat(output) .isEqualTo( """ list = List(1, 0, 0.0, 1) `list-nullable` = null `list-default` = List(1, 2, 300.0) set = Set(1, 0, 0.0) `set-nullable` = null `set-default` = Set(1, 2, 300.0) `enum-list` = List("a", "a", "b") `enum-set` = Set("a", "b") """ .trimIndent() ) } @Test fun `sequence args`() { val moduleUri = writePklFile( "cmd.pkl", renderOptions + """ class Options { @Argument list: List } """ .trimIndent(), ) val output = runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("1", "0", "0.0", "1")) assertThat(output) .isEqualTo( """ list = List(1, 0, 0.0, 1) """ .trimIndent() ) val moduleUri2 = writePklFile( "cmd.pkl", renderOptions + """ class Options { @Argument set: Set } """ .trimIndent(), ) val output2 = runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri2)), listOf("1", "0", "0.0", "1")) assertThat(output2) .isEqualTo( """ set = Set(1, 0, 0.0) """ .trimIndent() ) val moduleUri3 = writePklFile( "cmd.pkl", renderOptions + """ class Options { @Argument listing: Listing } """ .trimIndent(), ) val output3 = runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri3)), listOf("1", "0", "0.0", "1")) assertThat(output3) .isEqualTo( """ listing { 1 0 0.0 1 } """ .trimIndent() ) } @Test fun `keyval args`() { val moduleUri = writePklFile( "cmd.pkl", renderOptions + """ class Options { @Argument map: Map } """ .trimIndent(), ) val output = runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("1=0", "0.0=1")) assertThat(output) .isEqualTo( """ map = Map(1, 0, 0.0, 1) """ .trimIndent() ) val moduleUri2 = writePklFile( "cmd.pkl", renderOptions + """ class Options { @Argument mapping: Mapping } """ .trimIndent(), ) val output2 = runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri2)), listOf("1=0", "0.0=1")) assertThat(output2) .isEqualTo( """ mapping { [1] = 0 [0.0] = 1 } """ .trimIndent() ) val moduleUri3 = writePklFile( "cmd.pkl", renderOptions + """ class Options { @Argument pair: Pair } """ .trimIndent(), ) val output3 = runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri3)), listOf("1=0.0")) assertThat(output3) .isEqualTo( """ pair = Pair(1, 0.0) """ .trimIndent() ) } @Test fun `map flags`() { val moduleUri = writePklFile( "cmd.pkl", renderOptions + """ typealias MyEnum = "a" | "b" | *"c" class Options { map: Map `map-nullable`: Map? `map-default`: Map = Map("x", 123, "y", 456.789) `enum-map`: Map } """ .trimIndent(), ) val output = runToStdout( CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("--map=a=0.0", "--map=b=1", "--enum-map=a=b", "--enum-map=b=c"), ) assertThat(output) .isEqualTo( """ map = Map("a", 0.0, "b", 1) `map-nullable` = null `map-default` = Map("x", 123, "y", 456.789) `enum-map` = Map("a", "b", "b", "c") """ .trimIndent() ) } @Test fun `mapping flags`() { val moduleUri = writePklFile( "cmd.pkl", renderOptions + """ typealias MyEnum = "a" | "b" | *"c" class Options { mapping: Mapping `mapping-nullable`: Mapping? `mapping-default`: Mapping = new { ["x"] = 123; ["y"] = 456.789 } `enum-mapping`: Mapping } """ .trimIndent(), ) val output = runToStdout( CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("--mapping=a=0.0", "--mapping=b=1", "--enum-mapping=a=b", "--enum-mapping=b=c"), ) assertThat(output) .isEqualTo( """ mapping { ["a"] = 0.0 ["b"] = 1 } `mapping-nullable` = null `mapping-default` { ["x"] = 123 ["y"] = 456.789 } `enum-mapping` { ["a"] = "b" ["b"] = "c" } """ .trimIndent() ) } @Test fun `pair flags`() { val moduleUri = writePklFile( "cmd.pkl", renderOptions + """ typealias MyEnum = "a" | "b" | *"c" class Options { pair: Pair `pair-nullable`: Pair? `pair-default`: Pair = Pair("x", 123) `enum-pair`: Pair } """ .trimIndent(), ) val output = runToStdout( CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("--pair=a=0.0", "--enum-pair=a=b"), ) assertThat(output) .isEqualTo( """ pair = Pair("a", 0.0) `pair-nullable` = null `pair-default` = Pair("x", 123) `enum-pair` = Pair("a", "b") """ .trimIndent() ) } @Test fun `convert Duration`() { val moduleUri = writePklFile( "cmd.pkl", renderOptions + """ class Options { @Argument { convert = module.convertDuration } a: Duration @Argument { convert = module.convertDuration } b: Duration @Argument { convert = module.convertDuration } c: Duration @Argument { convert = module.convertDuration } d: Duration } """ .trimIndent(), ) val output = runToStdout( CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("10.h", "10H", "10.5.MS", "10.5d"), ) assertThat(output) .isEqualTo( """ a = 10.h b = 10.h c = 10.5.ms d = 10.5.d """ .trimIndent() ) } @Test fun `convert DataSize`() { val moduleUri = writePklFile( "cmd.pkl", renderOptions + """ class Options { @Argument { convert = module.convertDataSize } a: DataSize @Argument { convert = module.convertDataSize } b: DataSize @Argument { convert = module.convertDataSize } c: DataSize @Argument { convert = module.convertDataSize } d: DataSize } """ .trimIndent(), ) val output = runToStdout( CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("10.gb", "10GB", "10.5.MB", "10.5tib"), ) assertThat(output) .isEqualTo( """ a = 10.gb b = 10.gb c = 10.5.mb d = 10.5.tib """ .trimIndent() ) } @Test fun `convert import`() { val moduleUri = writePklFile( "cmd.pkl", """ extends "pkl:Command" options: Options output { value = (options) { fromImport { baz = true // assert that imported modules are not forced } } } class Options { @Argument { convert = (it) -> new Import{ uri = it } } fromImport: Module } """ .trimIndent(), ) val importUri = writePklFile( "import.pkl", """ foo = 1 bar = "baz" baz: Boolean """ .trimIndent(), ) val output = runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri)), listOf(importUri.toString())) assertThat(output) .isEqualTo( """ fromImport { foo = 1 bar = "baz" baz = true } """ .trimIndent() ) } @Test fun `transformAll import`() { val moduleUri = writePklFile( "cmd.pkl", """ extends "pkl:Command" options: Options output { value = (options) { fromImport { baz = true // assert that imported modules are not forced } } } class Options { @Flag { convert = (it) -> new Import{ uri = it } transformAll = (values) -> values.firstOrNull ?? new Import { uri = "./default.pkl" } } fromImport: Module } """ .trimIndent(), ) val importUri = writePklFile( "default.pkl", """ foo = 1 bar = "baz" baz: Boolean """ .trimIndent(), ) val output = runToStdout( CliBaseOptions(sourceModules = listOf(moduleUri), workingDir = tempDir), emptyList(), ) assertThat(output) .isEqualTo( """ fromImport { foo = 1 bar = "baz" baz = true } """ .trimIndent() ) } @Test fun `convert glob import`() { val moduleUri = writePklFile( "cmd.pkl", """ extends "pkl:Command" import "base.pkl" options: Options output { value = (options) { fromGlobImport { [[true]] { baz = true // assert that imported modules are not forced } } } } class Options { @Argument { convert = (it) -> new Import { uri = it; glob = true }; multiple = false } fromGlobImport: Mapping } """ .trimIndent(), ) val baseImport = writePklFile( "base.pkl", """ foo: Int bar: String baz: Boolean """ .trimIndent(), ) writePklFile( "glob1.pkl", """ amends "base.pkl" foo = 1 bar = "baz" """ .trimIndent(), ) writePklFile( "glob2.pkl", """ amends "base.pkl" foo = 2 bar = "qux" """ .trimIndent(), ) val importDirUri = baseImport.resolve(".") val output = runToStdout( CliBaseOptions(sourceModules = listOf(moduleUri)), listOf(importDirUri.resolve("./glob*.pkl").toString()), ) assertThat(output.replace(importDirUri.toString(), "file:/

/")) .isEqualTo( """ fromGlobImport { ["file://glob1.pkl"] { foo = 1 bar = "baz" baz = true } ["file://glob2.pkl"] { foo = 2 bar = "qux" baz = true } } """ .trimIndent() ) } @Test fun `convert that throws`() { val moduleUri = writePklFile( "cmd.pkl", renderOptions + """ class Options { @Argument { convert = (it) -> throw("oops!") } foo: String } """ .trimIndent(), ) val exc = assertThrows { runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("hi")) } assertThat(exc.message).contains("oops!") } @Test fun `convert with eval error`() { val moduleUri = writePklFile( "cmd.pkl", renderOptions + """ class Options { @Argument { convert = (it) -> it.noSuchMethod() } foo: String } """ .trimIndent(), ) val exc = assertThrows { runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("hi")) } assertThat(exc.message).contains("Cannot find method `noSuchMethod` in class `String`.") } @Test fun `convert with stack overflow`() { val moduleUri = writePklFile( "cmd.pkl", renderOptions + """ const function overflow(it) = overflow(it) class Options { @Argument { convert = (it) -> overflow(it) } foo: String } """ .trimIndent(), ) val exc = assertThrows { runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("hi")) } assertThat(exc.message).contains("A stack overflow occurred.") } @Test fun `boolean flag`() { val moduleUri = writePklFile( "cmd.pkl", renderOptions + """ class Options { @BooleanFlag `bool-true`: Boolean @BooleanFlag `bool-false`: Boolean @BooleanFlag `bool-nullable`: Boolean? @BooleanFlag `bool-default-true`: Boolean = true @BooleanFlag `bool-default-false`: Boolean = false } """ .trimIndent(), ) val output = runToStdout( CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("--bool-true", "--no-bool-false"), ) assertThat(output) .isEqualTo( """ `bool-true` = true `bool-false` = false `bool-nullable` = null `bool-default-true` = true `bool-default-false` = false """ .trimIndent() ) } @Test fun `boolean flag with bad type`() { val moduleUri = writePklFile( "cmd.pkl", renderOptions + """ class Options { @BooleanFlag foo: String } """ .trimIndent(), ) val exc = assertThrows { runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("hi")) } assertThat(exc.message) .contains("Option `foo` with annotation `@BooleanFlag` has invalid type `String`.") } @Test fun `counted flag`() { val moduleUri = writePklFile( "cmd.pkl", renderOptions + """ class Options { @CountedFlag { shortName = "a" } int: Int @CountedFlag { shortName = "b" } int8: Int8 @CountedFlag { shortName = "c" } int16: Int16 @CountedFlag { shortName = "d" } int32: Int32 @CountedFlag { shortName = "x" } uint: UInt @CountedFlag { shortName = "y" } uint8: UInt8 @CountedFlag { shortName = "g" } uint16: UInt16 @CountedFlag { shortName = "i" } uint32: UInt32 } """ .trimIndent(), ) val output = runToStdout( CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("-abbcccddddxxxxxyyyyyygggggggiiiiiiii"), ) assertThat(output) .isEqualTo( """ int = 1 int8 = 2 int16 = 3 int32 = 4 uint = 5 uint8 = 6 uint16 = 7 uint32 = 8 """ .trimIndent() ) } @Test fun `test transformAll`() { val moduleUri = writePklFile( "cmd.pkl", renderOptions + """ class Options { @Flag { multiple = true transformAll = (values) -> values.fold(0, (res, acc) -> res + acc) } foo: Int } """ .trimIndent(), ) val output = runToStdout( CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("--foo=1", "--foo=5", "--foo=8"), ) assertThat(output) .isEqualTo( """ foo = 14 """ .trimIndent() ) } @Test fun `completion candidates`() { val moduleUri = writePklFile( "cmd.pkl", renderOptions + """ class Options { none: String? enum: *"a" | "b" | "c" @Flag { completionCandidates = "paths" } path: String? @Flag { completionCandidates { "foo"; "bar"; "baz" } } explicit: String? @Argument enumArg: *"a" | "b" | "c" @Argument { completionCandidates = "paths" } pathArg: String @Argument { completionCandidates { "foo"; "bar"; "baz" } } explicitArg: String } """ .trimIndent(), ) val exc = assertThrows { runToStdout( CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("a", "foo", "bar", "shell-completion", "bash"), ) } assertThat(exc.message) .contains( """ "--none") ;; "--enum") COMPREPLY=($(compgen -W 'a b c' -- "${'$'}{word}")) ;; "--path") __complete_files "${'$'}{word}" ;; "--explicit") COMPREPLY=($(compgen -W 'bar baz foo' -- "${'$'}{word}")) ;; "--help") ;; "enumArg") COMPREPLY=($(compgen -W '' -- "${'$'}{word}")) ;; "pathArg") __complete_files "${'$'}{word}" ;; "explicitArg") COMPREPLY=($(compgen -W 'bar baz foo' -- "${'$'}{word}")) ;;""" ) } } ================================================ FILE: pkl-cli/src/test/kotlin/org/pkl/cli/CliEvaluatorTest.kt ================================================ /* * Copyright © 2024-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.cli import com.github.tomakehurst.wiremock.client.WireMock.* import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo import com.github.tomakehurst.wiremock.junit5.WireMockTest import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.net.ServerSocket import java.net.URI import java.nio.charset.StandardCharsets import java.nio.file.Files import java.nio.file.Path import java.time.Duration import java.util.regex.Pattern import kotlin.io.path.* import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatCode import org.junit.jupiter.api.* import org.junit.jupiter.api.condition.DisabledOnOs import org.junit.jupiter.api.condition.EnabledOnOs import org.junit.jupiter.api.condition.OS import org.junit.jupiter.api.io.TempDir import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.EnumSource import org.pkl.commons.cli.CliBaseOptions import org.pkl.commons.cli.CliException import org.pkl.commons.readString import org.pkl.commons.test.FileTestUtils import org.pkl.commons.test.PackageServer import org.pkl.commons.toPath import org.pkl.commons.writeString import org.pkl.core.OutputFormat import org.pkl.core.SecurityManagers import org.pkl.core.util.IoUtils @OptIn(ExperimentalPathApi::class) @WireMockTest(httpsEnabled = true, proxyMode = true) class CliEvaluatorTest { companion object { private val defaultContents = """ person { name = "pigeon" age = 20 + 10 } """ .trimIndent() private val packageServer = PackageServer() @AfterAll @JvmStatic fun afterAll() { packageServer.close() } } // use manually constructed temp dir instead of @TempDir to work around // https://forums.developer.apple.com/thread/118358 private val tempDir: Path = run { val baseDir = FileTestUtils.rootProjectDir.resolve("pkl-cli/build/tmp/CliEvaluatorTest") baseDir.createDirectories() Files.createTempDirectory(baseDir, null) } @AfterEach fun afterEach() { tempDir.deleteRecursively() } @Test fun `generate Pcf`() { val sourceFiles = listOf(writePklFile("test.pkl")) val outputFiles = evalToFiles( CliEvaluatorOptions(CliBaseOptions(sourceModules = sourceFiles), outputFormat = "pcf") ) assertThat(outputFiles).hasSize(1) checkOutputFile( outputFiles[0], "test.pcf", """ person { name = "pigeon" age = 30 } """, ) } @Test fun `generate JSON`() { val sourceFiles = listOf(writePklFile("test.pkl")) val outputFiles = evalToFiles( CliEvaluatorOptions(CliBaseOptions(sourceModules = sourceFiles), outputFormat = "json") ) assertThat(outputFiles).hasSize(1) checkOutputFile( outputFiles[0], "test.json", """ { "person": { "name": "pigeon", "age": 30 } } """, ) } @Test fun `generate YAML`() { val sourceFiles = listOf(writePklFile("test.pkl")) val outputFiles = evalToFiles( CliEvaluatorOptions(CliBaseOptions(sourceModules = sourceFiles), outputFormat = "yaml") ) assertThat(outputFiles).hasSize(1) checkOutputFile( outputFiles[0], "test.yaml", """ person: name: pigeon age: 30 """, ) } @Test fun `generate plist`() { val sourceFiles = listOf(writePklFile("test.pkl")) val outputFiles = evalToFiles( CliEvaluatorOptions(CliBaseOptions(sourceModules = sourceFiles), outputFormat = "plist") ) assertThat(outputFiles).hasSize(1) checkOutputFile( outputFiles[0], "test.plist", """ person name pigeon age 30 """, ) } @Test fun `generate XML`() { val sourceFiles = listOf(writePklFile("test.pkl")) val outputFiles = evalToFiles( CliEvaluatorOptions(CliBaseOptions(sourceModules = sourceFiles), outputFormat = "xml") ) assertThat(outputFiles).hasSize(1) checkOutputFile( outputFiles[0], "test.xml", """ pigeon 30 """, ) } @Test fun `unknown output format`() { val sourceFiles = listOf(writePklFile("test.pkl")) val e = assertThrows { evalToFiles( CliEvaluatorOptions(CliBaseOptions(sourceModules = sourceFiles), outputFormat = "unknown") ) } assertThat(e).hasMessageContaining("Unknown output format: `unknown`. ") } @Test fun `generate multiple files`() { val sourceFiles = listOf( writePklFile("file1.pkl", "x = 1 + 1"), writePklFile("file2.pkl", "x = 2 + 2"), writePklFile("file3.pkl", "x = 3 + 3"), ) val outputFiles = evalToFiles( CliEvaluatorOptions(CliBaseOptions(sourceModules = sourceFiles), outputFormat = "pcf") ) assertThat(outputFiles).hasSize(3) checkOutputFile(outputFiles[0], "file1.pcf", "x = 2") checkOutputFile(outputFiles[1], "file2.pcf", "x = 4") checkOutputFile(outputFiles[2], "file3.pcf", "x = 6") } @Test fun `module path module as source module`() { val dir = tempDir.resolve("foo").resolve("bar").createDirectories() dir.resolve("test.pkl").writeString(defaultContents) // check relative imports too dir .resolve("test2.pkl") .writeString( """ amends "test.pkl" person { name = "barn owl" } """ .trimIndent() ) val outputFiles = evalToFiles( CliEvaluatorOptions( CliBaseOptions( sourceModules = listOf(URI("modulepath:/foo/bar/test.pkl"), URI("modulepath:/foo/bar/test2.pkl")), modulePath = listOf(tempDir), ), outputFormat = "pcf", outputPath = "$tempDir/%{moduleName}.%{outputFormat}", ) ) assertThat(outputFiles).hasSize(2) checkOutputFile( outputFiles[0], "test.pcf", """ person { name = "pigeon" age = 30 } """, ) checkOutputFile( outputFiles[1], "test2.pcf", """ person { name = "barn owl" age = 30 } """, ) } @Test fun `external properties`() { val sourceFiles = listOf( writePklFile( "test.pkl", """ person { name = read("prop:name") age = read("prop:age").toInt() } """, ) ) val outputFiles = evalToFiles( CliEvaluatorOptions( CliBaseOptions( sourceModules = sourceFiles, externalProperties = mapOf("name" to "pigeon", "age" to "30"), ), outputFormat = "pcf", ) ) assertThat(outputFiles).hasSize(1) checkOutputFile( outputFiles[0], "test.pcf", """ person { name = "pigeon" age = 30 } """, ) } @Test fun `custom working directory given as absolute path`() { customWorkingDirectory(relativePath = false) } @Test fun `custom working directory given as relative path (the norm when using cli)`() { customWorkingDirectory(relativePath = true) } private fun customWorkingDirectory(relativePath: Boolean) { val dir = tempDir.resolve("foo").resolve("bar").createDirectories() val file = dir.resolve("test.pkl").writeString(defaultContents) val outputFiles = evalToFiles( CliEvaluatorOptions( CliBaseOptions( sourceModules = listOf(file.toUri()), workingDir = if (relativePath) IoUtils.getCurrentWorkingDir().relativize(dir.parent) else dir.parent, ), outputFormat = "pcf", outputPath = "baz/%{moduleName}.pcf", ) ) assertThat(outputFiles).hasSize(1) assertThat(outputFiles[0].normalize()).isEqualTo(dir.parent.resolve("baz/test.pcf")) checkOutputFile( outputFiles[0], "test.pcf", """ person { name = "pigeon" age = 30 } """, ) } @Test fun `source module with relative path`() { val dir = tempDir.resolve("foo").createDirectories() dir.resolve("test.pkl").writeString(defaultContents) val outputFiles = evalToFiles( CliEvaluatorOptions( CliBaseOptions(sourceModules = listOf(URI("foo/test.pkl")), workingDir = tempDir), outputFormat = "pcf", ) ) assertThat(outputFiles).hasSize(1) checkOutputFile( outputFiles[0], "test.pcf", """ person { name = "pigeon" age = 30 } """, ) } @Test fun `module path element with relative path`() { val libDir = tempDir.resolve("lib").resolve("foo").createDirectories() libDir.resolve("someLib.pkl").writeString("x = 1") val pklScript = writePklFile( "test.pkl", """ import "modulepath:/foo/someLib.pkl" result = someLib.x """, ) val outputFiles = evalToFiles( CliEvaluatorOptions( CliBaseOptions( sourceModules = listOf(pklScript), workingDir = tempDir, modulePath = listOf("lib".toPath()), ), outputFormat = "pcf", ) ) assertThat(outputFiles).hasSize(1) checkOutputFile(outputFiles[0], "test.pcf", "result = 1") } @Test fun `moduleDir is relative to workingDir even if not descendant`() { val contents = "foo = 42" val file = writePklFile("some/nested/structure.pkl", contents) val workingDir = tempDir.resolve("another/structure").createDirectories() val outputFiles = evalToFiles( CliEvaluatorOptions( CliBaseOptions(sourceModules = listOf(file), workingDir = workingDir), outputPath = "%{moduleDir}/result.pcf", outputFormat = "pcf", ) ) assertThat(outputFiles).hasSize(1) assertThat(outputFiles[0]).isEqualTo(tempDir.resolve("some/nested/result.pcf")) checkOutputFile(outputFiles[0], "result.pcf", contents) } // Can't reliably create symlinks on Windows. // Might get errors like "A required privilege is not held by the client". @Test @DisabledOnOs(OS.WINDOWS) fun `moduleDir is relative to workingDir even through symlinks`() { val contents = "foo = 42" val realWorkingDir = tempDir.resolve("workingDir").createDirectories() val symlinkToTempDir = Files.createSymbolicLink(tempDir.resolve("symlinkToTempDir"), tempDir) val workingDir = symlinkToTempDir.resolve("workingDir") val file = realWorkingDir.resolve("test.pkl").writeString(contents).toUri() val outputFiles = evalToFiles( CliEvaluatorOptions( CliBaseOptions(sourceModules = listOf(file), workingDir = workingDir), outputFormat = "pcf", ) ) assertThat(outputFiles).hasSize(1) assertThat(outputFiles[0].toString()).doesNotContain("symlinkToTempDir") checkOutputFile(outputFiles[0], "test.pcf", contents) } @Test fun `take input from stdin`() { val stdin = ByteArrayInputStream(defaultContents.toByteArray(StandardCharsets.UTF_8)) val stdout = ByteArrayOutputStream() val evaluator = CliEvaluator( CliEvaluatorOptions( CliBaseOptions(sourceModules = listOf(URI("repl:text"))), outputFormat = "pcf", ), stdin, stdout, ) evaluator.run() assertThat(stdout.toString(StandardCharsets.UTF_8).trim()) .isEqualTo(defaultContents.replace("20 + 10", "30").trim()) } @Test fun `write output to console`() { val module1 = writePklFile("mod1.pkl", "x = 21 + 21") val module2 = writePklFile("mod2.pkl", "y = 11 + 11") val output = evalToConsole(CliEvaluatorOptions(CliBaseOptions(sourceModules = listOf(module1, module2)))) assertThat(output).isEqualTo("x = 42\n---\ny = 22\n") } @Test fun `evaluation timeout`() { val sourceFiles = listOf( writePklFile( "test.pkl", """ function fib(n) = if (n < 2) 0 else fib(n - 1) + fib(n - 2) x = fib(100) """, ) ) val e = assertThrows { evalToFiles( CliEvaluatorOptions( CliBaseOptions(sourceModules = sourceFiles, timeout = Duration.ofMillis(100)), outputFormat = "pcf", ) ) } assertThat(e.message).contains("timed out") } @Test fun `cannot import module located outside root dir`() { val sourceFiles = listOf( writePklFile( "test.pkl", """ amends "/non/existing.pkl" """, ) ) val e = assertThrows { evalToFiles( CliEvaluatorOptions(CliBaseOptions(sourceModules = sourceFiles, rootDir = tempDir)) ) } assertThat(e.message).contains("Refusing to load module `file:///non/existing.pkl`") } @Test fun `concatenate file outputs`() { val sourceFiles = listOf( writePklFile("test1.pkl", "x = 1"), writePklFile("test2.pkl", "x = 2"), writePklFile("test3.pkl", "x = 3"), ) val outputFile = tempDir.resolve("output.yaml") evalToFiles( CliEvaluatorOptions( CliBaseOptions(sourceModules = sourceFiles), outputFile.toString(), "yaml", ) ) checkOutputFile(outputFile, "output.yaml", "x: 1\n---\nx: 2\n---\nx: 3") } @Test fun `concatenate file outputs - some empty YAML streams`() { val sourceFiles = listOf( writePklFile( "test0.pkl", "output { value = List(); renderer = new YamlRenderer { isStream = true } }", ), writePklFile("test1.pkl", "x = 1"), writePklFile( "test2.pkl", "output { value = List(); renderer = new YamlRenderer { isStream = true } }", ), writePklFile("test3.pkl", "x = 3"), writePklFile( "test4.pkl", "output { value = List(); renderer = new YamlRenderer { isStream = true } }", ), ) val outputFile = tempDir.resolve("output.yaml") evalToFiles( CliEvaluatorOptions( CliBaseOptions(sourceModules = sourceFiles), outputFile.toString(), "yaml", ) ) checkOutputFile(outputFile, "output.yaml", "x: 1\n---\nx: 3") } @Test fun `concatenate module outputs with custom separator`() { val sourceFiles = listOf( writePklFile("test1.pkl", "x = 1"), writePklFile("test2.pkl", "x = 2"), writePklFile("test3.pkl", "x = 3"), ) val outputFile = tempDir.resolve("output.pcf") evalToFiles( CliEvaluatorOptions( CliBaseOptions(sourceModules = sourceFiles), outputFile.toString(), outputFormat = "pcf", moduleOutputSeparator = "// my module separator", ) ) checkOutputFile( outputFile, "output.pcf", """ x = 1 // my module separator x = 2 // my module separator x = 3 """ .trimIndent(), ) } @Test fun `concatenate module outputs with empty custom separator`() { val sourceFiles = listOf( writePklFile("test1.pkl", "x = 1"), writePklFile("test2.pkl", "y = 2"), writePklFile("test3.pkl", "z = 3"), ) val outputFile = tempDir.resolve("output.pcf") evalToFiles( CliEvaluatorOptions( CliBaseOptions(sourceModules = sourceFiles), outputFile.toString(), outputFormat = "pcf", moduleOutputSeparator = "", ) ) checkOutputFile( outputFile, "output.pcf", """ x = 1 y = 2 z = 3 """ .trimIndent(), ) } @Test fun `concatenate console outputs`() { val sourceFiles = listOf( writePklFile("test1.pkl", "x = 1"), writePklFile("test2.pkl", "x = 2"), writePklFile("test3.pkl", "x = 3"), ) val output = evalToConsole( CliEvaluatorOptions(CliBaseOptions(sourceModules = sourceFiles), outputFormat = "yaml") ) assertThat(output).isEqualTo("x: 1\n---\nx: 2\n---\nx: 3\n") } @Test fun `concatenate console outputs - some empty YAML streams`() { val sourceFiles = listOf( writePklFile( "test0.pkl", "output { value = List(); renderer = new YamlRenderer { isStream = true } }", ), writePklFile("test1.pkl", "x = 1"), writePklFile( "test2.pkl", "output { value = List(); renderer = new YamlRenderer { isStream = true } }", ), writePklFile("test3.pkl", "x = 3"), writePklFile( "test4.pkl", "output { value = List(); renderer = new YamlRenderer { isStream = true } }", ), ) val output = evalToConsole( CliEvaluatorOptions(CliBaseOptions(sourceModules = sourceFiles), outputFormat = "yaml") ) assertThat(output).isEqualTo("x: 1\n---\nx: 3\n") } // prototext can't render `Dynamic`. @EnumSource(names = ["TEXTPROTO"], mode = EnumSource.Mode.EXCLUDE) @ParameterizedTest(name = "{0} console output ends with newline") fun `console output ends with newline`(outputFormat: OutputFormat) { val sourceFiles = listOf(writePklFile("test0.pkl", "foo = 0\nbar=\"Baz\"")) val output = evalToConsole( CliEvaluatorOptions( CliBaseOptions(sourceModules = sourceFiles), outputFormat = outputFormat.toString(), ) ) assertThat(output).endsWith("\n") } @Test fun `file output throws if output file is a directory`() { val sourceFiles = listOf( writePklFile( "test.pkl", """ name = "test" output { files { ["\(name).txt"] { text = "test" } } } """ .trimIndent(), ) ) val err = assertThrows { evalToFiles( CliEvaluatorOptions( CliBaseOptions(sourceModules = sourceFiles), outputPath = tempDir.toString(), ) ) } assertThat(err) .hasMessageContaining("Output file `$tempDir` is a directory. ") .hasMessageContaining("Did you mean `--multiple-file-output-path`?") } @Test fun `multiple file output writes multiple files to the provided directory`() { val contents = """ output { files { ["foo.pcf"] { value = new Dynamic { ["bar"] = "baz" } } ["bar/baz.pcf"] { value = new Dynamic { ["baz"] = "biz" } } ["buz.txt"] { text = "buz" } } } """ .trimIndent() val sourceFile = writePklFile("test.pkl", contents) val options = CliEvaluatorOptions( CliBaseOptions(sourceModules = listOf(sourceFile), workingDir = tempDir), outputPath = "my-outputs", multipleFileOutputPath = ".my-output/", ) val evaluator = CliEvaluator(options) evaluator.run() checkOutputFile( tempDir.resolve(".my-output/foo.pcf"), "foo.pcf", """ ["bar"] = "baz" """ .trimIndent(), ) checkOutputFile( tempDir.resolve(".my-output/bar/baz.pcf"), "baz.pcf", """ ["baz"] = "biz" """ .trimIndent(), ) checkOutputFile(tempDir.resolve(".my-output/buz.txt"), "buz.txt", "buz") } @Test fun `multiple file output writes multiple modules to the output path`() { val sourceModules = listOf( writePklFile( "test0.pkl", """ output { files { ["foo.pcf"] { value = new Dynamic { ["bar"] = "baz" } } } } """ .trimIndent(), ), writePklFile( "test1.pkl", """ output { files { ["bar.pcf"] { value = new Dynamic { ["bar"] = "baz" } } } } """ .trimIndent(), ), ) val options = CliEvaluatorOptions( CliBaseOptions(sourceModules = sourceModules, workingDir = tempDir), multipleFileOutputPath = ".", ) CliEvaluator(options).run() assertThat(tempDir.resolve("foo.pcf")).isRegularFile.hasFileName("foo.pcf") assertThat(tempDir.resolve("bar.pcf")).isRegularFile.hasFileName("bar.pcf") } @Test fun `multiple file output throws in case of conflict`() { val sourceModules = listOf( writePklFile( "bar.pkl", """ output { files { ["foo.pcf"] { text = "myBar" } } } """ .trimIndent(), ), writePklFile( "foo.pkl", """ output { files { ["foo.pcf"] { text = "myFoo" } } } """ .trimIndent(), ), ) val options = CliEvaluatorOptions( CliBaseOptions(sourceModules = sourceModules, workingDir = tempDir), multipleFileOutputPath = ".", ) assertThrows { CliEvaluator(options).run() } } @Test fun `multiple file output writes nothing if output files is null`() { val moduleUri = writePklFile("test.pkl", "") val options = CliEvaluatorOptions( CliBaseOptions(sourceModules = listOf(moduleUri), workingDir = tempDir), multipleFileOutputPath = ".output", ) val output = evalToConsole(options) assertThat(output).isEqualTo("") assertThat(tempDir.listDirectoryEntries()).hasSize(1) } @Test fun `multiple file output throws if files are written outside the base path`() { val moduleUri = writePklFile( "test.pkl", """ output { files { ["../foo.txt"] { text = "bar" } } } """ .trimIndent(), ) val options = CliEvaluatorOptions( CliBaseOptions(sourceModules = listOf(moduleUri), workingDir = tempDir), multipleFileOutputPath = ".output", ) assertThatCode { evalToConsole(options) } .hasMessageStartingWith("Output file conflict:") .hasMessageContaining("which is outside output directory") } @Test fun `multiple file output throws if file path is a directory`() { tempDir.resolve(".output/myDir").createDirectories() val moduleUris = listOf( writePklFile( "test1.pkl", """ output { files { ["."] { text = "bar" } } } """ .trimIndent(), ), writePklFile( "test2.pkl", """ output { files { ["myDir"] { text = "bar" } } } """ .trimIndent(), ), ) for (moduleUri in moduleUris) { val options = CliEvaluatorOptions( CliBaseOptions(sourceModules = listOf(moduleUri), workingDir = tempDir), multipleFileOutputPath = ".output", ) assertThatCode { evalToConsole(options) } .hasMessageStartingWith("Output file conflict:") .hasMessageContaining("which is a directory") } } @Test fun `multiple file output throws on conflicting files`() { val moduleUris = listOf( writePklFile( "test1.pkl", """ output { files { ["foo.txt"] { text = "bar" } } } """ .trimIndent(), ), writePklFile( "test2.pkl", """ output { files { ["foo.txt"] { text = "bar" } } } """ .trimIndent(), ), ) val options = CliEvaluatorOptions( CliBaseOptions(sourceModules = moduleUris, workingDir = tempDir), multipleFileOutputPath = ".output", ) assertThatCode { evalToConsole(options) } .hasMessageContaining("Output file conflict:") .hasMessageContaining("resolve to the same file path") } @Test fun `multi-output throws on conflicting files within the same module`() { val moduleUri = writePklFile( "test.pkl", """ output { files { ["foo.txt"] { text = "bar" } ["./foo.txt"] { text = "bar" } } } """ .trimIndent(), ) val options = CliEvaluatorOptions( CliBaseOptions(sourceModules = listOf(moduleUri), workingDir = tempDir), multipleFileOutputPath = ".output", ) assertThatCode { evalToConsole(options) } .hasMessageContaining("Output file conflict:") .hasMessageContaining("resolve to the same file path") } @Test @EnabledOnOs(OS.WINDOWS) fun `multiple-file output throws when using invalid Windows characters`() { val moduleUri = writePklFile( "test.pkl", """ output { files { ["foo:bar"] { text = "bar" } } } """ .trimIndent(), ) val options = CliEvaluatorOptions( CliBaseOptions(sourceModules = listOf(moduleUri), workingDir = tempDir), multipleFileOutputPath = ".output", ) assertThatCode { evalToConsole(options) } .hasMessageContaining("Path spec `foo:bar` contains illegal character `:`.") } @Test @EnabledOnOs(OS.WINDOWS) fun `multiple-file output - cannot use backslash as dir separator on Windows`() { val moduleUri = writePklFile( "test.pkl", """ output { files { ["foo\\bar"] { text = "bar" } } } """ .trimIndent(), ) val options = CliEvaluatorOptions( CliBaseOptions(sourceModules = listOf(moduleUri), workingDir = tempDir), multipleFileOutputPath = ".output", ) assertThatCode { evalToConsole(options) } .hasMessageContaining("Path spec `foo\\bar` contains illegal character `\\`.") } @Test fun `evaluate output expression`() { val moduleUri = writePklFile( "test.pkl", """ foo { bar = 1 } """ .trimIndent(), ) val options = CliEvaluatorOptions( CliBaseOptions(sourceModules = listOf(moduleUri), workingDir = tempDir), expression = "foo", ) val buffer = ByteArrayOutputStream() CliEvaluator(options, outputStream = buffer).run() assertThat(buffer.toString(StandardCharsets.UTF_8)) .isEqualTo( """ new Dynamic { bar = 1 } """ .trimIndent() ) } @Test fun `evaluate output expression - custom toString()`() { val moduleUri = writePklFile( "test.pkl", """ class Person { name: String function toString() = "Person(\(name))" } person: Person = new { name = "Frodo" } """ .trimIndent(), ) val options = CliEvaluatorOptions( CliBaseOptions(sourceModules = listOf(moduleUri), workingDir = tempDir), expression = "person", ) val buffer = ByteArrayOutputStream() CliEvaluator(options, outputStream = buffer).run() assertThat(buffer.toString(StandardCharsets.UTF_8)).isEqualTo("Person(Frodo)") } @Test fun `evaluate output expression - nested structure`() { val moduleUri = writePklFile( "test.pkl", """ person { friend { name = "Bilbo" } } """ .trimIndent(), ) val options = CliEvaluatorOptions( CliBaseOptions(sourceModules = listOf(moduleUri), workingDir = tempDir), expression = "person", ) val buffer = ByteArrayOutputStream() CliEvaluator(options, outputStream = buffer).run() assertThat(buffer.toString(StandardCharsets.UTF_8)) .isEqualTo("new Dynamic { friend { name = \"Bilbo\" } }") } @Test fun `skip PklProject file`() { val moduleUri = writePklFile( "test.pkl", """ res = 1 """ .trimIndent(), ) writePklFile( "PklProject", """ amends "pkl:Project" package = throw("invalid project package") """ .trimIndent(), ) val options = CliEvaluatorOptions( CliBaseOptions(sourceModules = listOf(moduleUri), workingDir = tempDir, noProject = true) ) val buffer = ByteArrayOutputStream() CliEvaluator(options, outputStream = buffer).run() assertThat(buffer.toString(StandardCharsets.UTF_8)).isEqualTo("res = 1\n") } @Test fun `settings from PklProject file`() { val moduleUri = writePklFile( "test.pkl", """ res = read*("env:**") """ .trimIndent(), ) writePklFile( "PklProject", // language=Pkl """ amends "pkl:Project" evaluatorSettings { env { ["foo"] = "foo" ["bar"] = "bar" } } """ .trimIndent(), ) val options = CliEvaluatorOptions(CliBaseOptions(sourceModules = listOf(moduleUri), workingDir = tempDir)) val buffer = ByteArrayOutputStream() CliEvaluator(options, outputStream = buffer).run() assertThat(buffer.toString(StandardCharsets.UTF_8)) .isEqualTo( """ res { ["env:bar"] = "bar" ["env:foo"] = "foo" } """ .trimIndent() ) } @Test fun `noProxy settings from PklProject file`() { val moduleUri = writePklFile( "test.pkl", """ res = read("https://localhost:${packageServer.port}/birds@0.5.0").bytes.sha256 """ .trimIndent(), ) writePklFile( "PklProject", // language=Pkl """ amends "pkl:Project" evaluatorSettings { http { proxy { address = "http://example.example" noProxy { "localhost:${packageServer.port}" } } } } """ .trimIndent(), ) val options = CliEvaluatorOptions( CliBaseOptions( sourceModules = listOf(moduleUri), workingDir = tempDir, caCertificates = listOf(FileTestUtils.selfSignedCertificate), ) ) val buffer = ByteArrayOutputStream() CliEvaluator(options, outputStream = buffer).run() assertThat(buffer.toString(StandardCharsets.UTF_8)) .isEqualTo("res = \"b27206b80f4f227752b6f02143887f3ea41e554542cec38f7b572b987566c4de\"\n") } @Test fun `noProxy settings from settings file`() { val moduleUri = writePklFile( "test.pkl", """ res = read("https://localhost:${packageServer.port}/birds@0.5.0").bytes.sha256 """ .trimIndent(), ) val settingsFile = writePklFile( "settings.pkl", // language=Pkl """ amends "pkl:settings" http { proxy { address = "http://example.example" noProxy { "localhost:${packageServer.port}" } } } """ .trimIndent(), ) val options = CliEvaluatorOptions( CliBaseOptions( sourceModules = listOf(moduleUri), workingDir = tempDir, caCertificates = listOf(FileTestUtils.selfSignedCertificate), settings = settingsFile, ) ) val buffer = ByteArrayOutputStream() CliEvaluator(options, outputStream = buffer).run() assertThat(buffer.toString(StandardCharsets.UTF_8)) .isEqualTo("res = \"b27206b80f4f227752b6f02143887f3ea41e554542cec38f7b572b987566c4de\"\n") } @Test fun `setting noCache will skip writing to the cache dir`() { val moduleUri = writePklFile( "test.pkl", """ import "package://localhost:0/birds@0.5.0#/catalog/Swallow.pkl" res = Swallow """ .trimIndent(), ) val buffer = ByteArrayOutputStream() val options = CliEvaluatorOptions( CliBaseOptions( sourceModules = listOf(moduleUri), workingDir = tempDir, moduleCacheDir = tempDir, noCache = true, caCertificates = listOf(FileTestUtils.selfSignedCertificate), testPort = packageServer.port, ) ) CliEvaluator(options, outputStream = buffer).run() assertThat(buffer.toString(StandardCharsets.UTF_8)) .isEqualTo( """ res { name = "Swallow" favoriteFruit { name = "Apple" } } """ .trimIndent() ) assertThat(tempDir.resolve("package-2")).doesNotExist() } @Test fun `gives decent error message if certificate file contains random text`() { val certsFile = tempDir.writeFile("random.pem", "RANDOM") val err = assertThrows { evalModuleThatImportsPackage(certsFile) } assertThat(err) .hasMessageContaining("Error parsing CA certificate file `${certsFile.pathString}`:") .hasMessageContaining("No certificate data found") .hasMessageNotContainingAny("java.", "sun.") // class names have been filtered out } @Test fun `gives decent error message if certificate file is empty`(@TempDir tempDir: Path) { val emptyCerts = tempDir.writeEmptyFile("empty.pem") val err = assertThrows { evalModuleThatImportsPackage(emptyCerts) } assertThat(err).hasMessageContaining("CA certificate file `${emptyCerts.pathString}` is empty.") } @Test fun `gives decent error message if certificate cannot be parsed`(@TempDir tempDir: Path) { val invalidCerts = FileTestUtils.writeCertificateWithMissingLines(tempDir) val err = assertThrows { evalModuleThatImportsPackage(invalidCerts) } assertThat(err) // no assert for detail message because it differs between JDK implementations .hasMessageContaining("Error parsing CA certificate file `${invalidCerts.pathString}`:") .hasMessageNotContainingAny("java.", "sun.") // class names have been filtered out } @Test fun `gives decent error message if CLI doesn't have the required CA certificate`() { val err = assertThrows { evalModuleThatImportsPackage(null, packageServer.port) } assertThat(err) .hasMessageContaining("Error during SSL handshake with host `localhost`:") .hasMessageContaining("unable to find valid certification path to requested target") .hasMessageNotContainingAny("java.", "sun.") // class names have been filtered out } @Test fun `eval http module from proxy`(wwRuntimeInfo: WireMockRuntimeInfo) { stubFor( get(urlEqualTo("/bar.pkl")).withHost(equalTo("not.a.valid.host")).willReturn(ok("foo = 1")) ) val options = CliEvaluatorOptions( CliBaseOptions( sourceModules = listOf(URI("http://not.a.valid.host/bar.pkl")), httpProxy = URI("http://localhost:${wwRuntimeInfo.httpPort}"), allowedModules = SecurityManagers.defaultAllowedModules + Pattern.compile("http:"), ) ) val output = evalToConsole(options) assertThat(output).isEqualTo("foo = 1\n") } @Test fun `eval https -- no proxy`(wwRuntimeInfo: WireMockRuntimeInfo) { // pick an address on the local machine so we can be sure this test is not making any outbound // connections. val openPort = ServerSocket(0).use { it.localPort } val targetAddress = "https://127.0.0.1:$openPort" val options = CliEvaluatorOptions( CliBaseOptions( // use loopback address to prevent test from making outbound http connection. sourceModules = listOf(URI("$targetAddress/foo.pkl")), httpProxy = URI(wwRuntimeInfo.httpBaseUrl), httpNoProxy = listOf("*"), allowedModules = SecurityManagers.defaultAllowedModules + Pattern.compile("http:"), ) ) assertThatCode { evalToConsole(options) } .hasMessageContaining("I/O error loading module `$targetAddress/foo.pkl`") } @Test @Disabled // TODO: figure out why this is failing. fun `eval package from proxy`(wwRuntimeInfo: WireMockRuntimeInfo) { stubFor( any(anyUrl()).willReturn(aResponse().proxiedFrom("https://localhost:${packageServer.port}")) ) val options = CliEvaluatorOptions( CliBaseOptions( sourceModules = listOf(URI("package://localhost:1/birds@0.5.0#/catalog/Ostrich.pkl")), noCache = true, httpProxy = URI(wwRuntimeInfo.httpBaseUrl), caCertificates = listOf(FileTestUtils.selfSignedCertificate), allowedModules = SecurityManagers.defaultAllowedModules + Pattern.compile("http:"), ) ) val output = evalToConsole(options) assertThat(output) .isEqualTo( """ name = "Ostrich" favoriteFruit { name = "Orange" } """ .trimIndent() ) verify(getRequestedFor(urlEqualTo("birds@0.5.0"))) verify(getRequestedFor(urlEqualTo("fruit@1.0.5"))) } @Test fun `eval http module from proxy -- configured in settings`( @TempDir tempDir: Path, wwRuntimeInfo: WireMockRuntimeInfo, ) { val settingsModule = tempDir.writeFile( "settings.pkl", """ amends "pkl:settings" http { proxy { address = "${wwRuntimeInfo.httpBaseUrl}" } } """ .trimIndent(), ) stubFor( get(urlEqualTo("/bar.pkl")).withHost(equalTo("not.a.valid.host")).willReturn(ok("foo = 1")) ) val options = CliEvaluatorOptions( CliBaseOptions( sourceModules = listOf(URI("http://not.a.valid.host/bar.pkl")), settings = settingsModule.toUri(), allowedModules = SecurityManagers.defaultAllowedModules + Pattern.compile("http:"), ) ) val output = evalToConsole(options) assertThat(output).isEqualTo("foo = 1\n") } @Test fun `eval http module from proxy -- configured in PklProject`( @TempDir tempDir: Path, wwRuntimeInfo: WireMockRuntimeInfo, ) { tempDir.writeFile( "PklProject", """ amends "pkl:Project" evaluatorSettings { http { proxy { address = "${wwRuntimeInfo.httpBaseUrl}" } } } """ .trimIndent(), ) stubFor( get(urlEqualTo("/bar.pkl")).withHost(equalTo("not.a.valid.host")).willReturn(ok("foo = 1")) ) val options = CliEvaluatorOptions( CliBaseOptions( sourceModules = listOf(URI("http://not.a.valid.host/bar.pkl")), allowedModules = SecurityManagers.defaultAllowedModules + Pattern.compile("http:"), projectDir = tempDir, ) ) val output = evalToConsole(options) assertThat(output).isEqualTo("foo = 1\n") } @Test fun `eval http module from proxy -- PklProject beats user settings`( @TempDir tempDir: Path, wwRuntimeInfo: WireMockRuntimeInfo, ) { val projectDir = tempDir.resolve("my-project") projectDir.writeFile( "PklProject", """ amends "pkl:Project" evaluatorSettings { http { proxy { address = "${wwRuntimeInfo.httpBaseUrl}" } } } """ .trimIndent(), ) val homeDir = tempDir.resolve("my-home") homeDir.writeFile( "settings.pkl", """ amends "pkl:settings" http { proxy { address = "http://invalid.proxy.address" } } """ .trimIndent(), ) val options = CliEvaluatorOptions( CliBaseOptions( sourceModules = listOf(URI("http://not.a.valid.host/bar.pkl")), allowedModules = SecurityManagers.defaultAllowedModules + Pattern.compile("http:"), projectDir = projectDir, settings = homeDir.resolve("settings.pkl").toUri(), ) ) stubFor(get(anyUrl()).willReturn(ok("result = 1"))) val output = evalToConsole(options) assertThat(output).isEqualTo("result = 1\n") } @Test fun `eval file with non-ASCII name`() { val tempDirUri = tempDir.toUri() val dir = tempDir.resolve("🤬").createDirectory() val file = writePklFile( dir.resolve("日本語.pkl").toString(), """ 日本語 = "Japanese language" readDir = read(".").text readDirFile = read("$tempDirUri🤬").text readOne = read("日本語.pkl").text.split("\n").first readOneFile = read("$tempDirUri🤬/日本語.pkl").text.split("\n").first readGlob = read*("./日*.pkl").keys readGlobFile = read*("$tempDirUri**/*.pkl").keys.map((it) -> it.replaceAll("$tempDirUri".replaceAll("///", "/"), "")) importOne = import("日本語.pkl").readOne importOneFile = import("$tempDirUri🤬/日本語.pkl").日本語 importGlob = import*("./日*.pkl").keys importGlobFile = import*("$tempDirUri**/*.pkl").keys.map((it) -> it.replaceAll("$tempDirUri".replaceAll("///", "/"), "")) """ .trimIndent(), ) val output = evalToConsole(CliEvaluatorOptions(CliBaseOptions(sourceModules = listOf(file)))) val tripleQuote = "\"\"\"" assertThat(output) .isEqualTo( """ 日本語 = "Japanese language" readDir = $tripleQuote 日本語.pkl $tripleQuote readDirFile = $tripleQuote 日本語.pkl $tripleQuote readOne = "日本語 = \"Japanese language\"" readOneFile = "日本語 = \"Japanese language\"" readGlob = Set("./日本語.pkl") readGlobFile = Set("🤬/日本語.pkl") importOne = "日本語 = \"Japanese language\"" importOneFile = "Japanese language" importGlob = Set("./日本語.pkl") importGlobFile = Set("🤬/日本語.pkl") """ .trimIndent() ) } @Test @DisabledOnOs(OS.WINDOWS) fun `multiple file output works with symlinked output directory`() { val realOutputDir = tempDir.resolve("real-output").createDirectories() val symlinkOutputDir = Files.createSymbolicLink(tempDir.resolve("symlink-output"), realOutputDir) val sourceFile = writePklFile( "test.pkl", """ pigeon { name = "Pigeon" diet = "Seeds" } parrot { name = "Parrot" diet = "Seeds" } output { files { ["pigeon.json"] { value = pigeon renderer = new JsonRenderer {} } ["birds/parrot.yaml"] { value = parrot renderer = new YamlRenderer {} } } } """ .trimIndent(), ) val options = CliEvaluatorOptions( CliBaseOptions(sourceModules = listOf(sourceFile), workingDir = tempDir), multipleFileOutputPath = symlinkOutputDir.toString(), ) CliEvaluator(options).run() checkOutputFile( realOutputDir.resolve("pigeon.json"), "pigeon.json", """ { "name": "Pigeon", "diet": "Seeds" } """ .trimIndent(), ) checkOutputFile( realOutputDir.resolve("birds/parrot.yaml"), "parrot.yaml", """ name: Parrot diet: Seeds """ .trimIndent(), ) checkOutputFile( symlinkOutputDir.resolve("pigeon.json"), "pigeon.json", """ { "name": "Pigeon", "diet": "Seeds" } """ .trimIndent(), ) checkOutputFile( symlinkOutputDir.resolve("birds/parrot.yaml"), "parrot.yaml", """ name: Parrot diet: Seeds """ .trimIndent(), ) } private fun evalModuleThatImportsPackage(certsFile: Path?, testPort: Int = -1) { val moduleUri = writePklFile( "test.pkl", """ import "package://localhost:0/birds@0.5.0#/catalog/Swallow.pkl" res = Swallow """, ) val options = CliEvaluatorOptions( CliBaseOptions( sourceModules = listOf(moduleUri), caCertificates = buildList { if (certsFile != null) add(certsFile) }, workingDir = tempDir, noCache = true, testPort = testPort, ) ) CliEvaluator(options).run() } private fun writePklFile(fileName: String, contents: String = defaultContents): URI { tempDir.resolve(fileName).createParentDirectories() return tempDir.resolve(fileName).writeString(contents).toUri() } private fun evalToFiles(options: CliEvaluatorOptions): List { val evaluator = CliEvaluator( options.copy( outputPath = options.outputPath ?: "%{moduleDir}/%{moduleName}.%{outputFormat}" ) ) evaluator.run() return evaluator.fileOutputPaths!!.values.toList() } private fun evalToConsole(options: CliEvaluatorOptions): String { val reader = ByteArrayInputStream(byteArrayOf()) val writer = ByteArrayOutputStream() CliEvaluator(options, reader, writer).run() return writer.toString(StandardCharsets.UTF_8) } private fun checkOutputFile(file: Path, name: String, contents: String) { assertThat(file).isRegularFile.hasFileName(name) assertThat(file.readString().trim()).isEqualTo(contents.trim()) } } ================================================ FILE: pkl-cli/src/test/kotlin/org/pkl/cli/CliFormatterTest.kt ================================================ /* * Copyright © 2025-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.cli import java.nio.file.Path import kotlin.io.path.writeText import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.io.TempDir import org.pkl.commons.cli.CliBugException import org.pkl.commons.cli.CliException import org.pkl.core.util.StringBuilderWriter import org.pkl.formatter.GrammarVersion class CliFormatterTest { @Test fun `no double newline when writing to stdout`(@TempDir tempDir: Path) { val file = tempDir.resolve("foo.pkl").also { it.writeText("foo = 1") } val sb = StringBuilder() val writer = StringBuilderWriter(sb) val cmd = CliFormatterCommand( listOf(file), GrammarVersion.latest(), overwrite = false, diffNameOnly = false, silent = false, consoleWriter = writer, ) try { cmd.run() } catch (_: CliException) {} assertThat(sb.toString()).isEqualTo("foo = 1\n") } @Test fun `parse errors do not result in bug exceptions`(@TempDir tempDir: Path) { val file = tempDir.resolve("foo.pkl").also { it.writeText("foo = \"/foo/\\\${BAR}/baz\"") } val sb = StringBuilder() val writer = StringBuilderWriter(sb) val cmd = CliFormatterCommand( listOf(file), GrammarVersion.latest(), overwrite = false, diffNameOnly = false, silent = false, consoleWriter = writer, ) val exc = assertThrows { cmd.run() } assertThat(exc).isNotInstanceOf(CliBugException::class.java) } } ================================================ FILE: pkl-cli/src/test/kotlin/org/pkl/cli/CliImportAnalyzerTest.kt ================================================ /* * Copyright © 2024-2025 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.cli import java.net.URI import java.nio.file.Path import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatCode import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir import org.pkl.commons.cli.CliBaseOptions import org.pkl.commons.writeString import org.pkl.core.OutputFormat import org.pkl.core.util.StringBuilderWriter class CliImportAnalyzerTest { @Test fun `write to console writer`(@TempDir tempDir: Path) { val file = tempDir.resolve("test.pkl").writeString("import \"bar.pkl\"") val otherFile = tempDir.resolve("bar.pkl").writeString("") val baseOptions = CliBaseOptions(sourceModules = listOf(file.toUri())) val sb = StringBuilder() val analyzer = CliImportAnalyzer(CliImportAnalyzerOptions(baseOptions), StringBuilderWriter(sb)) analyzer.run() assertThat(sb.toString()) .isEqualTo( """ imports { ["${otherFile.toUri()}"] {} ["${file.toUri()}"] { new { uri = "${otherFile.toUri()}" } } } resolvedImports { ["${otherFile.toUri()}"] = "${otherFile.toRealPath().toUri()}" ["${file.toUri()}"] = "${file.toRealPath().toUri()}" } """ .trimIndent() ) } @Test fun `different output format`(@TempDir tempDir: Path) { val file = tempDir.resolve("test.pkl").writeString("import \"bar.pkl\"") val otherFile = tempDir.resolve("bar.pkl").writeString("") val baseOptions = CliBaseOptions(sourceModules = listOf(file.toUri())) val sb = StringBuilder() val analyzer = CliImportAnalyzer( CliImportAnalyzerOptions(baseOptions, outputFormat = OutputFormat.JSON.toString()), StringBuilderWriter(sb), ) analyzer.run() assertThat(sb.toString()) .isEqualTo( """ { "imports": { "${otherFile.toUri()}": [], "${file.toUri()}": [ { "uri": "${otherFile.toUri()}" } ] }, "resolvedImports": { "${otherFile.toUri()}": "${otherFile.toRealPath().toUri()}", "${file.toUri()}": "${file.toRealPath().toUri()}" } } """ .trimIndent() ) } @Test fun `write to output file`(@TempDir tempDir: Path) { val file = tempDir.resolve("test.pkl").writeString("import \"bar.pkl\"") val otherFile = tempDir.resolve("bar.pkl").writeString("") val outputPath = tempDir.resolve("imports.pcf") val baseOptions = CliBaseOptions(sourceModules = listOf(file.toUri())) val analyzer = CliImportAnalyzer(CliImportAnalyzerOptions(baseOptions, outputPath = outputPath)) analyzer.run() assertThat(outputPath) .hasContent( """ imports { ["${otherFile.toUri()}"] {} ["${file.toUri()}"] { new { uri = "${otherFile.toUri()}" } } } resolvedImports { ["${otherFile.toUri()}"] = "${otherFile.toRealPath().toUri()}" ["${file.toUri()}"] = "${file.toRealPath().toUri()}" } """ .trimIndent() ) } @Test fun `invalid syntax in module`(@TempDir tempDir: Path) { val file = tempDir.resolve("test.pkl").writeString("foo = bar(]") assertThatCode { CliImportAnalyzer( CliImportAnalyzerOptions( CliBaseOptions(sourceModules = listOf(file.toUri()), settings = URI("pkl:settings")) ) ) .run() } .hasMessageContaining( """ –– Pkl Error –– Found a syntax error when parsing module `${file.toUri()}`. """ .trimIndent() ) } } ================================================ FILE: pkl-cli/src/test/kotlin/org/pkl/cli/CliMainTest.kt ================================================ /* * Copyright © 2024-2025 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.cli import com.github.ajalt.clikt.core.BadParameterValue import com.github.ajalt.clikt.core.CliktError import com.github.ajalt.clikt.core.parse import java.nio.file.Path import kotlin.io.path.createDirectory import kotlin.io.path.createSymbolicLinkPointingTo import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.AssertionsForClassTypes.assertThatCode import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.condition.DisabledOnOs import org.junit.jupiter.api.condition.OS import org.junit.jupiter.api.io.TempDir import org.pkl.cli.commands.EvalCommand import org.pkl.cli.commands.RootCommand import org.pkl.commons.writeString import org.pkl.core.Release class CliMainTest { private val rootCmd = RootCommand() @Test fun `duplicate CLI option produces meaningful error message`(@TempDir tempDir: Path) { val inputFile = tempDir.resolve("test.pkl").writeString("").toString() assertThatCode { rootCmd.parse( arrayOf("eval", "--output-path", "path1", "--output-path", "path2", inputFile) ) } .hasMessage("Option cannot be repeated") assertThatCode { rootCmd.parse(arrayOf("eval", "-o", "path1", "--output-path", "path2", inputFile)) } .hasMessage("Option cannot be repeated") } @Test fun `eval requires at least one file`() { assertThatCode { rootCmd.parse(arrayOf("eval")) } .isInstanceOf(CliktError::class.java) .extracting("paramName") .isEqualTo("modules") } // Can't reliably create symlinks on Windows. // Might get errors like "A required privilege is not held by the client". @Test @DisabledOnOs(OS.WINDOWS) fun `output to symlinked directory works`(@TempDir tempDir: Path) { val code = """ x = 3 output { value = x renderer = new JsonRenderer {} } """ .trimIndent() val inputFile = tempDir.resolve("test.pkl").writeString(code).toString() val outputFile = makeSymdir(tempDir, "out", "linkOut").resolve("test.pkl").toString() assertThatCode { rootCmd.parse(arrayOf("eval", inputFile, "-o", outputFile)) } .doesNotThrowAnyException() } @Test fun `cannot have multiple output with -o or -x`(@TempDir tempDir: Path) { val testIn = makeInput(tempDir) val testOut = tempDir.resolve("test").toString() val error = """Option is mutually exclusive with -o, --output-path and -x, --expression.""" assertThatCode { rootCmd.parse(arrayOf("eval", "-m", testOut, "-x", "x", testIn)) } .hasMessage(error) assertThatCode { rootCmd.parse(arrayOf("eval", "-m", testOut, "-o", "/tmp/test", testIn)) } .hasMessage(error) } @Test fun `showing version works`() { assertThatCode { rootCmd.parse(arrayOf("--version")) }.hasMessage(Release.current().versionInfo) } @Test fun `file paths get parsed into URIs`(@TempDir tempDir: Path) { val cmd = RootCommand() cmd.parse(arrayOf("eval", makeInput(tempDir, "my file.txt"))) val evalCmd = cmd.registeredSubcommands().filterIsInstance().first() val modules = evalCmd.baseOptions.baseOptions(evalCmd.modules).normalizedSourceModules assertThat(modules).hasSize(1) assertThat(modules[0].path).endsWith("my file.txt") } @Test fun `invalid URIs are not accepted`() { val ex = assertThrows { rootCmd.parse(arrayOf("eval", "file:my file.txt")) } assertThat(ex.message).contains("URI `file:my file.txt` has invalid syntax") } @Test fun `invalid rewrites -- non-HTTP URI`() { val ex = assertThrows { rootCmd.parse(arrayOf("eval", "--http-rewrite", "foo=bar", "mymodule.pkl")) } assertThat(ex.message) .contains("Rewrite rule must start with 'http://' or 'https://', but was 'foo'") } @Test fun `invalid rewrites -- invalid URI`() { val ex = assertThrows { rootCmd.parse(arrayOf("eval", "--http-rewrite", "https://foo bar=baz", "mymodule.pkl")) } assertThat(ex.message).contains("Rewrite target `https://foo bar` has invalid syntax") } @Test fun `invalid rewrites -- doesn't end with slash`() { val ex = assertThrows { rootCmd.parse( arrayOf("eval", "--http-rewrite", "http://foo.com=https://bar.com", "mymodule.pkl") ) } assertThat(ex.message).contains("Rewrite rule must end with '/', but was 'http://foo.com'") } @Test fun `missing --http-no-proxy flag is null`(@TempDir tempDir: Path) { val inputFile = tempDir.resolve("test.pkl").writeString("").toString() val command = EvalCommand() command.parse(arrayOf(inputFile)) assertThat(command.baseOptions.noProxy).isNull() } private fun makeInput(tempDir: Path, fileName: String = "test.pkl"): String { val code = "x = 1" return tempDir.resolve(fileName).writeString(code).toString() } private fun makeSymdir(baseDir: Path, name: String, linkName: String): Path { val dir = baseDir.resolve(name) val link = baseDir.resolve(linkName) dir.createDirectory() link.createSymbolicLinkPointingTo(dir) return link } } ================================================ FILE: pkl-cli/src/test/kotlin/org/pkl/cli/CliPackageDownloaderTest.kt ================================================ /* * Copyright © 2024-2025 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.cli import java.nio.file.Path import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatCode import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir import org.pkl.commons.cli.CliBaseOptions import org.pkl.commons.test.FileTestUtils import org.pkl.commons.test.PackageServer import org.pkl.core.packages.PackageUri class CliPackageDownloaderTest { companion object { private val server = PackageServer() @AfterAll @JvmStatic fun afterAll() { server.close() } } @Test fun `download packages`(@TempDir tempDir: Path) { val cmd = CliPackageDownloader( baseOptions = CliBaseOptions( moduleCacheDir = tempDir, caCertificates = listOf(FileTestUtils.selfSignedCertificate), testPort = server.port, ), packageUris = listOf( PackageUri("package://localhost:0/birds@0.5.0"), PackageUri("package://localhost:0/fruit@1.0.5"), PackageUri("package://localhost:0/fruit@1.1.0"), ), noTransitive = true, ) cmd.run() assertThat(tempDir.resolve("package-2/localhost(3a)0/birds@0.5.0/birds@0.5.0.zip")).exists() assertThat(tempDir.resolve("package-2/localhost(3a)0/birds@0.5.0/birds@0.5.0.json")).exists() assertThat(tempDir.resolve("package-2/localhost(3a)0/fruit@1.0.5/fruit@1.0.5.zip")).exists() assertThat(tempDir.resolve("package-2/localhost(3a)0/fruit@1.0.5/fruit@1.0.5.json")).exists() assertThat(tempDir.resolve("package-2/localhost(3a)0/fruit@1.1.0/fruit@1.1.0.zip")).exists() assertThat(tempDir.resolve("package-2/localhost(3a)0/fruit@1.1.0/fruit@1.1.0.json")).exists() } @Test fun `download packages with cache dir set by project`(@TempDir tempDir: Path) { tempDir.writeFile( "PklProject", """ amends "pkl:Project" evaluatorSettings { moduleCacheDir = ".my-cache" } """ .trimIndent(), ) val cmd = CliPackageDownloader( baseOptions = CliBaseOptions( workingDir = tempDir, caCertificates = listOf(FileTestUtils.selfSignedCertificate), testPort = server.port, ), packageUris = listOf(PackageUri("package://localhost:0/birds@0.5.0")), noTransitive = true, ) cmd.run() assertThat(tempDir.resolve(".my-cache/package-2/localhost(3a)0/birds@0.5.0/birds@0.5.0.zip")) .exists() assertThat(tempDir.resolve(".my-cache/package-2/localhost(3a)0/birds@0.5.0/birds@0.5.0.json")) .exists() } @Test fun `download package while specifying checksum`(@TempDir tempDir: Path) { val cmd = CliPackageDownloader( baseOptions = CliBaseOptions( moduleCacheDir = tempDir, caCertificates = listOf(FileTestUtils.selfSignedCertificate), testPort = server.port, ), packageUris = listOf( PackageUri("package://localhost:0/birds@0.5.0::sha256:${PackageServer.BIRDS_SHA}") ), noTransitive = true, ) cmd.run() assertThat(tempDir.resolve("package-2/localhost(3a)0/birds@0.5.0/birds@0.5.0.zip")).exists() assertThat(tempDir.resolve("package-2/localhost(3a)0/birds@0.5.0/birds@0.5.0.json")).exists() } @Test fun `download package with invalid checksum`(@TempDir tempDir: Path) { val cmd = CliPackageDownloader( baseOptions = CliBaseOptions( moduleCacheDir = tempDir, caCertificates = listOf(FileTestUtils.selfSignedCertificate), testPort = server.port, ), packageUris = listOf( PackageUri("package://localhost:0/birds@0.5.0::sha256:intentionallyBogusChecksum") ), noTransitive = true, ) assertThatCode { cmd.run() } .hasMessage( """ Cannot download package `package://localhost:0/birds@0.5.0` because the computed checksum for package metadata does not match the expected checksum. Computed checksum: "${PackageServer.BIRDS_SHA}" Expected checksum: "intentionallyBogusChecksum" Asset URL: "https://localhost:0/birds@0.5.0" """ .trimIndent() ) } @Test fun `disabling caching is an error`(@TempDir tempDir: Path) { val cmd = CliPackageDownloader( baseOptions = CliBaseOptions(workingDir = tempDir, noCache = true), packageUris = listOf(PackageUri("package://localhost:0/birds@0.5.0")), noTransitive = true, ) assertThatCode { cmd.run() } .hasMessage("Cannot download packages because no cache directory is specified.") } @Test fun `download packages with bad checksum`(@TempDir tempDir: Path) { val cmd = CliPackageDownloader( baseOptions = CliBaseOptions( moduleCacheDir = tempDir, caCertificates = listOf(FileTestUtils.selfSignedCertificate), testPort = server.port, ), packageUris = listOf(PackageUri("package://localhost:0/badChecksum@1.0.0")), noTransitive = true, ) assertThatCode { cmd.run() } .hasMessageStartingWith( "Cannot download package `package://localhost:0/badChecksum@1.0.0` because the computed checksum does not match the expected checksum." ) } @Test fun `download multiple failing packages`(@TempDir tempDir: Path) { val cmd = CliPackageDownloader( baseOptions = CliBaseOptions( moduleCacheDir = tempDir, caCertificates = listOf(FileTestUtils.selfSignedCertificate), testPort = server.port, ), packageUris = listOf( PackageUri("package://localhost:0/badChecksum@1.0.0"), PackageUri("package://bogus.domain/notAPackage@1.0.0"), ), noTransitive = true, ) assertThatCode { cmd.run() } .hasMessage( """ Failed to download some packages. Failed to download package://localhost:0/badChecksum@1.0.0 because: Cannot download package `package://localhost:0/badChecksum@1.0.0` because the computed checksum does not match the expected checksum. Computed checksum: "a6bf858cdd1c09da475c2abe50525902580910ee5cc1ff624999170591bf8f69" Expected checksum: "intentionally bogus checksum" Asset URL: "https://localhost:0/badChecksum@1.0.0/badChecksum@1.0.0.zip" Failed to download package://bogus.domain/notAPackage@1.0.0 because: Exception when making request `GET https://bogus.domain/notAPackage@1.0.0`: Error connecting to host `bogus.domain`. """ .trimIndent() ) } @Test fun `download package, including transitive dependencies`(@TempDir tempDir: Path) { CliPackageDownloader( baseOptions = CliBaseOptions( moduleCacheDir = tempDir, caCertificates = listOf(FileTestUtils.selfSignedCertificate), testPort = server.port, ), packageUris = listOf(PackageUri("package://localhost:0/birds@0.5.0")), noTransitive = false, ) .run() assertThat(tempDir.resolve("package-2/localhost(3a)0/birds@0.5.0/birds@0.5.0.zip")).exists() assertThat(tempDir.resolve("package-2/localhost(3a)0/birds@0.5.0/birds@0.5.0.json")).exists() assertThat(tempDir.resolve("package-2/localhost(3a)0/fruit@1.0.5/fruit@1.0.5.zip")).exists() assertThat(tempDir.resolve("package-2/localhost(3a)0/fruit@1.0.5/fruit@1.0.5.json")).exists() } } ================================================ FILE: pkl-cli/src/test/kotlin/org/pkl/cli/CliProjectPackagerTest.kt ================================================ /* * Copyright © 2024-2025 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.cli import java.io.File import java.io.StringWriter import java.net.URI import java.nio.file.FileSystems import java.nio.file.Files import java.nio.file.Path import java.util.stream.Collectors import kotlin.io.path.createDirectories import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatCode import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.condition.DisabledOnOs import org.junit.jupiter.api.condition.OS import org.junit.jupiter.api.io.TempDir import org.pkl.commons.cli.CliBaseOptions import org.pkl.commons.cli.CliException import org.pkl.commons.cli.CliTestOptions import org.pkl.commons.readString import org.pkl.commons.test.FileTestUtils import org.pkl.commons.test.PackageServer import org.pkl.commons.writeString import org.pkl.core.util.IoUtils class CliProjectPackagerTest { companion object { private val packageServer = PackageServer() @AfterAll @JvmStatic fun afterAll() { packageServer.close() } } @Test fun `missing PklProject when inferring a project dir`(@TempDir tempDir: Path) { val packager = CliProjectPackager( CliBaseOptions(workingDir = tempDir), listOf(), CliTestOptions(), ".out/%{name}@%{version}", skipPublishCheck = true, ) val err = assertThrows { packager.run() } assertThat(err).hasMessageStartingWith("No project visible to the working directory.") } @Test fun `missing PklProject when explicit dir is provided`(@TempDir tempDir: Path) { val packager = CliProjectPackager( CliBaseOptions(workingDir = tempDir), listOf(tempDir), CliTestOptions(), ".out/%{name}@%{version}", skipPublishCheck = true, ) val err = assertThrows { packager.run() } assertThat(err).hasMessageStartingWith("Directory $tempDir does not contain a PklProject file.") } @Test fun `PklProject missing package section`(@TempDir tempDir: Path) { tempDir .resolve("PklProject") .writeString( """ amends "pkl:Project" """ .trimIndent() ) val packager = CliProjectPackager( CliBaseOptions(workingDir = tempDir), listOf(tempDir), CliTestOptions(), ".out/%{name}@%{version}", skipPublishCheck = true, ) val err = assertThrows { packager.run() } assertThat(err) .hasMessageStartingWith("No package was declared in project `${tempDir.toUri()}PklProject`.") } @Test fun `failing apiTests`(@TempDir tempDir: Path) { tempDir.writeFile( "myTest.pkl", """ amends "pkl:test" facts { ["1 == 2"] { 1 == 2 } } """ .trimIndent(), ) tempDir.writeFile( "PklProject", """ amends "pkl:Project" package { name = "mypackage" version = "1.0.0" baseUri = "package://example.com/mypackage" packageZipUrl = "https://foo.com" apiTests { "myTest.pkl" } } """ .trimIndent(), ) val buffer = StringWriter() val packager = CliProjectPackager( CliBaseOptions(workingDir = tempDir), listOf(tempDir), CliTestOptions(), ".out/%{name}@%{version}", skipPublishCheck = true, consoleWriter = buffer, ) val err = assertThrows { packager.run() } assertThat(err).hasMessageContaining("because its API tests are failing") assertThat(buffer.toString()).contains("1 == 2") } @Test fun `passing apiTests`(@TempDir tempDir: Path) { tempDir .resolve("myTest.pkl") .writeString( """ amends "pkl:test" facts { ["1 == 1"] { 1 == 1 } } """ .trimIndent() ) tempDir .resolve("PklProject") .writeString( """ amends "pkl:Project" package { name = "mypackage" version = "1.0.0" baseUri = "package://example.com/mypackage" packageZipUrl = "https://foo.com" apiTests { "myTest.pkl" } } """ .trimIndent() ) val buffer = StringWriter() val packager = CliProjectPackager( CliBaseOptions(workingDir = tempDir), listOf(tempDir), CliTestOptions(), ".out/%{name}@%{version}", skipPublishCheck = true, consoleWriter = buffer, ) packager.run() } @Test fun `apiTests that import dependencies`(@TempDir tempDir: Path) { val cacheDir = tempDir.resolve("cache") val projectDir = tempDir.resolve("myProject").createDirectories() PackageServer.populateCacheDir(cacheDir) projectDir .resolve("myTest.pkl") .writeString( """ amends "pkl:test" import "@birds/Bird.pkl" examples { ["Bird"] { new Bird { name = "Finch"; favoriteFruit { name = "Tangerine" } } } } """ .trimIndent() ) projectDir .resolve("myTest.pkl-expected.pcf") .writeString( """ examples { ["Bird"] { new { name = "Finch" favoriteFruit { name = "Tangerine" } } } } """ .trimIndent() ) projectDir .resolve("PklProject") .writeString( """ amends "pkl:Project" package { name = "mypackage" version = "1.0.0" baseUri = "package://example.com/mypackage" packageZipUrl = "https://foo.com" apiTests { "myTest.pkl" } } dependencies { ["birds"] { uri = "package://localhost:0/birds@0.5.0" } } """ .trimIndent() ) projectDir .resolve("PklProject.deps.json") .writeString( """ { "schemaVersion": 1, "resolvedDependencies": { "package://localhost:0/birds@0": { "type": "remote", "uri": "projectpackage://localhost:0/birds@0.5.0", "checksums": { "sha256": "04eec465b217fb9779489525d26e9b587e5e47ff4d584c7673a450109715bc31" } }, "package://localhost:0/fruit@1": { "type": "remote", "uri": "projectpackage://localhost:0/fruit@1.0.5", "checksums": { "sha256": "${PackageServer.FRUIT_SHA}" } } } } """ .trimIndent() ) val buffer = StringWriter() val packager = CliProjectPackager( CliBaseOptions(workingDir = projectDir, moduleCacheDir = cacheDir), listOf(projectDir), CliTestOptions(), ".out", skipPublishCheck = true, consoleWriter = buffer, ) packager.run() } @Test fun `generate package`(@TempDir tempDir: Path) { val fooPkl = tempDir.writeFile( "a/b/foo.pkl", """ module foo name: String """ .trimIndent(), ) val fooTxt = tempDir.writeFile( "c/d/foo.txt", """ foo bar baz """ .trimIndent(), ) tempDir .resolve("PklProject") .writeString( """ amends "pkl:Project" package { name = "mypackage" version = "1.0.0" baseUri = "package://example.com/mypackage" packageZipUrl = "https://foo.com" } """ .trimIndent() ) val packager = CliProjectPackager( CliBaseOptions(workingDir = tempDir), listOf(tempDir), CliTestOptions(), ".out/%{name}@%{version}", skipPublishCheck = true, consoleWriter = StringWriter(), ) packager.run() val expectedMetadata = tempDir.resolve(".out/mypackage@1.0.0/mypackage@1.0.0") val expectedMetadataChecksum = tempDir.resolve(".out/mypackage@1.0.0/mypackage@1.0.0.sha256") val expectedArchive = tempDir.resolve(".out/mypackage@1.0.0/mypackage@1.0.0.zip") val expectedArchiveChecksum = tempDir.resolve(".out/mypackage@1.0.0/mypackage@1.0.0.zip.sha256") assertThat(expectedMetadata).exists() assertThat(expectedMetadata) .hasContent( """ { "name": "mypackage", "packageUri": "package://example.com/mypackage@1.0.0", "version": "1.0.0", "packageZipUrl": "https://foo.com", "packageZipChecksums": { "sha256": "e83b67722ea17ba41717ce6e99ae8ee02d66df6294bd319ce403075b1071c3e0" }, "dependencies": {}, "authors": [] } """ .trimIndent() ) assertThat(expectedArchive).exists() assertThat(expectedArchive.zipFilePaths()) .hasSameElementsAs(listOf("/", "/c", "/c/d", "/c/d/foo.txt", "/a", "/a/b", "/a/b/foo.pkl")) assertThat(expectedMetadataChecksum) .hasContent("72ab32b27393bde5f316b00f184faae919378e4d7643872c605f681b14b647bf") assertThat(expectedArchiveChecksum) .hasContent("e83b67722ea17ba41717ce6e99ae8ee02d66df6294bd319ce403075b1071c3e0") FileSystems.newFileSystem(URI("jar:" + expectedArchive.toUri()), mutableMapOf()) .use { fs -> assertThat(fs.getPath("a/b/foo.pkl")).hasSameTextualContentAs(fooPkl) assertThat(fs.getPath("c/d/foo.txt")).hasSameTextualContentAs(fooTxt) } } @Test fun `generate package with excludes`(@TempDir tempDir: Path) { tempDir.apply { writeEmptyFile("a/b/c/d.bin") writeEmptyFile("input/foo/bar.txt") writeEmptyFile("z.bin") writeEmptyFile("main.pkl") writeEmptyFile("main.test.pkl") writeEmptyFile("child/main.pkl") writeEmptyFile("child/main.test.pkl") writeEmptyFile("examples/Workflow.pkl") writeEmptyFile("examples/Ex1.pkl") writeEmptyFile("tests/Test1.pkl") writeEmptyFile("tests/integration/TestIng1.pkl") } tempDir.writeFile( "PklProject", """ amends "pkl:Project" package { name = "mypackage" version = "1.0.0" baseUri = "package://example.com/mypackage" packageZipUrl = "https://foo.com" exclude { "*.bin" "child/main.pkl" "*.test.pkl" "examples/Ex1.pkl" "tests/**" } } """ .trimIndent(), ) CliProjectPackager( CliBaseOptions(workingDir = tempDir), listOf(tempDir), CliTestOptions(), ".out/%{name}@%{version}", skipPublishCheck = true, consoleWriter = StringWriter(), ) .run() val expectedArchive = tempDir.resolve(".out/mypackage@1.0.0/mypackage@1.0.0.zip") assertThat(expectedArchive.zipFilePaths()) .hasSameElementsAs( listOf( "/", "/examples", "/examples/Workflow.pkl", "/input", "/input/foo", "/input/foo/bar.txt", "/main.pkl", ) ) } @Test fun `generate packages with local dependencies`(@TempDir tempDir: Path) { val projectDir = tempDir.resolve("project") val project2Dir = tempDir.resolve("project2") projectDir.writeFile( "PklProject", """ amends "pkl:Project" package { name = "mypackage" version = "1.0.0" baseUri = "package://example.com/mypackage" packageZipUrl = "https://foo.com" } dependencies { ["birds"] { uri = "package://localhost:0/birds@0.5.0" } ["project2"] = import("../project2/PklProject") } """ .trimIndent(), ) projectDir.writeFile( "PklProject.deps.json", """ { "schemaVersion": 1, "resolvedDependencies": { "package://localhost:0/birds@0": { "type": "remote", "uri": "projectpackage://localhost:0/birds@0.5.0", "checksums": { "sha256": "${PackageServer.BIRDS_SHA}" } }, "package://localhost:0/project2@5": { "type": "local", "uri": "projectpackage://localhost:0/project2@5.0.0", "path": "../project2" } } } """ .trimIndent(), ) project2Dir.writeFile( "PklProject", """ amends "pkl:Project" package { name = "project2" baseUri = "package://localhost:0/project2" version = "5.0.0" packageZipUrl = "https://foo.com/project2.zip" } """ .trimIndent(), ) project2Dir.writeFile( "PklProject.deps.json", """ { "schemaVersion": 1, "resolvedDependencies": {} } """ .trimIndent(), ) CliProjectPackager( CliBaseOptions(workingDir = tempDir), listOf(projectDir, project2Dir), CliTestOptions(), ".out/%{name}@%{version}", skipPublishCheck = true, consoleWriter = StringWriter(), ) .run() val expectedMetadata = tempDir.resolve(".out/mypackage@1.0.0/mypackage@1.0.0") assertThat(expectedMetadata).exists() assertThat(expectedMetadata) .hasContent( """ { "name": "mypackage", "packageUri": "package://example.com/mypackage@1.0.0", "version": "1.0.0", "packageZipUrl": "https://foo.com", "packageZipChecksums": { "sha256": "8739c76e681f900923b900c9df0ef75cf421d39cabb54650c4b9ad19b6a76d85" }, "dependencies": { "birds": { "uri": "package://localhost:0/birds@0.5.0", "checksums": { "sha256": "${PackageServer.BIRDS_SHA}" } }, "project2": { "uri": "package://localhost:0/project2@5.0.0", "checksums": { "sha256": "981787869571330b2f609a94a5912466990ce00e3fa94e7f290c2f99a6d5e5ed" } } }, "authors": [] } """ .trimIndent() ) val project2Metadata = tempDir.resolve(".out/project2@5.0.0/project2@5.0.0") assertThat(project2Metadata).exists() assertThat(project2Metadata.readString()) .isEqualTo( """ { "name": "project2", "packageUri": "package://localhost:0/project2@5.0.0", "version": "5.0.0", "packageZipUrl": "https://foo.com/project2.zip", "packageZipChecksums": { "sha256": "8739c76e681f900923b900c9df0ef75cf421d39cabb54650c4b9ad19b6a76d85" }, "dependencies": {}, "authors": [] } """ .trimIndent() ) } @Test fun `generate package with local dependencies fails if local dep is not included for packaging`( @TempDir tempDir: Path ) { val projectDir = tempDir.resolve("project") val project2Dir = tempDir.resolve("project2") projectDir.writeFile( "PklProject", """ amends "pkl:Project" package { name = "mypackage" version = "1.0.0" baseUri = "package://example.com/mypackage" packageZipUrl = "https://foo.com" } dependencies { ["birds"] { uri = "package://localhost:0/birds@0.5.0" } ["project2"] = import("../project2/PklProject") } """ .trimIndent(), ) projectDir.writeFile( "PklProject.deps.json", """ { "schemaVersion": 1, "resolvedDependencies": { "package://localhost:0/birds@0": { "type": "remote", "uri": "projectpackage://localhost:0/birds@0.5.0", "checksums": { "sha256": "0a5ad2dc13f06f73f96ba94e8d01d48252bc934e2de71a837620ca0fef8a7453" } }, "package://localhost:0/project2@5": { "type": "local", "uri": "projectpackage://localhost:0/project2@5.0.0", "path": "../project2" } } } """ .trimIndent(), ) project2Dir.writeFile( "PklProject", """ amends "pkl:Project" package { name = "project2" baseUri = "package://localhost:0/project2" version = "5.0.0" packageZipUrl = "https://foo.com/project2.zip" } """ .trimIndent(), ) project2Dir.writeFile( "PklProject.deps.json", """ { "schemaVersion": 1, "resolvedDependencies": {} } """ .trimIndent(), ) assertThatCode { CliProjectPackager( CliBaseOptions(workingDir = tempDir), listOf(projectDir), CliTestOptions(), ".out/%{name}@%{version}", skipPublishCheck = true, consoleWriter = StringWriter(), ) .run() } .hasMessageContaining("which is not included for packaging") } @Test fun `import path verification -- relative path outside project dir`(@TempDir tempDir: Path) { tempDir.writeFile( "main.pkl", """ import "../foo.pkl" res = foo """ .trimIndent(), ) tempDir.writeFile( "PklProject", """ amends "pkl:Project" package { name = "mypackage" version = "1.0.0" baseUri = "package://example.com/mypackage" packageZipUrl = "https://foo.com" } """ .trimIndent(), ) val e = assertThrows { CliProjectPackager( CliBaseOptions(workingDir = tempDir), listOf(tempDir), CliTestOptions(), ".out/%{name}@%{version}", skipPublishCheck = true, consoleWriter = StringWriter(), ) .run() } assertThat(e.message) .startsWith( """ –– Pkl Error –– Path `../foo.pkl` includes path segments that are outside the project root directory. 1 | import "../foo.pkl" ^^^^^^^^^^^^ """ .trimIndent() ) } // Absolute path imports on Windows must use an absolute URI (e.g. file:///C:/Foo/Bar), because // they must contain drive letters, which conflict with schemes. // We skip validation for absolute URIs, so effectively we skip this check on Windows. @Test @DisabledOnOs(OS.WINDOWS) fun `import path verification -- absolute import from root dir`(@TempDir tempDir: Path) { tempDir.writeFile( "main.pkl", """ import "$tempDir/foo.pkl" res = foo """ .trimIndent(), ) tempDir.writeFile( "PklProject", """ amends "pkl:Project" package { name = "mypackage" version = "1.0.0" baseUri = "package://example.com/mypackage" packageZipUrl = "https://foo.com" } """ .trimIndent(), ) val e = assertThrows { CliProjectPackager( CliBaseOptions(workingDir = tempDir), listOf(tempDir), CliTestOptions(), ".out/%{name}@%{version}", skipPublishCheck = true, consoleWriter = StringWriter(), ) .run() } assertThat(e.message) .startsWith( """ –– Pkl Error –– Path `$tempDir/foo.pkl` includes path segments that are outside the project root directory. """ .trimIndent() ) } @Test @DisabledOnOs(OS.WINDOWS) fun `import path verification -- absolute read from root dir`(@TempDir tempDir: Path) { tempDir.writeFile( "main.pkl", """ res = read("$tempDir/foo.pkl") """ .trimIndent(), ) tempDir.writeFile( "PklProject", """ amends "pkl:Project" package { name = "mypackage" version = "1.0.0" baseUri = "package://example.com/mypackage" packageZipUrl = "https://foo.com" } """ .trimIndent(), ) val e = assertThrows { CliProjectPackager( CliBaseOptions(workingDir = tempDir), listOf(tempDir), CliTestOptions(), ".out/%{name}@%{version}", skipPublishCheck = true, consoleWriter = StringWriter(), ) .run() } assertThat(e.message) .startsWith( """ –– Pkl Error –– Path `$tempDir/foo.pkl` includes path segments that are outside the project root directory. """ .trimIndent() ) } @Test fun `import path verification -- passing`(@TempDir tempDir: Path) { tempDir.writeFile( "foo/bar.pkl", """ import "baz.pkl" """ .trimIndent(), ) tempDir.writeFile( "PklProject", """ amends "pkl:Project" package { name = "mypackage" version = "1.0.0" baseUri = "package://example.com/mypackage" packageZipUrl = "https://foo.com" } """ .trimIndent(), ) CliProjectPackager( CliBaseOptions(workingDir = tempDir), listOf(tempDir), CliTestOptions(), ".out/%{name}@%{version}", skipPublishCheck = true, consoleWriter = StringWriter(), ) .run() } @Test fun `multiple projects`(@TempDir tempDir: Path) { tempDir.writeFile("project1/main.pkl", "res = 1") tempDir.writeFile( "project1/PklProject", """ amends "pkl:Project" package { name = "project1" version = "1.0.0" baseUri = "package://example.com/project1" packageZipUrl = "https://foo.com" } """ .trimIndent(), ) tempDir.writeFile("project2/main2.pkl", "res = 2") tempDir.writeFile( "project2/PklProject", """ amends "pkl:Project" package { name = "project2" version = "2.0.0" baseUri = "package://example.com/project2" packageZipUrl = "https://foo.com" } """ .trimIndent(), ) val out = StringWriter() CliProjectPackager( CliBaseOptions(workingDir = tempDir), listOf(tempDir.resolve("project1"), tempDir.resolve("project2")), CliTestOptions(), ".out/%{name}@%{version}", skipPublishCheck = true, consoleWriter = out, ) .run() val sep = File.separatorChar assertThat(out.toString()) .isEqualToNormalizingNewlines( """ .out${sep}project1@1.0.0${sep}project1@1.0.0.zip .out${sep}project1@1.0.0${sep}project1@1.0.0.zip.sha256 .out${sep}project1@1.0.0${sep}project1@1.0.0 .out${sep}project1@1.0.0${sep}project1@1.0.0.sha256 .out${sep}project2@2.0.0${sep}project2@2.0.0.zip .out${sep}project2@2.0.0${sep}project2@2.0.0.zip.sha256 .out${sep}project2@2.0.0${sep}project2@2.0.0 .out${sep}project2@2.0.0${sep}project2@2.0.0.sha256 """ .trimIndent() ) assertThat(tempDir.resolve(".out/project1@1.0.0/project1@1.0.0.zip").zipFilePaths()) .hasSameElementsAs(listOf("/", "/main.pkl")) assertThat(tempDir.resolve(".out/project2@2.0.0/project2@2.0.0.zip").zipFilePaths()) .hasSameElementsAs(listOf("/", "/main2.pkl")) } @Test fun `publish checks`(@TempDir tempDir: Path) { tempDir.writeFile("project/main.pkl", "res = 1") tempDir.writeFile( "project/PklProject", // intentionally conflict with localhost:0/birds@0.5.0 from our test fixtures """ amends "pkl:Project" package { name = "birds" version = "0.5.0" baseUri = "package://localhost:0/birds" packageZipUrl = "https://foo.com" } """ .trimIndent(), ) val e = assertThrows { CliProjectPackager( CliBaseOptions( workingDir = tempDir, caCertificates = listOf(FileTestUtils.selfSignedCertificate), testPort = packageServer.port, ), listOf(tempDir.resolve("project")), CliTestOptions(), ".out/%{name}@%{version}", skipPublishCheck = false, consoleWriter = StringWriter(), ) .run() } assertThat(e) .hasMessageStartingWith( """ Package `package://localhost:0/birds@0.5.0` was already published with different contents. Computed checksum: aa8c883841db22e92794f4708b01dc905b5da77645b7dfb5b22a73da8c347db1 Published checksum: ${PackageServer.BIRDS_SHA} """ .trimIndent() ) } @Test fun `publish check when package is not yet published`(@TempDir tempDir: Path) { tempDir.writeFile("project/main.pkl", "res = 1") tempDir.writeFile( "project/PklProject", """ amends "pkl:Project" package { name = "mangos" version = "1.0.0" baseUri = "package://localhost:0/mangos" packageZipUrl = "https://foo.com" } """ .trimIndent(), ) val out = StringWriter() CliProjectPackager( CliBaseOptions( workingDir = tempDir, caCertificates = listOf(FileTestUtils.selfSignedCertificate), testPort = packageServer.port, ), listOf(tempDir.resolve("project")), CliTestOptions(), ".out/%{name}@%{version}", skipPublishCheck = false, consoleWriter = out, ) .run() val sep = File.separatorChar assertThat(out.toString()) .isEqualToNormalizingNewlines( """ .out${sep}mangos@1.0.0${sep}mangos@1.0.0.zip .out${sep}mangos@1.0.0${sep}mangos@1.0.0.zip.sha256 .out${sep}mangos@1.0.0${sep}mangos@1.0.0 .out${sep}mangos@1.0.0${sep}mangos@1.0.0.sha256 """ .trimIndent() ) } @Test fun `generate annotations`(@TempDir tempDir: Path) { tempDir .resolve("PklProject") .writeString( """ @Unlisted @Deprecated { since = "0.26.1"; message = "do not use" } @ModuleInfo { minPklVersion = "0.26.0" } amends "pkl:Project" package { name = "mypackage" version = "1.0.0" baseUri = "package://example.com/mypackage" packageZipUrl = "https://foo.com" } """ .trimIndent() ) val packager = CliProjectPackager( CliBaseOptions(workingDir = tempDir), listOf(tempDir), CliTestOptions(), ".out/%{name}@%{version}", skipPublishCheck = true, consoleWriter = StringWriter(), ) packager.run() val expectedMetadata = tempDir.resolve(".out/mypackage@1.0.0/mypackage@1.0.0") assertThat(expectedMetadata).exists() assertThat(expectedMetadata) .hasContent( """ { "name": "mypackage", "packageUri": "package://example.com/mypackage@1.0.0", "version": "1.0.0", "packageZipUrl": "https://foo.com", "packageZipChecksums": { "sha256": "8739c76e681f900923b900c9df0ef75cf421d39cabb54650c4b9ad19b6a76d85" }, "dependencies": {}, "authors": [], "annotations": [ { "type": "PObject", "classInfo": { "moduleName": "pkl.base", "class": "Unlisted", "moduleUri": "pkl:base" }, "properties": {} }, { "type": "PObject", "classInfo": { "moduleName": "pkl.base", "class": "Deprecated", "moduleUri": "pkl:base" }, "properties": { "since": "0.26.1", "message": "do not use", "replaceWith": null } }, { "type": "PObject", "classInfo": { "moduleName": "pkl.base", "class": "ModuleInfo", "moduleUri": "pkl:base" }, "properties": { "minPklVersion": "0.26.0" } } ] } """ .trimIndent() ) } private fun Path.zipFilePaths(): List { return FileSystems.newFileSystem(URI("jar:${toUri()}"), emptyMap()).use { fs -> Files.walk(fs.getPath("/")).map(IoUtils::toNormalizedPathString).collect(Collectors.toList()) } } } ================================================ FILE: pkl-cli/src/test/kotlin/org/pkl/cli/CliProjectResolverTest.kt ================================================ /* * Copyright © 2024-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.cli import java.io.File import java.io.StringWriter import java.nio.file.Path import kotlin.io.path.createDirectories import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatCode import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.io.TempDir import org.pkl.commons.cli.CliBaseOptions import org.pkl.commons.cli.CliException import org.pkl.commons.test.FileTestUtils import org.pkl.commons.test.PackageServer import org.pkl.commons.writeString import org.pkl.core.util.IoUtils class CliProjectResolverTest { companion object { private val packageServer = PackageServer() @AfterAll @JvmStatic fun afterAll() { packageServer.close() } } @Test fun `missing PklProject when inferring a project dir`(@TempDir tempDir: Path) { val packager = CliProjectResolver( CliBaseOptions(workingDir = tempDir, noCache = true), emptyList(), consoleWriter = StringWriter(), errWriter = StringWriter(), ) val err = assertThrows { packager.run() } assertThat(err).hasMessageStartingWith("No project visible to the working directory.") } @Test fun `missing PklProject when explicit dir is provided`(@TempDir tempDir: Path) { val packager = CliProjectResolver( CliBaseOptions(noCache = true), listOf(tempDir), consoleWriter = StringWriter(), errWriter = StringWriter(), ) val err = assertThrows { packager.run() } assertThat(err).hasMessageStartingWith("Directory $tempDir does not contain a PklProject file.") } @Test fun `basic project`(@TempDir tempDir: Path) { tempDir.writeFile( "PklProject", """ amends "pkl:Project" dependencies { ["birds"] { uri = "package://localhost:0/birds@0.5.0" } } """ .trimIndent(), ) CliProjectResolver( CliBaseOptions( workingDir = tempDir, caCertificates = listOf(FileTestUtils.selfSignedCertificate), testPort = packageServer.port, noCache = true, ), listOf(tempDir), consoleWriter = StringWriter(), errWriter = StringWriter(), ) .run() val expectedOutput = tempDir.resolve("PklProject.deps.json") assertThat(expectedOutput) .hasContent( """ { "schemaVersion": 1, "resolvedDependencies": { "package://localhost:0/birds@0": { "type": "remote", "uri": "projectpackage://localhost:0/birds@0.5.0", "checksums": { "sha256": "${PackageServer.BIRDS_SHA}" } }, "package://localhost:0/fruit@1": { "type": "remote", "uri": "projectpackage://localhost:0/fruit@1.0.5", "checksums": { "sha256": "${PackageServer.FRUIT_SHA}" } } } } """ .trimIndent() ) } @Test fun `basic project, inferred from working dir`(@TempDir tempDir: Path) { tempDir.writeFile( "PklProject", """ amends "pkl:Project" dependencies { ["birds"] { uri = "package://localhost:0/birds@0.5.0" } } """ .trimIndent(), ) CliProjectResolver( CliBaseOptions( workingDir = tempDir, caCertificates = listOf(FileTestUtils.selfSignedCertificate), testPort = packageServer.port, noCache = true, ), emptyList(), consoleWriter = StringWriter(), errWriter = StringWriter(), ) .run() val expectedOutput = tempDir.resolve("PklProject.deps.json") assertThat(expectedOutput) .hasContent( """ { "schemaVersion": 1, "resolvedDependencies": { "package://localhost:0/birds@0": { "type": "remote", "uri": "projectpackage://localhost:0/birds@0.5.0", "checksums": { "sha256": "${PackageServer.BIRDS_SHA}" } }, "package://localhost:0/fruit@1": { "type": "remote", "uri": "projectpackage://localhost:0/fruit@1.0.5", "checksums": { "sha256": "${PackageServer.FRUIT_SHA}" } } } } """ .trimIndent() ) } @Test fun `local dependencies`(@TempDir tempDir: Path) { val projectDir = tempDir.resolve("theproject") projectDir.writeFile( "PklProject", """ amends "pkl:Project" dependencies { ["birds"] { uri = "package://localhost:0/birds@0.5.0" } ["project2"] = import("../project2/PklProject") } """ .trimIndent(), ) projectDir.writeFile( "../project2/PklProject", """ amends "pkl:Project" package { name = "project2" baseUri = "package://localhost:0/package2" version = "5.0.0" packageZipUrl = "https://foo.com/package2.zip" } dependencies { ["fruit"] { uri = "package://localhost:0/fruit@1.0.5" } ["project3"] = import("../project3/PklProject") } """ .trimIndent(), ) projectDir.writeFile( "../project3/PklProject", """ amends "pkl:Project" package { name = "project3" baseUri = "package://localhost:0/package3" version = "5.0.0" packageZipUrl = "https://foo.com/package3.zip" } dependencies { ["fruit"] { uri = "package://localhost:0/fruit@1.1.0" } } """ .trimIndent(), ) CliProjectResolver( CliBaseOptions( caCertificates = listOf(FileTestUtils.selfSignedCertificate), testPort = packageServer.port, noCache = true, ), listOf(projectDir), consoleWriter = StringWriter(), errWriter = StringWriter(), ) .run() val expectedOutput = projectDir.resolve("PklProject.deps.json") assertThat(expectedOutput) .hasContent( """ { "schemaVersion": 1, "resolvedDependencies": { "package://localhost:0/birds@0": { "type": "remote", "uri": "projectpackage://localhost:0/birds@0.5.0", "checksums": { "sha256": "${PackageServer.BIRDS_SHA}" } }, "package://localhost:0/fruit@1": { "type": "remote", "uri": "projectpackage://localhost:0/fruit@1.1.0", "checksums": { "sha256": "${PackageServer.FRUIT_1_1_SHA}" } }, "package://localhost:0/package2@5": { "type": "local", "uri": "projectpackage://localhost:0/package2@5.0.0", "path": "../project2" }, "package://localhost:0/package3@5": { "type": "local", "uri": "projectpackage://localhost:0/package3@5.0.0", "path": "../project3" } } } """ .trimIndent() ) } @Test fun `local dependency overridden by remote dependency`(@TempDir tempDir: Path) { val projectDir = tempDir.resolve("theproject") projectDir.writeFile( "PklProject", """ amends "pkl:Project" dependencies { ["birds"] { uri = "package://localhost:0/birds@0.5.0" } ["fruit"] = import("../fruit/PklProject") } """ .trimIndent(), ) projectDir.writeFile( "../fruit/PklProject", """ amends "pkl:Project" package { name = "fruit" baseUri = "package://localhost:0/fruit" version = "1.0.0" packageZipUrl = "https://foo.com/fruit.zip" } """ .trimIndent(), ) val consoleOut = StringWriter() val errOut = StringWriter() CliProjectResolver( CliBaseOptions( caCertificates = listOf(FileTestUtils.selfSignedCertificate), testPort = packageServer.port, noCache = true, ), listOf(projectDir), consoleWriter = consoleOut, errWriter = errOut, ) .run() val expectedOutput = projectDir.resolve("PklProject.deps.json") assertThat(expectedOutput) .hasContent( """ { "schemaVersion": 1, "resolvedDependencies": { "package://localhost:0/birds@0": { "type": "remote", "uri": "projectpackage://localhost:0/birds@0.5.0", "checksums": { "sha256": "${PackageServer.BIRDS_SHA}" } }, "package://localhost:0/fruit@1": { "type": "remote", "uri": "projectpackage://localhost:0/fruit@1.0.5", "checksums": { "sha256": "${PackageServer.FRUIT_SHA}" } } } } """ .trimIndent() ) assertThat(errOut.toString()) .isEqualTo( "WARN: local dependency `package://localhost:0/fruit@1.0.0` was overridden to remote dependency `package://localhost:0/fruit@1.0.5`.${IoUtils.getLineSeparator()}" ) } @Test fun `resolving multiple projects`(@TempDir tempDir: Path) { tempDir.writeFile( "project1/PklProject", """ amends "pkl:Project" dependencies { ["birds"] { uri = "package://localhost:0/birds@0.5.0" } } """ .trimIndent(), ) tempDir.writeFile( "project2/PklProject", """ amends "pkl:Project" dependencies { ["fruit"] { uri = "package://localhost:0/fruit@1.1.0" } } """ .trimIndent(), ) val consoleOut = StringWriter() val errOut = StringWriter() CliProjectResolver( CliBaseOptions( caCertificates = listOf(FileTestUtils.selfSignedCertificate), testPort = packageServer.port, noCache = true, ), listOf(tempDir.resolve("project1"), tempDir.resolve("project2")), consoleWriter = consoleOut, errWriter = errOut, ) .run() val sep = File.separatorChar assertThat(consoleOut.toString()) .isEqualToNormalizingNewlines( """ $tempDir${sep}project1${sep}PklProject.deps.json $tempDir${sep}project2${sep}PklProject.deps.json """ .trimIndent() ) assertThat(tempDir.resolve("project1/PklProject.deps.json")) .hasContent( """ { "schemaVersion": 1, "resolvedDependencies": { "package://localhost:0/birds@0": { "type": "remote", "uri": "projectpackage://localhost:0/birds@0.5.0", "checksums": { "sha256": "${PackageServer.BIRDS_SHA}" } }, "package://localhost:0/fruit@1": { "type": "remote", "uri": "projectpackage://localhost:0/fruit@1.0.5", "checksums": { "sha256": "${PackageServer.FRUIT_SHA}" } } } } """ .trimIndent() ) assertThat(tempDir.resolve("project2/PklProject.deps.json")) .hasContent( """ { "schemaVersion": 1, "resolvedDependencies": { "package://localhost:0/fruit@1": { "type": "remote", "uri": "projectpackage://localhost:0/fruit@1.1.0", "checksums": { "sha256": "${PackageServer.FRUIT_1_1_SHA}" } } } } """ .trimIndent() ) } @Test fun `IOException when writing to PklProject-deps-json`(@TempDir tempDir: Path) { val consoleOut = StringWriter() val errOut = StringWriter() tempDir .resolve("PklProject") .writeString( """ amends "pkl:Project" dependencies { ["birds"] { uri = "package://localhost:0/birds@0.5.0" } } """ .trimIndent() ) // coerce an IOException by making this a directory val depsJsonFile = tempDir.resolve("PklProject.deps.json").also { it.createDirectories() } assertThatCode { CliProjectResolver( CliBaseOptions( caCertificates = listOf(FileTestUtils.selfSignedCertificate), testPort = packageServer.port, noCache = true, ), listOf(tempDir), consoleWriter = consoleOut, errWriter = errOut, ) .run() } .hasMessageContaining("Failed to write to $depsJsonFile") } } ================================================ FILE: pkl-cli/src/test/kotlin/org/pkl/cli/CliShellCompletionTest.kt ================================================ /* * Copyright © 2024-2025 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.cli import com.github.ajalt.clikt.testing.test import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.pkl.cli.commands.RootCommand class CliShellCompletionTest { @Test fun `shell completion command supports required shells`() { val shellList = listOf("bash", "zsh", "fish") for (shell in shellList) { val result = RootCommand().test("shell-completion $shell") assertThat(result.stdout).contains("Command completion for pkl") } } } ================================================ FILE: pkl-cli/src/test/kotlin/org/pkl/cli/CliTestRunnerTest.kt ================================================ /* * Copyright © 2024-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.cli import com.github.ajalt.clikt.testing.test import java.io.StringWriter import java.io.Writer import java.net.URI import java.nio.file.Path import java.util.* import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatCode import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.io.TempDir import org.pkl.cli.commands.RootCommand import org.pkl.commons.cli.CliBaseOptions import org.pkl.commons.cli.CliException import org.pkl.commons.cli.CliTestOptions import org.pkl.commons.readString import org.pkl.commons.toUri import org.pkl.commons.writeString class CliTestRunnerTest { @Test fun `CliTestRunner succeed test`(@TempDir tempDir: Path) { val code = """ amends "pkl:test" facts { ["succeed"] { 8 == 8 3 == 3 } } """ .trimIndent() val input = tempDir.resolve("test.pkl").writeString(code).toString() val out = StringWriter() val err = StringWriter() val opts = CliBaseOptions( sourceModules = listOf(input.toUri()), settings = URI("pkl:settings"), powerAssertionsEnabled = true, ) val testOpts = CliTestOptions() val runner = CliTestRunner(opts, testOpts, consoleWriter = out, errWriter = err) runner.run() assertThat(out.toString().stripFileAndLines(tempDir)) .isEqualTo( """ module test facts ✔ succeed 100.0% tests pass [1 passed], 100.0% asserts pass [2 passed] """ .trimIndent() ) assertThat(err.toString()).isEqualTo("") } @Test fun `CliTestRunner fail test`(@TempDir tempDir: Path) { val code = """ amends "pkl:test" facts { ["fail"] { 4 == 9 1 == 5 } } """ .trimIndent() val input = tempDir.resolve("test.pkl").writeString(code).toString() val out = StringWriter() val err = StringWriter() val opts = CliBaseOptions( sourceModules = listOf(input.toUri()), settings = URI("pkl:settings"), powerAssertionsEnabled = true, ) val testOpts = CliTestOptions() val runner = CliTestRunner(opts, testOpts, consoleWriter = out, errWriter = err) assertThatCode { runner.run() }.hasMessage("Tests failed.") assertThat(out.toString().stripFileAndLines(tempDir)) .isEqualTo( """ module test facts ✘ fail 4 == 9 (/tempDir/test.pkl, line xx) │ false 1 == 5 (/tempDir/test.pkl, line xx) │ false 0.0% tests pass [1/1 failed], 0.0% asserts pass [2/2 failed] """ .trimIndent() ) assertThat(err.toString()).isEqualTo("") } @Test fun `CliTestRunner with thrown error in facts`(@TempDir tempDir: Path) { val code = """ amends "pkl:test" facts { ["fail"] { throw("uh oh") } } """ .trimIndent() val input = tempDir.resolve("test.pkl").writeString(code).toString() val out = StringWriter() val err = StringWriter() val opts = CliBaseOptions( sourceModules = listOf(input.toUri()), settings = URI("pkl:settings"), powerAssertionsEnabled = true, ) val testOpts = CliTestOptions() val runner = CliTestRunner(opts, testOpts, consoleWriter = out, errWriter = err) assertThatCode { runner.run() }.hasMessage("Tests failed.") assertThat(out.toString().stripFileAndLines(tempDir)) .isEqualToNormalizingNewlines( """ module test facts ✘ fail –– Pkl Error –– uh oh 5 | throw("uh oh") ^^^^^^^^^^^^^^ at test#facts["fail"][#1] (/tempDir/test.pkl, line xx) 0.0% tests pass [1/1 failed], 0.0% asserts pass [1/1 failed] """ .trimIndent() ) assertThat(err.toString()).isEqualTo("") } @Test fun `CliTestRunner with thrown error in examples -- no expected output`(@TempDir tempDir: Path) { val code = """ amends "pkl:test" examples { ["fail"] { throw("uh oh") } } """ .trimIndent() val input = tempDir.resolve("test.pkl").writeString(code).toString() val out = StringWriter() val err = StringWriter() val opts = CliBaseOptions( sourceModules = listOf(input.toUri()), settings = URI("pkl:settings"), powerAssertionsEnabled = true, ) val testOpts = CliTestOptions() val runner = CliTestRunner(opts, testOpts, consoleWriter = out, errWriter = err) assertThatCode { runner.run() }.hasMessage("Tests failed.") assertThat(out.toString().stripFileAndLines(tempDir)) .isEqualTo( """ module test examples ✘ fail –– Pkl Error –– uh oh 5 | throw("uh oh") ^^^^^^^^^^^^^^ at test#examples["fail"][#1] (/tempDir/test.pkl, line xx) 0.0% tests pass [1/1 failed], 0.0% asserts pass [1/1 failed] """ .trimIndent() ) assertThat(err.toString()).isEqualTo("") } @Test fun `CliTestRunner with thrown error in examples -- existing expected output`( @TempDir tempDir: Path ) { val code = """ amends "pkl:test" examples { ["fail"] { throw("uh oh") } } """ .trimIndent() val input = tempDir.resolve("test.pkl").writeString(code).toString() tempDir .resolve("test.pkl-expected.pcf") .writeString( """ examples { ["fail"] { "never compared to" } } """ .trimIndent() ) val out = StringWriter() val err = StringWriter() val opts = CliBaseOptions( sourceModules = listOf(input.toUri()), settings = URI("pkl:settings"), powerAssertionsEnabled = true, ) val testOpts = CliTestOptions() val runner = CliTestRunner(opts, testOpts, consoleWriter = out, errWriter = err) assertThatCode { runner.run() }.hasMessage("Tests failed.") assertThat(out.toString().stripFileAndLines(tempDir)) .isEqualToNormalizingNewlines( """ module test examples ✘ fail –– Pkl Error –– uh oh 5 | throw("uh oh") ^^^^^^^^^^^^^^ at test#examples["fail"][#1] (/tempDir/test.pkl, line xx) 0.0% tests pass [1/1 failed], 0.0% asserts pass [1/1 failed] """ .trimIndent() ) assertThat(err.toString()).isEqualTo("") } @Test fun `CliTestRunner JUnit reports`(@TempDir tempDir: Path) { val code = """ amends "pkl:test" facts { ["foo"] { 9 == trace(9) "foo" == "foo" } ["bar"] { "foo" == "foo" 5 == 9 } } """ .trimIndent() val input = tempDir.resolve("test.pkl").writeString(code).toString() val noopWriter = noopWriter() val opts = CliBaseOptions( sourceModules = listOf(input.toUri()), settings = URI("pkl:settings"), powerAssertionsEnabled = true, ) val testOpts = CliTestOptions(junitDir = tempDir) val runner = CliTestRunner(opts, testOpts, noopWriter, noopWriter) assertThatCode { runner.run() }.hasMessageContaining("failed") val junitReport = tempDir.resolve("test.xml").readString().stripFileAndLines(tempDir) assertThat(junitReport) .isEqualTo( """ 5 == 9 (/tempDir/test.pkl, line xx) │ false """ .trimIndent() ) } @Test fun `CliTestRunner JUnit reports, with thrown error`(@TempDir tempDir: Path) { val code = """ amends "pkl:test" facts { ["foo"] { 9 == trace(9) "foo" == "foo" } ["fail"] { throw("uh oh") } } """ .trimIndent() val input = tempDir.resolve("test.pkl").writeString(code).toString() val noopWriter = noopWriter() val opts = CliBaseOptions( sourceModules = listOf(input.toUri()), settings = URI("pkl:settings"), powerAssertionsEnabled = true, ) val testOpts = CliTestOptions(junitDir = tempDir) val runner = CliTestRunner(opts, testOpts, noopWriter, noopWriter) assertThatCode { runner.run() }.hasMessageContaining("failed") val junitReport = tempDir.resolve("test.xml").readString().stripFileAndLines(tempDir) assertThat(junitReport) .isEqualTo( """ –– Pkl Error –– uh oh 9 | throw("uh oh") ^^^^^^^^^^^^^^ at test#facts["fail"][#1] (/tempDir/test.pkl, line xx) """ .trimIndent() ) } @Test fun `CliTestRunner duplicated JUnit reports`(@TempDir tempDir: Path) { val foo = """ module foo amends "pkl:test" facts { ["foo"] { 1 == 1 } } """ .trimIndent() val bar = """ module foo amends "pkl:test" facts { ["foo"] { 1 == 1 } } """ .trimIndent() val input = tempDir.resolve("test.pkl").writeString(foo).toString() val input2 = tempDir.resolve("test.pkl").writeString(bar).toString() val noopWriter = noopWriter() val opts = CliBaseOptions( sourceModules = listOf(input.toUri(), input2.toUri()), settings = URI("pkl:settings"), ) val testOpts = CliTestOptions(junitDir = tempDir) val runner = CliTestRunner(opts, testOpts, noopWriter, noopWriter) assertThatCode { runner.run() }.hasMessageContaining("failed") } @Test fun `CliTestRunner test per module`(@TempDir tempDir: Path) { val code1 = """ amends "pkl:test" facts { ["foo"] { true } } """ .trimIndent() val code2 = """ amends "pkl:test" facts { ["bar"] { true } } """ .trimIndent() val input1 = tempDir.resolve("test1.pkl").writeString(code1).toString() val input2 = tempDir.resolve("test2.pkl").writeString(code2).toString() val noopWriter = noopWriter() val opts = CliBaseOptions( sourceModules = listOf(input1.toUri(), input2.toUri()), settings = URI("pkl:settings"), ) val testOpts = CliTestOptions(junitDir = tempDir) val runner = CliTestRunner(opts, testOpts, noopWriter, noopWriter) runner.run() assertThat(tempDir.resolve("test1.xml")).isNotEmptyFile() assertThat(tempDir.resolve("test2.xml")).isNotEmptyFile() } @Test fun `CliTestRunner test aggregate`(@TempDir tempDir: Path) { val code1 = """ amends "pkl:test" facts { ["foo"] { true } ["bar"] { 5 == 9 } } """ .trimIndent() val code2 = """ amends "pkl:test" facts { ["xxx"] { false } ["yyy"] { false } ["zzz"] { true } } """ .trimIndent() val input1 = tempDir.resolve("test1.pkl").writeString(code1).toString() val input2 = tempDir.resolve("test2.pkl").writeString(code2).toString() val noopWriter = noopWriter() val opts = CliBaseOptions( sourceModules = listOf(input1.toUri(), input2.toUri()), settings = URI("pkl:settings"), powerAssertionsEnabled = true, ) val testOpts = CliTestOptions(junitDir = tempDir, junitAggregateReports = true) val runner = CliTestRunner(opts, testOpts, noopWriter, noopWriter) assertThatCode { runner.run() }.hasMessageContaining("failed") assertThat(tempDir.resolve("pkl-tests.xml")).isNotEmptyFile() assertThat(tempDir.resolve("test1.xml")).doesNotExist() assertThat(tempDir.resolve("test2.xml")).doesNotExist() val junitReport = tempDir.resolve("pkl-tests.xml").readString().stripFileAndLines(tempDir) assertThat(junitReport) .isEqualTo( """ 5 == 9 (/tempDir/test1.pkl, line xx) │ false false (/tempDir/test2.pkl, line xx) false (/tempDir/test2.pkl, line xx) """ .trimIndent() ) } @Test fun `CliTestRunner test aggregate suite name`(@TempDir tempDir: Path) { val code1 = """ amends "pkl:test" facts { ["foo"] { true } } """ .trimIndent() val code2 = """ amends "pkl:test" facts { ["bar"] { true } } """ .trimIndent() val input1 = tempDir.resolve("test1.pkl").writeString(code1).toString() val input2 = tempDir.resolve("test2.pkl").writeString(code2).toString() val noopWriter = noopWriter() val opts = CliBaseOptions( sourceModules = listOf(input1.toUri(), input2.toUri()), settings = URI("pkl:settings"), ) val testOpts = CliTestOptions( junitDir = tempDir, junitAggregateReports = true, junitAggregateSuiteName = "custom", ) val runner = CliTestRunner(opts, testOpts, noopWriter, noopWriter) runner.run() assertThat(tempDir.resolve("custom.xml")).isNotEmptyFile() assertThat(tempDir.resolve("pkl-tests.xml")).doesNotExist() assertThat(tempDir.resolve("test1.xml")).doesNotExist() assertThat(tempDir.resolve("test2.xml")).doesNotExist() } @Test fun `no source modules specified has same message as pkl eval`() { val e1 = assertThrows { CliTestRunner(CliBaseOptions(), CliTestOptions()).run() } val e2 = RootCommand().test("eval") assertThat(e1).hasMessageContaining("missing argument ") assertThat(e1.message!!.replace("test", "eval") + "\n").isEqualTo(e2.stderr) } @Test fun `example length mismatch`(@TempDir tempDir: Path) { val code = """ amends "pkl:test" examples { ["nums"] { 1 2 } } """ .trimIndent() val input = tempDir.resolve("test.pkl").writeString(code).toString() tempDir .resolve("test.pkl-expected.pcf") .writeString( """ examples { ["nums"] { 1 } } """ .trimIndent() ) val out = StringWriter() val err = StringWriter() val opts = CliBaseOptions( sourceModules = listOf(input.toUri()), settings = URI("pkl:settings"), powerAssertionsEnabled = true, ) val testOpts = CliTestOptions() val runner = CliTestRunner(opts, testOpts, consoleWriter = out, errWriter = err) assertThatCode { runner.run() }.hasMessage("Tests failed.") assertThat(out.toString().stripFileAndLines(tempDir)) .isEqualToNormalizingNewlines( """ module test examples ✘ nums (/tempDir/test.pkl, line xx) Output mismatch: Expected "nums" to contain 1 examples, but found 2 0.0% tests pass [1/1 failed], 0.0% asserts pass [1/1 failed] """ .trimIndent() ) } @Test fun `only written examples`(@TempDir tempDir: Path) { val code = """ amends "pkl:test" examples { ["nums"] { 1 2 } } """ .trimIndent() val input = tempDir.resolve("test.pkl").writeString(code).toString() val out = StringWriter() val err = StringWriter() val opts = CliBaseOptions( sourceModules = listOf(input.toUri()), settings = URI("pkl:settings"), powerAssertionsEnabled = true, ) val testOpts = CliTestOptions() val runner = CliTestRunner(opts, testOpts, consoleWriter = out, errWriter = err) val exception = assertThrows { runner.run() } assertThat(exception.exitCode).isEqualTo(10) assertThat(out.toString()) .isEqualTo( """ module test examples ✍️ nums 1 examples written """ .trimIndent() ) } @Test fun `CliTestRunner locale independence test`(@TempDir tempDir: Path) { val originalLocale = Locale.getDefault() Locale.setDefault(Locale.GERMANY) try { val code = """ amends "pkl:test" facts { ["localeTest"] { 1 == 1 } } """ .trimIndent() val input = tempDir.resolve("test.pkl").writeString(code).toString() val out = StringWriter() val err = StringWriter() val opts = CliBaseOptions( sourceModules = listOf(input.toUri()), settings = URI("pkl:settings"), powerAssertionsEnabled = true, ) val testOpts = CliTestOptions() val runner = CliTestRunner(opts, testOpts, consoleWriter = out, errWriter = err) runner.run() assertThat(out.toString().stripFileAndLines(tempDir)) .isEqualTo( """ module test facts ✔ localeTest 100.0% tests pass [1 passed], 100.0% asserts pass [1 passed] """ .trimIndent() ) assertThat(err.toString()).isEqualTo("") } finally { Locale.setDefault(originalLocale) } } private fun String.stripFileAndLines(tmpDir: Path): String { // handle platform differences in handling of file URIs // (file:/// on *nix vs. file:/ on Windows) return replace(tmpDir.toFile().toURI().toString(), "/tempDir/") .replace(tmpDir.toUri().toString(), "/tempDir/") .replace(Regex("line \\d+"), "line xx") } private fun noopWriter(): Writer = object : Writer() { override fun close() {} override fun flush() {} override fun write(cbuf: CharArray, off: Int, len: Int) {} } } ================================================ FILE: pkl-cli/src/test/kotlin/org/pkl/cli/repl/ReplMessagesTest.kt ================================================ /* * Copyright © 2024-2025 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.cli.repl import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.pkl.commons.toPath import org.pkl.core.Loggers import org.pkl.core.SecurityManagers import org.pkl.core.StackFrameTransformers import org.pkl.core.evaluatorSettings.TraceMode import org.pkl.core.http.HttpClient import org.pkl.core.module.ModuleKeyFactories import org.pkl.core.repl.ReplRequest import org.pkl.core.repl.ReplResponse import org.pkl.core.repl.ReplServer class ReplMessagesTest { private val server = ReplServer( SecurityManagers.defaultManager, HttpClient.dummyClient(), Loggers.stdErr(), listOf(ModuleKeyFactories.standardLibrary), listOf(), mapOf(), mapOf(), null, null, null, "/".toPath(), StackFrameTransformers.defaultTransformer, false, TraceMode.COMPACT, ) @Test fun `run examples`() { val examples = ReplMessages.examples var startIndex = examples.indexOf("```") while (startIndex != -1) { val endIndex = examples.indexOf("```", startIndex + 3) assertThat(endIndex).isNotEqualTo(-1) val text = examples .substring(startIndex + 3, endIndex) .lines() .filterNot { it.contains(":force") } .joinToString("\n") val responses = server.handleRequest(ReplRequest.Eval("1", text, true, true)) assertThat(responses.size).isBetween(1, 9) assertThat(responses).hasOnlyElementsOfType(ReplResponse.EvalSuccess::class.java) startIndex = examples.indexOf("```", endIndex + 3) } } @Test fun `handle single backtick`() { val responses = server.handleRequest(ReplRequest.Eval("1", "`", true, true)) assertThat(responses.size).isEqualTo(1) assertThat(responses).hasOnlyElementsOfType(ReplResponse.EvalError::class.java) } } ================================================ FILE: pkl-cli/src/test/kotlin/org/pkl/cli/testExtensions.kt ================================================ /* * Copyright © 2024-2025 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.cli import java.nio.charset.StandardCharsets import java.nio.file.Path import kotlin.io.path.createParentDirectories import org.pkl.commons.writeString fun Path.writeFile(fileName: String, contents: String): Path { return resolve(fileName).apply { createParentDirectories() writeString(contents, StandardCharsets.UTF_8) } } fun Path.writeEmptyFile(fileName: String): Path = writeFile(fileName, "") ================================================ FILE: pkl-codegen-java/gradle.lockfile ================================================ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. com.github.ajalt.clikt:clikt-core-jvm:5.0.3=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.clikt:clikt-core:5.0.3=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.github.ajalt.clikt:clikt-jvm:5.0.3=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.clikt:clikt-markdown-jvm:5.0.3=runtimeClasspath,testRuntimeClasspath com.github.ajalt.clikt:clikt-markdown:5.0.3=runtimeClasspath,testRuntimeClasspath com.github.ajalt.clikt:clikt:5.0.3=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.github.ajalt.colormath:colormath-jvm:3.6.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.colormath:colormath:3.6.0=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.github.ajalt.mordant:mordant-core-jvm:3.0.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-core:3.0.1=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm-ffm-jvm:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm-ffm:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm-graal-ffi-jvm:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm-graal-ffi:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm-jna-jvm:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm-jna:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm:3.0.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-markdown-jvm:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-markdown:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant:3.0.1=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.github.ben-manes.caffeine:caffeine:2.9.3=swiftExportClasspathResolvable com.google.errorprone:error_prone_annotations:2.28.0=swiftExportClasspathResolvable com.palantir.javapoet:javapoet:0.7.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath io.github.java-diff-utils:java-diff-utils:4.12=kotlinInternalAbiValidation io.leangen.geantyref:geantyref:1.3.16=testRuntimeClasspath io.opentelemetry:opentelemetry-api:1.41.0=swiftExportClasspathResolvable io.opentelemetry:opentelemetry-context:1.41.0=swiftExportClasspathResolvable net.bytebuddy:byte-buddy:1.17.7=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.java.dev.jna:jna:5.14.0=runtimeClasspath,testRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata org.assertj:assertj-core:3.27.6=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.bouncycastle:bcpg-jdk18on:1.80=kotlinBouncyCastleConfiguration org.bouncycastle:bcpkix-jdk18on:1.80=kotlinBouncyCastleConfiguration org.bouncycastle:bcprov-jdk18on:1.80=kotlinBouncyCastleConfiguration org.bouncycastle:bcutil-jdk18on:1.80=kotlinBouncyCastleConfiguration org.checkerframework:checker-qual:3.43.0=swiftExportClasspathResolvable org.graalvm.polyglot:polyglot:25.0.0=runtimeClasspath,testRuntimeClasspath org.graalvm.sdk:collections:25.0.0=runtimeClasspath,testRuntimeClasspath org.graalvm.sdk:graal-sdk:25.0.0=runtimeClasspath,testRuntimeClasspath org.graalvm.sdk:nativeimage:25.0.0=runtimeClasspath,testRuntimeClasspath org.graalvm.sdk:word:25.0.0=runtimeClasspath,testRuntimeClasspath org.graalvm.truffle:truffle-api:25.0.0=runtimeClasspath,testRuntimeClasspath org.jetbrains.kotlin:abi-tools-api:2.2.20=kotlinInternalAbiValidation org.jetbrains.kotlin:abi-tools:2.2.20=kotlinInternalAbiValidation org.jetbrains.kotlin:kotlin-build-tools-api:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-build-tools-impl:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-compiler-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlin:kotlin-compiler-runner:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-daemon-client:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-daemon-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:2.2.20=kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-metadata-jvm:2.2.20=kotlinInternalAbiValidation org.jetbrains.kotlin:kotlin-native-prebuilt:2.0.21=kotlinNativeBundleConfiguration org.jetbrains.kotlin:kotlin-reflect:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlin:kotlin-script-runtime:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlin:kotlin-scripting-common:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-jvm:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.2.20=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.20=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib:2.2.20=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,runtimeClasspath,swiftExportClasspathResolvable,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.kotlin:swift-export-embeddable:2.2.20=swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-serialization-bom:1.7.3=swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.7.3=swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3=swiftExportClasspathResolvable org.jetbrains:annotations:13.0=compileClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,runtimeClasspath,swiftExportClasspathResolvable,testCompileClasspath,testRuntimeClasspath org.jetbrains:markdown-jvm:0.7.3=runtimeClasspath,testRuntimeClasspath org.jetbrains:markdown:0.7.3=runtimeClasspath,testRuntimeClasspath org.junit.jupiter:junit-jupiter-api:5.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.jupiter:junit-jupiter-engine:5.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.jupiter:junit-jupiter-params:5.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.platform:junit-platform-commons:1.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.platform:junit-platform-engine:1.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.platform:junit-platform-launcher:1.14.0=testRuntimeClasspath org.junit:junit-bom:5.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.msgpack:msgpack-core:0.9.8=runtimeClasspath,testRuntimeClasspath org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.organicdesign:Paguro:3.10.3=runtimeClasspath,testRuntimeClasspath org.snakeyaml:snakeyaml-engine:2.10=runtimeClasspath,testRuntimeClasspath empty=annotationProcessor,compileOnlyDependenciesMetadata,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinNativeCompilerPluginClasspath,kotlinScriptDefExtensions,shadow,signatures,sourcesJar,testAnnotationProcessor,testApiDependenciesMetadata,testCompileOnlyDependenciesMetadata,testIntransitiveDependenciesMetadata,testKotlinScriptDefExtensions ================================================ FILE: pkl-codegen-java/pkl-codegen-java.gradle.kts ================================================ /* * Copyright © 2024-2025 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. */ plugins { pklAllProjects pklKotlinLibrary pklPublishLibrary pklJavaExecutable } dependencies { // CliJavaCodeGeneratorOptions exposes CliBaseOptions api(projects.pklCommonsCli) implementation(projects.pklCommons) implementation(projects.pklCore) implementation(libs.javaPoet) testImplementation(projects.pklConfigJava) testImplementation(projects.pklCommonsTest) } executable { javaName = "pkl-codegen-java" documentationName = "Pkl Codegen Java" javaPublicationName = "pkl-cli-codegen-java" mainClass = "org.pkl.codegen.java.Main" website = "https://pkl-lang.org/main/current/java-binding/codegen.html" } // with `org.gradle.parallel=true` and without the line below, `test` strangely runs into: // java.lang.NoClassDefFoundError: Lorg/pkl/config/java/ConfigEvaluator; // perhaps somehow related to InMemoryJavaCompiler? tasks.test { mustRunAfter(":pkl-config-java:testFatJar") } tasks.jar { manifest { attributes += mapOf("Main-Class" to "org.pkl.codegen.java.Main") } } publishing { publications { named("library") { pom { url.set("https://github.com/apple/pkl/tree/main/pkl-codegen-java") description.set( """ Java source code generator that generates corresponding Java classes for Pkl classes, simplifying consumption of Pkl configuration as statically typed Java objects. """ .trimIndent() ) } } } } ================================================ FILE: pkl-codegen-java/src/main/kotlin/org/pkl/codegen/java/CliJavaCodeGenerator.kt ================================================ /* * Copyright © 2024-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.codegen.java import java.io.IOException import kotlin.io.path.createParentDirectories import org.pkl.commons.cli.CliCommand import org.pkl.commons.cli.CliException import org.pkl.commons.writeString import org.pkl.core.Closeables import org.pkl.core.ModuleSource /** API for the Java code generator CLI. */ class CliJavaCodeGenerator(private val options: CliJavaCodeGeneratorOptions) : CliCommand(options.base) { override fun doRun() { val builder = evaluatorBuilder() try { builder.build().use { evaluator -> for (moduleUri in resolvedSourceModules) { val schema = evaluator.evaluateSchema(ModuleSource.uri(moduleUri)) val codeGenerator = JavaCodeGenerator(schema, options.toJavaCodeGeneratorOptions()) try { for ((fileName, fileContents) in codeGenerator.output) { val outputFile = options.outputDir.resolve(fileName) try { outputFile.createParentDirectories().writeString(fileContents) } catch (e: IOException) { throw CliException("I/O error writing file `$outputFile`.\nCause: ${e.message}") } } } catch (e: JavaCodeGeneratorException) { throw CliException(e.message!!) } } } } finally { Closeables.closeQuietly(builder.moduleKeyFactories) Closeables.closeQuietly(builder.resourceReaders) } } } ================================================ FILE: pkl-codegen-java/src/main/kotlin/org/pkl/codegen/java/CliJavaCodeGeneratorOptions.kt ================================================ /* * Copyright © 2024-2025 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.codegen.java import java.nio.file.Path import org.pkl.commons.cli.CliBaseOptions /** Configuration options for [CliJavaCodeGenerator]. */ data class CliJavaCodeGeneratorOptions( /** Base options shared between CLI commands. */ val base: CliBaseOptions, /** The directory where generated source code is placed. */ val outputDir: Path, /** The characters to use for indenting generated source code. */ val indent: String = " ", /** Whether to add a `@Generated` annotation to the types to be generated. */ val addGeneratedAnnotation: Boolean = false, /** * Whether to generate public getter methods and private/protected fields instead of public * fields. */ val generateGetters: Boolean = false, /** Whether to preserve Pkl doc comments by generating corresponding Javadoc comments. */ val generateJavadoc: Boolean = false, /** Whether to generate config classes for use with Spring Boot. */ val generateSpringBootConfig: Boolean = false, /** * Fully qualified name of the annotation type to use for annotating constructor parameters with * their name. * * The specified annotation type must have a `value` parameter of type [java.lang.String] or the * generated code may not compile. * * If set to `null`, constructor parameters are not annotated. The default value is `null` if * [generateSpringBootConfig] is `true` and `"org.pkl.config.java.mapper.Named"` otherwise. */ val paramsAnnotation: String? = if (generateSpringBootConfig) null else "org.pkl.config.java.mapper.Named", /** * Fully qualified name of the annotation type to use for annotating non-null types. * * The specified annotation type must have a [java.lang.annotation.Target] of * [java.lang.annotation.ElementType.TYPE_USE] or the generated code may not compile. If set to * `null`, [org.pkl.config.java.mapper.NonNull] will be used. */ val nonNullAnnotation: String? = null, /** Whether to generate classes that implement [java.io.Serializable]. */ val implementSerializable: Boolean = false, /** * A rename mapping for class names. * * When you need to have Java class or package names different from the default names derived from * Pkl module names, you can define a rename mapping, where the key is a prefix of the original * Pkl module name, and the value is the desired replacement. */ val renames: Map = emptyMap(), ) { @Suppress("DeprecatedCallableAddReplaceWith") @Deprecated("deprecated without replacement") fun toJavaCodegenOptions() = toJavaCodeGeneratorOptions() internal fun toJavaCodeGeneratorOptions() = JavaCodeGeneratorOptions( indent, addGeneratedAnnotation, generateGetters, generateJavadoc, generateSpringBootConfig, paramsAnnotation, nonNullAnnotation, implementSerializable, renames, ) } ================================================ FILE: pkl-codegen-java/src/main/kotlin/org/pkl/codegen/java/JavaCodeGenerator.kt ================================================ /* * Copyright © 2024-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. */ @file:Suppress("unused") package org.pkl.codegen.java import com.palantir.javapoet.* import java.io.StringWriter import java.lang.Deprecated import java.util.* import java.util.regex.Pattern import javax.lang.model.element.Modifier import kotlin.AssertionError import kotlin.Boolean import kotlin.Int import kotlin.Long import kotlin.RuntimeException import kotlin.String import kotlin.Suppress import kotlin.Unit import kotlin.apply import kotlin.let import kotlin.takeIf import kotlin.to import org.pkl.commons.NameMapper import org.pkl.core.* import org.pkl.core.util.CodeGeneratorUtils import org.pkl.core.util.IoUtils class JavaCodeGeneratorException(message: String) : RuntimeException(message) @kotlin.Deprecated("renamed to JavaCodeGeneratorOptions", ReplaceWith("JavaCodeGeneratorOptions")) typealias JavaCodegenOptions = JavaCodeGeneratorOptions data class JavaCodeGeneratorOptions( /** The characters to use for indenting generated Java code. */ val indent: String = " ", /** Adds the `org.pkl.config.java.Generated` annotation to the classes to be generated. */ val addGeneratedAnnotation: Boolean = false, /** * Whether to generate public getter methods and protected final fields instead of public final * fields. */ val generateGetters: Boolean = false, /** Whether to preserve Pkl doc comments by generating corresponding Javadoc comments. */ val generateJavadoc: Boolean = false, /** Whether to generate config classes for use with Spring Boot. */ val generateSpringBootConfig: Boolean = false, /** * Fully qualified name of the annotation type to use for annotating constructor parameters with * their name. * * The specified annotation type must have a `value` parameter of type [java.lang.String] or the * generated code may not compile. * * If set to `null`, constructor parameters are not annotated. The default value is `null` if * [generateSpringBootConfig] is `true` and `"org.pkl.config.java.mapper.Named"` otherwise. */ val paramsAnnotation: String? = if (generateSpringBootConfig) null else "org.pkl.config.java.mapper.Named", /** * Fully qualified name of the annotation type to use for annotating non-null types. * * The specified annotation type must have a [java.lang.annotation.Target] of * [java.lang.annotation.ElementType.TYPE_USE] or the generated code may not compile. If set to * `null`, [org.pkl.config.java.mapper.NonNull] will be used. */ val nonNullAnnotation: String? = null, /** Whether to generate classes that implement [java.io.Serializable]. */ val implementSerializable: Boolean = false, /** * A mapping from Pkl module name prefixes to their replacements. * * Can be used when the class or package name in the generated source code should be different * from the corresponding name derived from the Pkl module declaration . */ val renames: Map = emptyMap(), ) /** Entrypoint for the Java code generator API. */ class JavaCodeGenerator( private val schema: ModuleSchema, private val codegenOptions: JavaCodeGeneratorOptions, ) { companion object { private val OBJECT = ClassName.get(Object::class.java) private val STRING = ClassName.get(String::class.java) private val DURATION = ClassName.get(Duration::class.java) private val DURATION_UNIT = ClassName.get(DurationUnit::class.java) private val DATA_SIZE = ClassName.get(DataSize::class.java) private val DATASIZE_UNIT = ClassName.get(DataSizeUnit::class.java) private val PAIR = ClassName.get(Pair::class.java) private val COLLECTION = ClassName.get(Collection::class.java) private val LIST = ClassName.get(List::class.java) private val SET = ClassName.get(Set::class.java) private val MAP = ClassName.get(Map::class.java) private val PMODULE = ClassName.get(PModule::class.java) private val PCLASS = ClassName.get(PClass::class.java) private val PATTERN = ClassName.get(Pattern::class.java) private val URI = ClassName.get(java.net.URI::class.java) private val VERSION = ClassName.get(Version::class.java) private const val PROPERTY_PREFIX: String = "org.pkl.config.java.mapper." private fun toClassName(fqn: String): ClassName { val index = fqn.lastIndexOf(".") if (index == -1) { throw JavaCodeGeneratorException( """ Annotation `$fqn` is not a valid Java class. The name of the annotation should be the canonical Java name of the class, for example, `com.example.Foo`. """ .trimIndent() ) } val packageName = fqn.substring(0, index) val classParts = fqn.substring(index + 1).split('$') return if (classParts.size == 1) { ClassName.get(packageName, classParts.first()) } else { ClassName.get(packageName, classParts.first(), *classParts.drop(1).toTypedArray()) } } } val output: Map get() { return mapOf(javaFileName to javaFile, propertyFileName to propertiesFile) } private val propertyFileName: String get() = "resources/META-INF/org/pkl/config/java/mapper/classes/${IoUtils.encodePath(schema.moduleName)}.properties" private val propertiesFile: String get() { val props = Properties() props["$PROPERTY_PREFIX${schema.moduleClass.qualifiedName}"] = schema.moduleClass.toJavaPoetName().reflectionName() for (pClass in schema.classes.values) { props["$PROPERTY_PREFIX${pClass.qualifiedName}"] = pClass.toJavaPoetName().reflectionName() } return StringWriter() .apply { props.store(this, "Java mappings for Pkl module `${schema.moduleName}`") } .toString() } private val nonNullAnnotation: AnnotationSpec get() { val annotation = codegenOptions.nonNullAnnotation val className = if (annotation == null) { ClassName.get("org.pkl.config.java.mapper", "NonNull") } else { toClassName(annotation) } return AnnotationSpec.builder(className).build() } private val javaFileName: String get() { val (packageName, className) = nameMapper.map(schema.moduleName) val dirPath = packageName.replace('.', '/') return if (dirPath.isEmpty()) { "java/$className.java" } else { "java/$dirPath/$className.java" } } val javaFile: String get() { if (schema.moduleUri.scheme == "pkl") { throw JavaCodeGeneratorException( "Cannot generate Java code for a Pkl standard library module (`${schema.moduleUri}`)." ) } val pModuleClass = schema.moduleClass val moduleClass = generateTypeSpec(pModuleClass, schema) for (pClass in schema.classes.values) { moduleClass.addType(generateTypeSpec(pClass, schema).build()) } for (typeAlias in schema.typeAliases.values) { val stringLiterals = mutableSetOf() if (CodeGeneratorUtils.isRepresentableAsEnum(typeAlias.aliasedType, stringLiterals)) { moduleClass.addType(generateEnumTypeSpec(typeAlias, stringLiterals).build()) } } // generate static append method for module classes w/o parent class; reuse in subclasses and // nested classes if (pModuleClass.superclass!!.info == PClassInfo.Module) { val modifier = if (pModuleClass.isOpen || pModuleClass.isAbstract) Modifier.PROTECTED else Modifier.PRIVATE moduleClass.addMethod(appendPropertyMethod().addModifiers(modifier).build()) } val (packageName, _) = nameMapper.map(schema.moduleName) return JavaFile.builder(packageName, moduleClass.build()) .indent(codegenOptions.indent) .build() .toString() } private fun generateTypeSpec(pClass: PClass, schema: ModuleSchema): TypeSpec.Builder { val isModuleClass = pClass == schema.moduleClass val javaPoetClassName = pClass.toJavaPoetName() val superclass = pClass.superclass?.takeIf { it.info != PClassInfo.Typed && it.info != PClassInfo.Module } val superProperties = superclass?.let { renameIfReservedWord(it.allProperties) }?.filterValues { !it.isHidden } ?: mapOf() val properties = renameIfReservedWord(pClass.properties).filterValues { !it.isHidden } val allProperties = superProperties + properties fun PClass.Property.isRegex(): Boolean = (this.type as? PType.Class)?.pClass?.info == PClassInfo.Regex fun addCtorParameter( builder: MethodSpec.Builder, propJavaName: String, property: PClass.Property, ) { val paramBuilder = ParameterSpec.builder(property.type.toJavaPoetName(), propJavaName) if (paramsAnnotationName != null) { paramBuilder.addAnnotation( AnnotationSpec.builder(paramsAnnotationName) .addMember("value", "\$S", property.simpleName) .build() ) } builder.addParameter(paramBuilder.build()) } fun generateConstructor(isInstantiable: Boolean): MethodSpec { val builder = MethodSpec.constructorBuilder() // choose most restrictive access modifier possible .addModifiers( when { isInstantiable -> Modifier.PUBLIC pClass.isAbstract || pClass.isOpen -> Modifier.PROTECTED else -> Modifier.PRIVATE } ) if (superProperties.isNotEmpty()) { for ((name, property) in superProperties) { if (properties.containsKey(name)) continue addCtorParameter(builder, name, property) } // $W inserts space or newline (automatic line wrapping) val callArgsStr = superProperties.keys.joinToString(",\$W") // use kotlin interpolation rather than javapoet $L interpolation // as otherwise the $W won't get processed builder.addStatement("super($callArgsStr)") } for ((name, property) in properties) { addCtorParameter(builder, name, property) builder.addStatement("this.\$N = \$N", name, name) } return builder.build() } fun generateEqualsMethod(): MethodSpec { val builder = MethodSpec.methodBuilder("equals") .addModifiers(Modifier.PUBLIC) .addAnnotation(Override::class.java) .addParameter(Object::class.java, "obj") .returns(Boolean::class.java) .addStatement("if (this == obj) return true") .addStatement("if (obj == null) return false") // generating this.getClass() instead of class literal avoids a SpotBugs warning .addStatement("if (this.getClass() != obj.getClass()) return false") .addStatement("\$T other = (\$T) obj", javaPoetClassName, javaPoetClassName) for ((propertyName, property) in allProperties) { val accessor = if (property.isRegex()) "\$N.pattern()" else "\$N" builder.addStatement( "if (!\$T.equals(this.$accessor, other.$accessor)) return false", Objects::class.java, propertyName, propertyName, ) } builder.addStatement("return true") return builder.build() } fun generateHashCodeMethod(): MethodSpec { val builder = MethodSpec.methodBuilder("hashCode") .addModifiers(Modifier.PUBLIC) .addAnnotation(Override::class.java) .returns(Int::class.java) .addStatement("int result = 1") for ((propertyName, property) in allProperties) { val accessor = if (property.isRegex()) "this.\$N.pattern()" else "this.\$N" builder.addStatement( "result = 31 * result + \$T.hashCode($accessor)", Objects::class.java, propertyName, ) } builder.addStatement("return result") return builder.build() } fun generateToStringMethod(): MethodSpec { val builder = MethodSpec.methodBuilder("toString") .addModifiers(Modifier.PUBLIC) .addAnnotation(Override::class.java) .returns(String::class.java) var builderSize = 50 val appendBuilder = CodeBlock.builder() for ((propertyName, property) in allProperties) { builderSize += 50 if (property.type.isBytesClass) { appendBuilder.addStatement( "appendProperty(builder, \$S, \$T.toString(this.\$N))", propertyName, Arrays::class.java, propertyName, ) } else { appendBuilder.addStatement( "appendProperty(builder, \$S, this.\$N)", propertyName, propertyName, ) } } builder .addStatement( "\$T builder = new \$T(\$L)", StringBuilder::class.java, StringBuilder::class.java, builderSize, ) .addStatement("builder.append(\$T.class.getSimpleName()).append(\" {\")", javaPoetClassName) .addCode(appendBuilder.build()) // not using $S here because it generates `"\n" + "{"` // with a line break in the generated code after `+` .addStatement("builder.append(\"\\n}\")") .addStatement("return builder.toString()") return builder.build() } // do the minimum work necessary to avoid (most) java compile errors // generating idiomatic Javadoc would require parsing doc comments, converting member links, // etc. fun renderAsJavadoc(docComment: String): String { val escaped = docComment.replace("*/", "*/") return if (escaped[escaped.length - 1] != '\n') escaped + '\n' else escaped } fun generateDeprecation( annotations: Collection, hasJavadoc: Boolean, addAnnotation: (Class<*>) -> Unit, addJavadoc: (String) -> Unit, ) { annotations .firstOrNull { it.classInfo == PClassInfo.Deprecated } ?.let { deprecation -> addAnnotation(Deprecated::class.java) if (codegenOptions.generateJavadoc) { (deprecation["message"] as String?)?.let { if (hasJavadoc) { addJavadoc("\n") } addJavadoc(renderAsJavadoc("@deprecated $it")) } } } } fun generateField(propertyName: String, property: PClass.Property): FieldSpec { val builder = FieldSpec.builder(property.type.toJavaPoetName(), propertyName) val docComment = property.docComment val hasJavadoc = docComment != null && codegenOptions.generateJavadoc && !codegenOptions.generateGetters if (hasJavadoc) { builder.addJavadoc(renderAsJavadoc(docComment!!)) } if (codegenOptions.generateGetters) { builder.addModifiers( if (pClass.isAbstract || pClass.isOpen) Modifier.PROTECTED else Modifier.PRIVATE ) } else { generateDeprecation( property.annotations, hasJavadoc, { builder.addAnnotation(it) }, { builder.addJavadoc(it) }, ) builder.addModifiers(Modifier.PUBLIC) } builder.addModifiers(Modifier.FINAL) return builder.build() } @Suppress("DuplicatedCode") fun generateGetter( propertyName: String, property: PClass.Property, isOverridden: Boolean, ): MethodSpec { val propertyType = property.type val isBooleanProperty = propertyType is PType.Class && propertyType.pClass.info == PClassInfo.Boolean val methodName = (if (isBooleanProperty) "is" else "get") + // can use original name here (property.name rather than propertyName) // because getter name cannot possibly conflict with reserved words property.simpleName.replaceFirstChar { it.titlecaseChar() } val builder = MethodSpec.methodBuilder(methodName) .addModifiers(Modifier.PUBLIC) .returns(propertyType.toJavaPoetName()) .addStatement("return \$N", propertyName) if (isOverridden) { builder.addAnnotation(Override::class.java) } val docComment = property.docComment val hasJavadoc = docComment != null && codegenOptions.generateJavadoc if (hasJavadoc) { builder.addJavadoc(renderAsJavadoc(docComment!!)) } generateDeprecation( property.annotations, hasJavadoc, { builder.addAnnotation(it) }, { builder.addJavadoc(it) }, ) return builder.build() } fun generateWithMethod(propertyName: String, property: PClass.Property): MethodSpec { val methodName = "with" + property.simpleName.replaceFirstChar { it.titlecaseChar() } val methodBuilder = MethodSpec.methodBuilder(methodName) .addModifiers(Modifier.PUBLIC) .addParameter(property.type.toJavaPoetName(), propertyName) .returns(javaPoetClassName) generateDeprecation( property.annotations, false, { methodBuilder.addAnnotation(it) }, { methodBuilder.addJavadoc(it) }, ) val codeBuilder = CodeBlock.builder() codeBuilder.add("return new \$T(", javaPoetClassName) var firstProperty = true for (name in superProperties.keys) { if (name in properties) continue if (firstProperty) { firstProperty = false codeBuilder.add("\$N", name) } else { codeBuilder.add(", \$N", name) } } for (name in properties.keys) { if (firstProperty) { firstProperty = false codeBuilder.add("\$N", name) } else { codeBuilder.add(", \$N", name) } } codeBuilder.add(");\n") methodBuilder.addCode(codeBuilder.build()) return methodBuilder.build() } fun generateSpringBootAnnotations(builder: TypeSpec.Builder) { if (isModuleClass) { builder.addAnnotation( ClassName.get("org.springframework.boot.context.properties", "ConfigurationProperties") ) } else { // not very efficient to repeat computing module property base types for every class val modulePropertiesWithMatchingType = schema.moduleClass.allProperties.values.filter { property -> var propertyType = property.type while (propertyType is PType.Constrained || propertyType is PType.Nullable) { if (propertyType is PType.Constrained) { propertyType = propertyType.baseType } else if (propertyType is PType.Nullable) { propertyType = propertyType.baseType } } propertyType is PType.Class && propertyType.pClass == pClass } if (modulePropertiesWithMatchingType.size == 1) { // exactly one module property has this type -> make it available for direct injection // (potential improvement: make type available for direct injection if it occurs exactly // once in property tree) builder.addAnnotation( AnnotationSpec.builder( ClassName.get( "org.springframework.boot.context.properties", "ConfigurationProperties", ) ) // use "value" instead of "prefix" to entice JavaPoet to generate a single-line // annotation // that can easily be filtered out by JavaCodeGeneratorTest.`spring boot config` .addMember("value", "\$S", modulePropertiesWithMatchingType.first().simpleName) .build() ) } } } @Suppress("DuplicatedCode") fun generateClass(): TypeSpec.Builder { val builder = TypeSpec.classBuilder(javaPoetClassName.simpleName()).addModifiers(Modifier.PUBLIC) if (codegenOptions.addGeneratedAnnotation) { val name = ClassName.get("org.pkl.config.java", "Generated") val generated = AnnotationSpec.builder(name).build() builder.addAnnotation(generated) } // stateless final module classes are non-instantiable by choice val isInstantiable = !(pClass.isAbstract || (isModuleClass && !pClass.isOpen && allProperties.isEmpty())) if (codegenOptions.implementSerializable && isInstantiable) { builder.addSuperinterface(java.io.Serializable::class.java) builder.addField(generateSerialVersionUIDField()) } val docComment = pClass.docComment val hasJavadoc = docComment != null && codegenOptions.generateJavadoc if (hasJavadoc) { builder.addJavadoc(renderAsJavadoc(docComment!!)) } generateDeprecation( pClass.annotations, hasJavadoc, { builder.addAnnotation(it) }, { builder.addJavadoc(it) }, ) if (!isModuleClass) { builder.addModifiers(Modifier.STATIC) } if (pClass.isAbstract) { builder.addModifiers(Modifier.ABSTRACT) } else if (!pClass.isOpen) { builder.addModifiers(Modifier.FINAL) } if (codegenOptions.generateSpringBootConfig) { generateSpringBootAnnotations(builder) } builder.addMethod(generateConstructor(isInstantiable)) superclass?.let { builder.superclass(it.toJavaPoetName()) } // generate fields, plus getter methods and either setters or `with` methods in alternating // order // `with` methods also need to be generated for superclass properties so that return type is // self type for ((name, property) in allProperties) { if (name in properties) { builder.addField(generateField(name, property)) if (codegenOptions.generateGetters) { val isOverridden = name in superProperties builder.addMethod(generateGetter(name, property, isOverridden)) } } if (isInstantiable) { builder.addMethod(generateWithMethod(name, property)) } } if (isInstantiable) { builder .addMethod(generateEqualsMethod()) .addMethod(generateHashCodeMethod()) .addMethod(generateToStringMethod()) } return builder } return generateClass() } private fun generateSerialVersionUIDField(): FieldSpec { return FieldSpec.builder(Long::class.java, "serialVersionUID", Modifier.PRIVATE) .addModifiers(Modifier.STATIC, Modifier.FINAL) .initializer("0L") .build() } private fun generateEnumTypeSpec( typeAlias: TypeAlias, stringLiterals: Set, ): TypeSpec.Builder { val enumConstantToPklNames = stringLiterals .groupingBy { literal -> CodeGeneratorUtils.toEnumConstantName(literal) ?: throw JavaCodeGeneratorException( "Cannot generate Java enum class for Pkl type alias `${typeAlias.displayName}` " + "because string literal type \"$literal\" cannot be converted to a valid enum constant name." ) } .reduce { enumConstantName, firstLiteral, secondLiteral -> throw JavaCodeGeneratorException( "Cannot generate Java enum class for Pkl type alias `${typeAlias.displayName}` " + "because string literal types \"$firstLiteral\" and \"$secondLiteral\" " + "would both be converted to enum constant name `$enumConstantName`." ) } val builder = TypeSpec.enumBuilder(typeAlias.simpleName) .addModifiers(Modifier.PUBLIC) .addField(String::class.java, "value", Modifier.PRIVATE) .addMethod( MethodSpec.constructorBuilder() .addModifiers(Modifier.PRIVATE) .addParameter(String::class.java, "value") .addStatement("this.value = value") .build() ) .addMethod( MethodSpec.methodBuilder("toString") .addModifiers(Modifier.PUBLIC) .addAnnotation(Override::class.java) .returns(String::class.java) .addStatement("return this.value") .build() ) for ((enumConstantName, pklName) in enumConstantToPklNames) { builder.addEnumConstant( enumConstantName, TypeSpec.anonymousClassBuilder("\$S", pklName).build(), ) } return builder } private val paramsAnnotationName: ClassName? = codegenOptions.paramsAnnotation?.let { toClassName(it) } private fun appendPropertyMethod() = MethodSpec.methodBuilder("appendProperty") .addModifiers(Modifier.STATIC) .addParameter(StringBuilder::class.java, "builder") .addParameter(String::class.java, "name") .addParameter(Object::class.java, "value") .addStatement("builder.append(\"\\n \").append(name).append(\" = \")") .addStatement( "\$T lines = \$T.toString(value).split(\"\\n\")", ArrayTypeName.of(String::class.java), Objects::class.java, ) .addStatement("builder.append(lines[0])") .beginControlFlow("for (int i = 1; i < lines.length; i++)") .addStatement("builder.append(\"\\n \").append(lines[i])") .endControlFlow() private fun PClass.toJavaPoetName(): ClassName { val (packageName, moduleClassName) = nameMapper.map(moduleName) return if (isModuleClass) { ClassName.get(packageName, moduleClassName) } else { ClassName.get(packageName, moduleClassName, simpleName) } } // generated type is a nested enum class private fun TypeAlias.toJavaPoetName(): ClassName { val (packageName, moduleClassName) = nameMapper.map(moduleName) return ClassName.get(packageName, moduleClassName, simpleName) } /** Generate `List` if `Foo` is `abstract` or `open`, to allow subclassing. */ private fun PType.toJavaPoetTypeArgumentName(): TypeName { val baseName = toJavaPoetName(boxed = true) return if (this is PType.Class && (pClass.isAbstract || pClass.isOpen)) { WildcardTypeName.subtypeOf(baseName) } else { baseName } } private val PType.isBytesClass: Boolean get() = this is PType.Class && this.pClass.info == PClassInfo.Bytes private fun PType.toJavaPoetName(nullable: Boolean = false, boxed: Boolean = false): TypeName = when (this) { PType.UNKNOWN -> OBJECT.nullableIf(nullable) PType.NOTHING -> TypeName.VOID is PType.StringLiteral -> STRING.nullableIf(nullable) is PType.Class -> { // if in doubt, spell it out when (val classInfo = pClass.info) { PClassInfo.Any -> OBJECT PClassInfo.Typed, PClassInfo.Dynamic -> OBJECT.nullableIf(nullable) PClassInfo.Boolean -> TypeName.BOOLEAN.boxIf(boxed).nullableIf(nullable) PClassInfo.String -> STRING.nullableIf(nullable) // seems more useful to generate `double` than `java.lang.Number` PClassInfo.Number -> TypeName.DOUBLE.boxIf(boxed).nullableIf(nullable) PClassInfo.Int -> TypeName.LONG.boxIf(boxed).nullableIf(nullable) PClassInfo.Float -> TypeName.DOUBLE.boxIf(boxed).nullableIf(nullable) PClassInfo.Duration -> DURATION.nullableIf(nullable) PClassInfo.DataSize -> DATA_SIZE.nullableIf(nullable) PClassInfo.Bytes -> ArrayTypeName.of(TypeName.BYTE) PClassInfo.Pair -> ParameterizedTypeName.get( PAIR, if (typeArguments.isEmpty()) { OBJECT } else { typeArguments[0].toJavaPoetTypeArgumentName() }, if (typeArguments.isEmpty()) { OBJECT } else { typeArguments[1].toJavaPoetTypeArgumentName() }, ) .nullableIf(nullable) PClassInfo.Collection -> ParameterizedTypeName.get( COLLECTION, if (typeArguments.isEmpty()) { OBJECT } else { typeArguments[0].toJavaPoetTypeArgumentName() }, ) .nullableIf(nullable) PClassInfo.List, PClassInfo.Listing -> { ParameterizedTypeName.get( LIST, if (typeArguments.isEmpty()) { OBJECT } else { typeArguments[0].toJavaPoetTypeArgumentName() }, ) .nullableIf(nullable) } PClassInfo.Set -> ParameterizedTypeName.get( SET, if (typeArguments.isEmpty()) { OBJECT } else { typeArguments[0].toJavaPoetTypeArgumentName() }, ) .nullableIf(nullable) PClassInfo.Map, PClassInfo.Mapping -> ParameterizedTypeName.get( MAP, if (typeArguments.isEmpty()) { OBJECT } else { typeArguments[0].toJavaPoetTypeArgumentName() }, if (typeArguments.isEmpty()) { OBJECT } else { typeArguments[1].toJavaPoetTypeArgumentName() }, ) .nullableIf(nullable) PClassInfo.Module -> PMODULE.nullableIf(nullable) PClassInfo.Class -> PCLASS.nullableIf(nullable) PClassInfo.Regex -> PATTERN.nullableIf(nullable) PClassInfo.Version -> VERSION.nullableIf(nullable) else -> when { !classInfo.isStandardLibraryClass -> pClass.toJavaPoetName().nullableIf(nullable) else -> throw JavaCodeGeneratorException( "Standard library class `${pClass.qualifiedName}` is not supported by Java code generator. " + "If you think this is an omission, please let us know." ) } } } is PType.Nullable -> baseType.toJavaPoetName(nullable = true, boxed = true) is PType.Constrained -> baseType.toJavaPoetName(nullable = nullable, boxed = boxed) is PType.Alias -> when (typeAlias.qualifiedName) { "pkl.base#NonNull" -> OBJECT.nullableIf(nullable) "pkl.base#Int8" -> TypeName.BYTE.boxIf(boxed).nullableIf(nullable) "pkl.base#Int16", "pkl.base#UInt8" -> TypeName.SHORT.boxIf(boxed).nullableIf(nullable) "pkl.base#Int32", "pkl.base#UInt16" -> TypeName.INT.boxIf(boxed).nullableIf(nullable) "pkl.base#UInt", "pkl.base#UInt32" -> TypeName.LONG.boxIf(boxed).nullableIf(nullable) "pkl.base#DurationUnit" -> DURATION_UNIT.nullableIf(nullable) "pkl.base#DataSizeUnit" -> DATASIZE_UNIT.nullableIf(nullable) "pkl.base#Uri" -> URI.nullableIf(nullable) else -> { if (CodeGeneratorUtils.isRepresentableAsEnum(aliasedType, null)) { if (typeAlias.isStandardLibraryMember) { throw JavaCodeGeneratorException( "Standard library typealias `${typeAlias.qualifiedName}` is not supported by Java code generator. " + "If you think this is an omission, please let us know." ) } else { // reference generated enum class typeAlias.toJavaPoetName().nullableIf(nullable) } } else { // inline type alias aliasedType.toJavaPoetName(nullable) } } } is PType.Function -> throw JavaCodeGeneratorException( "Pkl function types are not supported by the Java code generator." ) is PType.Union -> if (CodeGeneratorUtils.isRepresentableAsString(this)) STRING.nullableIf(nullable) else throw JavaCodeGeneratorException( "Pkl union types are not supported by the Java code generator." ) else -> // should never encounter PType.TypeVariableNode because it can only occur in stdlib classes throw AssertionError("Encountered unexpected PType subclass: $this") } private fun TypeName.nullableIf(isNullable: Boolean): TypeName = if (isPrimitive && isNullable) box() else if (isPrimitive || isNullable) this else annotated(nonNullAnnotation) private fun TypeName.boxIf(shouldBox: Boolean): TypeName = if (shouldBox) box() else this private fun renameIfReservedWord(map: Map): Map { return map.mapKeys { (key, _) -> if (key in javaReservedWords) { generateSequence("_$key") { "_$it" }.first { it !in map.keys } } else key } } private val nameMapper = NameMapper(codegenOptions.renames) } internal val javaReservedWords = setOf( "_", // java 9+ "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "default", "double", "do", "else", "enum", "extends", "false", "final", "finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long", "native", "new", "null", "package", "private", "protected", "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "true", "try", "void", "volatile", "while", ) ================================================ FILE: pkl-codegen-java/src/main/kotlin/org/pkl/codegen/java/Main.kt ================================================ /* * Copyright © 2024-2025 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. */ @file:JvmName("Main") package org.pkl.codegen.java import com.github.ajalt.clikt.core.main import com.github.ajalt.clikt.parameters.options.* import com.github.ajalt.clikt.parameters.types.path import java.nio.file.Path import org.pkl.commons.cli.CliBaseOptions import org.pkl.commons.cli.cliMain import org.pkl.commons.cli.commands.ModulesCommand import org.pkl.commons.cli.commands.installCommonOptions import org.pkl.commons.toPath import org.pkl.core.Release /** Main method for the Java code generator CLI. */ internal fun main(args: Array) { cliMain { PklJavaCodegenCommand().main(args) } } internal val helpLink = "${Release.current().documentation.homepage}java-binding/codegen.html#cli" class PklJavaCodegenCommand : ModulesCommand(name = "pkl-codegen-java", helpLink) { private val defaults = CliJavaCodeGeneratorOptions(CliBaseOptions(), "".toPath()) private val outputDir: Path by option( names = arrayOf("-o", "--output-dir"), metavar = "path", help = "The directory where generated source code is placed.", ) .path() .default(defaults.outputDir) private val indent: String by option( names = arrayOf("--indent"), metavar = "chars", help = "The characters to use for indenting generated source code.", ) .default(defaults.indent) private val addGeneratedAnnotation: Boolean by option( names = arrayOf("--add-generated-annotation"), help = "Whether to add a @Generated annotation to the types to be generated.", ) .flag() private val generateGetters: Boolean by option( names = arrayOf("--generate-getters"), help = "Whether to generate public getter methods and " + "private final fields instead of public final fields.", ) .flag() private val generateJavadoc: Boolean by option( names = arrayOf("--generate-javadoc"), help = "Whether to preserve Pkl doc comments by generating corresponding Javadoc comments.", ) .flag() private val generateSpringBoot: Boolean by option( names = arrayOf("--generate-spring-boot"), help = "Whether to generate config classes for use with Spring Boot.", ) .flag() private val paramsAnnotation: String by option( names = arrayOf("--params-annotation"), help = "Fully qualified name of the annotation type to use for annotating constructor parameters with their name.", ) .defaultLazy( "`none` if `--generate-spring-boot` is set, `org.pkl.config.java.mapper.Named` otherwise" ) { if (generateSpringBoot) "none" else "org.pkl.config.java.mapper.Named" } private val nonNullAnnotation: String? by option( names = arrayOf("--non-null-annotation"), help = """ Fully qualified name of the annotation type to use for annotating non-null types. The specified annotation type must be annotated with `@java.lang.annotation.Target(ElementType.TYPE_USE)` or the generated code may not compile. """ .trimIndent(), ) private val implementSerializable: Boolean by option( names = arrayOf("--implement-serializable"), help = "Whether to generate classes that implement java.io.Serializable.", ) .flag() private val renames: Map by option( names = arrayOf("--rename"), metavar = "old_name=new_name", help = """ Replace a prefix in the names of the generated Java classes (repeatable). By default, the names of generated classes are derived from the Pkl module names. With this option, you can override or modify the default names, renaming entire classes or just their packages. """ .trimIndent(), ) .associate() override val helpString: String = "Generate Java classes and interfaces from Pkl module(s)" override fun run() { val options = CliJavaCodeGeneratorOptions( base = baseOptions.baseOptions(modules, projectOptions), outputDir = outputDir, indent = indent, addGeneratedAnnotation = addGeneratedAnnotation, generateGetters = generateGetters, generateJavadoc = generateJavadoc, generateSpringBootConfig = generateSpringBoot, paramsAnnotation = if (paramsAnnotation == "none") null else paramsAnnotation, nonNullAnnotation = nonNullAnnotation, implementSerializable = implementSerializable, renames = renames, ) CliJavaCodeGenerator(options).run() } init { installCommonOptions() } } ================================================ FILE: pkl-codegen-java/src/test/kotlin/org/pkl/codegen/java/CliJavaCodeGeneratorTest.kt ================================================ /* * Copyright © 2024-2025 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.codegen.java import java.nio.file.Path import kotlin.io.path.listDirectoryEntries import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir import org.pkl.commons.cli.CliBaseOptions import org.pkl.commons.readString class CliJavaCodeGeneratorTest { private val dollar = "$" @Test fun `module inheritance`(@TempDir tempDir: Path) { val module1 = PklModule( "org.mod1", """ open module org.mod1 pigeon: Person class Person { name: String age: Int } """, ) val module2 = PklModule( "org.mod2", """ module org.mod2 extends "mod1.pkl" parrot: Person """, ) val module1File = module1.writeToDisk(tempDir.resolve("org/mod1.pkl")) val module2File = module2.writeToDisk(tempDir.resolve("org/mod2.pkl")) val outputDir = tempDir.resolve("output") val generator = CliJavaCodeGenerator( CliJavaCodeGeneratorOptions( CliBaseOptions(listOf(module1File.toUri(), module2File.toUri())), outputDir, ) ) generator.run() val javaDir = outputDir.resolve("java") val moduleJavaFiles = javaDir.resolve("org").listDirectoryEntries() assertThat(moduleJavaFiles.map { it.fileName.toString() }) .containsExactlyInAnyOrder("Mod1.java", "Mod2.java") val module1JavaFile = javaDir.resolve("org/Mod1.java") assertContains( """ |public class Mod1 { | public final @NonNull Person pigeon; """, module1JavaFile.readString(), ) val module2JavaFile = javaDir.resolve("org/Mod2.java") assertContains( """ |public final class Mod2 extends Mod1 { | public final Mod1. @NonNull Person parrot; """, module2JavaFile.readString(), ) val resourcesDir = outputDir.resolve("resources/META-INF/org/pkl/config/java/mapper/classes/") val module1PropertiesFile = resourcesDir.resolve("org.mod1.properties") val module1PropertiesString = module1PropertiesFile.readString() // use two assertions because java.util.Properties doesn't guarantee order assertContains( """org.pkl.config.java.mapper.org.mod1\#Person=org.Mod1${dollar}Person""", module1PropertiesString, ) assertContains( """org.pkl.config.java.mapper.org.mod1\#ModuleClass=org.Mod1""", module1PropertiesString, ) val module2PropertiesFile = resourcesDir.resolve("org.mod2.properties") assertContains( """org.pkl.config.java.mapper.org.mod2\#ModuleClass=org.Mod2""", module2PropertiesFile.readString(), ) } @Test fun `class name clashes`(@TempDir tempDir: Path) { val module1 = PklModule( "org.mod1", """ module org.mod1 class Person { name: String } """, ) val module2 = PklModule( "org.mod2", """ module org.mod2 import "mod1.pkl" person1: mod1.Person person2: Person class Person { age: Int } """, ) val module1PklFile = module1.writeToDisk(tempDir.resolve("org/mod1.pkl")) val module2PklFile = module2.writeToDisk(tempDir.resolve("org/mod2.pkl")) val outputDir = tempDir.resolve("output") val generator = CliJavaCodeGenerator( CliJavaCodeGeneratorOptions( CliBaseOptions(listOf(module1PklFile.toUri(), module2PklFile.toUri())), outputDir, ) ) generator.run() val module2JavaFile = outputDir.resolve("java/org/Mod2.java") assertContains( """ |public final class Mod2 { | public final Mod1. @NonNull Person person1; | | public final @NonNull Person person2; """, module2JavaFile.readString(), ) } @Test fun `custom package names`(@TempDir tempDir: Path) { val module1 = PklModule( "org.foo.Module1", """ module org.foo.Module1 class Person { name: String } """, ) val module2 = PklModule( "org.bar.Module2", """ module org.bar.Module2 import "../../org/foo/Module1.pkl" class Group { owner: Module1.Person name: String } """, ) val module3 = PklModule( "org.baz.Module3", """ module org.baz.Module3 import "../../org/bar/Module2.pkl" class Supergroup { owner: Module2.Group } """, ) val module1PklFile = module1.writeToDisk(tempDir.resolve("org/foo/Module1.pkl")) val module2PklFile = module2.writeToDisk(tempDir.resolve("org/bar/Module2.pkl")) val module3PklFile = module3.writeToDisk(tempDir.resolve("org/baz/Module3.pkl")) val outputDir = tempDir.resolve("output") val generator = CliJavaCodeGenerator( CliJavaCodeGeneratorOptions( CliBaseOptions(listOf(module1PklFile, module2PklFile, module3PklFile).map { it.toUri() }), outputDir, renames = mapOf("org.foo" to "com.foo.x", "org.baz" to "com.baz.a.b"), ) ) generator.run() val module1JavaFile = outputDir.resolve("java/com/foo/x/Module1.java") module1JavaFile.readString().let { assertContains("package com.foo.x;", it) assertContains("public final class Module1 {", it) assertContains( """ | public static final class Person { | public final @NonNull String name; """, it, ) } val module2JavaFile = outputDir.resolve("java/org/bar/Module2.java") module2JavaFile.readString().let { assertContains("package org.bar;", it) assertContains("import com.foo.x.Module1;", it) assertContains("public final class Module2 {", it) assertContains( """ | public static final class Group { | public final Module1. @NonNull Person owner; """, it, ) } val module3JavaFile = outputDir.resolve("java/com/baz/a/b/Module3.java") module3JavaFile.readString().let { assertContains("package com.baz.a.b;", it) assertContains("import org.bar.Module2;", it) assertContains("public final class Module3 {", it) assertContains( """ | public static final class Supergroup { | public final Module2. @NonNull Group owner; """, it, ) } } private fun assertContains(part: String, code: String) { val trimmedPart = part.trim().trimMargin() if (!code.contains(trimmedPart)) { // check for equality to get better error output (ide diff dialog) assertThat(code).isEqualTo(trimmedPart) } } } ================================================ FILE: pkl-codegen-java/src/test/kotlin/org/pkl/codegen/java/InMemoryJavaCompiler.kt ================================================ /* * Copyright © 2024-2025 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.codegen.java import java.io.ByteArrayOutputStream import java.io.InputStream import java.io.OutputStream import java.net.URI import javax.tools.* class CompilationFailedException(msg: String) : RuntimeException(msg) object InMemoryJavaCompiler { fun compile(sourceFiles: Map): Map> { val compiler = ToolProvider.getSystemJavaCompiler() val diagnosticsCollector = DiagnosticCollector() val fileManager = InMemoryFileManager(compiler.getStandardFileManager(diagnosticsCollector, null, null)) val sourceObjects = sourceFiles .filter { (filename, _) -> filename.endsWith(".java") } .map { (filename, contents) -> ReadableSourceFileObject(filename, contents) } val task = compiler.getTask(null, fileManager, diagnosticsCollector, null, null, sourceObjects) val result = task.call() if (!result) { throw CompilationFailedException( buildString { appendLine("Compilation failed. Error(s):") for (diagnostic in diagnosticsCollector.diagnostics) { appendLine(diagnostic.getMessage(null)) } } ) } val loader = ClassFileObjectLoader(fileManager.outputFiles) return fileManager.outputFiles.mapValues { loader.loadClass(it.key) } } } private class ClassFileObjectLoader(val fileObjects: Map) : ClassLoader(ClassFileObjectLoader::class.java.classLoader) { override fun findClass(name: String): Class<*> { val obj = fileObjects[name] if (obj == null || obj.kind != JavaFileObject.Kind.CLASS) { throw ClassNotFoundException(name) } val array = obj.out.toByteArray() return defineClass(name, array, 0, array.size) } } private class ReadableSourceFileObject(path: String, private val contents: String) : SimpleJavaFileObject(URI(path), JavaFileObject.Kind.SOURCE) { override fun openInputStream(): InputStream = contents.byteInputStream() override fun getCharContent(ignoreEncodingErrors: Boolean): CharSequence = contents } private class WritableBinaryFileObject(className: String, kind: JavaFileObject.Kind) : SimpleJavaFileObject(URI("/${className.replace(".", "/")}.${kind.extension}"), kind) { val out = ByteArrayOutputStream() override fun openOutputStream(): OutputStream = out } private class InMemoryFileManager(delegate: JavaFileManager) : ForwardingJavaFileManager(delegate) { val outputFiles = mutableMapOf() override fun getJavaFileForOutput( location: JavaFileManager.Location, className: String, kind: JavaFileObject.Kind, sibling: FileObject, ): JavaFileObject { return WritableBinaryFileObject(className, kind).also { outputFiles[className] = it } } } ================================================ FILE: pkl-codegen-java/src/test/kotlin/org/pkl/codegen/java/JavaCodeGeneratorTest.kt ================================================ /* * Copyright © 2024-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.codegen.java import java.io.* import java.nio.file.Path import java.util.function.Consumer import java.util.regex.Pattern import org.assertj.core.api.AbstractAssert import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatCode import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertAll import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.io.TempDir import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource import org.pkl.core.* import org.pkl.core.ModuleSource.path import org.pkl.core.ModuleSource.text import org.pkl.core.util.IoUtils class JavaCodeGeneratorTest { companion object { private const val MAPPER_PREFIX = "resources/META-INF/org/pkl/config/java/mapper/classes" private val simpleClass: Class<*> by lazy { generateJavaCode( """ module my.mod class Simple { str: String list: List } """ .trimIndent() ) .compile() .getValue("my.Mod\$Simple") } private val propertyTypesSources: JavaSourceCode by lazy { generateJavaCode( """ module my.mod class PropertyTypes { boolean: Boolean int: Int float: Float string: String duration: Duration durationUnit: DurationUnit dataSize: DataSize dataSizeUnit: DataSizeUnit nullable: String? nullable2: String? pair: Pair pair2: Pair coll: Collection coll2: Collection list: List list2: List set: Set set2: Set map: Map map2: Map container: Mapping container2: Mapping other: Other regex: Regex any: Any nonNull: NonNull enum: Direction bytes: Bytes } class Other { name: String } typealias Direction = "north"|"east"|"south"|"west" """ ) } private val propertyTypesClasses: Map> by lazy { propertyTypesSources.compile() } private fun generateJavaCode( pklCode: String, options: JavaCodeGeneratorOptions = JavaCodeGeneratorOptions(), ): JavaSourceCode { val module = Evaluator.preconfigured().evaluateSchema(text(pklCode)) val generator = JavaCodeGenerator(module, options) return JavaSourceCode(generator.javaFile) } } @TempDir lateinit var tempDir: Path @Test fun testEquals() { val ctor = simpleClass.constructors.first() val instance1 = ctor.newInstance("foo", listOf(1, 2, 3)) val instance2 = ctor.newInstance("foo", listOf(1, 2, 3)) val instance3 = ctor.newInstance("foo", listOf(1, 3, 2)) val instance4 = ctor.newInstance("bar", listOf(1, 2, 3)) assertThat(instance1).isEqualTo(instance1).isEqualTo(instance2) assertThat(instance2).isEqualTo(instance1) assertThat(instance3).isNotEqualTo(instance1) assertThat(instance4).isNotEqualTo(instance1) } @Test fun testHashCode() { val ctor = simpleClass.constructors.first() val instance1 = ctor.newInstance("foo", listOf(1, 2, 3)) val instance2 = ctor.newInstance("foo", listOf(1, 2, 3)) val instance3 = ctor.newInstance("foo", listOf(1, 3, 2)) val instance4 = ctor.newInstance("bar", listOf(1, 2, 3)) assertThat(instance1.hashCode()).isEqualTo(instance1.hashCode()).isEqualTo(instance2.hashCode()) assertThat(instance3.hashCode()).isNotEqualTo(instance1.hashCode()) assertThat(instance4.hashCode()).isNotEqualTo(instance1.hashCode()) } @Test fun testToString() { val (_, propertyTypes) = instantiateOtherAndPropertyTypes() assertThat(propertyTypes.toString()) .isEqualTo( """ PropertyTypes { _boolean = true _int = 42 _float = 42.3 string = string duration = 5.min durationUnit = min dataSize = 3.gb dataSizeUnit = gb nullable = idea nullable2 = null pair = Pair(1, 2) pair2 = Pair(pigeon, Other { name = pigeon }) coll = [1, 2, 3] coll2 = [Other { name = pigeon }, Other { name = pigeon }] list = [1, 2, 3] list2 = [Other { name = pigeon }, Other { name = pigeon }] set = [1, 2, 3] set2 = [Other { name = pigeon }] map = {1=one, 2=two} map2 = {one=Other { name = pigeon }, two=Other { name = pigeon }} container = {1=one, 2=two} container2 = {one=Other { name = pigeon }, two=Other { name = pigeon }} other = Other { name = pigeon } regex = (i?)\w* any = Other { name = pigeon } nonNull = Other { name = pigeon } _enum = north bytes = [1, 2, 3, 4] } """ .trimIndent() ) } @Test fun `deprecated property with message`() { val javaCode = generateJavaCode( """ class ClassWithDeprecatedProperty { @Deprecated { message = "property deprecation message" } deprecatedProperty: Int = 1337 } """ .trimIndent(), JavaCodeGeneratorOptions(generateJavadoc = true), ) assertThat(javaCode) .contains( """ | public static final class ClassWithDeprecatedProperty { | /** | * @deprecated property deprecation message | */ | @Deprecated | public final long deprecatedProperty; """ .trimMargin() ) .contains( """ | /** | * @deprecated property deprecation message | */ | @Deprecated | public ClassWithDeprecatedProperty withDeprecatedProperty(long deprecatedProperty) { | return new ClassWithDeprecatedProperty(deprecatedProperty); | } """ .trimMargin() ) } @Test fun `deprecated property's getter with message`() { val javaCode = generateJavaCode( """ class ClassWithDeprecatedProperty { @Deprecated { message = "property deprecation message" } deprecatedProperty: Int = 1337 } """ .trimIndent(), JavaCodeGeneratorOptions(generateGetters = true, generateJavadoc = true), ) assertThat(javaCode) .contains( """ | public static final class ClassWithDeprecatedProperty { | private final long deprecatedProperty; """ .trimMargin() ) .contains( """ | /** | * @deprecated property deprecation message | */ | @Deprecated | public long getDeprecatedProperty() { | return deprecatedProperty; | } """ .trimMargin() ) .contains( """ | /** | * @deprecated property deprecation message | */ | @Deprecated | public ClassWithDeprecatedProperty withDeprecatedProperty(long deprecatedProperty) { | return new ClassWithDeprecatedProperty(deprecatedProperty); | } """ .trimMargin() ) } @Test fun `deprecated class with message`() { val javaCode = generateJavaCode( """ @Deprecated { message = "class deprecation message" } class DeprecatedClass { propertyOfDeprecatedClass: Int = 42 } """ .trimIndent(), JavaCodeGeneratorOptions(generateJavadoc = true), ) assertThat(javaCode) .contains( """ | /** | * @deprecated class deprecation message | */ | @Deprecated | public static final class DeprecatedClass { """ .trimMargin() ) } @ParameterizedTest @ValueSource(booleans = [false, true]) fun `deprecated module class with message`(generateJavadoc: Boolean) { val javaCode = generateJavaCode( """ @Deprecated{ message = "module class deprecation message" } module DeprecatedModule propertyInDeprecatedModuleClass : Int = 42 """ .trimIndent(), JavaCodeGeneratorOptions(generateJavadoc = generateJavadoc), ) assertThat(javaCode) .contains( """ |@Deprecated |public final class DeprecatedModule { """ .trimMargin() ) if (generateJavadoc) { assertThat(javaCode) .contains( """ |/** | * @deprecated module class deprecation message | */ """ .trimMargin() ) } else { assertThat(javaCode).doesNotContain("* @deprecated") } } @ParameterizedTest @ValueSource(booleans = [false, true]) fun `deprecated property`(generateJavadoc: Boolean) { val javaCode = generateJavaCode( """ class ClassWithDeprecatedProperty { @Deprecated deprecatedProperty: Int = 1337 } """ .trimIndent(), // no message, so no Javadoc, regardless of flag JavaCodeGeneratorOptions(generateJavadoc = generateJavadoc), ) assertThat(javaCode) .contains( """ | public static final class ClassWithDeprecatedProperty { | @Deprecated | public final long deprecatedProperty; """ .trimMargin() ) .contains( """ | @Deprecated | public ClassWithDeprecatedProperty withDeprecatedProperty(long deprecatedProperty) { | return new ClassWithDeprecatedProperty(deprecatedProperty); | } """ .trimMargin() ) .doesNotContain("* @deprecated") } @Test fun `deprecated property's getter`() { val javaCode = generateJavaCode( """ class ClassWithDeprecatedProperty { @Deprecated deprecatedProperty: Int = 1337 } """ .trimIndent(), JavaCodeGeneratorOptions(generateGetters = true), ) assertThat(javaCode) .contains( """ | public static final class ClassWithDeprecatedProperty { | private final long deprecatedProperty; """ .trimMargin() ) .contains( """ | @Deprecated | public long getDeprecatedProperty() { | return deprecatedProperty; | } """ .trimMargin() ) .contains( """ | @Deprecated | public ClassWithDeprecatedProperty withDeprecatedProperty(long deprecatedProperty) { | return new ClassWithDeprecatedProperty(deprecatedProperty); | } """ .trimMargin() ) .doesNotContain("* @deprecated") } @Test fun `deprecated class`() { val javaCode = generateJavaCode( """ @Deprecated class DeprecatedClass { propertyOfDeprecatedClass: Int = 42 } """ .trimIndent() ) assertThat(javaCode) .contains( """ | @Deprecated | public static final class DeprecatedClass { """ .trimMargin() ) .doesNotContain("* @deprecated") } @Test fun `deprecated module class`() { val javaCode = generateJavaCode( """ @Deprecated module DeprecatedModule propertyInDeprecatedModuleClass : Int = 42 """ .trimIndent() ) assertThat(javaCode) .contains( """ |@Deprecated |public final class DeprecatedModule { """ .trimMargin() ) .doesNotContain("* @deprecated") } @Test fun `deprecation with message and doc comment on the same property`() { val javaCode = generateJavaCode( """ /// Documenting deprecatedProperty @Deprecated { message = "property is deprecated" } deprecatedProperty: Int """, JavaCodeGeneratorOptions(generateJavadoc = true), ) assertThat(javaCode) .contains( """ | /** | * Documenting deprecatedProperty | * | * @deprecated property is deprecated | */ | @Deprecated | public final long deprecatedProperty; """ .trimMargin() ) } @Test fun properties() { val (other, propertyTypes) = instantiateOtherAndPropertyTypes() assertThat(readProperty(other, "name")).isEqualTo("pigeon") assertThat(readProperty(propertyTypes, "_boolean")).isEqualTo(true) assertThat(readProperty(propertyTypes, "_int")).isEqualTo(42L) assertThat(readProperty(propertyTypes, "_float")).isEqualTo(42.3) assertThat(readProperty(propertyTypes, "string")).isEqualTo("string") assertThat(readProperty(propertyTypes, "duration")) .isEqualTo(Duration(5.0, DurationUnit.MINUTES)) assertThat(readProperty(propertyTypes, "dataSize")) .isEqualTo(DataSize(3.0, DataSizeUnit.GIGABYTES)) assertThat(readProperty(propertyTypes, "nullable")).isEqualTo("idea") assertThat(readProperty(propertyTypes, "nullable2")).isEqualTo(null as String?) assertThat(readProperty(propertyTypes, "list")).isEqualTo(listOf(1, 2, 3)) assertThat(readProperty(propertyTypes, "list2")).isEqualTo(listOf(other, other)) assertThat(readProperty(propertyTypes, "set")).isEqualTo(setOf(1, 2, 3)) assertThat(readProperty(propertyTypes, "set2")).isEqualTo(setOf(other)) assertThat(readProperty(propertyTypes, "map")).isEqualTo(mapOf(1 to "one", 2 to "two")) assertThat(readProperty(propertyTypes, "map2")).isEqualTo(mapOf("one" to other, "two" to other)) assertThat(readProperty(propertyTypes, "container")).isEqualTo(mapOf(1 to "one", 2 to "two")) assertThat(readProperty(propertyTypes, "container2")) .isEqualTo(mapOf("one" to other, "two" to other)) assertThat(readProperty(propertyTypes, "other")).isEqualTo(other) assertThat(readProperty(propertyTypes, "regex")).isInstanceOf(Pattern::class.java) assertThat(readProperty(propertyTypes, "any")).isEqualTo(other) assertThat(readProperty(propertyTypes, "nonNull")).isEqualTo(other) assertThat(readProperty(propertyTypes, "bytes")).isEqualTo(byteArrayOf(1, 2, 3, 4)) } private fun readProperty(obj: Any, property: String): Any? = obj::class.java.getField(property).get(obj) @Test fun `properties 2`() { assertThat(propertyTypesSources).isEqualToResourceFile("PropertyTypes.jva") } @Test fun `enum constant names`() { val cases = listOf( "camelCasedName" to "CAMEL_CASED_NAME", "hyphenated-name" to "HYPHENATED_NAME", "EnQuad\u2000EmSpace\u2003IdeographicSpace\u3000" to "EN_QUAD_EM_SPACE_IDEOGRAPHIC_SPACE_", "ᾊᾨ" to "ᾊᾨ", "0-digit" to "_0_DIGIT", "digit-1" to "DIGIT_1", "42" to "_42", "àœü" to "ÀŒÜ", "日本-つくば" to "日本_つくば", ) val javaCode = generateJavaCode( """ module my.mod typealias MyTypeAlias = ${cases.joinToString(" | ") { "\"${it.first}\"" }} """ .trimIndent() ) val javaClass = javaCode.compile().getValue("my.Mod\$MyTypeAlias") assertThat(javaClass.enumConstants.size) .isEqualTo(cases.size) // make sure zip doesn't drop cases assertAll( "generated enum constants have correct names", javaClass.declaredFields.zip(cases) { field, (_, kotlinName) -> { assertThat(field.name).isEqualTo(kotlinName) Unit } }, ) assertAll( "toString() returns Pkl name", javaClass.enumConstants.zip(cases) { enumConstant, (pklName, _) -> { assertThat(enumConstant.toString()).isEqualTo(pklName) } }, ) } @Test fun `conflicting enum constant names`() { val exception = assertThrows { generateJavaCode( """ module my.mod typealias MyTypeAlias = "foo-bar" | "foo bar" """ .trimIndent() ) } assertThat(exception) .hasMessageContainingAll("both be converted to enum constant name", "FOO_BAR") } @Test fun `empty enum constant name`() { val exception = assertThrows { generateJavaCode( """ module my.mod typealias MyTypeAlias = "foo" | "" | "bar" """ .trimIndent() ) } assertThat(exception).hasMessageContaining("cannot be converted") } @Test fun `inconvertible enum constant name`() { val exception = assertThrows { generateJavaCode( """ module my.mod typealias MyTypeAlias = "foo" | "✅" | "bar" """ .trimIndent() ) } assertThat(exception).hasMessageContainingAll("✅", "cannot be converted") } @Test fun `recursive types`() { val javaCode = generateJavaCode( """ module my.mod class Foo { other: Int bar: Bar } class Bar { foo: Foo other: String } """ .trimIndent() ) assertThat(javaCode) .compilesSuccessfully() .contains( """ | public static final class Foo { | public final long other; | | public final @NonNull Bar bar; | | public Foo(@Named("other") long other, @Named("bar") @NonNull Bar bar) { | this.other = other; | this.bar = bar; | } """ .trimMargin() ) .contains( """ | public static final class Bar { | public final @NonNull Foo foo; | | public final @NonNull String other; | | public Bar(@Named("foo") @NonNull Foo foo, @Named("other") @NonNull String other) { | this.foo = foo; | this.other = other; | } """ .trimMargin() ) } @Test fun inheritance() { val javaCode = generateJavaCode( """ module my.mod abstract class Foo { one: Int } open class None extends Foo {} open class Bar extends None { two: String? } class Baz extends Bar { three: Duration } """ .trimIndent(), JavaCodeGeneratorOptions(generateGetters = true), ) assertThat(javaCode) .compilesSuccessfully() .contains( """ | public abstract static class Foo { | protected final long one; | | protected Foo(@Named("one") long one) { | this.one = one; | } """ .trimMargin() ) .contains( """ | public static class None extends Foo { | public None(@Named("one") long one) { | super(one); | } """ .trimMargin() ) .contains( """ | public static class Bar extends None { | protected final String two; | | public Bar(@Named("one") long one, @Named("two") String two) { | super(one); | this.two = two; | } """ .trimMargin() ) .isEqualToResourceFile("Inheritance.jva") } @Test fun `stateless classes`() { val javaCode = generateJavaCode( """ module my.mod class Foo abstract class Bar class Baz extends Bar """ .trimIndent() ) assertThat(javaCode) .contains( """ | public static final class Foo { | public Foo() { | } """ .trimMargin() ) .contains( """ | public abstract static class Bar { | protected Bar() { | } """ .trimMargin() ) .contains( """ | public static final class Baz extends Bar { | public Baz() { | } """ .trimMargin() ) } @Test fun `stateless module classes`() { var javaCode = generateJavaCode("module my.mod") assertThat(javaCode) .contains( """ |public final class Mod { | private Mod() { | } """ .trimMargin() ) javaCode = generateJavaCode("abstract module my.mod") assertThat(javaCode) .contains( """ |public abstract class Mod { | protected Mod() { | } """ .trimMargin() ) javaCode = generateJavaCode("open module my.mod") assertThat(javaCode) .contains( """ |public class Mod { | public Mod() { | } """ .trimMargin() ) } @Test fun `reserved words`() { val props = javaReservedWords.joinToString("\n") { "`$it`: Int" } val fooClass = generateJavaCode( """ module my.mod class Foo { $props } """ .trimIndent() ) .compile() .getValue("my.Mod\$Foo") assertThat(fooClass.declaredFields).allSatisfy(Consumer { it.name.startsWith("_") }) } @Test fun generatedAnnotation() { val javaCode = generateJavaCode( """ module my.mod class GeneratedAnnotation { test: Boolean = true } """ .trimIndent(), JavaCodeGeneratorOptions(addGeneratedAnnotation = true), ) assertThat(javaCode).compilesSuccessfully().isEqualToResourceFile("GeneratedAnnotation.jva") } @Test fun getters() { val javaCode = generateJavaCode( """ module my.mod class GenerateGetters { urgent: Boolean = true url: String = "https://apple.com" diskSize: DataSize = 4.mb ETA: Duration = 3.s package: String } """ .trimIndent(), JavaCodeGeneratorOptions(generateGetters = true), ) assertThat(javaCode).compilesSuccessfully().isEqualToResourceFile("GenerateGetters.jva") } @Test fun `'with' methods`() { val javaCode = generateJavaCode( """ module my.mod abstract class Foo { x: Int } class Bar extends Foo { y: String } """ ) assertThat(javaCode) .compilesSuccessfully() .contains( """ | public Bar withX(long x) { | return new Bar(x, y); | } """ .trimMargin() ) .contains( """ | public Bar withY(@NonNull String y) { | return new Bar(x, y); | } """ .trimMargin() ) .doesNotContain("public Foo withX") // because `Foo` is abstract } @Test fun `module class`() { val javaCode = generateJavaCode( """ module my.mod pigeon: String parrot: String """ .trimIndent() ) assertThat(javaCode) .contains( """ |public final class Mod { | public final @NonNull String pigeon; | | public final @NonNull String parrot; """ .trimMargin() ) } @Test fun `hidden properties`() { val javaCode = generateJavaCode( """ hidden pigeon1: String parrot1: String class Persons { hidden pigeon2: String parrot2: String } """ .trimIndent() ) assertThat(javaCode) .doesNotContain("final String pigeon1") .contains("final @NonNull String parrot1") .doesNotContain("final String pigeon2") .contains("final @NonNull String parrot2") } @Test fun javadoc() { val javaCode = generateJavaCode( """ /// module comment. /// *emphasized* `code`. module my.mod /// module property comment. /// *emphasized* `code`. pigeon: Person /// class comment. /// *emphasized* `code`. class Person { /// class property comment. /// *emphasized* `code`. name: String } """ .trimIndent(), JavaCodeGeneratorOptions(generateJavadoc = true), ) assertThat(javaCode).compilesSuccessfully().isEqualToResourceFile("Javadoc.jva") } @Test fun `javadoc 2`() { val javaCode = generateJavaCode( """ module my.mod /// module property comment. /// can contain /* and */ characters. pigeon: Person class Person { /// class property comment. /// can contain /* and */ characters. name: String } """ .trimIndent(), JavaCodeGeneratorOptions(generateGetters = true, generateJavadoc = true), ) assertThat(javaCode) .compilesSuccessfully() .contains( """ | /** | * module property comment. | * can contain /* and */ characters. | */ | public @NonNull Person getPigeon() { """ .trimMargin() ) .contains( """ | /** | * class property comment. | * can contain /* and */ characters. | */ | public @NonNull String getName() { """ .trimMargin() ) } @Test fun `pkl_base type aliases`() { val javaCode = generateJavaCode( """ module mod uint8: UInt8 uint16: UInt16 uint32: UInt32 uint: UInt int8: Int8 int16: Int16 int32: Int32 uri: Uri pair: Pair list: List set: Set map: Map listing: Listing mapping: Mapping nullable: UInt16? class Foo { uint8: UInt8 uint16: UInt16 uint32: UInt32 uint: UInt int8: Int8 int16: Int16 int32: Int32 uri: Uri list: List } """ .trimIndent() ) assertThat(javaCode) .compilesSuccessfully() .contains( """ |public final class Mod { | public final short uint8; | | public final int uint16; | | public final long uint32; | | public final long uint; | | public final byte int8; | | public final short int16; | | public final int int32; | | public final @NonNull URI uri; """ .trimMargin() ) .contains( """ | public final @NonNull Pair<@NonNull Short, @NonNull Integer> pair; | | public final @NonNull List<@NonNull Long> list; | | public final @NonNull Set<@NonNull Long> set; | | public final @NonNull Map<@NonNull Byte, @NonNull Short> map; | | public final @NonNull List<@NonNull Integer> listing; | | public final @NonNull Map<@NonNull URI, @NonNull Short> mapping; | | public final Integer nullable; """ .trimMargin() ) .contains( """ | public static final class Foo { | public final short uint8; | | public final int uint16; | | public final long uint32; | | public final long uint; | | public final byte int8; | | public final short int16; | | public final int int32; | | public final @NonNull URI uri; | | public final @NonNull List<@NonNull Long> list; """ .trimMargin() ) } @Test fun `nullable properties`() { var javaCode = generateJavaCode( """ module mod foo: String """ .trimIndent(), JavaCodeGeneratorOptions(nonNullAnnotation = "com.example.Annotations\$NonNull"), ) assertThat(javaCode) .contains("import com.example.Annotations;") .contains("public final @Annotations.NonNull String foo;") javaCode = generateJavaCode( """ module mod foo: Int bar: Int? baz: Any qux: String foo2: List? bar2: List baz2: List qux2: List """ .trimIndent() ) assertThat(javaCode) .contains( """ |public final class Mod { | public final long foo; | | public final Long bar; | | public final Object baz; | | public final @NonNull String qux; | | public final List<@NonNull String> foo2; | | public final @NonNull List bar2; | | public final @NonNull List<@NonNull String> baz2; | | public final @NonNull List<@NonNull Long> qux2; """ .trimMargin() ) } @Test fun `user defined type aliases`() { val javaCode = generateJavaCode( """ module mod typealias Simple = String typealias Constrained = String(length >= 3) typealias Parameterized = List typealias Recursive1 = Parameterized(nonEmpty) typealias Recursive2 = List simple: Simple constrained: Constrained parameterized: Parameterized recursive1: Recursive1 recursive2: Recursive2 class Foo { simple: Simple constrained: Constrained parameterized: Parameterized recursive1: Recursive1 recursive2: Recursive2 } """ ) assertThat(javaCode) .compilesSuccessfully() .contains( """ |public final class Mod { | public final @NonNull String simple; | | public final @NonNull String constrained; | | public final @NonNull List<@NonNull Long> parameterized; | | public final @NonNull List<@NonNull Long> recursive1; | | public final @NonNull List<@NonNull String> recursive2; """ .trimMargin() ) .contains( """ | public static final class Foo { | public final @NonNull String simple; | | public final @NonNull String constrained; | | public final @NonNull List<@NonNull Long> parameterized; | | public final @NonNull List<@NonNull Long> recursive1; | | public final @NonNull List<@NonNull String> recursive2; """ .trimMargin() ) } @Test fun `generic type aliases`() { val javaCode = generateJavaCode( """ module mod class Person { name: String } typealias List2 = List typealias Map2 = Map typealias StringMap = Map typealias MMap = Map res1: List2 res2: List2> res3: Map2 res4: StringMap res5: MMap res6: List2 res7: Map2 res8: StringMap res9: MMap class Foo { res1: List2 res2: List2> res3: Map2 res4: StringMap res5: MMap res6: List2 res7: Map2 res8: StringMap res9: MMap } """ .trimIndent() ) assertThat(javaCode) .compilesSuccessfully() .contains( """ |public final class Mod { | public final @NonNull List<@NonNull Long> res1; | | public final @NonNull List<@NonNull List<@NonNull String>> res2; | | public final @NonNull Map<@NonNull Long, @NonNull String> res3; | | public final @NonNull Map<@NonNull String, @NonNull Duration> res4; | | public final @NonNull Map res5; | | public final @NonNull List<@NonNull Object> res6; | | public final @NonNull Map<@NonNull Object, @NonNull Object> res7; | | public final @NonNull Map<@NonNull String, @NonNull Object> res8; | | public final @NonNull Map<@NonNull Object, @NonNull Object> res9; """ .trimMargin() ) .contains( """ | public static final class Foo { | public final @NonNull List<@NonNull Long> res1; | | public final @NonNull List<@NonNull List<@NonNull String>> res2; | | public final @NonNull Map<@NonNull Long, @NonNull String> res3; | | public final @NonNull Map<@NonNull String, @NonNull Duration> res4; | | public final @NonNull Map res5; | | public final @NonNull List<@NonNull Object> res6; | | public final @NonNull Map<@NonNull Object, @NonNull Object> res7; | | public final @NonNull Map<@NonNull String, @NonNull Object> res8; | | public final @NonNull Map<@NonNull Object, @NonNull Object> res9; """ .trimMargin() ) } @Test fun `union of string literals`() { val javaCode = generateJavaCode( """ module mod x: "Pigeon"|"Barn Owl"|"Parrot" """ .trimIndent() ) assertThat(javaCode).compilesSuccessfully().contains("public final @NonNull String x;") } @Test fun `other union type`() { val e = assertThrows { generateJavaCode( """ module mod x: "Pigeon"|Int|"Parrot" """ .trimIndent() ) } assertThat(e).hasMessageContaining("Pkl union types are not supported") } @Test fun `stringy type`() { val javaCode = generateJavaCode( """ module mod v1: "RELEASE" v2: "RELEASE"|String v3: String|"RELEASE" v4: "RELEASE"|String|"LATEST" v5: Version|String|"LATEST" v6: (Version|String)|("LATEST"|String) typealias Version = "RELEASE"|String|"LATEST" """ .trimIndent() ) assertThat(javaCode) .contains("public final @NonNull String v1;") .contains("public final @NonNull String v2;") .contains("public final @NonNull String v3;") .contains("public final @NonNull String v4;") .contains("public final @NonNull String v5;") .contains("public final @NonNull String v6;") } @Test fun `stringy type alias`() { val javaCode = generateJavaCode( """ module mod typealias Version1 = "RELEASE"|String typealias Version2 = String|"RELEASE" typealias Version3 = "RELEASE"|String|"LATEST" typealias Version4 = Version3|String|"LATEST" typealias Version5 = (Version4|String)|("LATEST"|String) typealias Version6 = Version5 v1: Version1 v2: Version2 v3: Version3 v4: Version4 v5: Version5 v6: Version6 """ .trimIndent() ) assertThat(javaCode) .contains("public final @NonNull String v1;") .contains("public final @NonNull String v2;") .contains("public final @NonNull String v3;") .contains("public final @NonNull String v4;") .contains("public final @NonNull String v5;") .contains("public final @NonNull String v6;") } @Test fun `custom constructor parameter annotation`() { val javaCode = generateJavaCode( """ module my.mod name: String """ .trimIndent(), JavaCodeGeneratorOptions(paramsAnnotation = "org.project.MyAnnotation"), ) assertThat(javaCode) .contains("import org.project.MyAnnotation;") .contains("public Mod(@MyAnnotation(\"name\") @NonNull String name)") } @Test fun `no constructor parameter annotation`() { val javaCode = generateJavaCode( """ module my.mod name: String """ .trimIndent(), JavaCodeGeneratorOptions(paramsAnnotation = null), ) assertThat(javaCode).contains("public Mod(@NonNull String name)") } @Test fun `spring boot config`() { val javaCode = generateJavaCode( """ module my.mod server: Server class Server { port: Int urls: Listing } """ .trimIndent(), JavaCodeGeneratorOptions(generateSpringBootConfig = true), ) assertThat(javaCode) .contains( """ |@ConfigurationProperties |public final class Mod { """ .trimMargin() ) .contains( """ | public final @NonNull Server server; """ .trimMargin() ) .contains( """ | @ConfigurationProperties("server") | public static final class Server { """ .trimMargin() ) .contains( """ | public final long port; | | public final @NonNull List<@NonNull URI> urls; """ .trimMargin() ) .doesNotContain("@ConstructorBinding") .doesNotContain("@Named") // not worthwhile to add spring & spring boot dependency just so that this test can compile // their annotations val javaCodeWithoutSpringAnnotations = javaCode.deleteLines { it.contains("ConfigurationProperties") } assertThat(javaCodeWithoutSpringAnnotations).compilesSuccessfully() } @Test fun `import module`() { val library = PklModule( "library", """ module library class Person { name: String; age: Int } pigeon: Person """ .trimIndent(), ) val client = PklModule( "client", """ module client import "library.pkl" lib: library parrot: library.Person """ .trimIndent(), ) val javaSourceFiles = generateFiles(library, client) assertDoesNotThrow { InMemoryJavaCompiler.compile(javaSourceFiles.mapValues { it.value.text }) } val javaClientCode = javaSourceFiles.entries.find { (fileName, _) -> fileName.endsWith("Client.java") }!!.value assertThat(javaClientCode) .contains( """ |public final class Client { | public final @NonNull Library lib; | | public final Library. @NonNull Person parrot; """ .trimMargin() ) } @Test fun `extend module`() { val base = PklModule( "base", """ open module base open class Person { name: String } pigeon: Person """ .trimIndent(), ) val derived = PklModule( "derived", """ module derived extends "base.pkl" class Person2 extends Person { age: Int } person1: Person person2: Person2 """ .trimIndent(), ) val javaSourceFiles = generateFiles(base, derived) assertDoesNotThrow { InMemoryJavaCompiler.compile(javaSourceFiles.mapValues { it.value.text }) } val javaDerivedCode = javaSourceFiles.entries.find { (filename, _) -> filename.endsWith("Derived.java") }!!.value assertThat(javaDerivedCode) .contains( """ |public final class Derived extends Base { | public final Base. @NonNull Person person1; | | public final @NonNull Person2 person2; """ .trimMargin() ) } @Test fun `empty module`() { val javaCode = generateJavaCode("module mod") assertThat(javaCode).contains("public final class Mod {") } @Test fun `extend module that only contains type aliases`() { val base = PklModule( "base", """ abstract module base typealias Version = "LATEST"|String """ .trimIndent(), ) val derived = PklModule( "derived", """ module derived extends "base.pkl" v: Version = "1.2.3" """ .trimIndent(), ) val javaSourceFiles = generateFiles(base, derived) assertDoesNotThrow { InMemoryJavaCompiler.compile(javaSourceFiles.mapValues { it.value.text }) } val javaDerivedCode = javaSourceFiles.entries.find { (filename, _) -> filename.endsWith("Derived.java") }!!.value assertThat(javaDerivedCode) .contains( """ |public final class Derived extends Base { | public final @NonNull String v; """ .trimMargin() ) } @Test fun `generated properties files`() { val pklModule = PklModule( "Mod.pkl", """ module org.pkl.Mod foo: Foo bar: Bar class Foo { prop: String } class Bar { prop: Int } """ .trimIndent(), ) val generated = generateFiles(pklModule) val expectedPropertyFile = "resources/META-INF/org/pkl/config/java/mapper/classes/org.pkl.Mod.properties" assertThat(generated).containsKey(expectedPropertyFile) val generatedFile = generated[expectedPropertyFile]!! assertThat(generatedFile) .contains("org.pkl.config.java.mapper.org.pkl.Mod\\#ModuleClass=org.pkl.Mod") .contains("org.pkl.config.java.mapper.org.pkl.Mod\\#Foo=org.pkl.Mod\$Foo") .contains("org.pkl.config.java.mapper.org.pkl.Mod\\#Bar=org.pkl.Mod\$Bar") } @Test fun `generated properties files with normalized java name`() { val pklModule = PklModule( "mod.pkl", """ module my.mod foo: Foo bar: Bar class Foo { prop: String } class Bar { prop: Int } """ .trimIndent(), ) val generated = generateFiles(pklModule) val expectedPropertyFile = "resources/META-INF/org/pkl/config/java/mapper/classes/my.mod.properties" assertThat(generated).containsKey(expectedPropertyFile) val generatedFile = generated[expectedPropertyFile]!! assertThat(generatedFile) .contains("org.pkl.config.java.mapper.my.mod\\#ModuleClass=my.Mod") .contains("org.pkl.config.java.mapper.my.mod\\#Foo=my.Mod\$Foo") .contains("org.pkl.config.java.mapper.my.mod\\#Bar=my.Mod\$Bar") } @Test fun `generates serializable classes`() { val javaCode = generateJavaCode( """ module mod class BigStruct { boolean: Boolean int: Int float: Float string: String duration: Duration dataSize: DataSize pair: Pair pair2: Pair coll: Collection coll2: Collection list: List list2: List set: Set set2: Set map: Map map2: Map container: Mapping container2: Mapping other: SmallStruct regex: Regex nonNull: NonNull enum: Direction } class SmallStruct { name: String } typealias Direction = "north"|"east"|"south"|"west" """ .trimIndent(), JavaCodeGeneratorOptions(implementSerializable = true), ) assertThat(javaCode) .contains("implements Serializable") .contains("private static final long serialVersionUID = 0L;") val classes = javaCode.compile() val smallStructCtor = classes.getValue("Mod\$SmallStruct").constructors.first() val smallStruct = smallStructCtor.newInstance("pigeon") val enumClass = classes.getValue("Mod\$Direction") val enumValue = enumClass.enumConstants.first() val bigStructCtor = classes.getValue("Mod\$BigStruct").constructors.first() val bigStruct = bigStructCtor.newInstance( true, 42L, 42.3, "string", Duration(5.0, DurationUnit.MINUTES), DataSize(3.0, DataSizeUnit.GIGABYTES), Pair(1, 2), Pair("pigeon", smallStruct), listOf(1, 2, 3), listOf(smallStruct, smallStruct), listOf(1, 2, 3), listOf(smallStruct, smallStruct), setOf(1, 2, 3), setOf(smallStruct, smallStruct), mapOf(1 to "one", 2 to "two"), mapOf("one" to smallStruct, "two" to smallStruct), mapOf(1 to "one", 2 to "two"), mapOf("one" to smallStruct, "two" to smallStruct), smallStruct, Pattern.compile("(i?)\\w*"), smallStruct, enumValue, ) fun confirmSerDe(instance: Any) { var restoredInstance: Any? = null assertThatCode { // serialize val baos = ByteArrayOutputStream() val oos = ObjectOutputStream(baos) oos.writeObject(instance) oos.flush() // deserialize val bais = ByteArrayInputStream(baos.toByteArray()) val ois = object : ObjectInputStream(bais) { override fun resolveClass(desc: ObjectStreamClass?): Class<*> { return Class.forName(desc!!.name, false, instance.javaClass.classLoader) } } restoredInstance = ois.readObject() } .doesNotThrowAnyException() assertThat(restoredInstance!!).isEqualTo(instance) } confirmSerDe(enumValue) confirmSerDe(smallStruct) confirmSerDe(bigStruct) } @Test fun `non-instantiable classes aren't made serializable`() { var javaCode = generateJavaCode( """ module my.mod abstract class Foo { str: String } """ .trimIndent(), JavaCodeGeneratorOptions(implementSerializable = true), ) assertThat(javaCode).doesNotContain("Serializable") javaCode = generateJavaCode( """ module my.mod """ .trimIndent(), JavaCodeGeneratorOptions(implementSerializable = true), ) assertThat(javaCode).doesNotContain("Serializable") } @Test fun `generates serializable module classes`() { val javaCode = generateJavaCode( """ module Person name: String address: Address class Address { city: String } """ .trimIndent(), JavaCodeGeneratorOptions(implementSerializable = true), ) assertThat(javaCode) .contains( """ |public final class Person implements Serializable { | private static final long serialVersionUID = 0L; """ .trimMargin() ) .contains( """ | public static final class Address implements Serializable { | private static final long serialVersionUID = 0L; """ .trimMargin() ) } @Test fun `override property type`() { val javaCode = generateJavaCode( """ module my.mod open class Foo class TheFoo extends Foo { fooProp: String } open class OpenClass { prop: Foo } class TheClass extends OpenClass { prop: TheFoo } """ .trimIndent() ) assertThat(javaCode) .compilesSuccessfully() .contains( """ | public static final class TheClass extends OpenClass { | public final @NonNull TheFoo prop; | | public TheClass(@Named("prop") @NonNull TheFoo prop) { | super(prop); | this.prop = prop; | } | | public TheClass withProp(@NonNull TheFoo prop) { | return new TheClass(prop); | } """ .trimMargin() ) } @Test fun `override property type, with getters`() { val javaCode = generateJavaCode( """ module my.mod open class Foo class TheFoo extends Foo { fooProp: String } open class OpenClass { prop: Foo } class TheClass extends OpenClass { prop: TheFoo } """ .trimIndent(), JavaCodeGeneratorOptions(generateGetters = true), ) assertThat(javaCode) .compilesSuccessfully() .contains( """ | public static final class TheClass extends OpenClass { | private final @NonNull TheFoo prop; | | public TheClass(@Named("prop") @NonNull TheFoo prop) { | super(prop); | this.prop = prop; | } | | @Override | public @NonNull TheFoo getProp() { | return prop; | } """ .trimMargin() ) } @Test fun `override names in a standalone module`() { val files = JavaCodeGeneratorOptions( renames = mapOf("a.b.c." to "x.y.z.", "d.e.f.AnotherModule" to "u.v.w.RenamedModule") ) .generateFiles( "MyModule.pkl" to """ module a.b.c.MyModule foo: String = "abc" """ .trimIndent(), "AnotherModule.pkl" to """ module d.e.f.AnotherModule bar: Int = 123 """ .trimIndent(), ) .toMutableMap() files.validateContents( "java/x/y/z/MyModule.java" to listOf("package x.y.z;", "public final class MyModule {"), "$MAPPER_PREFIX/a.b.c.MyModule.properties" to listOf("org.pkl.config.java.mapper.a.b.c.MyModule\\#ModuleClass=x.y.z.MyModule"), // --- "java/u/v/w/RenamedModule.java" to listOf("package u.v.w;", "public final class RenamedModule {"), "$MAPPER_PREFIX/d.e.f.AnotherModule.properties" to listOf("org.pkl.config.java.mapper.d.e.f.AnotherModule\\#ModuleClass=u.v.w.RenamedModule"), ) } @Test fun `override names based on the longest prefix`() { val files = JavaCodeGeneratorOptions( renames = mapOf("com.foo.bar." to "x.", "com.foo." to "y.", "com." to "z.", "" to "w.") ) .generateFiles( "com/foo/bar/Module1" to """ module com.foo.bar.Module1 bar: String """ .trimIndent(), "com/Module2" to """ module com.Module2 com: String """ .trimIndent(), "org/baz/Module3" to """ module org.baz.Module3 baz: String """ .trimIndent(), ) files.validateContents( "java/x/Module1.java" to listOf("package x;", "public final class Module1 {"), "$MAPPER_PREFIX/com.foo.bar.Module1.properties" to listOf("org.pkl.config.java.mapper.com.foo.bar.Module1\\#ModuleClass=x.Module1"), // --- "java/z/Module2.java" to listOf("package z;", "public final class Module2 {"), "$MAPPER_PREFIX/com.Module2.properties" to listOf("org.pkl.config.java.mapper.com.Module2\\#ModuleClass=z.Module2"), // --- "java/w/org/baz/Module3.java" to listOf("package w.org.baz;", "public final class Module3 {"), "$MAPPER_PREFIX/org.baz.Module3.properties" to listOf("org.pkl.config.java.mapper.org.baz.Module3\\#ModuleClass=w.org.baz.Module3"), ) } @Test fun `override names in multiple modules using each other`() { val files = JavaCodeGeneratorOptions( renames = mapOf( "org.foo." to "com.foo.x.", "org.bar.Module2" to "org.bar.RenamedModule", "org.baz." to "com.baz.a.b.", ) ) .generateFiles( "org/foo/Module1" to """ module org.foo.Module1 class Person { name: String } """ .trimIndent(), "org/bar/Module2" to """ module org.bar.Module2 import "../../org/foo/Module1.pkl" class Group { owner: Module1.Person name: String } """ .trimIndent(), "org/baz/Module3" to """ module org.baz.Module3 import "../../org/bar/Module2.pkl" class Supergroup { owner: Module2.Group } """ .trimIndent(), ) files.validateContents( "java/com/foo/x/Module1.java" to listOf("package com.foo.x;", "public final class Module1 {"), "$MAPPER_PREFIX/org.foo.Module1.properties" to listOf( "org.pkl.config.java.mapper.org.foo.Module1\\#ModuleClass=com.foo.x.Module1", "org.pkl.config.java.mapper.org.foo.Module1\\#Person=com.foo.x.Module1${'$'}Person", ), // --- "java/org/bar/RenamedModule.java" to listOf( "package org.bar;", "import com.foo.x.Module1;", "public final class RenamedModule {", "public final Module1. @NonNull Person owner;", ), "$MAPPER_PREFIX/org.bar.Module2.properties" to listOf( "org.pkl.config.java.mapper.org.bar.Module2\\#ModuleClass=org.bar.RenamedModule", "org.pkl.config.java.mapper.org.bar.Module2\\#Group=org.bar.RenamedModule${'$'}Group", ), // --- "java/com/baz/a/b/Module3.java" to listOf( "package com.baz.a.b;", "import org.bar.RenamedModule;", "public final class Module3 {", "public final RenamedModule. @NonNull Group owner;", ), "$MAPPER_PREFIX/org.baz.Module3.properties" to listOf( "org.pkl.config.java.mapper.org.baz.Module3\\#ModuleClass=com.baz.a.b.Module3", "org.pkl.config.java.mapper.org.baz.Module3\\#Supergroup=com.baz.a.b.Module3${'$'}Supergroup", ), ) } @Test fun `do not capitalize names of renamed classes`() { val files = JavaCodeGeneratorOptions( renames = mapOf("a.b.c.MyModule" to "x.y.z.renamed_module", "d.e.f." to "u.v.w.") ) .generateFiles( "MyModule.pkl" to """ module a.b.c.MyModule foo: String = "abc" """ .trimIndent(), "lower_module.pkl" to """ module d.e.f.lower_module bar: Int = 123 """ .trimIndent(), ) files.validateContents( "java/x/y/z/renamed_module.java" to listOf("package x.y.z;", "public final class renamed_module {"), "$MAPPER_PREFIX/a.b.c.MyModule.properties" to listOf("org.pkl.config.java.mapper.a.b.c.MyModule\\#ModuleClass=x.y.z.renamed_module"), // --- "java/u/v/w/Lower_module.java" to listOf("package u.v.w;", "public final class Lower_module {"), "$MAPPER_PREFIX/d.e.f.lower_module.properties" to listOf("org.pkl.config.java.mapper.d.e.f.lower_module\\#ModuleClass=u.v.w.Lower_module"), ) } @Test fun `equals,hashCode,toString work correctly for class that doesn't declare properties`() { val javaCode = generateJavaCode( """ module my.mod open class Foo { name: String } class Bar extends Foo {} """ .trimIndent() ) val classes = javaCode.compile() val fooClass = classes.getValue("my.Mod\$Foo") val foo1 = fooClass.getDeclaredConstructor(String::class.java).newInstance("name1") val barClass = classes.getValue("my.Mod\$Bar") val bar1 = barClass.getDeclaredConstructor(String::class.java).newInstance("name1") val anotherBar1 = barClass.getDeclaredConstructor(String::class.java).newInstance("name1") val bar2 = barClass.getDeclaredConstructor(String::class.java).newInstance("name2") assertThat(bar1) .isEqualTo(bar1) .isEqualTo(anotherBar1) .isNotEqualTo(bar2) .isNotEqualTo(foo1) .hasSameHashCodeAs(bar1) .hasSameHashCodeAs(anotherBar1) assertThat(bar1.toString()) .isEqualTo( """ Bar { name = name1 } """ .trimIndent() ) } private fun Map.validateContents( @Suppress("RemoveRedundantQualifierName") vararg assertions: kotlin.Pair> ) { val files = toMutableMap() for ((fileName, lines) in assertions) { assertThat(files).containsKey(fileName) assertThat(files.remove(fileName)).describedAs("Contents of $fileName").contains(lines) } assertThat(files).isEmpty() } private fun JavaCodeGeneratorOptions.generateFiles( vararg pklModules: PklModule ): Map { val pklFiles = pklModules.map { it.writeToDisk(tempDir.resolve("pkl/${it.name}.pkl")) } val evaluator = Evaluator.preconfigured() return pklFiles.fold(mapOf()) { acc, pklFile -> val pklSchema = evaluator.evaluateSchema(path(pklFile)) val generator = JavaCodeGenerator(pklSchema, this) acc + generator.output } } private fun JavaCodeGeneratorOptions.generateFiles( @Suppress("RemoveRedundantQualifierName") vararg pklModules: kotlin.Pair ): Map = generateFiles(*pklModules.map { (name, text) -> PklModule(name, text) }.toTypedArray()) private fun generateFiles(vararg pklModules: PklModule): Map = JavaCodeGeneratorOptions().generateFiles(*pklModules).mapValues { JavaSourceCode(it.value) } private fun instantiateOtherAndPropertyTypes(): kotlin.Pair { val otherCtor = propertyTypesClasses.getValue("my.Mod\$Other").constructors.first() val other = otherCtor.newInstance("pigeon") val enumClass = propertyTypesClasses.getValue("my.Mod\$Direction") val enumValue = enumClass.enumConstants.first() val propertyTypesCtor = propertyTypesClasses.getValue("my.Mod\$PropertyTypes").constructors.first() val propertyTypes = propertyTypesCtor.newInstance( true, 42, 42.3, "string", Duration(5.0, DurationUnit.MINUTES), DurationUnit.MINUTES, DataSize(3.0, DataSizeUnit.GIGABYTES), DataSizeUnit.GIGABYTES, "idea", (null as String?), Pair(1, 2), Pair("pigeon", other), listOf(1, 2, 3), listOf(other, other), listOf(1, 2, 3), listOf(other, other), setOf(1, 2, 3), setOf(other, other), mapOf(1 to "one", 2 to "two"), mapOf("one" to other, "two" to other), mapOf(1 to "one", 2 to "two"), mapOf("one" to other, "two" to other), other, Pattern.compile("(i?)\\w*"), other, other, enumValue, byteArrayOf(1, 2, 3, 4), ) return other to propertyTypes } private fun assertThat(actual: JavaSourceCode): JavaSourceCodeAssert = JavaSourceCodeAssert(actual) private data class JavaSourceCode(val text: String) { fun compile(): Map> = InMemoryJavaCompiler.compile(mapOf("/org/Mod.java" to text)) fun deleteLines(predicate: (String) -> Boolean): JavaSourceCode = JavaSourceCode(text.lines().filterNot(predicate).joinToString("\n")) } private class JavaSourceCodeAssert(actual: JavaSourceCode) : AbstractAssert(actual, JavaSourceCodeAssert::class.java) { fun contains(expected: String): JavaSourceCodeAssert { if (!actual.text.contains(expected)) { // check for equality to get better error output (IDE diff dialog) assertThat(actual.text).isEqualTo(expected) } return this } fun doesNotContain(expected: String): JavaSourceCodeAssert { assertThat(actual.text).doesNotContain(expected) return this } fun compilesSuccessfully(): JavaSourceCodeAssert { assertThatCode { actual.compile() }.doesNotThrowAnyException() return this } fun isEqualTo(expected: String): JavaSourceCodeAssert { assertThat(actual.text).isEqualTo(expected) return this } fun isEqualToResourceFile(fileName: String): JavaSourceCodeAssert { isEqualTo(IoUtils.readClassPathResourceAsString(javaClass, fileName)) return this } } } ================================================ FILE: pkl-codegen-java/src/test/kotlin/org/pkl/codegen/java/PklModule.kt ================================================ /* * Copyright © 2024-2025 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.codegen.java import java.nio.file.Path import kotlin.io.path.createParentDirectories import org.pkl.commons.writeString data class PklModule(val name: String, val content: String) { fun writeToDisk(path: Path): Path { return path.createParentDirectories().writeString(content) } } ================================================ FILE: pkl-codegen-java/src/test/resources/org/pkl/codegen/java/GenerateGetters.jva ================================================ package my; import java.lang.Object; import java.lang.Override; import java.lang.String; import java.lang.StringBuilder; import java.util.Objects; import org.pkl.config.java.mapper.Named; import org.pkl.config.java.mapper.NonNull; import org.pkl.core.DataSize; import org.pkl.core.Duration; public final class Mod { private Mod() { } private static void appendProperty(StringBuilder builder, String name, Object value) { builder.append("\n ").append(name).append(" = "); String[] lines = Objects.toString(value).split("\n"); builder.append(lines[0]); for (int i = 1; i < lines.length; i++) { builder.append("\n ").append(lines[i]); } } public static final class GenerateGetters { private final boolean urgent; private final @NonNull String url; private final @NonNull DataSize diskSize; private final @NonNull Duration ETA; private final @NonNull String _package; public GenerateGetters(@Named("urgent") boolean urgent, @Named("url") @NonNull String url, @Named("diskSize") @NonNull DataSize diskSize, @Named("ETA") @NonNull Duration ETA, @Named("package") @NonNull String _package) { this.urgent = urgent; this.url = url; this.diskSize = diskSize; this.ETA = ETA; this._package = _package; } public boolean isUrgent() { return urgent; } public GenerateGetters withUrgent(boolean urgent) { return new GenerateGetters(urgent, url, diskSize, ETA, _package); } public @NonNull String getUrl() { return url; } public GenerateGetters withUrl(@NonNull String url) { return new GenerateGetters(urgent, url, diskSize, ETA, _package); } public @NonNull DataSize getDiskSize() { return diskSize; } public GenerateGetters withDiskSize(@NonNull DataSize diskSize) { return new GenerateGetters(urgent, url, diskSize, ETA, _package); } public @NonNull Duration getETA() { return ETA; } public GenerateGetters withETA(@NonNull Duration ETA) { return new GenerateGetters(urgent, url, diskSize, ETA, _package); } public @NonNull String getPackage() { return _package; } public GenerateGetters withPackage(@NonNull String _package) { return new GenerateGetters(urgent, url, diskSize, ETA, _package); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (this.getClass() != obj.getClass()) return false; GenerateGetters other = (GenerateGetters) obj; if (!Objects.equals(this.urgent, other.urgent)) return false; if (!Objects.equals(this.url, other.url)) return false; if (!Objects.equals(this.diskSize, other.diskSize)) return false; if (!Objects.equals(this.ETA, other.ETA)) return false; if (!Objects.equals(this._package, other._package)) return false; return true; } @Override public int hashCode() { int result = 1; result = 31 * result + Objects.hashCode(this.urgent); result = 31 * result + Objects.hashCode(this.url); result = 31 * result + Objects.hashCode(this.diskSize); result = 31 * result + Objects.hashCode(this.ETA); result = 31 * result + Objects.hashCode(this._package); return result; } @Override public String toString() { StringBuilder builder = new StringBuilder(300); builder.append(GenerateGetters.class.getSimpleName()).append(" {"); appendProperty(builder, "urgent", this.urgent); appendProperty(builder, "url", this.url); appendProperty(builder, "diskSize", this.diskSize); appendProperty(builder, "ETA", this.ETA); appendProperty(builder, "_package", this._package); builder.append("\n}"); return builder.toString(); } } } ================================================ FILE: pkl-codegen-java/src/test/resources/org/pkl/codegen/java/GeneratedAnnotation.jva ================================================ package my; import java.lang.Object; import java.lang.Override; import java.lang.String; import java.lang.StringBuilder; import java.util.Objects; import org.pkl.config.java.Generated; import org.pkl.config.java.mapper.Named; @Generated public final class Mod { private Mod() { } private static void appendProperty(StringBuilder builder, String name, Object value) { builder.append("\n ").append(name).append(" = "); String[] lines = Objects.toString(value).split("\n"); builder.append(lines[0]); for (int i = 1; i < lines.length; i++) { builder.append("\n ").append(lines[i]); } } @Generated public static final class GeneratedAnnotation { public final boolean test; public GeneratedAnnotation(@Named("test") boolean test) { this.test = test; } public GeneratedAnnotation withTest(boolean test) { return new GeneratedAnnotation(test); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (this.getClass() != obj.getClass()) return false; GeneratedAnnotation other = (GeneratedAnnotation) obj; if (!Objects.equals(this.test, other.test)) return false; return true; } @Override public int hashCode() { int result = 1; result = 31 * result + Objects.hashCode(this.test); return result; } @Override public String toString() { StringBuilder builder = new StringBuilder(100); builder.append(GeneratedAnnotation.class.getSimpleName()).append(" {"); appendProperty(builder, "test", this.test); builder.append("\n}"); return builder.toString(); } } } ================================================ FILE: pkl-codegen-java/src/test/resources/org/pkl/codegen/java/Inheritance.jva ================================================ package my; import java.lang.Object; import java.lang.Override; import java.lang.String; import java.lang.StringBuilder; import java.util.Objects; import org.pkl.config.java.mapper.Named; import org.pkl.config.java.mapper.NonNull; import org.pkl.core.Duration; public final class Mod { private Mod() { } private static void appendProperty(StringBuilder builder, String name, Object value) { builder.append("\n ").append(name).append(" = "); String[] lines = Objects.toString(value).split("\n"); builder.append(lines[0]); for (int i = 1; i < lines.length; i++) { builder.append("\n ").append(lines[i]); } } public abstract static class Foo { protected final long one; protected Foo(@Named("one") long one) { this.one = one; } public long getOne() { return one; } } public static class None extends Foo { public None(@Named("one") long one) { super(one); } public None withOne(long one) { return new None(one); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (this.getClass() != obj.getClass()) return false; None other = (None) obj; if (!Objects.equals(this.one, other.one)) return false; return true; } @Override public int hashCode() { int result = 1; result = 31 * result + Objects.hashCode(this.one); return result; } @Override public String toString() { StringBuilder builder = new StringBuilder(100); builder.append(None.class.getSimpleName()).append(" {"); appendProperty(builder, "one", this.one); builder.append("\n}"); return builder.toString(); } } public static class Bar extends None { protected final String two; public Bar(@Named("one") long one, @Named("two") String two) { super(one); this.two = two; } public Bar withOne(long one) { return new Bar(one, two); } public String getTwo() { return two; } public Bar withTwo(String two) { return new Bar(one, two); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (this.getClass() != obj.getClass()) return false; Bar other = (Bar) obj; if (!Objects.equals(this.one, other.one)) return false; if (!Objects.equals(this.two, other.two)) return false; return true; } @Override public int hashCode() { int result = 1; result = 31 * result + Objects.hashCode(this.one); result = 31 * result + Objects.hashCode(this.two); return result; } @Override public String toString() { StringBuilder builder = new StringBuilder(150); builder.append(Bar.class.getSimpleName()).append(" {"); appendProperty(builder, "one", this.one); appendProperty(builder, "two", this.two); builder.append("\n}"); return builder.toString(); } } public static final class Baz extends Bar { private final @NonNull Duration three; public Baz(@Named("one") long one, @Named("two") String two, @Named("three") @NonNull Duration three) { super(one, two); this.three = three; } public Baz withOne(long one) { return new Baz(one, two, three); } public Baz withTwo(String two) { return new Baz(one, two, three); } public @NonNull Duration getThree() { return three; } public Baz withThree(@NonNull Duration three) { return new Baz(one, two, three); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (this.getClass() != obj.getClass()) return false; Baz other = (Baz) obj; if (!Objects.equals(this.one, other.one)) return false; if (!Objects.equals(this.two, other.two)) return false; if (!Objects.equals(this.three, other.three)) return false; return true; } @Override public int hashCode() { int result = 1; result = 31 * result + Objects.hashCode(this.one); result = 31 * result + Objects.hashCode(this.two); result = 31 * result + Objects.hashCode(this.three); return result; } @Override public String toString() { StringBuilder builder = new StringBuilder(200); builder.append(Baz.class.getSimpleName()).append(" {"); appendProperty(builder, "one", this.one); appendProperty(builder, "two", this.two); appendProperty(builder, "three", this.three); builder.append("\n}"); return builder.toString(); } } } ================================================ FILE: pkl-codegen-java/src/test/resources/org/pkl/codegen/java/Javadoc.jva ================================================ package my; import java.lang.Object; import java.lang.Override; import java.lang.String; import java.lang.StringBuilder; import java.util.Objects; import org.pkl.config.java.mapper.Named; import org.pkl.config.java.mapper.NonNull; /** * module comment. * *emphasized* `code`. */ public final class Mod { /** * module property comment. * *emphasized* `code`. */ public final @NonNull Person pigeon; public Mod(@Named("pigeon") @NonNull Person pigeon) { this.pigeon = pigeon; } public Mod withPigeon(@NonNull Person pigeon) { return new Mod(pigeon); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (this.getClass() != obj.getClass()) return false; Mod other = (Mod) obj; if (!Objects.equals(this.pigeon, other.pigeon)) return false; return true; } @Override public int hashCode() { int result = 1; result = 31 * result + Objects.hashCode(this.pigeon); return result; } @Override public String toString() { StringBuilder builder = new StringBuilder(100); builder.append(Mod.class.getSimpleName()).append(" {"); appendProperty(builder, "pigeon", this.pigeon); builder.append("\n}"); return builder.toString(); } private static void appendProperty(StringBuilder builder, String name, Object value) { builder.append("\n ").append(name).append(" = "); String[] lines = Objects.toString(value).split("\n"); builder.append(lines[0]); for (int i = 1; i < lines.length; i++) { builder.append("\n ").append(lines[i]); } } /** * class comment. * *emphasized* `code`. */ public static final class Person { /** * class property comment. * *emphasized* `code`. */ public final @NonNull String name; public Person(@Named("name") @NonNull String name) { this.name = name; } public Person withName(@NonNull String name) { return new Person(name); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (this.getClass() != obj.getClass()) return false; Person other = (Person) obj; if (!Objects.equals(this.name, other.name)) return false; return true; } @Override public int hashCode() { int result = 1; result = 31 * result + Objects.hashCode(this.name); return result; } @Override public String toString() { StringBuilder builder = new StringBuilder(100); builder.append(Person.class.getSimpleName()).append(" {"); appendProperty(builder, "name", this.name); builder.append("\n}"); return builder.toString(); } } } ================================================ FILE: pkl-codegen-java/src/test/resources/org/pkl/codegen/java/PropertyTypes.jva ================================================ package my; import java.lang.Object; import java.lang.Override; import java.lang.String; import java.lang.StringBuilder; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.regex.Pattern; import org.pkl.config.java.mapper.Named; import org.pkl.config.java.mapper.NonNull; import org.pkl.core.DataSize; import org.pkl.core.DataSizeUnit; import org.pkl.core.Duration; import org.pkl.core.DurationUnit; import org.pkl.core.Pair; public final class Mod { private Mod() { } private static void appendProperty(StringBuilder builder, String name, Object value) { builder.append("\n ").append(name).append(" = "); String[] lines = Objects.toString(value).split("\n"); builder.append(lines[0]); for (int i = 1; i < lines.length; i++) { builder.append("\n ").append(lines[i]); } } public static final class PropertyTypes { public final boolean _boolean; public final long _int; public final double _float; public final @NonNull String string; public final @NonNull Duration duration; public final @NonNull DurationUnit durationUnit; public final @NonNull DataSize dataSize; public final @NonNull DataSizeUnit dataSizeUnit; public final String nullable; public final String nullable2; public final @NonNull Pair pair; public final @NonNull Pair<@NonNull String, @NonNull Other> pair2; public final @NonNull Collection coll; public final @NonNull Collection<@NonNull Other> coll2; public final @NonNull List list; public final @NonNull List<@NonNull Other> list2; public final @NonNull Set set; public final @NonNull Set<@NonNull Other> set2; public final @NonNull Map map; public final @NonNull Map<@NonNull String, @NonNull Other> map2; public final @NonNull Map container; public final @NonNull Map<@NonNull String, @NonNull Other> container2; public final @NonNull Other other; public final @NonNull Pattern regex; public final Object any; public final @NonNull Object nonNull; public final @NonNull Direction _enum; public final byte[] bytes; public PropertyTypes(@Named("boolean") boolean _boolean, @Named("int") long _int, @Named("float") double _float, @Named("string") @NonNull String string, @Named("duration") @NonNull Duration duration, @Named("durationUnit") @NonNull DurationUnit durationUnit, @Named("dataSize") @NonNull DataSize dataSize, @Named("dataSizeUnit") @NonNull DataSizeUnit dataSizeUnit, @Named("nullable") String nullable, @Named("nullable2") String nullable2, @Named("pair") @NonNull Pair pair, @Named("pair2") @NonNull Pair<@NonNull String, @NonNull Other> pair2, @Named("coll") @NonNull Collection coll, @Named("coll2") @NonNull Collection<@NonNull Other> coll2, @Named("list") @NonNull List list, @Named("list2") @NonNull List<@NonNull Other> list2, @Named("set") @NonNull Set set, @Named("set2") @NonNull Set<@NonNull Other> set2, @Named("map") @NonNull Map map, @Named("map2") @NonNull Map<@NonNull String, @NonNull Other> map2, @Named("container") @NonNull Map container, @Named("container2") @NonNull Map<@NonNull String, @NonNull Other> container2, @Named("other") @NonNull Other other, @Named("regex") @NonNull Pattern regex, @Named("any") Object any, @Named("nonNull") @NonNull Object nonNull, @Named("enum") @NonNull Direction _enum, @Named("bytes") byte[] bytes) { this._boolean = _boolean; this._int = _int; this._float = _float; this.string = string; this.duration = duration; this.durationUnit = durationUnit; this.dataSize = dataSize; this.dataSizeUnit = dataSizeUnit; this.nullable = nullable; this.nullable2 = nullable2; this.pair = pair; this.pair2 = pair2; this.coll = coll; this.coll2 = coll2; this.list = list; this.list2 = list2; this.set = set; this.set2 = set2; this.map = map; this.map2 = map2; this.container = container; this.container2 = container2; this.other = other; this.regex = regex; this.any = any; this.nonNull = nonNull; this._enum = _enum; this.bytes = bytes; } public PropertyTypes withBoolean(boolean _boolean) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withInt(long _int) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withFloat(double _float) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withString(@NonNull String string) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withDuration(@NonNull Duration duration) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withDurationUnit(@NonNull DurationUnit durationUnit) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withDataSize(@NonNull DataSize dataSize) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withDataSizeUnit(@NonNull DataSizeUnit dataSizeUnit) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withNullable(String nullable) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withNullable2(String nullable2) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withPair(@NonNull Pair pair) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withPair2(@NonNull Pair<@NonNull String, @NonNull Other> pair2) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withColl(@NonNull Collection coll) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withColl2(@NonNull Collection<@NonNull Other> coll2) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withList(@NonNull List list) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withList2(@NonNull List<@NonNull Other> list2) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withSet(@NonNull Set set) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withSet2(@NonNull Set<@NonNull Other> set2) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withMap(@NonNull Map map) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withMap2(@NonNull Map<@NonNull String, @NonNull Other> map2) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withContainer(@NonNull Map container) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withContainer2(@NonNull Map<@NonNull String, @NonNull Other> container2) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withOther(@NonNull Other other) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withRegex(@NonNull Pattern regex) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withAny(Object any) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withNonNull(@NonNull Object nonNull) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withEnum(@NonNull Direction _enum) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } public PropertyTypes withBytes(byte[] bytes) { return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum, bytes); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (this.getClass() != obj.getClass()) return false; PropertyTypes other = (PropertyTypes) obj; if (!Objects.equals(this._boolean, other._boolean)) return false; if (!Objects.equals(this._int, other._int)) return false; if (!Objects.equals(this._float, other._float)) return false; if (!Objects.equals(this.string, other.string)) return false; if (!Objects.equals(this.duration, other.duration)) return false; if (!Objects.equals(this.durationUnit, other.durationUnit)) return false; if (!Objects.equals(this.dataSize, other.dataSize)) return false; if (!Objects.equals(this.dataSizeUnit, other.dataSizeUnit)) return false; if (!Objects.equals(this.nullable, other.nullable)) return false; if (!Objects.equals(this.nullable2, other.nullable2)) return false; if (!Objects.equals(this.pair, other.pair)) return false; if (!Objects.equals(this.pair2, other.pair2)) return false; if (!Objects.equals(this.coll, other.coll)) return false; if (!Objects.equals(this.coll2, other.coll2)) return false; if (!Objects.equals(this.list, other.list)) return false; if (!Objects.equals(this.list2, other.list2)) return false; if (!Objects.equals(this.set, other.set)) return false; if (!Objects.equals(this.set2, other.set2)) return false; if (!Objects.equals(this.map, other.map)) return false; if (!Objects.equals(this.map2, other.map2)) return false; if (!Objects.equals(this.container, other.container)) return false; if (!Objects.equals(this.container2, other.container2)) return false; if (!Objects.equals(this.other, other.other)) return false; if (!Objects.equals(this.regex.pattern(), other.regex.pattern())) return false; if (!Objects.equals(this.any, other.any)) return false; if (!Objects.equals(this.nonNull, other.nonNull)) return false; if (!Objects.equals(this._enum, other._enum)) return false; if (!Objects.equals(this.bytes, other.bytes)) return false; return true; } @Override public int hashCode() { int result = 1; result = 31 * result + Objects.hashCode(this._boolean); result = 31 * result + Objects.hashCode(this._int); result = 31 * result + Objects.hashCode(this._float); result = 31 * result + Objects.hashCode(this.string); result = 31 * result + Objects.hashCode(this.duration); result = 31 * result + Objects.hashCode(this.durationUnit); result = 31 * result + Objects.hashCode(this.dataSize); result = 31 * result + Objects.hashCode(this.dataSizeUnit); result = 31 * result + Objects.hashCode(this.nullable); result = 31 * result + Objects.hashCode(this.nullable2); result = 31 * result + Objects.hashCode(this.pair); result = 31 * result + Objects.hashCode(this.pair2); result = 31 * result + Objects.hashCode(this.coll); result = 31 * result + Objects.hashCode(this.coll2); result = 31 * result + Objects.hashCode(this.list); result = 31 * result + Objects.hashCode(this.list2); result = 31 * result + Objects.hashCode(this.set); result = 31 * result + Objects.hashCode(this.set2); result = 31 * result + Objects.hashCode(this.map); result = 31 * result + Objects.hashCode(this.map2); result = 31 * result + Objects.hashCode(this.container); result = 31 * result + Objects.hashCode(this.container2); result = 31 * result + Objects.hashCode(this.other); result = 31 * result + Objects.hashCode(this.regex.pattern()); result = 31 * result + Objects.hashCode(this.any); result = 31 * result + Objects.hashCode(this.nonNull); result = 31 * result + Objects.hashCode(this._enum); result = 31 * result + Objects.hashCode(this.bytes); return result; } @Override public String toString() { StringBuilder builder = new StringBuilder(1450); builder.append(PropertyTypes.class.getSimpleName()).append(" {"); appendProperty(builder, "_boolean", this._boolean); appendProperty(builder, "_int", this._int); appendProperty(builder, "_float", this._float); appendProperty(builder, "string", this.string); appendProperty(builder, "duration", this.duration); appendProperty(builder, "durationUnit", this.durationUnit); appendProperty(builder, "dataSize", this.dataSize); appendProperty(builder, "dataSizeUnit", this.dataSizeUnit); appendProperty(builder, "nullable", this.nullable); appendProperty(builder, "nullable2", this.nullable2); appendProperty(builder, "pair", this.pair); appendProperty(builder, "pair2", this.pair2); appendProperty(builder, "coll", this.coll); appendProperty(builder, "coll2", this.coll2); appendProperty(builder, "list", this.list); appendProperty(builder, "list2", this.list2); appendProperty(builder, "set", this.set); appendProperty(builder, "set2", this.set2); appendProperty(builder, "map", this.map); appendProperty(builder, "map2", this.map2); appendProperty(builder, "container", this.container); appendProperty(builder, "container2", this.container2); appendProperty(builder, "other", this.other); appendProperty(builder, "regex", this.regex); appendProperty(builder, "any", this.any); appendProperty(builder, "nonNull", this.nonNull); appendProperty(builder, "_enum", this._enum); appendProperty(builder, "bytes", Arrays.toString(this.bytes)); builder.append("\n}"); return builder.toString(); } } public static final class Other { public final @NonNull String name; public Other(@Named("name") @NonNull String name) { this.name = name; } public Other withName(@NonNull String name) { return new Other(name); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (this.getClass() != obj.getClass()) return false; Other other = (Other) obj; if (!Objects.equals(this.name, other.name)) return false; return true; } @Override public int hashCode() { int result = 1; result = 31 * result + Objects.hashCode(this.name); return result; } @Override public String toString() { StringBuilder builder = new StringBuilder(100); builder.append(Other.class.getSimpleName()).append(" {"); appendProperty(builder, "name", this.name); builder.append("\n}"); return builder.toString(); } } public enum Direction { NORTH("north"), EAST("east"), SOUTH("south"), WEST("west"); private String value; private Direction(String value) { this.value = value; } @Override public String toString() { return this.value; } } } ================================================ FILE: pkl-codegen-kotlin/gradle.lockfile ================================================ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. com.github.ajalt.clikt:clikt-core-jvm:5.0.3=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.clikt:clikt-core:5.0.3=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.github.ajalt.clikt:clikt-jvm:5.0.3=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.clikt:clikt-markdown-jvm:5.0.3=runtimeClasspath,testRuntimeClasspath com.github.ajalt.clikt:clikt-markdown:5.0.3=runtimeClasspath,testRuntimeClasspath com.github.ajalt.clikt:clikt:5.0.3=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.github.ajalt.colormath:colormath-jvm:3.6.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.colormath:colormath:3.6.0=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.github.ajalt.mordant:mordant-core-jvm:3.0.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-core:3.0.1=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm-ffm-jvm:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm-ffm:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm-graal-ffi-jvm:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm-graal-ffi:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm-jna-jvm:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm-jna:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm:3.0.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-markdown-jvm:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-markdown:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant:3.0.1=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.github.ben-manes.caffeine:caffeine:2.9.3=swiftExportClasspathResolvable com.google.errorprone:error_prone_annotations:2.28.0=swiftExportClasspathResolvable com.squareup:kotlinpoet:1.6.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath io.github.java-diff-utils:java-diff-utils:4.12=kotlinInternalAbiValidation io.opentelemetry:opentelemetry-api:1.41.0=swiftExportClasspathResolvable io.opentelemetry:opentelemetry-context:1.41.0=swiftExportClasspathResolvable net.bytebuddy:byte-buddy:1.17.7=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.java.dev.jna:jna:5.14.0=runtimeClasspath,testRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata org.assertj:assertj-core:3.27.6=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.bouncycastle:bcpg-jdk18on:1.80=kotlinBouncyCastleConfiguration org.bouncycastle:bcpkix-jdk18on:1.80=kotlinBouncyCastleConfiguration org.bouncycastle:bcprov-jdk18on:1.80=kotlinBouncyCastleConfiguration org.bouncycastle:bcutil-jdk18on:1.80=kotlinBouncyCastleConfiguration org.checkerframework:checker-qual:3.43.0=swiftExportClasspathResolvable org.graalvm.polyglot:polyglot:25.0.0=runtimeClasspath,testRuntimeClasspath org.graalvm.sdk:collections:25.0.0=runtimeClasspath,testRuntimeClasspath org.graalvm.sdk:graal-sdk:25.0.0=runtimeClasspath,testRuntimeClasspath org.graalvm.sdk:nativeimage:25.0.0=runtimeClasspath,testRuntimeClasspath org.graalvm.sdk:word:25.0.0=runtimeClasspath,testRuntimeClasspath org.graalvm.truffle:truffle-api:25.0.0=runtimeClasspath,testRuntimeClasspath org.jetbrains.kotlin:abi-tools-api:2.2.20=kotlinInternalAbiValidation org.jetbrains.kotlin:abi-tools:2.2.20=kotlinInternalAbiValidation org.jetbrains.kotlin:kotlin-build-tools-api:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-build-tools-impl:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-compiler-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable,testRuntimeClasspath org.jetbrains.kotlin:kotlin-compiler-runner:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-daemon-client:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-daemon-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable,testRuntimeClasspath org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:2.2.20=kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-metadata-jvm:2.2.20=kotlinInternalAbiValidation org.jetbrains.kotlin:kotlin-native-prebuilt:2.0.21=kotlinNativeBundleConfiguration org.jetbrains.kotlin:kotlin-reflect:2.2.20=compileClasspath,implementationDependenciesMetadata,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,runtimeClasspath,swiftExportClasspathResolvable,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.kotlin:kotlin-script-runtime:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable,testRuntimeClasspath org.jetbrains.kotlin:kotlin-scripting-common:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,testRuntimeClasspath org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,testRuntimeClasspath org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,testRuntimeClasspath org.jetbrains.kotlin:kotlin-scripting-jsr223:2.2.20=testRuntimeClasspath org.jetbrains.kotlin:kotlin-scripting-jvm-host:2.2.20=testRuntimeClasspath org.jetbrains.kotlin:kotlin-scripting-jvm:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,testRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.2.20=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.20=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib:2.2.20=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,runtimeClasspath,swiftExportClasspathResolvable,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.kotlin:swift-export-embeddable:2.2.20=swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable,testRuntimeClasspath org.jetbrains.kotlinx:kotlinx-serialization-bom:1.7.3=swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.7.3=swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3=swiftExportClasspathResolvable org.jetbrains:annotations:13.0=compileClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,runtimeClasspath,swiftExportClasspathResolvable,testCompileClasspath,testRuntimeClasspath org.jetbrains:markdown-jvm:0.7.3=runtimeClasspath,testRuntimeClasspath org.jetbrains:markdown:0.7.3=runtimeClasspath,testRuntimeClasspath org.junit.jupiter:junit-jupiter-api:5.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.jupiter:junit-jupiter-engine:5.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.jupiter:junit-jupiter-params:5.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.platform:junit-platform-commons:1.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.platform:junit-platform-engine:1.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.platform:junit-platform-launcher:1.14.0=testRuntimeClasspath org.junit:junit-bom:5.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.msgpack:msgpack-core:0.9.8=runtimeClasspath,testRuntimeClasspath org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.organicdesign:Paguro:3.10.3=runtimeClasspath,testRuntimeClasspath org.snakeyaml:snakeyaml-engine:2.10=runtimeClasspath,testRuntimeClasspath empty=annotationProcessor,compileOnlyDependenciesMetadata,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinNativeCompilerPluginClasspath,kotlinScriptDefExtensions,shadow,signatures,sourcesJar,testAnnotationProcessor,testApiDependenciesMetadata,testCompileOnlyDependenciesMetadata,testIntransitiveDependenciesMetadata,testKotlinScriptDefExtensions ================================================ FILE: pkl-codegen-kotlin/pkl-codegen-kotlin.gradle.kts ================================================ /* * Copyright © 2024-2025 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. */ plugins { pklAllProjects pklKotlinLibrary pklPublishLibrary pklJavaExecutable } publishing { publications { named("library") { pom { url.set("https://github.com/apple/pkl/tree/main/pkl-codegen-kotlin") description.set( """ Kotlin source code generator that generates corresponding Kotlin classes for Pkl classes, simplifying consumption of Pkl configuration as statically typed Kotlin objects. """ .trimIndent() ) } } } } tasks.jar { manifest { attributes += mapOf("Main-Class" to "org.pkl.codegen.kotlin.Main") } } dependencies { implementation(projects.pklCommons) api(projects.pklCommonsCli) api(projects.pklCore) implementation(libs.kotlinPoet) { exclude(group = "org.jetbrains.kotlin", module = "kotlin-reflect") exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk8") } implementation(libs.kotlinReflect) testImplementation(projects.pklConfigKotlin) testImplementation(projects.pklCommonsTest) testRuntimeOnly(libs.kotlinScripting) } executable { javaName = "pkl-codegen-kotlin" documentationName = "Pkl Codegen Kotlin" javaPublicationName = "pkl-cli-codegen-kotlin" mainClass = "org.pkl.codegen.kotlin.Main" website = "https://pkl-lang.org/main/current/kotlin-binding/codegen.html" } ================================================ FILE: pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/CliKotlinCodeGenerator.kt ================================================ /* * Copyright © 2024-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.codegen.kotlin import java.io.IOException import kotlin.io.path.createParentDirectories import org.pkl.commons.cli.CliCommand import org.pkl.commons.cli.CliException import org.pkl.commons.writeString import org.pkl.core.Closeables import org.pkl.core.ModuleSource /** API for the Kotlin code generator CLI. */ class CliKotlinCodeGenerator(private val options: CliKotlinCodeGeneratorOptions) : CliCommand(options.base) { override fun doRun() { val builder = evaluatorBuilder() try { builder.build().use { evaluator -> for (moduleUri in resolvedSourceModules) { val schema = evaluator.evaluateSchema(ModuleSource.uri(moduleUri)) val codeGenerator = KotlinCodeGenerator(schema, options.toKotlinCodeGeneratorOptions()) try { for ((fileName, fileContents) in codeGenerator.output) { val outputFile = options.outputDir.resolve(fileName) try { outputFile.createParentDirectories().writeString(fileContents) } catch (e: IOException) { throw CliException("I/O error writing file `$outputFile`.\nCause: ${e.message}") } } } catch (e: KotlinCodeGeneratorException) { throw CliException(e.message!!) } } } } finally { Closeables.closeQuietly(builder.moduleKeyFactories) Closeables.closeQuietly(builder.resourceReaders) } } } ================================================ FILE: pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/CliKotlinCodeGeneratorOptions.kt ================================================ /* * Copyright © 2024-2025 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.codegen.kotlin import java.nio.file.Path import org.pkl.commons.cli.CliBaseOptions /** Configuration options for [CliKotlinCodeGenerator]. */ data class CliKotlinCodeGeneratorOptions( /** Base options shared between CLI commands. */ val base: CliBaseOptions, /** The directory where generated source code is placed. */ val outputDir: Path, /** The characters to use for indenting generated source code. */ val indent: String = " ", /** Whether to preserve Pkl doc comments by generating corresponding KDoc comments. */ val generateKdoc: Boolean = false, /** Whether to generate config classes for use with Spring Boot. */ val generateSpringBootConfig: Boolean = false, /** Whether generated classes should implement [java.io.Serializable]. */ val implementSerializable: Boolean = false, /** Whether to add the `@Generated` annotation to types */ val addGeneratedAnnotation: Boolean = false, /** * A rename mapping for class names. * * When you need to have Kotlin class or package names different from the default names derived * from Pkl module names, you can define a rename mapping, where the key is a prefix of the * original Pkl module name, and the value is the desired replacement. */ val renames: Map = emptyMap(), ) { @Suppress("DeprecatedCallableAddReplaceWith") @Deprecated("deprecated without replacement") fun toKotlinCodegenOptions(): KotlinCodeGeneratorOptions = toKotlinCodeGeneratorOptions() internal fun toKotlinCodeGeneratorOptions(): KotlinCodeGeneratorOptions = KotlinCodeGeneratorOptions( indent, generateKdoc, generateSpringBootConfig, implementSerializable, addGeneratedAnnotation, renames, ) } ================================================ FILE: pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/KotlinCodeGenerator.kt ================================================ /* * Copyright © 2024-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. */ @file:Suppress("unused") package org.pkl.codegen.kotlin import com.squareup.kotlinpoet.* import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import java.io.StringWriter import java.util.* import org.pkl.commons.NameMapper import org.pkl.core.* import org.pkl.core.util.CodeGeneratorUtils import org.pkl.core.util.IoUtils @Deprecated("renamed to KotlinCodeGeneratorOptions", ReplaceWith("KotlinCodeGeneratorOptions")) typealias KotlinCodegenOptions = KotlinCodeGeneratorOptions data class KotlinCodeGeneratorOptions( /** The characters to use for indenting generated Kotlin code. */ val indent: String = " ", /** Whether to preserve Pkl doc comments by generating corresponding KDoc comments. */ val generateKdoc: Boolean = false, /** Whether to generate config classes for use with Spring Boot. */ val generateSpringBootConfig: Boolean = false, /** Whether to generate classes that implement [java.io.Serializable]. */ val implementSerializable: Boolean = false, /** Whether to add the `@Generated` to generated types. */ val addGeneratedAnnotation: Boolean = false, /** * A mapping from Pkl module name prefixes to their replacements. * * Can be used when the class or package name in the generated source code should be different * from the corresponding name derived from the Pkl module declaration . */ val renames: Map = emptyMap(), ) class KotlinCodeGeneratorException(message: String) : RuntimeException(message) /** Entrypoint for the Kotlin code generator API. */ class KotlinCodeGenerator( /** The schema for the module to generate */ private val moduleSchema: ModuleSchema, /** The options to use for the code generator */ private val options: KotlinCodeGeneratorOptions, ) { companion object { private val STRING = String::class.asClassName() private val ANY_NULL = ANY.copy(nullable = true) private val NOTHING = Nothing::class.asClassName() @Suppress("RemoveRedundantQualifierName") private val KOTLIN_PAIR = kotlin.Pair::class.asClassName() private val COLLECTION = Collection::class.asClassName() private val LIST = List::class.asClassName() private val SET = Set::class.asClassName() private val MAP = Map::class.asClassName() private val DURATION = Duration::class.asClassName() private val DURATION_UNIT = DurationUnit::class.asClassName() private val DATA_SIZE = DataSize::class.asClassName() private val DATA_SIZE_UNIT = DataSizeUnit::class.asClassName() private val PMODULE = PModule::class.asClassName() private val PCLASS = PClass::class.asClassName() private val REGEX = Regex::class.asClassName() private val URI = java.net.URI::class.asClassName() private val VERSION = Version::class.asClassName() private const val PROPERTY_PREFIX: String = "org.pkl.config.java.mapper." } val output: Map get() { return mapOf(kotlinFileName to kotlinFile, propertyFileName to propertiesFile) } private val propertyFileName: String get() = "resources/META-INF/org/pkl/config/java/mapper/classes/${IoUtils.encodePath(moduleSchema.moduleName)}.properties" private val propertiesFile: String get() { val props = Properties() props["$PROPERTY_PREFIX${moduleSchema.moduleClass.qualifiedName}"] = moduleSchema.moduleClass.toKotlinPoetName().reflectionName() for (pClass in moduleSchema.classes.values) { props["$PROPERTY_PREFIX${pClass.qualifiedName}"] = pClass.toKotlinPoetName().reflectionName() } return StringWriter() .apply { props.store(this, "Kotlin mappings for Pkl module `${moduleSchema.moduleName}`") } .toString() } private val kotlinFileName: String get() = buildString { val (packageName, className) = nameMapper.map(moduleSchema.moduleName) val dirPath = packageName.split('.').joinToString("/", transform = IoUtils::encodePath) val fileName = IoUtils.encodePath(className) append("kot") append("lin/") if (dirPath.isNotEmpty()) { append("$dirPath/") } append("$fileName.kt") } val kotlinFile: String get() { if (moduleSchema.moduleUri.scheme == "pkl") { throw KotlinCodeGeneratorException( "Cannot generate Kotlin code for a Pkl standard library module (`${moduleSchema.moduleUri}`)." ) } val pModuleClass = moduleSchema.moduleClass val hasModuleProperties = pModuleClass.properties.any { !it.value.isHidden } val isGenerateModuleClass = hasModuleProperties || pModuleClass.isOpen || pModuleClass.isAbstract fun generateCompanionRelatedCode( builder: TypeSpec.Builder, isModuleType: Boolean = false, ): TypeSpec.Builder { // ensure that at most one companion object is generated for this type val companionObjectBuilder: Lazy = lazy { TypeSpec.companionObjectBuilder() } // generate serialization code if ( options.implementSerializable && (!isModuleType || isGenerateModuleClass) && !builder.modifiers.contains(KModifier.ABSTRACT) ) { builder.addSuperinterface(java.io.Serializable::class.java) companionObjectBuilder.value.addProperty( PropertySpec.builder( "serialVersionUID", Long::class.java, KModifier.PRIVATE, KModifier.CONST, ) .initializer("0L") .build() ) } if (companionObjectBuilder.isInitialized()) { builder.addType(companionObjectBuilder.value.build()) } return builder } val moduleType = if (isGenerateModuleClass) { generateTypeSpec(pModuleClass, moduleSchema) } else { generateObjectSpec(pModuleClass) } for (pClass in moduleSchema.classes.values) { moduleType.addType( generateCompanionRelatedCode(generateTypeSpec(pClass, moduleSchema)).build() ) } val moduleName = moduleSchema.moduleName val (packageName, moduleTypeName) = nameMapper.map(moduleName) val fileSpec = FileSpec.builder(packageName, moduleTypeName).indent(options.indent) for (typeAlias in moduleSchema.typeAliases.values) { if (typeAlias.aliasedType is PType.Alias) { // generate top-level type alias (Kotlin doesn't support nested type aliases) fileSpec.addTypeAlias(generateTypeAliasSpec(typeAlias).build()) } else { val stringLiterals = mutableSetOf() if (CodeGeneratorUtils.isRepresentableAsEnum(typeAlias.aliasedType, stringLiterals)) { // generate nested enum class moduleType.addType(generateEnumTypeSpec(typeAlias, stringLiterals).build()) } else { // generate top-level type alias (Kotlin doesn't support nested type aliases) fileSpec.addTypeAlias(generateTypeAliasSpec(typeAlias).build()) } } } fileSpec.addType(generateCompanionRelatedCode(moduleType, isModuleType = true).build()) return fileSpec.build().toString() } private fun generateObjectSpec(pClass: PClass): TypeSpec.Builder { val builder = TypeSpec.objectBuilder(pClass.toKotlinPoetName()) val docComment = pClass.docComment if (docComment != null && options.generateKdoc) { builder.addKdoc(renderAsKdoc(docComment)) } return builder } private fun generateTypeSpec(pClass: PClass, schema: ModuleSchema): TypeSpec.Builder { val isModuleClass = pClass == schema.moduleClass val kotlinPoetClassName = pClass.toKotlinPoetName() val superclass = pClass.superclass?.takeIf { it.info != PClassInfo.Typed && it.info != PClassInfo.Module } val superProperties = superclass?.allProperties?.filterValues { !it.isHidden } ?: mapOf() val properties = pClass.properties.filterValues { !it.isHidden } val allProperties = superProperties + properties fun PClass.Property.isRegex(): Boolean = (this.type as? PType.Class)?.pClass?.info == PClassInfo.Regex fun PClass.Property.isByteArray(): Boolean = (this.type as? PType.Class)?.pClass?.info == PClassInfo.Bytes fun generateConstructor(): FunSpec { val builder = FunSpec.constructorBuilder() for ((name, property) in allProperties) { builder.addParameter(name, property.type.toKotlinPoetName()) } return builder.build() } fun generateCopyMethod(parameters: Map, isOverride: Boolean): FunSpec { val methodBuilder = FunSpec.builder("copy").returns(kotlinPoetClassName) if (isOverride) { methodBuilder.addModifiers(KModifier.OVERRIDE) } if (pClass.isOpen || pClass.isAbstract) { methodBuilder.addModifiers(KModifier.OPEN) } for ((name, property) in parameters) { val paramBuilder = ParameterSpec.builder(name, property.type.toKotlinPoetName()) if (!isOverride) { paramBuilder.defaultValue("this.%N", name) } methodBuilder.addParameter(paramBuilder.build()) } val codeBuilder = CodeBlock.builder().add("return %T(", kotlinPoetClassName) for ((index, name) in allProperties.keys.withIndex()) { codeBuilder.add(if (index == 0) "%N" else ", %N", name) } codeBuilder.add(")\n") return methodBuilder.addCode(codeBuilder.build()).build() } fun inheritsCopyMethodWithSameArity(): Boolean { val nearestNonAbstractAncestor = generateSequence(pClass.superclass) { it.superclass }.firstOrNull { !it.isAbstract } ?: return false return nearestNonAbstractAncestor.allProperties.values.count { !it.isHidden } == allProperties.size } // besides generating copy method for current class, // override copy methods inherited from parent classes fun generateCopyMethods(typeBuilder: TypeSpec.Builder) { // copy methods don't make sense for abstract classes if (pClass.isAbstract) return var prevParameterCount = Int.MAX_VALUE for (currClass in generateSequence(pClass) { it.superclass }) { if (currClass.isAbstract) continue val currParameters = currClass.allProperties.filter { !it.value.isHidden } // avoid generating multiple methods with same no. of parameters if (currParameters.size < prevParameterCount) { val isOverride = currClass !== pClass || inheritsCopyMethodWithSameArity() typeBuilder.addFunction(generateCopyMethod(currParameters, isOverride)) prevParameterCount = currParameters.size } } } fun generateEqualsMethod(): FunSpec { val builder = FunSpec.builder("equals") .addModifiers(KModifier.OVERRIDE) .addParameter("other", ANY_NULL) .returns(BOOLEAN) .addStatement("if (this === other) return true") // generating this.javaClass instead of class literal avoids a SpotBugs warning .addStatement("if (this.javaClass != other?.javaClass) return false") .addStatement("other as %T", kotlinPoetClassName) for ((propertyName, property) in allProperties) { if (property.isByteArray()) { builder.addStatement( "if (!this.%N.contentEquals(other.%N)) return false", propertyName, propertyName, ) } else { val accessor = if (property.isRegex()) "%N.pattern" else "%N" builder.addStatement( "if (this.$accessor != other.$accessor) return false", propertyName, propertyName, ) } } builder.addStatement("return true") return builder.build() } fun generateHashCodeMethod(): FunSpec { val builder = FunSpec.builder("hashCode") .addModifiers(KModifier.OVERRIDE) .returns(INT) .addStatement("var result = 1") for ((propertyName, property) in allProperties) { if (property.isByteArray()) { builder.addStatement("result = 31 * result + this.%N.contentHashCode()", propertyName) } else { val accessor = if (property.isRegex()) "this.%N.pattern" else "this.%N" // use Objects.hashCode() because Kotlin's Any?.hashCode() // doesn't work for platform types (will get NPE if null) builder.addStatement( "result = 31 * result + %T.hashCode($accessor)", Objects::class, propertyName, ) } } builder.addStatement("return result") return builder.build() } // produce same output as default toString() method of data classes fun generateToStringMethod(): FunSpec { return FunSpec.builder("toString") .addModifiers(KModifier.OVERRIDE) .returns(STRING) .addStatement( "return %P", CodeBlock.builder() .apply { add("%L", pClass.toKotlinPoetName().simpleName) add("(") for ((index, entry) in allProperties.entries.withIndex()) { val (propertyName, property) = entry add(if (index == 0) "%L" else ", %L", propertyName) if (property.isByteArray()) { add("=\${%T.toString(%L)}", Arrays::class, propertyName) } else { add("=$") add("%N", propertyName) } } add(")") } .build(), ) .build() } fun generateDeprecation( annotations: Collection, addAnnotation: (AnnotationSpec) -> Unit, ) { annotations .firstOrNull { it.classInfo == PClassInfo.Deprecated } ?.let { deprecation -> val builder = AnnotationSpec.builder(Deprecated::class) (deprecation["message"] as String?)?.let { builder.addMember("message = %S", it) } addAnnotation(builder.build()) } } fun generateProperty(propertyName: String, property: PClass.Property): PropertySpec { val typeName = property.type.toKotlinPoetName() val builder = PropertySpec.builder(propertyName, typeName).initializer("%L", propertyName) generateDeprecation(property.annotations) { builder.addAnnotation(it) } val docComment = property.docComment if (docComment != null && options.generateKdoc) { builder.addKdoc(renderAsKdoc(docComment)) } if (propertyName in superProperties) { builder.addModifiers(KModifier.OVERRIDE) } if (pClass.isOpen || pClass.isAbstract) { builder.addModifiers(KModifier.OPEN) } return builder.build() } fun generateSpringBootAnnotations(builder: TypeSpec.Builder) { if (isModuleClass) { builder.addAnnotation( ClassName("org.springframework.boot.context.properties", "ConfigurationProperties") ) } else { // not very efficient to repeat computing module property base types for every class val modulePropertiesWithMatchingType = schema.moduleClass.allProperties.values.filter { property -> var propertyType = property.type while (propertyType is PType.Constrained || propertyType is PType.Nullable) { if (propertyType is PType.Constrained) { propertyType = propertyType.baseType } else if (propertyType is PType.Nullable) { propertyType = propertyType.baseType } } propertyType is PType.Class && propertyType.pClass == pClass } if (modulePropertiesWithMatchingType.size == 1) { // exactly one module property has this type -> make it available for direct injection // (potential improvement: make type available for direct injection if it occurs exactly // once in property tree) builder.addAnnotation( AnnotationSpec.builder( ClassName("org.springframework.boot.context.properties", "ConfigurationProperties") ) // use "value" instead of "prefix" to entice JavaPoet to generate a single-line // annotation // that can easily be filtered out by JavaCodeGeneratorTest.`spring boot config` .addMember("%S", modulePropertiesWithMatchingType.first().simpleName) .build() ) } } } fun generateRegularClass(): TypeSpec.Builder { val builder = TypeSpec.classBuilder(kotlinPoetClassName) if (options.generateSpringBootConfig) { generateSpringBootAnnotations(builder) } if (options.addGeneratedAnnotation) { builder.addAnnotation(ClassName("org.pkl.config.java", "Generated")) } builder.primaryConstructor(generateConstructor()) val docComment = pClass.docComment if (docComment != null && options.generateKdoc) { builder.addKdoc(renderAsKdoc(docComment)) } if (pClass.isAbstract) { builder.addModifiers(KModifier.ABSTRACT) } else if (pClass.isOpen) { builder.addModifiers(KModifier.OPEN) } superclass?.let { superclass -> val superclassName = superclass.toKotlinPoetName() builder.superclass(superclassName) for (propertyName in superProperties.keys) { builder.addSuperclassConstructorParameter(propertyName) } } for ((name, property) in properties) { builder.addProperty(generateProperty(name, property)) } if (!pClass.isAbstract) { generateCopyMethods(builder) builder .addFunction(generateEqualsMethod()) .addFunction(generateHashCodeMethod()) .addFunction(generateToStringMethod()) } return builder } fun generateDataClass(): TypeSpec.Builder { val builder = TypeSpec.classBuilder(kotlinPoetClassName).addModifiers(KModifier.DATA) if (options.generateSpringBootConfig) { generateSpringBootAnnotations(builder) } if (options.addGeneratedAnnotation) { builder.addAnnotation(ClassName("org.pkl.config.java", "Generated")) } builder.primaryConstructor(generateConstructor()) generateDeprecation(pClass.annotations) { builder.addAnnotation(it) } val docComment = pClass.docComment if (docComment != null && options.generateKdoc) { builder.addKdoc(renderAsKdoc(docComment)) } var hasRegexOrByteArray = false for ((name, property) in properties) { hasRegexOrByteArray = hasRegexOrByteArray || property.isRegex() || property.isByteArray() builder.addProperty(generateProperty(name, property)) } // Regex and ByteArray define equaltiy as identity. // To match Pkl semantics, override equals and hashCode if the data class has a property of // type Regex or ByteArray. if (hasRegexOrByteArray) { builder.addFunction(generateEqualsMethod()).addFunction(generateHashCodeMethod()) } return builder } return if (superclass == null && !pClass.isAbstract && !pClass.isOpen) generateDataClass() else generateRegularClass() } private fun generateEnumTypeSpec( typeAlias: TypeAlias, stringLiterals: Set, ): TypeSpec.Builder { val enumConstantToPklNames = stringLiterals .groupingBy { literal -> CodeGeneratorUtils.toEnumConstantName(literal) ?: throw KotlinCodeGeneratorException( "Cannot generate Kotlin enum class for Pkl type alias `${typeAlias.displayName}` " + "because string literal type \"$literal\" cannot be converted to a valid enum constant name." ) } .reduce { enumConstantName, firstLiteral, secondLiteral -> throw KotlinCodeGeneratorException( "Cannot generate Kotlin enum class for Pkl type alias `${typeAlias.displayName}` " + "because string literal types \"$firstLiteral\" and \"$secondLiteral\" " + "would both be converted to enum constant name `$enumConstantName`." ) } val builder = TypeSpec.enumBuilder(typeAlias.simpleName) .primaryConstructor( FunSpec.constructorBuilder().addParameter("value", String::class).build() ) .addProperty(PropertySpec.builder("value", String::class).initializer("value").build()) .addFunction( FunSpec.builder("toString") .addModifiers(KModifier.OVERRIDE) .addStatement("return value") .build() ) for ((enumConstantName, pklName) in enumConstantToPklNames) { builder.addEnumConstant( enumConstantName, TypeSpec.anonymousClassBuilder().addSuperclassConstructorParameter("%S", pklName).build(), ) } return builder } private fun generateTypeAliasSpec(typeAlias: TypeAlias): TypeAliasSpec.Builder { val builder = TypeAliasSpec.builder(typeAlias.simpleName, typeAlias.aliasedType.toKotlinPoetName()) for (typeParameter in typeAlias.typeParameters) { builder.addTypeVariable( TypeVariableName(typeParameter.name, typeParameter.variance.toKotlinPoet()) ) } val docComment = typeAlias.docComment if (docComment != null && options.generateKdoc) { builder.addKdoc(renderAsKdoc(docComment)) } return builder } private fun TypeParameter.Variance.toKotlinPoet(): KModifier? = when (this) { TypeParameter.Variance.COVARIANT -> KModifier.OUT TypeParameter.Variance.CONTRAVARIANT -> KModifier.IN else -> null } // do the minimum work necessary to avoid kotlin compile errors // generating idiomatic KDoc would require parsing doc comments, converting member links, etc. private fun renderAsKdoc(docComment: String): String = docComment private fun PClass.toKotlinPoetName(): ClassName { val (packageName, moduleTypeName) = nameMapper.map(moduleName) return if (isModuleClass) { ClassName(packageName, moduleTypeName) } else { ClassName(packageName, moduleTypeName, simpleName) } } private fun TypeAlias.toKotlinPoetName(): ClassName { val (packageName, moduleTypeName) = nameMapper.map(moduleName) return when { aliasedType is PType.Alias -> { // Kotlin type generated for [this] is a top-level type alias ClassName(packageName, simpleName) } CodeGeneratorUtils.isRepresentableAsEnum(aliasedType, null) -> { if (isStandardLibraryMember) { throw KotlinCodeGeneratorException( "Standard library typealias `${qualifiedName}` is not supported by Kotlin code generator." + " If you think this is an omission, please let us know." ) } // Kotlin type generated for [this] is a nested enum class ClassName(packageName, moduleTypeName, simpleName) } else -> { // Kotlin type generated for [this] is a top-level type alias ClassName(packageName, simpleName) } } } private fun PType.toKotlinPoetName(): TypeName = when (this) { PType.UNKNOWN -> ANY_NULL PType.NOTHING -> NOTHING is PType.StringLiteral -> STRING is PType.Class -> { // if in doubt, spell it out when (val classInfo = pClass.info) { PClassInfo.Any -> ANY_NULL PClassInfo.Typed, PClassInfo.Dynamic -> ANY PClassInfo.Boolean -> BOOLEAN PClassInfo.String -> STRING // seems more useful to generate `Double` than `kotlin.Number` PClassInfo.Number -> DOUBLE PClassInfo.Int -> LONG PClassInfo.Float -> DOUBLE PClassInfo.Duration -> DURATION PClassInfo.DataSize -> DATA_SIZE PClassInfo.Bytes -> BYTE_ARRAY PClassInfo.Pair -> KOTLIN_PAIR.parameterizedBy( if (typeArguments.isEmpty()) ANY_NULL else typeArguments[0].toKotlinPoetName(), if (typeArguments.isEmpty()) ANY_NULL else typeArguments[1].toKotlinPoetName(), ) PClassInfo.Collection -> COLLECTION.parameterizedBy( if (typeArguments.isEmpty()) ANY_NULL else typeArguments[0].toKotlinPoetName() ) PClassInfo.List, PClassInfo.Listing -> LIST.parameterizedBy( if (typeArguments.isEmpty()) ANY_NULL else typeArguments[0].toKotlinPoetName() ) PClassInfo.Set -> SET.parameterizedBy( if (typeArguments.isEmpty()) ANY_NULL else typeArguments[0].toKotlinPoetName() ) PClassInfo.Map, PClassInfo.Mapping -> MAP.parameterizedBy( if (typeArguments.isEmpty()) ANY_NULL else typeArguments[0].toKotlinPoetName(), if (typeArguments.isEmpty()) ANY_NULL else typeArguments[1].toKotlinPoetName(), ) PClassInfo.Module -> PMODULE PClassInfo.Class -> PCLASS PClassInfo.Regex -> REGEX PClassInfo.Version -> VERSION else -> when { !classInfo.isStandardLibraryClass -> pClass.toKotlinPoetName() else -> throw KotlinCodeGeneratorException( "Standard library class `${pClass.qualifiedName}` is not supported by Kotlin code generator. " + "If you think this is an omission, please let us know." ) } } } is PType.Nullable -> baseType.toKotlinPoetName().copy(nullable = true) is PType.Constrained -> baseType.toKotlinPoetName() is PType.Alias -> when (typeAlias.qualifiedName) { "pkl.base#NonNull" -> ANY // Not currently generating Kotlin unsigned types // because it's not clear if the benefits outweigh the drawbacks: // - breaking change // - Kotlin unsigned types aren't intended for domain modeling // - diverts from Java code generator // - doesn't increase safety // - range already checked on Pkl side // - conversion to signed type doesn't perform range check "pkl.base#Int8" -> BYTE "pkl.base#Int16", "pkl.base#UInt8" -> SHORT "pkl.base#Int32", "pkl.base#UInt16" -> INT "pkl.base#UInt", "pkl.base#UInt32" -> LONG "pkl.base#DurationUnit" -> DURATION_UNIT "pkl.base#DataSizeUnit" -> DATA_SIZE_UNIT "pkl.base#Uri" -> URI else -> { val className = typeAlias.toKotlinPoetName() when { typeAlias.typeParameters.isEmpty() -> className typeArguments.isEmpty() -> { // no type arguments provided for a type alias with type parameters -> fill in // `Any?` (equivalent of `unknown`) val typeArgs = Array(typeAlias.typeParameters.size) { ANY_NULL } className.parameterizedBy(*typeArgs) } else -> className.parameterizedBy(*typeArguments.toKotlinPoet()) } } } is PType.Function -> throw KotlinCodeGeneratorException( "Pkl function types are not supported by the Kotlin code generator." ) is PType.Union -> if (CodeGeneratorUtils.isRepresentableAsString(this)) STRING else throw KotlinCodeGeneratorException( "Pkl union types are not supported by the Kotlin code generator." ) // occurs on RHS of generic type aliases is PType.TypeVariable -> TypeVariableName(typeParameter.name) else -> throw AssertionError("Encountered unexpected PType subclass: $this") } private fun List.toKotlinPoet(): Array = map { it.toKotlinPoetName() }.toTypedArray() private val nameMapper = NameMapper(options.renames) } ================================================ FILE: pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/Main.kt ================================================ /* * Copyright © 2024-2025 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. */ @file:JvmName("Main") package org.pkl.codegen.kotlin import com.github.ajalt.clikt.core.main import com.github.ajalt.clikt.parameters.options.associate import com.github.ajalt.clikt.parameters.options.default import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.types.path import java.nio.file.Path import org.pkl.commons.cli.CliBaseOptions import org.pkl.commons.cli.cliMain import org.pkl.commons.cli.commands.ModulesCommand import org.pkl.commons.cli.commands.installCommonOptions import org.pkl.commons.toPath import org.pkl.core.Release /** Main method for the Kotlin code generator CLI. */ internal fun main(args: Array) { cliMain { PklKotlinCodegenCommand().main(args) } } val helpLink = "${Release.current().documentation.homepage}kotlin-binding/codegen.html#cli" class PklKotlinCodegenCommand : ModulesCommand(name = "pkl-codegen-kotlin", helpLink = helpLink) { private val defaults = CliKotlinCodeGeneratorOptions(CliBaseOptions(), "".toPath()) private val outputDir: Path by option( names = arrayOf("-o", "--output-dir"), metavar = "path", help = "The directory where generated source code is placed.", ) .path() .default(defaults.outputDir) private val indent: String by option( names = arrayOf("--indent"), metavar = "chars", help = "The characters to use for indenting generated source code.", ) .default(defaults.indent) private val generateKdoc: Boolean by option( names = arrayOf("--generate-kdoc"), help = "Whether to preserve Pkl doc comments by generating corresponding KDoc comments.", ) .flag() private val generateSpringboot: Boolean by option( names = arrayOf("--generate-spring-boot"), help = "Whether to generate config classes for use with Spring Boot.", ) .flag() private val implementSerializable: Boolean by option( names = arrayOf("--implement-serializable"), help = "Whether to generate classes that implement java.io.Serializable.", ) .flag() private val addGeneratedAnnotation: Boolean by option( names = arrayOf("--add-generated-annotation"), help = "Whether to add a @Generated annotation to the types to be generated.", ) .flag() private val renames: Map by option( names = arrayOf("--rename"), metavar = "old_name=new_name", help = """ Replace a prefix in the names of the generated Kotlin classes (repeatable). By default, the names of generated classes are derived from the Pkl module names. With this option, you can override or modify the default names, renaming entire classes or just their packages. """ .trimIndent(), ) .associate() override val helpString: String = "Generate Kotlin classes and interfaces from Pkl module(s)" override fun run() { val options = CliKotlinCodeGeneratorOptions( base = baseOptions.baseOptions(modules, projectOptions), outputDir = outputDir, indent = indent, generateKdoc = generateKdoc, generateSpringBootConfig = generateSpringboot, implementSerializable = implementSerializable, addGeneratedAnnotation = addGeneratedAnnotation, renames = renames, ) CliKotlinCodeGenerator(options).run() } init { installCommonOptions() } } ================================================ FILE: pkl-codegen-kotlin/src/test/kotlin/org/pkl/codegen/kotlin/CliKotlinCodeGeneratorTest.kt ================================================ /* * Copyright © 2024-2025 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.codegen.kotlin import java.nio.file.Path import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir import org.pkl.commons.cli.CliBaseOptions import org.pkl.commons.readString class CliKotlinCodeGeneratorTest { @Test fun `module inheritance`(@TempDir tempDir: Path) { val module1 = PklModule( "org.mod1", """ open module org.mod1 pigeon: Person class Person { name: String age: Int } """, ) val module2 = PklModule( "org.mod2", """ module org.mod2 extends "mod1.pkl" parrot: Person """, ) val module1File = module1.writeToDisk(tempDir.resolve("org/mod1.pkl")) val module2File = module2.writeToDisk(tempDir.resolve("org/mod2.pkl")) val outputDir = tempDir.resolve("output") val generator = CliKotlinCodeGenerator( CliKotlinCodeGeneratorOptions( CliBaseOptions(listOf(module1File.toUri(), module2File.toUri())), outputDir, ) ) generator.run() val module1KotlinFile = outputDir.resolve("kotlin/org/Mod1.kt") assertThat(module1KotlinFile).exists() val module2KotlinFile = outputDir.resolve("kotlin/org/Mod2.kt") assertThat(module2KotlinFile).exists() assertContains( """ open class Mod1( open val pigeon: Person ) { """ .trimIndent(), module1KotlinFile.readString(), ) assertContains( """ class Mod2( pigeon: Mod1.Person, val parrot: Mod1.Person ) : Mod1(pigeon) { """ .trimIndent(), module2KotlinFile.readString(), ) } @Test fun `class name clashes`(@TempDir tempDir: Path) { val module1 = PklModule( "org.mod1", """ module org.mod1 class Person { name: String } """, ) val module2 = PklModule( "org.mod2", """ module org.mod2 import "mod1.pkl" person1: mod1.Person person2: Person class Person { age: Int } """, ) val module1PklFile = module1.writeToDisk(tempDir.resolve("org/mod1.pkl")) val module2PklFile = module2.writeToDisk(tempDir.resolve("org/mod2.pkl")) val outputDir = tempDir.resolve("output") val generator = CliKotlinCodeGenerator( CliKotlinCodeGeneratorOptions( CliBaseOptions(listOf(module1PklFile.toUri(), module2PklFile.toUri())), outputDir, ) ) generator.run() val module2KotlinFile = outputDir.resolve("kotlin/org/Mod2.kt") assertContains( """ data class Mod2( val person1: Mod1.Person, val person2: Person ) """ .trimIndent(), module2KotlinFile.readString(), ) } @Test fun `custom package names`(@TempDir tempDir: Path) { val module1 = PklModule( "org.foo.Module1", """ module org.foo.Module1 class Person { name: String } """, ) val module2 = PklModule( "org.bar.Module2", """ module org.bar.Module2 import "../../org/foo/Module1.pkl" class Group { owner: Module1.Person name: String } """, ) val module3 = PklModule( "org.baz.Module3", """ module org.baz.Module3 import "../../org/bar/Module2.pkl" class Supergroup { owner: Module2.Group } """, ) val module1PklFile = module1.writeToDisk(tempDir.resolve("org/foo/Module1.pkl")) val module2PklFile = module2.writeToDisk(tempDir.resolve("org/bar/Module2.pkl")) val module3PklFile = module3.writeToDisk(tempDir.resolve("org/baz/Module3.pkl")) val outputDir = tempDir.resolve("output") val generator = CliKotlinCodeGenerator( CliKotlinCodeGeneratorOptions( CliBaseOptions(listOf(module1PklFile, module2PklFile, module3PklFile).map { it.toUri() }), outputDir, renames = mapOf("org.foo" to "com.foo.x", "org.baz" to "com.baz.a.b"), ) ) generator.run() val module1KotlinFile = outputDir.resolve("kotlin/com/foo/x/Module1.kt") module1KotlinFile.readString().let { assertContains("package com.foo.x", it) assertContains("object Module1 {", it) assertContains( """ | data class Person( | val name: String | ) """, it, ) } val module2KotlinFile = outputDir.resolve("kotlin/org/bar/Module2.kt") module2KotlinFile.readString().let { assertContains("package org.bar", it) assertContains("import com.foo.x.Module1", it) assertContains("object Module2 {", it) assertContains( """ | data class Group( | val owner: Module1.Person, | val name: String | ) """, it, ) } val module3KotlinFile = outputDir.resolve("kotlin/com/baz/a/b/Module3.kt") module3KotlinFile.readString().let { assertContains("package com.baz.a.b", it) assertContains("import org.bar.Module2", it) assertContains("object Module3 {", it) assertContains( """ | data class Supergroup( | val owner: Module2.Group | ) """, it, ) } } private fun assertContains(part: String, code: String) { val trimmedPart = part.trim().trimMargin() if (!code.contains(trimmedPart)) { // check for equality to get better error output (ide diff dialog) assertThat(code).isEqualTo(trimmedPart) } } } ================================================ FILE: pkl-codegen-kotlin/src/test/kotlin/org/pkl/codegen/kotlin/InMemoryKotlinCompiler.kt ================================================ /* * Copyright © 2024-2025 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.codegen.kotlin import javax.script.ScriptEngineManager import javax.script.ScriptException import kotlin.reflect.KClass import kotlin.text.RegexOption.MULTILINE import kotlin.text.RegexOption.UNIX_LINES class CompilationFailedException(msg: String?, cause: Throwable? = null) : RuntimeException(msg, cause) object InMemoryKotlinCompiler { // Implementation notes: // * all [sourceFiles] are currently combined into a single file // * implementation makes assumptions about structure of generated source files fun compile(sourceFiles: Map): Map> { fun String.findClasses( prefix: String = "", nameGroup: Int = 2, bodyGroup: Int = 4, regex: String = "^(data |open |enum )?class\\s+(\\w+) *(\\([^)]*\\))?.*$\\n((^ .*\\n|^$\\n)*)", transform: (String, String) -> Sequence> = { name, body -> sequenceOf(Pair(name, prefix + name)) + body.findClasses("$prefix$name.") }, ): Sequence> = // (simpleName1, qualifiedName1), ... Regex(regex, setOf(MULTILINE, UNIX_LINES)).findAll(this).flatMap { transform(it.groupValues[nameGroup], it.groupValues[bodyGroup].trimIndent()) } fun String.findOuterObjects(): Sequence> = // (simpleName, qualifiedName) findClasses(nameGroup = 1, bodyGroup = 2, regex = "^object\\s+(\\w+).*$\n((^ .*$\n|^$\n)*)") { name, body -> body.findClasses("$name.") } val (importLines, remainder) = sourceFiles.entries .filter { (filename, _) -> filename.endsWith(".kt") } .flatMap { (_, text) -> text.lines() } .partition { it.startsWith("import") } val importBlock = importLines.sorted().distinct() val (packageLines, code) = remainder.partition { it.startsWith("package") } val packageBlock = packageLines.distinct() assert( packageBlock.size <= 1 ) // everything is in the same package and/or there is no package line val sourceText = listOf(packageBlock, importBlock, code).flatten().joinToString("\n") val (simpleNames, qualifiedNames) = sourceText.findClasses().plus(sourceText.findOuterObjects()).unzip() val instrumentation = "listOf>(${qualifiedNames.joinToString(",") { "$it::class" }})" // create new engine for each compilation // (otherwise we sometimes get kotlin compiler exceptions) val engine = ScriptEngineManager().getEngineByExtension("kts")!! val classes = try { @Suppress("UNCHECKED_CAST") engine.eval("$sourceText\n\n$instrumentation") as List> } catch (e: ScriptException) { throw CompilationFailedException(e.message, e) } return simpleNames.zip(classes).toMap() } } ================================================ FILE: pkl-codegen-kotlin/src/test/kotlin/org/pkl/codegen/kotlin/KotlinCodeGeneratorTest.kt ================================================ /* * Copyright © 2024-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.codegen.kotlin import java.io.* import java.nio.file.Path import kotlin.reflect.KClass import kotlin.reflect.full.declaredMemberProperties import kotlin.reflect.full.memberProperties import org.assertj.core.api.AbstractAssert import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatCode import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertAll import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.io.TempDir import org.pkl.commons.test.ReflectionUtils.enumValues import org.pkl.core.* import org.pkl.core.util.IoUtils class KotlinCodeGeneratorTest { companion object { private const val MAPPER_PREFIX = "resources/META-INF/org/pkl/config/java/mapper/classes" // according to: // https://github.com/JetBrains/kotlin/blob/master/core/descriptors/ // src/org/jetbrains/kotlin/renderer/KeywordStringsGenerated.java private val kotlinKeywords = setOf( "package", "as", "typealias", "class", "this", "super", "val", "var", "fun", "for", "null", "true", "false", "is", "in", "throw", "return", "break", "continue", "object", "if", "try", "else", "while", "do", "when", "interface", "typeof", ) private val simpleClass: KClass<*> by lazy { generateKotlinCode( """ module my.mod open class Simple { str: String list: List } """ .trimIndent() ) .compile() .getValue("Simple") } private val propertyTypesKotlinCode: KotlinSourceCode by lazy { generateKotlinCode( """ module my.mod open class PropertyTypes { boolean: Boolean int: Int float: Float string: String duration: Duration durationUnit: DurationUnit dataSize: DataSize dataSizeUnit: DataSizeUnit nullable: String? nullable2: String? pair: Pair pair2: Pair coll: Collection coll2: Collection list: List list2: List set: Set set2: Set map: Map map2: Map container: Mapping container2: Mapping other: Other regex: Regex any: Any nonNull: NonNull enum: Direction bytes: Bytes } open class Other { name: String } typealias Direction = "north"|"east"|"south"|"west" """ .trimIndent() ) } private val propertyTypesClasses: Map> by lazy { propertyTypesKotlinCode.compile() } private fun generateKotlinCode( pklCode: String, generateKdoc: Boolean = false, generateSpringBootConfig: Boolean = false, implementSerializable: Boolean = false, ): KotlinSourceCode { val module = Evaluator.preconfigured().evaluateSchema(ModuleSource.text(pklCode)) val generator = KotlinCodeGenerator( module, KotlinCodeGeneratorOptions( generateKdoc = generateKdoc, generateSpringBootConfig = generateSpringBootConfig, implementSerializable = implementSerializable, ), ) return KotlinSourceCode(generator.kotlinFile) } } @TempDir lateinit var tempDir: Path @Test fun testEquals() { val ctor = simpleClass.constructors.first() val instance1 = ctor.call("foo", listOf(1, 2, 3)) val instance2 = ctor.call("foo", listOf(1, 2, 3)) val instance3 = ctor.call("foo", listOf(1, 3, 2)) val instance4 = ctor.call("bar", listOf(1, 2, 3)) assertThat(instance1).isEqualTo(instance1) assertThat(instance1).isEqualTo(instance2) assertThat(instance2).isEqualTo(instance1) assertThat(instance3).isNotEqualTo(instance1) assertThat(instance4).isNotEqualTo(instance1) } @Test fun testHashCode() { val ctor = simpleClass.constructors.first() val instance1 = ctor.call("foo", listOf(1, 2, 3)) val instance2 = ctor.call("foo", listOf(1, 2, 3)) val instance3 = ctor.call("foo", listOf(1, 3, 2)) val instance4 = ctor.call("bar", listOf(1, 2, 3)) assertThat(instance1.hashCode()).isEqualTo(instance1.hashCode()) assertThat(instance1.hashCode()).isEqualTo(instance2.hashCode()) assertThat(instance3.hashCode()).isNotEqualTo(instance1.hashCode()) assertThat(instance4.hashCode()).isNotEqualTo(instance1.hashCode()) } @Test fun testToString() { val (_, propertyTypes) = instantiateOtherAndPropertyTypes() assertThat(propertyTypes.toString()) .isEqualTo( """PropertyTypes(boolean=true, int=42, float=42.3, string=string, duration=5.min, """ + """durationUnit=min, dataSize=3.gb, dataSizeUnit=gb, nullable=idea, nullable2=null, """ + """pair=(1, 2), pair2=(pigeon, Other(name=pigeon)), coll=[1, 2], """ + """coll2=[Other(name=pigeon), Other(name=pigeon)], list=[1, 2], """ + """list2=[Other(name=pigeon), Other(name=pigeon)], set=[1, 2], """ + """set2=[Other(name=pigeon)], map={1=one, 2=two}, map2={one=Other(name=pigeon), """ + """two=Other(name=pigeon)}, container={1=one, 2=two}, container2={one=Other(name=pigeon), """ + """two=Other(name=pigeon)}, other=Other(name=pigeon), regex=(i?)\w*, any=Other(name=pigeon), """ + """nonNull=Other(name=pigeon), enum=north, """ + """bytes=[1, 2, 3, 4])""" ) } @Test fun `quoted identifiers`() { val kotlinCode = generateKotlinCode( """ open class `A Person` { `first name`: String } """ .trimIndent() ) assertThat(kotlinCode) .compilesSuccessfully() .contains( """ | open class `A Person`( | open val `first name`: String | ) """ .trimMargin() ) .contains( """ | override fun toString(): String = ""${'"'}A Person(first name=${'$'}`first name`)""${'"'} """ .trimMargin() ) .contains( """ | open fun copy(`first name`: String = this.`first name`): `A Person` = `A Person`(`first name`) """ .trimMargin() ) } @Test fun `deprecated property with message`() { val kotlinCode = generateKotlinCode( """ class ClassWithDeprecatedProperty { @Deprecated { message = "property deprecation message" } deprecatedProperty: Int = 1337 } """ .trimIndent() ) assertThat(kotlinCode) .contains( """ | data class ClassWithDeprecatedProperty( | @Deprecated(message = "property deprecation message") | val deprecatedProperty: Long """ .trimMargin() ) } @Test fun `deprecated class with message`() { val kotlinCode = generateKotlinCode( """ @Deprecated { message = "class deprecation message" } class DeprecatedClass { propertyOfDeprecatedClass: Int = 42 } """ .trimIndent() ) assertThat(kotlinCode) .contains( """ | @Deprecated(message = "class deprecation message") | data class DeprecatedClass( """ .trimMargin() ) } @Test fun `deprecated module class with message`() { val kotlinCode = generateKotlinCode( """ @Deprecated{ message = "module class deprecation message" } module DeprecatedModule propertyInDeprecatedModuleClass : Int = 42 """ .trimIndent() ) assertThat(kotlinCode) .contains( """ |@Deprecated(message = "module class deprecation message") |data class DeprecatedModule( """ .trimMargin() ) } @Test fun `deprecated property`() { val kotlinCode = generateKotlinCode( """ class ClassWithDeprecatedProperty { @Deprecated deprecatedProperty: Int = 1337 } """ .trimIndent() ) assertThat(kotlinCode) .contains( """ | data class ClassWithDeprecatedProperty( | @Deprecated | val deprecatedProperty: Long """ .trimMargin() ) } @Test fun `deprecated class`() { val kotlinCode = generateKotlinCode( """ @Deprecated class DeprecatedClass { propertyOfDeprecatedClass: Int = 42 } """ .trimIndent() ) assertThat(kotlinCode) .contains( """ | @Deprecated | data class DeprecatedClass( """ .trimMargin() ) } @Test fun `deprecated module class`() { val kotlinCode = generateKotlinCode( """ @Deprecated module DeprecatedModule propertyInDeprecatedModuleClass : Int = 42 """ .trimIndent() ) assertThat(kotlinCode) .contains( """ |@Deprecated |data class DeprecatedModule( """ .trimMargin() ) } @Test fun properties() { val (other, propertyTypes) = instantiateOtherAndPropertyTypes() assertThat(readProperty(other, "name")).isEqualTo("pigeon") assertThat(readProperty(propertyTypes, "boolean")).isEqualTo(true) assertThat(readProperty(propertyTypes, "int")).isEqualTo(42L) assertThat(readProperty(propertyTypes, "float")).isEqualTo(42.3) assertThat(readProperty(propertyTypes, "string")).isEqualTo("string") assertThat(readProperty(propertyTypes, "duration")) .isEqualTo(Duration(5.0, DurationUnit.MINUTES)) assertThat(readProperty(propertyTypes, "dataSize")) .isEqualTo(DataSize(3.0, DataSizeUnit.GIGABYTES)) assertThat(readProperty(propertyTypes, "nullable")).isEqualTo("idea") assertThat(readProperty(propertyTypes, "nullable2")).isEqualTo(null) assertThat(readProperty(propertyTypes, "list")).isEqualTo(listOf(1, 2)) assertThat(readProperty(propertyTypes, "list2")).isEqualTo(listOf(other, other)) assertThat(readProperty(propertyTypes, "set")).isEqualTo(setOf(1, 2)) assertThat(readProperty(propertyTypes, "set2")).isEqualTo(setOf(other)) assertThat(readProperty(propertyTypes, "map")).isEqualTo(mapOf(1 to "one", 2 to "two")) assertThat(readProperty(propertyTypes, "map2")).isEqualTo(mapOf("one" to other, "two" to other)) assertThat(readProperty(propertyTypes, "container")).isEqualTo(mapOf(1 to "one", 2 to "two")) assertThat(readProperty(propertyTypes, "container2")) .isEqualTo(mapOf("one" to other, "two" to other)) assertThat(readProperty(propertyTypes, "other")).isEqualTo(other) assertThat(readProperty(propertyTypes, "regex")).isInstanceOf(Regex::class.java) assertThat(readProperty(propertyTypes, "any")).isEqualTo(other) assertThat(readProperty(propertyTypes, "nonNull")).isEqualTo(other) assertThat(readProperty(propertyTypes, "bytes")).isEqualTo(byteArrayOf(1, 2, 3, 4)) } private fun readProperty(receiver: Any, name: String): Any? { val property = receiver.javaClass.kotlin.memberProperties.find { it.name == name }!! return property.invoke(receiver) } @Test fun `properties 2`() { assertThat(propertyTypesKotlinCode).isEqualToResourceFile("PropertyTypes.kotlin") } @Test fun `enum constant names`() { val cases = listOf( "camelCasedName" to "CAMEL_CASED_NAME", "hyphenated-name" to "HYPHENATED_NAME", "EnQuad\u2000EmSpace\u2003IdeographicSpace\u3000" to "EN_QUAD_EM_SPACE_IDEOGRAPHIC_SPACE_", "ᾊᾨ" to "ᾊᾨ", "0-digit" to "_0_DIGIT", "digit-1" to "DIGIT_1", "42" to "_42", "àœü" to "ÀŒÜ", "日本-つくば" to "日本_つくば", ) val kotlinCode = generateKotlinCode( """ module my.mod typealias MyTypeAlias = ${cases.joinToString(" | ") { "\"${it.first}\"" }} """ .trimIndent() ) val kotlinClass = kotlinCode.compile().getValue("MyTypeAlias") assertThat(kotlinClass.enumValues().size) .isEqualTo(cases.size) // make sure zip doesn't drop cases assertAll( kotlinClass.enumValues().zip(cases) { enumValue, (pklName, kotlinName) -> { assertThat(enumValue.name).isEqualTo(kotlinName) assertThat(enumValue.toString()).isEqualTo(pklName) } } ) } @Test fun `conflicting enum constant names`() { val exception = assertThrows { generateKotlinCode( """ module my.mod typealias MyTypeAlias = "foo-bar" | "foo bar" """ .trimIndent() ) } assertThat(exception) .hasMessageContainingAll("both be converted to enum constant name", "FOO_BAR") } @Test fun `empty enum constant name`() { val exception = assertThrows { generateKotlinCode( """ module my.mod typealias MyTypeAlias = "foo" | "" | "bar" """ .trimIndent() ) } assertThat(exception).hasMessageContaining("cannot be converted") } @Test fun `inconvertible enum constant name`() { val exception = assertThrows { generateKotlinCode( """ module my.mod typealias MyTypeAlias = "foo" | "✅" | "bar" """ .trimIndent() ) } assertThat(exception).hasMessageContainingAll("✅", "cannot be converted") } @Test fun `data class`() { val kotlinCode = generateKotlinCode( """ module my.mod class Person { name: String age: Int hobbies: List friends: Map sibling: Person? } """ .trimIndent() ) assertThat(kotlinCode) .compilesSuccessfully() .isEqualTo( """ package my import kotlin.Long import kotlin.String import kotlin.collections.List import kotlin.collections.Map object Mod { data class Person( val name: String, val age: Long, val hobbies: List, val friends: Map, val sibling: Person? ) } """ .trimIndent() ) } @Test fun `data class with Regex property has custom equals and hashCode methods`() { val kotlinCode = generateKotlinCode( """ module my.mod class Person { age: Int name: Regex } """ .trimIndent() ) assertThat(kotlinCode) .contains("if (this.name.pattern != other.name.pattern) return false") .contains("result = 31 * result + Objects.hashCode(this.name.pattern)") } @Test fun `data class with ByteArray property has custom equals and hashCode methods`() { val kotlinCode = generateKotlinCode( """ module my.mod class Foo { bytes: Bytes } """ .trimIndent() ) assertThat(kotlinCode) .contains("if (!this.bytes.contentEquals(other.bytes)) return false") .contains("result = 31 * result + this.bytes.contentHashCode()") } @Test fun `recursive types`() { val kotlinCode = generateKotlinCode( """ module my.mod open class Foo { other: Int bar: Bar } open class Bar { foo: Foo other: String } """ .trimIndent() ) assertThat(kotlinCode) .compilesSuccessfully() .contains( """ | open class Foo( | open val other: Long, | open val bar: Bar | ) """ .trimMargin() ) .contains( """ | open class Bar( | open val foo: Foo, | open val other: String | ) """ .trimMargin() ) } @Test fun inheritance() { val kotlinCode = generateKotlinCode( """ module my.mod open class Foo { one: Int } open class None extends Foo {} open class Bar extends None { two: String } class Baz extends Bar { three: Duration } """ .trimIndent() ) assertThat(kotlinCode) .compilesSuccessfully() .contains( """ | open class Foo( | open val one: Long | ) """ .trimMargin() ) .contains( """ | open class None( | one: Long | ) : Foo(one) """ .trimMargin() ) .contains( """ | open class Bar( | one: Long, | open val two: String | ) : None(one) """ .trimMargin() ) .isEqualToResourceFile("Inheritance.kotlin") } @Test fun `stateless classes`() { val kotlinCode = generateKotlinCode( """ module my.mod class Foo abstract class Bar class Baz extends Bar """ .trimIndent() ) assertThat(kotlinCode) .contains( """ | data class Foo | | abstract class Bar | | class Baz : Bar() { | fun copy(): Baz = Baz() """ .trimMargin() ) } // https://github.com/apple/pkl/issues/569 @Test fun `abstract classes`() { val kotlinCode = generateKotlinCode( """ module my.mod abstract class Foo { one: Int } abstract class Bar extends Foo { two: String } class Baz extends Bar { three: Duration } class Qux extends Bar {} """ .trimIndent() ) assertThat(kotlinCode) .compilesSuccessfully() // missing trailing `{` proves that no methods are generated .contains( """ | abstract class Foo( | open val one: Long | ) """ .trimMargin() ) // missing trailing `{` proves that no methods are generated .contains( """ | abstract class Bar( | one: Long, | open val two: String | ) : Foo(one) """ .trimMargin() ) .contains( """ | class Baz( | one: Long, | two: String, | val three: Duration | ) : Bar(one, two) { | fun copy( | one: Long = this.one, | two: String = this.two, | three: Duration = this.three | ): Baz = Baz(one, two, three) """ .trimMargin() ) .contains( """ | class Qux( | one: Long, | two: String | ) : Bar(one, two) { | fun copy(one: Long = this.one, two: String = this.two): Qux = Qux(one, two) """ .trimMargin() ) } // https://github.com/apple/pkl/issues/569 @Test fun `abstract class that extends open class`() { val kotlinCode = generateKotlinCode( """ module my.mod open class Foo { one: Int } abstract class Bar extends Foo { two: String } class Baz extends Bar { three: Duration } class Qux extends Bar {} """ .trimIndent() ) assertThat(kotlinCode) .compilesSuccessfully() .contains( """ | open class Foo( | open val one: Long | ) { | open fun copy(one: Long = this.one): Foo = Foo(one) """ .trimMargin() ) // missing trailing `{` proves that no methods are generated .contains( """ | abstract class Bar( | one: Long, | open val two: String | ) : Foo(one) """ .trimMargin() ) .contains( """ | class Baz( | one: Long, | two: String, | val three: Duration | ) : Bar(one, two) { | fun copy( | one: Long = this.one, | two: String = this.two, | three: Duration = this.three | ): Baz = Baz(one, two, three) | | override fun copy(one: Long): Baz = Baz(one, two, three) """ .trimMargin() ) .contains( """ | class Qux( | one: Long, | two: String | ) : Bar(one, two) { | fun copy(one: Long = this.one, two: String = this.two): Qux = Qux(one, two) | | override fun copy(one: Long): Qux = Qux(one, two) """ .trimMargin() ) } // https://github.com/apple/pkl/issues/569 @Test fun `abstract class that extends open class without adding properties`() { val kotlinCode = generateKotlinCode( """ module my.mod open class Foo { one: Int } abstract class Bar extends Foo {} class Baz extends Bar { two: Duration } class Qux extends Bar {} """ .trimIndent() ) assertThat(kotlinCode) .compilesSuccessfully() .contains( """ | open class Foo( | open val one: Long | ) { | open fun copy(one: Long = this.one): Foo = Foo(one) """ .trimMargin() ) // missing trailing `{` proves that no methods are generated .contains( """ | abstract class Bar( | one: Long | ) : Foo(one) """ .trimMargin() ) .contains( """ | class Baz( | one: Long, | val two: Duration | ) : Bar(one) { | fun copy(one: Long = this.one, two: Duration = this.two): Baz = Baz(one, two) | | override fun copy(one: Long): Baz = Baz(one, two) """ .trimMargin() ) .contains( """ | class Qux( | one: Long | ) : Bar(one) { | override fun copy(one: Long): Qux = Qux(one) """ .trimMargin() ) } @Test fun keywords() { val props = kotlinKeywords.joinToString("\n") { "`$it`: Int" } val fooClass = generateKotlinCode( """ module my.mod class Foo { $props } """ .trimIndent() ) .compile() .getValue("Foo") assertThat(fooClass.declaredMemberProperties.map { it.name }).hasSameElementsAs(kotlinKeywords) } @Test fun `module properties`() { val kotlinCode = generateKotlinCode( """ module my.mod pigeon: Person parrot: Person class Person { name: String } """ .trimIndent() ) assertThat(kotlinCode) .compilesSuccessfully() .isEqualTo( """ package my import kotlin.String data class Mod( val pigeon: Person, val parrot: Person ) { data class Person( val name: String ) } """ .trimIndent() ) } @Test fun `simple module name`() { val kotlinCode = generateKotlinCode( """ module mod pigeon: Person parrot: Person class Person { name: String } """ .trimIndent() ) assertThat(kotlinCode) .compilesSuccessfully() .isEqualTo( """ import kotlin.String data class Mod( val pigeon: Person, val parrot: Person ) { data class Person( val name: String ) } """ .trimIndent() ) } @Test fun `hidden properties`() { val kotlinCode = generateKotlinCode( """ module my.mod hidden pigeon1: String parrot1: String class Persons { hidden pigeon2: String parrot2: String } """ .trimIndent() ) assertThat(kotlinCode) .doesNotContain("pigeon1: String") .contains("parrot1: String") .doesNotContain("pigeon2: String") .contains("parrot2: String") } @Test fun kdoc() { val kotlinCode = generateKotlinCode( """ /// module comment. /// *emphasized* `code`. module my.mod /// module property comment. /// *emphasized* `code`. pigeon: Person /// class comment. /// *emphasized* `code`. open class Product { /// class property comment. /// *emphasized* `code`. price: String } /// class comment. /// *emphasized* `code`. class Person { /// class property comment. /// *emphasized* `code`. name: String } /// type alias comment. /// *emphasized* `code`. typealias Email = String(contains("@")) """ .trimIndent(), generateKdoc = true, ) assertThat(kotlinCode).compilesSuccessfully().isEqualToResourceFile("Kdoc.kotlin") } @Test fun `kdoc 2`() { val kotlinCode = generateKotlinCode( """ /// module comment. /// *emphasized* `code`. module my.mod class Product """ .trimIndent(), generateKdoc = true, ) assertThat(kotlinCode) .isEqualTo( """ package my /** * module comment. * *emphasized* `code`. */ object Mod { data class Product } """ .trimIndent() ) } @Test fun `pkl_base type aliases`() { val kotlinCode = generateKotlinCode( """ module mod uint8: UInt8 uint16: UInt16 uint32: UInt32 uint: UInt int8: Int8 int16: Int16 int32: Int32 uri: Uri pair: Pair list: List set: Set map: Map listing: Listing mapping: Mapping nullable: UInt16? class Foo { uint8: UInt8 uint16: UInt16 uint32: UInt32 uint: UInt int8: Int8 int16: Int16 int32: Int32 uri: Uri list: List } """ .trimIndent() ) assertThat(kotlinCode) .compilesSuccessfully() .isEqualTo( """ import java.net.URI import kotlin.Byte import kotlin.Int import kotlin.Long import kotlin.Pair import kotlin.Short import kotlin.collections.List import kotlin.collections.Map import kotlin.collections.Set data class Mod( val uint8: Short, val uint16: Int, val uint32: Long, val uint: Long, val int8: Byte, val int16: Short, val int32: Int, val uri: URI, val pair: Pair, val list: List, val set: Set, val map: Map, val listing: List, val mapping: Map, val nullable: Int? ) { data class Foo( val uint8: Short, val uint16: Int, val uint32: Long, val uint: Long, val int8: Byte, val int16: Short, val int32: Int, val uri: URI, val list: List ) } """ .trimIndent() ) } @Test fun `user defined type aliases`() { val kotlinCode = generateKotlinCode( """ module mod typealias Simple = String typealias Constrained = String(length >= 3) typealias Parameterized = List typealias Recursive1 = Parameterized(nonEmpty) typealias Recursive2 = List simple: Simple constrained: Constrained parameterized: Parameterized recursive1: Recursive1 recursive2: Recursive2 class Foo { simple: Simple constrained: Constrained parameterized: Parameterized recursive1: Recursive1 recursive2: Recursive2 } """ .trimIndent() ) assertThat(kotlinCode) .compilesSuccessfully() .isEqualTo( """ import kotlin.Long import kotlin.String import kotlin.collections.List typealias Simple = String typealias Constrained = String typealias Parameterized = List typealias Recursive1 = Parameterized typealias Recursive2 = List data class Mod( val simple: Simple, val constrained: Constrained, val parameterized: Parameterized, val recursive1: Recursive1, val recursive2: Recursive2 ) { data class Foo( val simple: Simple, val constrained: Constrained, val parameterized: Parameterized, val recursive1: Recursive1, val recursive2: Recursive2 ) } """ .trimIndent() ) } @Test fun genericTypeAliases() { val kotlinCode = generateKotlinCode( """ module mod class Person { name: String } typealias List2 = List typealias Map2 = Map typealias StringMap = Map typealias MMap = Map res1: List2 res2: List2> res3: Map2 res4: StringMap res5: MMap res6: List2 res7: Map2 res8: StringMap res9: MMap class Foo { res1: List2 res2: List2> res3: Map2 res4: StringMap res5: MMap res6: List2 res7: Map2 res8: StringMap res9: MMap } """ .trimIndent() ) assertThat(kotlinCode) .compilesSuccessfully() .contains( """ |data class Mod( | val res1: List2, | val res2: List2>, | val res3: Map2, | val res4: StringMap, | val res5: MMap, | val res6: List2, | val res7: Map2, | val res8: StringMap, | val res9: MMap """ .trimMargin() ) .contains( """ | data class Foo( | val res1: List2, | val res2: List2>, | val res3: Map2, | val res4: StringMap, | val res5: MMap, | val res6: List2, | val res7: Map2, | val res8: StringMap, | val res9: MMap """ .trimMargin() ) } @Test fun `union of string literals`() { val kotlinCode = generateKotlinCode( """ module mod x: "Pigeon"|"Barn Owl"|"Parrot" """ .trimIndent() ) assertThat(kotlinCode) .compilesSuccessfully() .contains( """ |data class Mod( | val x: String |) """ .trimMargin() ) } @Test fun `other union type`() { val e = assertThrows { generateKotlinCode( """ module mod x: "Pigeon"|Int|"Parrot" """ .trimIndent() ) } assertThat(e).hasMessageContaining("Pkl union types are not supported") } @Test fun `stringy type`() { val kotlinCode = generateKotlinCode( """ module mod v1: "RELEASE" v2: "RELEASE"|String v3: String|"RELEASE" v4: "RELEASE"|String|"LATEST" v5: Version|String|"LATEST" v6: (Version|String)|("LATEST"|String) typealias Version = "RELEASE"|String|"LATEST" """ .trimIndent() ) assertThat(kotlinCode) .contains("v1: String") .contains("v2: String") .contains("v3: String") .contains("v4: String") .contains("v5: String") .contains("v6: String") } @Test fun `stringy type alias`() { val kotlinCode = generateKotlinCode( """ module mod typealias Version1 = "RELEASE"|String typealias Version2 = String|"RELEASE" typealias Version3 = "RELEASE"|String|"LATEST" typealias Version4 = Version3|String|"LATEST" // ideally wouldn't be inlined typealias Version5 = (Version4|String)|("LATEST"|String) typealias Version6 = Version5 // not inlined """ .trimIndent() ) assertThat(kotlinCode) .contains("typealias Version1 = String") .contains("typealias Version2 = String") .contains("typealias Version3 = String") .contains("typealias Version4 = String") .contains("typealias Version5 = String") .contains("typealias Version6 = Version5") } @Test fun `spring boot config`() { val kotlinCode = generateKotlinCode( """ module my.mod server: Server class Server { port: Int urls: Listing } """ .trimIndent(), generateSpringBootConfig = true, ) assertThat(kotlinCode) .contains( """ |@ConfigurationProperties |data class Mod( | val server: Server """ .trimMargin() ) .contains( """ | @ConfigurationProperties("server") | data class Server( | val port: Long, | val urls: List """ .trimMargin() ) .doesNotContain("@ConstructorBinding") // not worthwhile to add spring & spring boot dependency just so that this test can compile // their annotations val kotlinCodeWithoutSpringAnnotations = kotlinCode.deleteLines { it.contains("ConfigurationProperties") } assertThat(kotlinCodeWithoutSpringAnnotations).compilesSuccessfully() } @Test fun `import module`() { val library = PklModule( "library", """ module library class Person { name: String; age: Int } pigeon: Person """ .trimIndent(), ) val client = PklModule( "client", """ module client import "library.pkl" lib: library parrot: library.Person """ .trimIndent(), ) val kotlinSourceFiles = generateFiles(library, client) assertDoesNotThrow { InMemoryKotlinCompiler.compile(kotlinSourceFiles.mapValues { it.value.text }) } val kotlinClientCode = kotlinSourceFiles.entries.find { (fileName, _) -> fileName.endsWith("Client.kt") }!!.value assertThat(kotlinClientCode) .contains( """ |data class Client( | val lib: Library, | val parrot: Library.Person |) """ .trimMargin() ) } @Test fun `extend module`() { val base = PklModule( "base", """ open module base open class Person { name: String } pigeon: Person """ .trimIndent(), ) val derived = PklModule( "derived", """ module derived extends "base.pkl" class Person2 extends Person { age: Int } person1: Person person2: Person2 """ .trimIndent(), ) val kotlinSourceFiles = generateFiles(base, derived) assertDoesNotThrow { InMemoryKotlinCompiler.compile(kotlinSourceFiles.mapValues { it.value.text }) } val kotlinDerivedCode = kotlinSourceFiles.entries.find { (filename, _) -> filename.endsWith("Derived.kt") }!!.value assertThat(kotlinDerivedCode) .contains( """ |class Derived( | pigeon: Base.Person, | val person1: Base.Person, | val person2: Person2 |) : Base(pigeon) """ .trimMargin() ) .contains( """ | class Person2( | name: String, | val age: Long | ) : Base.Person(name) """ .trimMargin() ) } @Test fun `empty module`() { val kotlinCode = generateKotlinCode("module mod") assertThat(kotlinCode).isEqualTo("object Mod\n") } @Test fun `extend module that only contains type aliases`() { val moduleOne = PklModule( "base", """ abstract module base typealias Version = "LATEST"|String """ .trimIndent(), ) val moduleTwo = PklModule( "derived", """ module derived extends "base.pkl" v: Version = "1.2.3" """ .trimIndent(), ) val kotlinSourceFiles = generateFiles(moduleOne, moduleTwo) assertDoesNotThrow { InMemoryKotlinCompiler.compile(kotlinSourceFiles.mapValues { it.value.text }) } val kotlinDerivedCode = kotlinSourceFiles.entries.find { (filename, _) -> filename.endsWith("Derived.kt") }!!.value assertThat(kotlinDerivedCode) .contains( """ |class Derived( | val v: Version |) : Base() """ .trimMargin() ) } @Test fun `generated properties files`() { val pklModule = PklModule( "Mod.pkl", """ module org.pkl.Mod foo: Foo bar: Bar class Foo { prop: String } class Bar { prop: Int } """ .trimIndent(), ) val generated = generateFiles(pklModule) val expectedPropertyFile = "resources/META-INF/org/pkl/config/java/mapper/classes/org.pkl.Mod.properties" assertThat(generated).containsKey(expectedPropertyFile) val propertyFileContents = generated[expectedPropertyFile]!! assertThat(propertyFileContents) .contains("org.pkl.config.java.mapper.org.pkl.Mod\\#ModuleClass=org.pkl.Mod") .contains("org.pkl.config.java.mapper.org.pkl.Mod\\#Foo=org.pkl.Mod\$Foo") .contains("org.pkl.config.java.mapper.org.pkl.Mod\\#Bar=org.pkl.Mod\$Bar") } @Test fun `generates serializable classes`() { val kotlinCode = generateKotlinCode( """ module mod class BigStruct { boolean: Boolean int: Int float: Float string: String duration: Duration dataSize: DataSize pair: Pair pair2: Pair coll: Collection coll2: Collection list: List list2: List set: Set set2: Set map: Map map2: Map container: Mapping container2: Mapping other: SmallStruct regex: Regex nonNull: NonNull enum: Direction } class SmallStruct { name: String } typealias Direction = "north"|"east"|"south"|"west" abstract class NotSerializable """ .trimIndent(), implementSerializable = true, ) assertThat(kotlinCode) .contains(": Serializable") .contains("private const val serialVersionUID: Long = 0L") .contains("abstract class NotSerializable\n") val classes = kotlinCode.compile() val enumClass = classes.getValue("Direction") val enumValue = enumClass.java.enumConstants.first() val smallStructCtor = classes.getValue("SmallStruct").constructors.first() val smallStruct = smallStructCtor.call("pigeon") val bigStructCtor = classes.getValue("BigStruct").constructors.first() val bigStruct = bigStructCtor.call( true, 42L, 42.3, "string", Duration(5.0, DurationUnit.MINUTES), DataSize(3.0, DataSizeUnit.GIGABYTES), kotlin.Pair(1, 2), kotlin.Pair("pigeon", smallStruct), listOf(1, 2, 3), listOf(smallStruct, smallStruct), listOf(1, 2, 3), listOf(smallStruct, smallStruct), setOf(1, 2, 3), setOf(smallStruct, smallStruct), mapOf(1 to "one", 2 to "two"), mapOf("one" to smallStruct, "two" to smallStruct), mapOf(1 to "one", 2 to "two"), mapOf("one" to smallStruct, "two" to smallStruct), smallStruct, Regex("(i?)\\w*"), smallStruct, enumValue, ) fun confirmSerDe(instance: Any) { var restoredInstance: Any? = null assertThatCode { // serialize val baos = ByteArrayOutputStream() val oos = ObjectOutputStream(baos) oos.writeObject(instance) oos.flush() // deserialize val bais = ByteArrayInputStream(baos.toByteArray()) val ois = object : ObjectInputStream(bais) { override fun resolveClass(desc: ObjectStreamClass?): Class<*> { return Class.forName(desc!!.name, false, instance.javaClass.classLoader) } } restoredInstance = ois.readObject() } .doesNotThrowAnyException() assertThat(restoredInstance!!).isEqualTo(instance) } confirmSerDe(enumValue) confirmSerDe(smallStruct) confirmSerDe(bigStruct) } @Test fun `generates serializable module classes`() { val kotlinCode = generateKotlinCode( """ module Person address: Address class Address { street: String } """, implementSerializable = true, ) assertThat(kotlinCode) .contains( """ |data class Person( | val address: Address |) : Serializable { | data class Address( | val street: String | ) : Serializable { | companion object { | private const val serialVersionUID: Long = 0L | } | } | | companion object { | private const val serialVersionUID: Long = 0L | } |} """ .trimMargin() ) } @Test fun `encoded file paths`() { val kotlinCode = generateFiles( PklModule( "FooBar.pkl", """ module `Foo*Bar` someProp: String """ .trimIndent(), ) ) assertThat(kotlinCode).containsKey("kotlin/Foo(2a)Bar.kt") } @Test fun `override names in a standalone module`() { val files = KotlinCodeGeneratorOptions( renames = mapOf("a.b.c" to "x.y.z", "d.e.f.AnotherModule" to "u.v.w.RenamedModule") ) .generateFiles( "MyModule.pkl" to """ module a.b.c.MyModule foo: String = "abc" """ .trimIndent(), "AnotherModule.pkl" to """ module d.e.f.AnotherModule bar: Int = 123 """ .trimIndent(), ) .toMutableMap() files.validateContents( "kotlin/x/y/z/MyModule.kt" to listOf("package x.y.z", "data class MyModule("), "$MAPPER_PREFIX/a.b.c.MyModule.properties" to listOf("org.pkl.config.java.mapper.a.b.c.MyModule\\#ModuleClass=x.y.z.MyModule"), // --- "kotlin/u/v/w/RenamedModule.kt" to listOf("package u.v.w", "data class RenamedModule("), "$MAPPER_PREFIX/d.e.f.AnotherModule.properties" to listOf("org.pkl.config.java.mapper.d.e.f.AnotherModule\\#ModuleClass=u.v.w.RenamedModule"), ) } @Test fun `override names based on the longest prefix`() { val files = KotlinCodeGeneratorOptions( renames = mapOf("com.foo.bar." to "x.", "com.foo." to "y.", "com." to "z.", "" to "w.") ) .generateFiles( "com/foo/bar/Module1" to """ module com.foo.bar.Module1 bar: String """ .trimIndent(), "com/Module2" to """ module com.Module2 com: String """ .trimIndent(), "org/baz/Module3" to """ module org.baz.Module3 baz: String """ .trimIndent(), ) .toMutableMap() files.validateContents( "kotlin/x/Module1.kt" to listOf("package x", "data class Module1("), "$MAPPER_PREFIX/com.foo.bar.Module1.properties" to listOf("org.pkl.config.java.mapper.com.foo.bar.Module1\\#ModuleClass=x.Module1"), // --- "kotlin/z/Module2.kt" to listOf("package z", "data class Module2("), "$MAPPER_PREFIX/com.Module2.properties" to listOf("org.pkl.config.java.mapper.com.Module2\\#ModuleClass=z.Module2"), // --- "kotlin/w/org/baz/Module3.kt" to listOf("package w.org.baz", "data class Module3("), "$MAPPER_PREFIX/org.baz.Module3.properties" to listOf("org.pkl.config.java.mapper.org.baz.Module3\\#ModuleClass=w.org.baz.Module3"), ) } @Test fun `override names in multiple modules using each other`() { val files = KotlinCodeGeneratorOptions( renames = mapOf( "org.foo" to "com.foo.x", "org.bar.Module2" to "org.bar.RenamedModule", "org.baz" to "com.baz.a.b", ) ) .generateFiles( "org/foo/Module1" to """ module org.foo.Module1 class Person { name: String } """ .trimIndent(), "org/bar/Module2" to """ module org.bar.Module2 import "../../org/foo/Module1.pkl" class Group { owner: Module1.Person name: String } """ .trimIndent(), "org/baz/Module3" to """ module org.baz.Module3 import "../../org/bar/Module2.pkl" class Supergroup { owner: Module2.Group } """ .trimIndent(), ) files.validateContents( "kotlin/com/foo/x/Module1.kt" to listOf("package com.foo.x", "object Module1 {", "data class Person("), "$MAPPER_PREFIX/org.foo.Module1.properties" to listOf( "org.pkl.config.java.mapper.org.foo.Module1\\#ModuleClass=com.foo.x.Module1", "org.pkl.config.java.mapper.org.foo.Module1\\#Person=com.foo.x.Module1${'$'}Person", ), // --- "kotlin/org/bar/RenamedModule.kt" to listOf( "package org.bar", "import com.foo.x.Module1", "object RenamedModule {", "val owner: Module1.Person", ), "$MAPPER_PREFIX/org.bar.Module2.properties" to listOf( "org.pkl.config.java.mapper.org.bar.Module2\\#ModuleClass=org.bar.RenamedModule", "org.pkl.config.java.mapper.org.bar.Module2\\#Group=org.bar.RenamedModule${'$'}Group", ), // --- "kotlin/com/baz/a/b/Module3.kt" to listOf( "package com.baz.a.b", "import org.bar.RenamedModule", "object Module3 {", "val owner: RenamedModule.Group", ), "$MAPPER_PREFIX/org.baz.Module3.properties" to listOf( "org.pkl.config.java.mapper.org.baz.Module3\\#ModuleClass=com.baz.a.b.Module3", "org.pkl.config.java.mapper.org.baz.Module3\\#Supergroup=com.baz.a.b.Module3${'$'}Supergroup", ), ) } @Test fun `do not capitalize names of renamed classes`() { val files = KotlinCodeGeneratorOptions( renames = mapOf("a.b.c.MyModule" to "x.y.z.renamed_module", "d.e.f." to "u.v.w.") ) .generateFiles( "MyModule.pkl" to """ module a.b.c.MyModule foo: String = "abc" """ .trimIndent(), "lower_module.pkl" to """ module d.e.f.lower_module bar: Int = 123 """ .trimIndent(), ) files.validateContents( "kotlin/x/y/z/renamed_module.kt" to listOf("package x.y.z", "data class renamed_module("), "$MAPPER_PREFIX/a.b.c.MyModule.properties" to listOf("org.pkl.config.java.mapper.a.b.c.MyModule\\#ModuleClass=x.y.z.renamed_module"), // --- "kotlin/u/v/w/Lower_module.kt" to listOf("package u.v.w", "data class Lower_module("), "$MAPPER_PREFIX/d.e.f.lower_module.properties" to listOf("org.pkl.config.java.mapper.d.e.f.lower_module\\#ModuleClass=u.v.w.Lower_module"), ) } @Test fun `add generated annotation`() { val files = KotlinCodeGeneratorOptions(addGeneratedAnnotation = true) .generateFiles("com.example.MyModule" to "foo: String") assertThat(files).containsKey("kotlin/com/example/MyModule.kt") assertThat(files["kotlin/com/example/MyModule.kt"]) .isEqualTo( """ package com.example import kotlin.String import org.pkl.config.java.Generated @Generated data class MyModule( val foo: String ) """ .trimIndent() ) } private fun Map.validateContents( @Suppress("RemoveRedundantQualifierName") vararg assertions: kotlin.Pair> ) { val files = toMutableMap() for ((fileName, lines) in assertions) { assertThat(files).containsKey(fileName) assertThat(files.remove(fileName)).describedAs("Contents of $fileName").contains(lines) } assertThat(files).isEmpty() } private fun KotlinCodeGeneratorOptions.generateFiles( vararg pklModules: PklModule ): Map { val pklFiles = pklModules.map { it.writeToDisk(tempDir.resolve("pkl/${it.name}.pkl")) } val evaluator = Evaluator.preconfigured() return pklFiles.fold(mapOf()) { acc, pklFile -> val pklSchema = evaluator.evaluateSchema(ModuleSource.path(pklFile)) val generator = KotlinCodeGenerator(pklSchema, this) acc + generator.output } } private fun KotlinCodeGeneratorOptions.generateFiles( @Suppress("RemoveRedundantQualifierName") vararg pklModules: kotlin.Pair ): Map = generateFiles(*pklModules.map { (name, text) -> PklModule(name, text) }.toTypedArray()) private fun generateFiles(vararg pklModules: PklModule): Map = KotlinCodeGeneratorOptions().generateFiles(*pklModules).mapValues { KotlinSourceCode(it.value) } private fun instantiateOtherAndPropertyTypes(): kotlin.Pair { val otherCtor = propertyTypesClasses.getValue("Other").constructors.first() val other = otherCtor.call("pigeon") val enumClass = propertyTypesClasses.getValue("Direction").java val enumValue = enumClass.enumConstants.first() val propertyTypesCtor = propertyTypesClasses.getValue("PropertyTypes").constructors.first() val propertyTypes = propertyTypesCtor.call( true, 42, 42.3, "string", Duration(5.0, DurationUnit.MINUTES), DurationUnit.MINUTES, DataSize(3.0, DataSizeUnit.GIGABYTES), DataSizeUnit.GIGABYTES, "idea", null, kotlin.Pair(1, 2), kotlin.Pair("pigeon", other), listOf(1, 2), listOf(other, other), listOf(1, 2), listOf(other, other), setOf(1, 2), setOf(other), mapOf(1 to "one", 2 to "two"), mapOf("one" to other, "two" to other), mapOf(1 to "one", 2 to "two"), mapOf("one" to other, "two" to other), other, Regex("(i?)\\w*"), other, other, enumValue, byteArrayOf(1, 2, 3, 4), ) return other to propertyTypes } private fun assertThat(actual: KotlinSourceCode): KotlinSourceCodeAssert = KotlinSourceCodeAssert(actual) private class KotlinSourceCodeAssert(actual: KotlinSourceCode) : AbstractAssert( actual, KotlinSourceCodeAssert::class.java, ) { fun contains(expected: String): KotlinSourceCodeAssert { if (!actual.text.contains(expected)) { // check for equality to get better error output (IDE diff dialog) assertThat(actual.text).isEqualTo(expected) } return this } fun doesNotContain(expected: String): KotlinSourceCodeAssert { assertThat(actual.text).doesNotContain(expected) return this } fun compilesSuccessfully(): KotlinSourceCodeAssert { assertThatCode { actual.compile() }.doesNotThrowAnyException() return this } fun isEqualTo(expected: String): KotlinSourceCodeAssert { assertThat(actual.text).isEqualTo(expected) return this } fun isEqualToResourceFile(fileName: String): KotlinSourceCodeAssert { isEqualTo(IoUtils.readClassPathResourceAsString(javaClass, fileName)) return this } } data class KotlinSourceCode(val text: String) { fun compile(): Map> = InMemoryKotlinCompiler.compile(mapOf("my/Mod.kt" to text)) fun deleteLines(predicate: (String) -> Boolean): KotlinSourceCode = KotlinSourceCode(text.lines().filterNot(predicate).joinToString("\n")) } } ================================================ FILE: pkl-codegen-kotlin/src/test/kotlin/org/pkl/codegen/kotlin/PklModule.kt ================================================ /* * Copyright © 2024-2025 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.codegen.kotlin import java.nio.file.Path import kotlin.io.path.createParentDirectories import org.pkl.commons.writeString data class PklModule(val name: String, val content: String) { fun writeToDisk(path: Path): Path { return path.createParentDirectories().writeString(content) } } ================================================ FILE: pkl-codegen-kotlin/src/test/resources/org/pkl/codegen/kotlin/Inheritance.kotlin ================================================ package my import java.util.Objects import kotlin.Any import kotlin.Boolean import kotlin.Int import kotlin.Long import kotlin.String import org.pkl.core.Duration object Mod { open class Foo( open val one: Long ) { open fun copy(one: Long = this.one): Foo = Foo(one) override fun equals(other: Any?): Boolean { if (this === other) return true if (this.javaClass != other?.javaClass) return false other as Foo if (this.one != other.one) return false return true } override fun hashCode(): Int { var result = 1 result = 31 * result + Objects.hashCode(this.one) return result } override fun toString(): String = """Foo(one=$one)""" } open class None( one: Long ) : Foo(one) { open override fun copy(one: Long): None = None(one) override fun equals(other: Any?): Boolean { if (this === other) return true if (this.javaClass != other?.javaClass) return false other as None if (this.one != other.one) return false return true } override fun hashCode(): Int { var result = 1 result = 31 * result + Objects.hashCode(this.one) return result } override fun toString(): String = """None(one=$one)""" } open class Bar( one: Long, open val two: String ) : None(one) { open fun copy(one: Long = this.one, two: String = this.two): Bar = Bar(one, two) open override fun copy(one: Long): Bar = Bar(one, two) override fun equals(other: Any?): Boolean { if (this === other) return true if (this.javaClass != other?.javaClass) return false other as Bar if (this.one != other.one) return false if (this.two != other.two) return false return true } override fun hashCode(): Int { var result = 1 result = 31 * result + Objects.hashCode(this.one) result = 31 * result + Objects.hashCode(this.two) return result } override fun toString(): String = """Bar(one=$one, two=$two)""" } class Baz( one: Long, two: String, val three: Duration ) : Bar(one, two) { fun copy( one: Long = this.one, two: String = this.two, three: Duration = this.three ): Baz = Baz(one, two, three) override fun copy(one: Long, two: String): Baz = Baz(one, two, three) override fun copy(one: Long): Baz = Baz(one, two, three) override fun equals(other: Any?): Boolean { if (this === other) return true if (this.javaClass != other?.javaClass) return false other as Baz if (this.one != other.one) return false if (this.two != other.two) return false if (this.three != other.three) return false return true } override fun hashCode(): Int { var result = 1 result = 31 * result + Objects.hashCode(this.one) result = 31 * result + Objects.hashCode(this.two) result = 31 * result + Objects.hashCode(this.three) return result } override fun toString(): String = """Baz(one=$one, two=$two, three=$three)""" } } ================================================ FILE: pkl-codegen-kotlin/src/test/resources/org/pkl/codegen/kotlin/Kdoc.kotlin ================================================ package my import java.util.Objects import kotlin.Any import kotlin.Boolean import kotlin.Int import kotlin.String /** * type alias comment. * *emphasized* `code`. */ typealias Email = String /** * module comment. * *emphasized* `code`. */ data class Mod( /** * module property comment. * *emphasized* `code`. */ val pigeon: Person ) { /** * class comment. * *emphasized* `code`. */ open class Product( /** * class property comment. * *emphasized* `code`. */ open val price: String ) { open fun copy(price: String = this.price): Product = Product(price) override fun equals(other: Any?): Boolean { if (this === other) return true if (this.javaClass != other?.javaClass) return false other as Product if (this.price != other.price) return false return true } override fun hashCode(): Int { var result = 1 result = 31 * result + Objects.hashCode(this.price) return result } override fun toString(): String = """Product(price=$price)""" } /** * class comment. * *emphasized* `code`. */ data class Person( /** * class property comment. * *emphasized* `code`. */ val name: String ) } ================================================ FILE: pkl-codegen-kotlin/src/test/resources/org/pkl/codegen/kotlin/PropertyTypes.kotlin ================================================ package my import java.util.Arrays import java.util.Objects import kotlin.Any import kotlin.Boolean import kotlin.ByteArray import kotlin.Double import kotlin.Int import kotlin.Long import kotlin.Pair import kotlin.String import kotlin.collections.Collection import kotlin.collections.List import kotlin.collections.Map import kotlin.collections.Set import kotlin.text.Regex import org.pkl.core.DataSize import org.pkl.core.DataSizeUnit import org.pkl.core.Duration import org.pkl.core.DurationUnit object Mod { open class PropertyTypes( open val boolean: Boolean, open val int: Long, open val float: Double, open val string: String, open val duration: Duration, open val durationUnit: DurationUnit, open val dataSize: DataSize, open val dataSizeUnit: DataSizeUnit, open val nullable: String?, open val nullable2: String?, open val pair: Pair, open val pair2: Pair, open val coll: Collection, open val coll2: Collection, open val list: List, open val list2: List, open val set: Set, open val set2: Set, open val map: Map, open val map2: Map, open val container: Map, open val container2: Map, open val other: Other, open val regex: Regex, open val any: Any?, open val nonNull: Any, open val enum: Direction, open val bytes: ByteArray ) { open fun copy( boolean: Boolean = this.boolean, int: Long = this.int, float: Double = this.float, string: String = this.string, duration: Duration = this.duration, durationUnit: DurationUnit = this.durationUnit, dataSize: DataSize = this.dataSize, dataSizeUnit: DataSizeUnit = this.dataSizeUnit, nullable: String? = this.nullable, nullable2: String? = this.nullable2, pair: Pair = this.pair, pair2: Pair = this.pair2, coll: Collection = this.coll, coll2: Collection = this.coll2, list: List = this.list, list2: List = this.list2, set: Set = this.set, set2: Set = this.set2, map: Map = this.map, map2: Map = this.map2, container: Map = this.container, container2: Map = this.container2, other: Other = this.other, regex: Regex = this.regex, any: Any? = this.any, nonNull: Any = this.nonNull, enum: Direction = this.enum, bytes: ByteArray = this.bytes ): PropertyTypes = PropertyTypes(boolean, int, float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, enum, bytes) override fun equals(other: Any?): Boolean { if (this === other) return true if (this.javaClass != other?.javaClass) return false other as PropertyTypes if (this.boolean != other.boolean) return false if (this.int != other.int) return false if (this.float != other.float) return false if (this.string != other.string) return false if (this.duration != other.duration) return false if (this.durationUnit != other.durationUnit) return false if (this.dataSize != other.dataSize) return false if (this.dataSizeUnit != other.dataSizeUnit) return false if (this.nullable != other.nullable) return false if (this.nullable2 != other.nullable2) return false if (this.pair != other.pair) return false if (this.pair2 != other.pair2) return false if (this.coll != other.coll) return false if (this.coll2 != other.coll2) return false if (this.list != other.list) return false if (this.list2 != other.list2) return false if (this.set != other.set) return false if (this.set2 != other.set2) return false if (this.map != other.map) return false if (this.map2 != other.map2) return false if (this.container != other.container) return false if (this.container2 != other.container2) return false if (this.other != other.other) return false if (this.regex.pattern != other.regex.pattern) return false if (this.any != other.any) return false if (this.nonNull != other.nonNull) return false if (this.enum != other.enum) return false if (!this.bytes.contentEquals(other.bytes)) return false return true } override fun hashCode(): Int { var result = 1 result = 31 * result + Objects.hashCode(this.boolean) result = 31 * result + Objects.hashCode(this.int) result = 31 * result + Objects.hashCode(this.float) result = 31 * result + Objects.hashCode(this.string) result = 31 * result + Objects.hashCode(this.duration) result = 31 * result + Objects.hashCode(this.durationUnit) result = 31 * result + Objects.hashCode(this.dataSize) result = 31 * result + Objects.hashCode(this.dataSizeUnit) result = 31 * result + Objects.hashCode(this.nullable) result = 31 * result + Objects.hashCode(this.nullable2) result = 31 * result + Objects.hashCode(this.pair) result = 31 * result + Objects.hashCode(this.pair2) result = 31 * result + Objects.hashCode(this.coll) result = 31 * result + Objects.hashCode(this.coll2) result = 31 * result + Objects.hashCode(this.list) result = 31 * result + Objects.hashCode(this.list2) result = 31 * result + Objects.hashCode(this.set) result = 31 * result + Objects.hashCode(this.set2) result = 31 * result + Objects.hashCode(this.map) result = 31 * result + Objects.hashCode(this.map2) result = 31 * result + Objects.hashCode(this.container) result = 31 * result + Objects.hashCode(this.container2) result = 31 * result + Objects.hashCode(this.other) result = 31 * result + Objects.hashCode(this.regex.pattern) result = 31 * result + Objects.hashCode(this.any) result = 31 * result + Objects.hashCode(this.nonNull) result = 31 * result + Objects.hashCode(this.enum) result = 31 * result + this.bytes.contentHashCode() return result } override fun toString(): String = """PropertyTypes(boolean=$boolean, int=$int, float=$float, string=$string, duration=$duration, durationUnit=$durationUnit, dataSize=$dataSize, dataSizeUnit=$dataSizeUnit, nullable=$nullable, nullable2=$nullable2, pair=$pair, pair2=$pair2, coll=$coll, coll2=$coll2, list=$list, list2=$list2, set=$set, set2=$set2, map=$map, map2=$map2, container=$container, container2=$container2, other=$other, regex=$regex, any=$any, nonNull=$nonNull, enum=$enum, bytes=${Arrays.toString(bytes)})""" } open class Other( open val name: String ) { open fun copy(name: String = this.name): Other = Other(name) override fun equals(other: Any?): Boolean { if (this === other) return true if (this.javaClass != other?.javaClass) return false other as Other if (this.name != other.name) return false return true } override fun hashCode(): Int { var result = 1 result = 31 * result + Objects.hashCode(this.name) return result } override fun toString(): String = """Other(name=$name)""" } enum class Direction( val value: String ) { NORTH("north"), EAST("east"), SOUTH("south"), WEST("west"); override fun toString() = value } } ================================================ FILE: pkl-commons/gradle.lockfile ================================================ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. com.github.ben-manes.caffeine:caffeine:2.9.3=swiftExportClasspathResolvable com.google.errorprone:error_prone_annotations:2.28.0=swiftExportClasspathResolvable io.github.java-diff-utils:java-diff-utils:4.12=kotlinInternalAbiValidation io.opentelemetry:opentelemetry-api:1.41.0=swiftExportClasspathResolvable io.opentelemetry:opentelemetry-context:1.41.0=swiftExportClasspathResolvable net.bytebuddy:byte-buddy:1.17.7=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata org.assertj:assertj-core:3.27.6=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.bouncycastle:bcpg-jdk18on:1.80=kotlinBouncyCastleConfiguration org.bouncycastle:bcpkix-jdk18on:1.80=kotlinBouncyCastleConfiguration org.bouncycastle:bcprov-jdk18on:1.80=kotlinBouncyCastleConfiguration org.bouncycastle:bcutil-jdk18on:1.80=kotlinBouncyCastleConfiguration org.checkerframework:checker-qual:3.43.0=swiftExportClasspathResolvable org.jetbrains.kotlin:abi-tools-api:2.2.20=kotlinInternalAbiValidation org.jetbrains.kotlin:abi-tools:2.2.20=kotlinInternalAbiValidation org.jetbrains.kotlin:kotlin-build-tools-api:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-build-tools-impl:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-compiler-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlin:kotlin-compiler-runner:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-daemon-client:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-daemon-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:2.2.20=kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-metadata-jvm:2.2.20=kotlinInternalAbiValidation org.jetbrains.kotlin:kotlin-native-prebuilt:2.0.21=kotlinNativeBundleConfiguration org.jetbrains.kotlin:kotlin-reflect:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlin:kotlin-script-runtime:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlin:kotlin-scripting-common:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-jvm:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.2.20=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.20=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib:2.2.20=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,runtimeClasspath,swiftExportClasspathResolvable,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.kotlin:swift-export-embeddable:2.2.20=swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-serialization-bom:1.7.3=swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.7.3=swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3=swiftExportClasspathResolvable org.jetbrains:annotations:13.0=compileClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,runtimeClasspath,swiftExportClasspathResolvable,testCompileClasspath,testRuntimeClasspath org.junit.jupiter:junit-jupiter-api:5.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.jupiter:junit-jupiter-engine:5.14.0=testRuntimeClasspath org.junit.jupiter:junit-jupiter-params:5.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.platform:junit-platform-commons:1.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.platform:junit-platform-engine:1.14.0=testRuntimeClasspath org.junit.platform:junit-platform-launcher:1.14.0=testRuntimeClasspath org.junit:junit-bom:5.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath empty=annotationProcessor,compileOnlyDependenciesMetadata,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinNativeCompilerPluginClasspath,kotlinScriptDefExtensions,signatures,sourcesJar,testAnnotationProcessor,testApiDependenciesMetadata,testCompileOnlyDependenciesMetadata,testIntransitiveDependenciesMetadata,testKotlinScriptDefExtensions ================================================ FILE: pkl-commons/pkl-commons.gradle.kts ================================================ /* * Copyright © 2024 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. */ plugins { pklAllProjects pklKotlinLibrary pklPublishLibrary } publishing { publications { named("library") { pom { url.set("https://github.com/apple/pkl/tree/main/pkl-commons") description.set("Internal utilities. NOT A PUBLIC API.") } } } } ================================================ FILE: pkl-commons/src/main/kotlin/org/pkl/commons/Control.kt ================================================ /* * Copyright © 2025 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.commons import java.util.WeakHashMap import kotlin.reflect.KProperty // Adapted from https://stackoverflow.com/a/38084930 fun lazyWithReceiver( initializer: This.() -> Return ): LazyWithReceiver = LazyWithReceiver(initializer) class LazyWithReceiver(val initializer: This.() -> Return) { private val values = WeakHashMap() private val lock = Object() operator fun getValue(thisValue: This, property: KProperty<*>): Return = synchronized(lock) { values.getOrPut(thisValue) { thisValue.initializer() } } } ================================================ FILE: pkl-commons/src/main/kotlin/org/pkl/commons/NameMapper.kt ================================================ /* * Copyright © 2024 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.commons /** * A helper class for translating names of Pkl modules to different names of classes and/or objects * in the target language of a code generation execution. * * The `mapping` parameter is expected to contain valid prefixes of Pkl module names, with an * optional dot at the end, and values should be valid class names in the language for which code * generation is performed. * * If the rename patterns do not explicitly rename the class, the class name is capitalized. * * When computing the appropriate target name, the longest matching prefix is used. * * Prefix replacements are literal, and therefore dots are important. When renaming packages, in * most cases, you must ensure that you have an ending dot on both sides of a mapping (except for * the empty mapping, if you use it), otherwise you may get unexpected results: * ```kotlin * val mapper = NameMapper( * mapOf( * "com.foo." to "x", // Dot on the left only * "org.bar" to "y.", // Dot on the right only * "net.baz" to "z" // No dots * ) * ) * * assertThat(mapper.map("com.foo.bar")).isEqualTo("" to "xbar") // Target prefix merged into the suffix * assertThat(mapper.map("org.bar.baz")).isEqualTo("y." to "Baz") // Double dot, invalid package name * assertThat(mapper.map("net.baz.qux")).isEqualTo("z" to "Qux") // Looks okay, but... * assertThat(mapper.map("net.bazqux")).isEqualTo("" to "zqux") // ...may cut the package name in the middle. * ``` */ class NameMapper(mapping: Map) { private val sortedMapping = mapping.toList().sortedBy { -it.first.length } private fun doMap(sourceName: String): Pair { for ((sourcePrefix, targetPrefix) in sortedMapping) { if (sourceName.startsWith(sourcePrefix)) { val rest = sourceName.substring(sourcePrefix.length) val mapped = targetPrefix + rest val wasClassRenamed = !targetPrefix.endsWith('.') && (sourcePrefix.length - 1) >= sourceName.lastIndexOf('.') return mapped to wasClassRenamed } } return sourceName to false } fun map(sourceName: String): Pair { val (mappedName, wasClassRenamed) = doMap(sourceName) val packageName = mappedName.substringBeforeLast(".", "") val mappedClassName = mappedName.substringAfterLast(".") val className = if (wasClassRenamed) mappedClassName else mappedClassName.replaceFirstChar { it.titlecaseChar() } return packageName to className } } ================================================ FILE: pkl-commons/src/main/kotlin/org/pkl/commons/Paths.kt ================================================ /* * Copyright © 2024-2025 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.commons import java.io.* import java.nio.charset.Charset import java.nio.file.* import java.nio.file.attribute.FileAttribute import java.util.stream.Stream import kotlin.io.path.copyTo import kotlin.io.path.createParentDirectories import kotlin.io.path.exists // not stored to avoid build-time initialization by native-image val currentWorkingDir: Path get() = System.getProperty("user.dir").toPath() // unlike `Path.resolve`, this works across file systems if `other` is absolute fun Path.resolveSafely(other: Path): Path = if (other.isAbsolute) other else resolve(other) @Throws(IOException::class) fun Path.walk(maxDepth: Int = Int.MAX_VALUE, vararg options: FileVisitOption): Stream = Files.walk(this, maxDepth, *options) @Throws(IOException::class) fun Path.createTempFile( prefix: String? = null, suffix: String? = null, vararg attributes: FileAttribute<*>, ): Path = Files.createTempFile(this, prefix, suffix, *attributes) /** [Files.writeString] seems more efficient than [kotlin.io.path.writeText]. */ @Throws(IOException::class) fun Path.writeString( text: String, charset: Charset = Charsets.UTF_8, vararg options: OpenOption, ): Path = Files.writeString(this, text, charset, *options) /** [Files.readString] seems more efficient than [kotlin.io.path.readText]. */ @Throws(IOException::class) fun Path.readString(charset: Charset = Charsets.UTF_8): String = Files.readString(this, charset) @Throws(IOException::class) fun Path.copyRecursively(target: Path) { if (exists()) { target.createParentDirectories() walk().use { paths -> paths.forEach { src -> val dst = target.resolve(this@copyRecursively.relativize(src)) src.copyTo(dst, overwrite = true) } } } } private val isWindows by lazy { System.getProperty("os.name").contains("Windows") } /** Copy implementation from IoUtils.toNormalizedPathString */ fun Path.toNormalizedPathString(): String { if (isWindows) { return toString().replace("\\", "/") } return toString() } ================================================ FILE: pkl-commons/src/main/kotlin/org/pkl/commons/Strings.kt ================================================ /* * Copyright © 2024-2025 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.commons import java.io.File import java.net.URI import java.nio.file.Path import java.util.regex.Pattern fun String.toPath(): Path = Path.of(this) private val uriLike = Pattern.compile("\\w+:[^\\\\].*") private val windowsPathLike = Pattern.compile("\\w:\\\\.*") /** Copy of org.pkl.core.util.IoUtils.toUri */ fun String.toUri(): URI { if (uriLike.matcher(this).matches()) { return URI(this) } if (windowsPathLike.matcher(this).matches()) { return File(this).toURI() } return URI(null, null, this, null) } /** Lex a string into tokens similar to how a shell would */ fun shlex(input: String): List { val result = mutableListOf() var inEscape = false var quote: Char? = null var lastCloseQuoteIndex = Int.MIN_VALUE val current = StringBuilder() for ((idx, char) in input.withIndex()) { when { // if in an escape always append the next character inEscape -> { inEscape = false current.append(char) } // enter an escape on \ if not in a quote or in a non-single quote char == '\\' && quote != '\'' -> inEscape = true // if in a quote and encounter the delimiter, tentatively exit the quote // this handles cases with adjoining quotes e.g. `abc'123''xyz'` quote == char -> { quote = null lastCloseQuoteIndex = idx } // if not in a quote and encounter a quote character, enter a quote quote == null && (char == '\'' || char == '"') -> { quote = char } // if not in a quote and whitespace is encountered quote == null && char.isWhitespace() -> { // if the current token isn't empty or if a quote has just ended, finalize the current token // otherwise do nothing, which handles multiple whitespace cases e.g. `abc 123` if (current.isNotEmpty() || lastCloseQuoteIndex == (idx - 1)) { result.add(current.toString()) current.clear() } } // in other cases, append to the current token else -> current.append(char) } } // clean up last token // if the current token isn't empty or if a quote has just ended, finalize the token // if this condition is false, the input likely ended in whitespace if (current.isNotEmpty() || lastCloseQuoteIndex == (input.length - 1)) { result.add(current.toString()) } return result } ================================================ FILE: pkl-commons/src/main/kotlin/org/pkl/commons/Throwables.kt ================================================ /* * Copyright © 2024 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.commons import java.io.PrintWriter import java.io.StringWriter /** Same as [Throwable.printStackTrace] except that it prints to a [String]. */ fun Throwable.printStackTraceToString(): String = StringWriter().also { printStackTrace(PrintWriter(it)) }.toString() ================================================ FILE: pkl-commons/src/main/kotlin/org/pkl/commons/Uris.kt ================================================ /* * Copyright © 2024 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.commons import java.net.URI import java.nio.file.Path fun URI.toPath(): Path = Path.of(this) ================================================ FILE: pkl-commons/src/test/kotlin/org/pkl/commons/NameMapperTest.kt ================================================ /* * Copyright © 2024-2025 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.commons import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test class NameMapperTest { @Test fun `empty prefixes everything`() { val mapper = NameMapper(mapOf("" to "bar.")) assertThat(mapper.map("foo.bar.Baz")).isEqualTo("bar.foo.bar" to "Baz") assertThat(mapper.map("Baz")).isEqualTo("bar" to "Baz") } @Test fun `longest prefix wins`() { val mapper = NameMapper(mapOf("bar." to "com.bar.", "bar.baz." to "foo.bar.")) assertThat(mapper.map("bar.baz.Buzzy")).isEqualTo("foo.bar" to "Buzzy") } @Test fun `implicit uppercase classname`() { val mapper = NameMapper(mapOf("foo." to "bar.")) assertThat(mapper.map("foo.bar.baz")).isEqualTo("bar.bar" to "Baz") assertThat(mapper.map("foo.bar")).isEqualTo("bar" to "Bar") assertThat(mapper.map("baz")).isEqualTo("" to "Baz") assertThat(mapper.map("baz")).isEqualTo("" to "Baz") } @Test fun `no implicit uppercased classname if explicitly renamed`() { val mapper = NameMapper(mapOf("foo.bar" to "bar.bar", "foo.c" to "foo.z", "com.foo." to "x")) assertThat(mapper.map("foo.bar")).isEqualTo("bar" to "bar") assertThat(mapper.map("foo.bar")).isEqualTo("bar" to "bar") assertThat(mapper.map("foo.cow")).isEqualTo("foo" to "zow") assertThat(mapper.map("com.foo.bar")).isEqualTo("" to "xbar") } } ================================================ FILE: pkl-commons/src/test/kotlin/org/pkl/commons/ShlexTest.kt ================================================ /* * Copyright © 2024 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.commons import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test class ShlexTest { @Test fun `empty input produces empty output`() { assertThat(shlex("")).isEqualTo(emptyList()) } @Test fun `whitespace input produces empty output`() { assertThat(shlex(" \n \t ")).isEqualTo(emptyList()) } @Test fun `regular token parsing`() { assertThat(shlex("\nabc def\tghi ")).isEqualTo(listOf("abc", "def", "ghi")) } @Test fun `single quoted token parsing`() { assertThat(shlex("'this is a single token'")).isEqualTo(listOf("this is a single token")) } @Test fun `double quoted token parsing`() { assertThat(shlex("\"this is a single token\"")).isEqualTo(listOf("this is a single token")) } @Test fun `escaping handles double quotes`() { assertThat(shlex(""""\"this is a single double quoted token\""""")) .isEqualTo(listOf("\"this is a single double quoted token\"")) } @Test fun `escaping does not apply within single quotes`() { assertThat(shlex("""'this is a single \" token'""")) .isEqualTo(listOf("""this is a single \" token""")) } @Test fun `adjacent quoted strings are one token`() { assertThat(shlex(""""single"' joined 'token""")).isEqualTo(listOf("single joined token")) assertThat(shlex(""""single"' 'token""")).isEqualTo(listOf("single token")) } @Test fun `space escapes do not split tokens`() { assertThat(shlex("""single\ token""")).isEqualTo(listOf("single token")) } @Test fun `empty quotes produce a single empty token`() { assertThat(shlex("\"\"")).isEqualTo(listOf("")) assertThat(shlex("''")).isEqualTo(listOf("")) assertThat(shlex("'' ''")).isEqualTo(listOf("", "")) assertThat(shlex("''''")).isEqualTo(listOf("")) } } ================================================ FILE: pkl-commons-cli/gradle.lockfile ================================================ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. com.github.ajalt.clikt:clikt-core-jvm:5.0.3=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.clikt:clikt-core:5.0.3=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.github.ajalt.clikt:clikt-jvm:5.0.3=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.clikt:clikt-markdown-jvm:5.0.3=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.clikt:clikt-markdown:5.0.3=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.github.ajalt.clikt:clikt:5.0.3=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.github.ajalt.colormath:colormath-jvm:3.6.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.colormath:colormath:3.6.0=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.github.ajalt.mordant:mordant-core-jvm:3.0.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-core:3.0.1=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm-ffm-jvm:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm-ffm:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm-graal-ffi-jvm:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm-graal-ffi:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm-jna-jvm:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm-jna:3.0.1=runtimeClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-jvm:3.0.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-markdown-jvm:3.0.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.mordant:mordant-markdown:3.0.1=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.github.ajalt.mordant:mordant:3.0.1=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.github.ben-manes.caffeine:caffeine:2.9.3=swiftExportClasspathResolvable com.google.errorprone:error_prone_annotations:2.28.0=swiftExportClasspathResolvable io.github.java-diff-utils:java-diff-utils:4.12=kotlinInternalAbiValidation io.opentelemetry:opentelemetry-api:1.41.0=swiftExportClasspathResolvable io.opentelemetry:opentelemetry-context:1.41.0=swiftExportClasspathResolvable net.bytebuddy:byte-buddy:1.17.7=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.java.dev.jna:jna:5.14.0=runtimeClasspath,testRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata org.assertj:assertj-core:3.27.6=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.bouncycastle:bcpg-jdk18on:1.80=kotlinBouncyCastleConfiguration org.bouncycastle:bcpkix-jdk18on:1.80=kotlinBouncyCastleConfiguration org.bouncycastle:bcprov-jdk18on:1.80=kotlinBouncyCastleConfiguration org.bouncycastle:bcutil-jdk18on:1.80=kotlinBouncyCastleConfiguration org.checkerframework:checker-qual:3.43.0=swiftExportClasspathResolvable org.graalvm.compiler:compiler:25.0.0=svmClasspath org.graalvm.espresso:espresso-svm:25.0.0=svmClasspath org.graalvm.nativeimage:native-image-base:25.0.0=svmClasspath org.graalvm.nativeimage:objectfile:25.0.0=svmClasspath org.graalvm.nativeimage:pointsto:25.0.0=svmClasspath org.graalvm.nativeimage:svm-capnproto-runtime:25.0.0=svmClasspath org.graalvm.nativeimage:svm-configure:25.0.0=svmClasspath org.graalvm.nativeimage:svm:25.0.0=svmClasspath org.graalvm.nativeimage:truffle-runtime-svm:25.0.0=svmClasspath org.graalvm.polyglot:polyglot:25.0.0=runtimeClasspath,svmClasspath,testRuntimeClasspath org.graalvm.sdk:collections:25.0.0=runtimeClasspath,svmClasspath,testRuntimeClasspath org.graalvm.sdk:graal-sdk:25.0.0=runtimeClasspath,svmClasspath,testRuntimeClasspath org.graalvm.sdk:jniutils:25.0.0=svmClasspath org.graalvm.sdk:nativeimage-libgraal:25.0.0=svmClasspath org.graalvm.sdk:nativeimage:25.0.0=runtimeClasspath,svmClasspath,testRuntimeClasspath org.graalvm.sdk:word:25.0.0=runtimeClasspath,svmClasspath,testRuntimeClasspath org.graalvm.truffle:truffle-api:25.0.0=runtimeClasspath,svmClasspath,testRuntimeClasspath org.graalvm.truffle:truffle-compiler:25.0.0=svmClasspath org.graalvm.truffle:truffle-runtime:25.0.0=svmClasspath org.jetbrains.kotlin:abi-tools-api:2.2.20=kotlinInternalAbiValidation org.jetbrains.kotlin:abi-tools:2.2.20=kotlinInternalAbiValidation org.jetbrains.kotlin:kotlin-build-tools-api:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-build-tools-impl:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-compiler-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlin:kotlin-compiler-runner:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-daemon-client:2.2.20=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-daemon-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:2.2.20=kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-metadata-jvm:2.2.20=kotlinInternalAbiValidation org.jetbrains.kotlin:kotlin-native-prebuilt:2.0.21=kotlinNativeBundleConfiguration org.jetbrains.kotlin:kotlin-reflect:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlin:kotlin-script-runtime:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathSvm,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlin:kotlin-scripting-common:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathSvm,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathSvm,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathSvm,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-jvm:2.2.20=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathSvm,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.2.20=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.20=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib:2.2.20=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathSvm,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,runtimeClasspath,swiftExportClasspathResolvable,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.kotlin:swift-export-embeddable:2.2.20=swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-serialization-bom:1.7.3=swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.7.3=swiftExportClasspathResolvable org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3=swiftExportClasspathResolvable org.jetbrains:annotations:13.0=compileClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathSvm,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,runtimeClasspath,swiftExportClasspathResolvable,testCompileClasspath,testRuntimeClasspath org.jetbrains:markdown-jvm:0.7.3=runtimeClasspath,testRuntimeClasspath org.jetbrains:markdown:0.7.3=implementationDependenciesMetadata,runtimeClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.jupiter:junit-jupiter-api:5.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.jupiter:junit-jupiter-engine:5.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.jupiter:junit-jupiter-params:5.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.platform:junit-platform-commons:1.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.platform:junit-platform-engine:1.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.platform:junit-platform-launcher:1.14.0=testRuntimeClasspath org.junit:junit-bom:5.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.msgpack:msgpack-core:0.9.8=runtimeClasspath,svmClasspath,testRuntimeClasspath org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.organicdesign:Paguro:3.10.3=runtimeClasspath,svmClasspath,testRuntimeClasspath org.snakeyaml:snakeyaml-engine:2.10=runtimeClasspath,svmClasspath,testRuntimeClasspath empty=annotationProcessor,compileOnlyDependenciesMetadata,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinNativeCompilerPluginClasspath,kotlinScriptDefExtensions,signatures,sourcesJar,svmAnnotationProcessor,svmApiDependenciesMetadata,svmCompileClasspath,svmCompileOnlyDependenciesMetadata,svmImplementationDependenciesMetadata,svmIntransitiveDependenciesMetadata,svmKotlinScriptDefExtensions,svmRuntimeClasspath,testAnnotationProcessor,testApiDependenciesMetadata,testCompileOnlyDependenciesMetadata,testIntransitiveDependenciesMetadata,testKotlinScriptDefExtensions ================================================ FILE: pkl-commons-cli/pkl-commons-cli.gradle.kts ================================================ /* * Copyright © 2024-2025 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. */ plugins { pklAllProjects pklKotlinLibrary pklPublishLibrary } val svmClasspath: Configuration by configurations.creating // used by pklNativeExecutable.gradle.kts @Suppress("unused") val svm: SourceSet by sourceSets.creating { compileClasspath = svmClasspath } dependencies { api(projects.pklCore) api(libs.clikt) implementation(libs.cliktMarkdown) implementation(projects.pklCommons) testImplementation(projects.pklCommonsTest) svmClasspath(libs.svm) svmClasspath(libs.truffleSvm) svmClasspath(projects.pklCore) } publishing { publications { named("library") { pom { url.set("https://github.com/apple/pkl/tree/main/pkl-commons-cli") description.set("Internal CLI utilities. NOT A PUBLIC API.") } } } } ================================================ FILE: pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/CliBaseOptions.kt ================================================ /* * Copyright © 2024-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.commons.cli import java.net.URI import java.nio.file.Files import java.nio.file.Path import java.time.Duration import java.util.regex.Pattern import org.pkl.core.evaluatorSettings.Color import org.pkl.core.evaluatorSettings.PklEvaluatorSettings.ExternalReader import org.pkl.core.evaluatorSettings.TraceMode import org.pkl.core.module.ProjectDependenciesManager import org.pkl.core.util.IoUtils /** Base options shared between CLI commands. */ data class CliBaseOptions( /** The source modules to evaluate. Relative URIs are resolved against [workingDir]. */ val sourceModules: List = listOf(), /** * The URI patterns that determine which modules can be loaded and evaluated. Patterns are matched * against the beginning of module URIs. At least one pattern needs to match for a module to be * loadable. Both [sourceModules] and module imports are subject to this check. */ val allowedModules: List? = null, /** * The URI patterns that determine which external resources can be read. Patterns are matched * against the beginning of resource URIs. At least one pattern needs to match for a resource to * be readable. */ val allowedResources: List? = null, /** * The environment variables to set. Pkl code can read environment variables with * `read("env:")`. */ val environmentVariables: Map? = null, /** * The external properties to set. Pkl code can read external properties with * `read("prop:")`. */ val externalProperties: Map? = null, /** * The directories, ZIP archives, or JAR archives to search when resolving `modulepath:` URIs. * Relative paths are resolved against [workingDir]. */ private val modulePath: List? = null, /** * The base path that relative module paths passed as command-line arguments are resolved against. */ private val workingDir: Path = IoUtils.getCurrentWorkingDir(), /** * The root directory for `file:` modules and resources. If non-null, access to file-based modules * and resources is restricted to those located under [rootDir]. Any symlinks are resolved before * this check is performed. */ private val rootDir: Path? = null, /** * 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. */ private val settings: URI? = null, /** * The root directory of the project. The directory must contain a `PklProject` that amends the * `pkl.Project` standard library module. * * If `null`, looks for a `PklProject` file in [workingDir], traversing up to [rootDir], or `/` if * [rootDir] is `null`. * * This can be disabled with [noProject]. */ private val projectDir: Path? = null, /** * The duration after which evaluation of a source module will be timed out. Note that a timeout * is treated the same as a program error in that any subsequent source modules will not be * evaluated. */ val timeout: Duration? = null, /** The cache directory for storing packages. */ private val moduleCacheDir: Path? = null, /** Whether to render errors in ANSI color. */ val color: Color? = null, /** Whether to disable the module cache. */ val noCache: Boolean = false, /** Ignores any evaluator settings set in the PklProject file. */ val omitProjectSettings: Boolean = false, /** Disables all behavior related to projects. */ val noProject: Boolean = false, /** Tells whether to run the CLI in test mode. This is an internal option. */ val testMode: Boolean = false, /** * Unless -1, rewrites HTTP requests that specify port 0 to the given port. This is an internal * test option. */ val testPort: Int = -1, /** * The CA certificates to trust. * * 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. */ val caCertificates: List = listOf(), /** The proxy to connect to. */ val httpProxy: URI? = null, /** Hostnames, IP addresses, or CIDR blocks to not proxy. */ val httpNoProxy: List? = null, /** URL prefixes to rewrite. */ val httpRewrites: Map? = null, /** External module reader process specs */ val externalModuleReaders: Map = mapOf(), /** External resource reader process specs */ val externalResourceReaders: Map = mapOf(), /** Defines options for the formatting of calls to the trace() method. */ val traceMode: TraceMode? = null, /** Whether power assertions are enabled. */ val powerAssertionsEnabled: Boolean = false, ) { companion object { tailrec fun Path.getProjectFile(rootDir: Path?): Path? { val candidate = resolve(ProjectDependenciesManager.PKL_PROJECT_FILENAME) return when { Files.exists(candidate) -> candidate parent == null -> null rootDir != null && !parent.startsWith(rootDir) -> null else -> parent.getProjectFile(rootDir) } } } /** [workingDir] after normalization. */ val normalizedWorkingDir: Path = IoUtils.getCurrentWorkingDir().resolve(workingDir) /** [rootDir] after normalization. */ val normalizedRootDir: Path? = rootDir?.let(normalizedWorkingDir::resolve) /** The effective project directory, if exists. */ val normalizedProjectFile: Path? by lazy { projectDir?.resolve(ProjectDependenciesManager.PKL_PROJECT_FILENAME) ?: normalizedWorkingDir.getProjectFile(rootDir) } /** [sourceModules] after normalization. */ val normalizedSourceModules: List = sourceModules .map { uri -> if (uri.isAbsolute) uri else if (uri.path.startsWith("@") && !noProject && normalizedProjectFile != null) uri else IoUtils.resolve(normalizedWorkingDir.toUri(), uri) } // sort modules to make cli output independent of source module order .sorted() val normalizedSettingsModule: URI? = settings?.let { uri -> if (uri.isAbsolute) uri else IoUtils.resolve(normalizedWorkingDir.toUri(), uri) } /** [modulePath] after normalization. */ val normalizedModulePath: List? = modulePath?.map(normalizedWorkingDir::resolve) /** [moduleCacheDir] after normalization. */ val normalizedModuleCacheDir: Path? = moduleCacheDir?.let(normalizedWorkingDir::resolve) /** [caCertificates] after normalization. */ val normalizedCaCertificates: List = caCertificates.map(normalizedWorkingDir::resolve) } ================================================ FILE: pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/CliCommand.kt ================================================ /* * Copyright © 2024-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.commons.cli import com.github.ajalt.clikt.core.CliktError import java.net.URI import java.nio.file.Files import java.nio.file.Path import java.util.regex.Pattern import kotlin.io.path.isRegularFile import org.pkl.core.* import org.pkl.core.evaluatorSettings.PklEvaluatorSettings import org.pkl.core.evaluatorSettings.TraceMode import org.pkl.core.externalreader.ExternalReaderProcess import org.pkl.core.http.HttpClient import org.pkl.core.module.ModuleKeyFactories import org.pkl.core.module.ModuleKeyFactory import org.pkl.core.module.ModulePathResolver 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.IoUtils /** Building block for CLI commands. Configured programmatically to allow for embedding. */ abstract class CliCommand(protected val cliOptions: CliBaseOptions) { /** Runs this command. */ fun run() { if (cliOptions.testMode) { IoUtils.setTestMode() } try { proxyAddress?.let(IoUtils::setSystemProxy) doRun() } catch (e: PklException) { if (e.cause is CliktError) throw e.cause!! throw CliException(e.message!!) } catch (e: CliException) { throw e } catch (e: Exception) { throw CliBugException(e) } } /** * Implements this command. May throw [PklException] or [CliException]. Any other thrown exception * is treated as a bug. */ protected abstract fun doRun() /** The Pkl settings used by this command. */ @Suppress("MemberVisibilityCanBePrivate") protected val settings: PklSettings by lazy { try { if (cliOptions.normalizedSettingsModule != null) { PklSettings.load(ModuleSource.uri(cliOptions.normalizedSettingsModule)) } else { PklSettings.loadFromPklHomeDir() } } catch (e: PklException) { // do not use `errorRenderer` because it depends on `settings` throw CliException(e.toString()) } } /** The Project used by this command. */ protected val project: Project? by lazy { if (cliOptions.noProject) { null } else { cliOptions.normalizedProjectFile?.let { loadProject(it) } } } protected fun resolveModuleUri(uri: URI): URI = if (uri.isAbsolute) uri else { // must be @dep/mod.pkl notation!! if (!uri.path.startsWith('@')) throw CliBugException( RuntimeException("tried to resolve project URI `$uri` with no @ prefix") ) if (project == null) throw CliBugException( RuntimeException("tried to resolve project URI `$uri` with no project present") ) val dep = uri.path.substringBefore('/').drop(1) val path = uri.path.dropWhile { it != '/' } if (path.isEmpty()) throw CliException("Invalid project dependency URI `$uri`.") val remoteDep = project!!.dependencies.remoteDependencies()[dep] ?: if (project!!.dependencies.localDependencies().containsKey(dep)) throw CliException( "Only remote project dependencies may be referenced using @-notation. Dependency `@$dep` is a local dependency." ) else throw CliException("Project does not contain dependency `@$dep`.") remoteDep.packageUri.toPackageAssetUri(path).uri } protected val resolvedSourceModules: List by lazy { if (project == null) cliOptions.normalizedSourceModules else cliOptions.normalizedSourceModules.map(::resolveModuleUri) } protected fun loadProject(projectFile: Path): Project { val securityManager = SecurityManagers.standard( cliOptions.allowedModules ?: SecurityManagers.defaultAllowedModules, cliOptions.allowedResources ?: SecurityManagers.defaultAllowedResources, SecurityManagers.defaultTrustLevels, cliOptions.normalizedRootDir, ) val envVars = cliOptions.environmentVariables ?: System.getenv() val stackFrameTransformer = if (IoUtils.isTestMode()) StackFrameTransformers.empty else StackFrameTransformers.defaultTransformer return Project.loadFromPath( projectFile, securityManager, cliOptions.timeout, stackFrameTransformer, envVars, cliOptions.powerAssertionsEnabled, ) } private val evaluatorSettings: PklEvaluatorSettings? by lazy { @Suppress("PklCliDirectProjectEvaluatorSettingsAccess") if (cliOptions.omitProjectSettings) null else project?.evaluatorSettings } protected val allowedModules: List by lazy { cliOptions.allowedModules ?: evaluatorSettings?.allowedModules ?: (SecurityManagers.defaultAllowedModules + externalModuleReaders.keys.map { Pattern.compile(Pattern.quote("$it:")) }.toList()) } protected val allowedResources: List by lazy { cliOptions.allowedResources ?: evaluatorSettings?.allowedResources ?: (SecurityManagers.defaultAllowedResources + externalResourceReaders.keys.map { Pattern.compile(Pattern.quote("$it:")) }.toList()) } protected val rootDir: Path? by lazy { cliOptions.normalizedRootDir ?: evaluatorSettings?.rootDir } protected val environmentVariables: Map by lazy { cliOptions.environmentVariables ?: evaluatorSettings?.env ?: System.getenv() } protected val externalProperties: Map by lazy { cliOptions.externalProperties ?: evaluatorSettings?.externalProperties ?: emptyMap() } protected val moduleCacheDir: Path? by lazy { if (cliOptions.noCache) null else cliOptions.normalizedModuleCacheDir ?: evaluatorSettings?.let { settings -> if (settings.noCache == true) null else settings.moduleCacheDir } ?: IoUtils.getDefaultModuleCacheDir() } protected val modulePath: List by lazy { cliOptions.normalizedModulePath ?: evaluatorSettings?.modulePath ?: emptyList() } protected val stackFrameTransformer: StackFrameTransformer by lazy { if (cliOptions.testMode) { StackFrameTransformers.empty } else { StackFrameTransformers.createDefault(settings) } } protected val securityManager: SecurityManager by lazy { SecurityManagers.standard( allowedModules, allowedResources, SecurityManagers.defaultTrustLevels, rootDir, ) } protected val useColor: Boolean by lazy { cliOptions.color?.hasColor() ?: evaluatorSettings?.color?.hasColor() ?: false } protected val proxyAddress: URI? by lazy { cliOptions.httpProxy ?: evaluatorSettings?.http?.proxy?.address ?: settings.http?.proxy?.address } protected val noProxy: List? by lazy { cliOptions.httpNoProxy ?: evaluatorSettings?.http?.proxy?.noProxy ?: settings.http?.proxy?.noProxy } protected val httpRewrites: Map? by lazy { cliOptions.httpRewrites ?: evaluatorSettings?.http?.rewrites ?: settings.http?.rewrites() } protected val externalModuleReaders: Map by lazy { (evaluatorSettings?.externalModuleReaders ?: emptyMap()) + cliOptions.externalModuleReaders } protected val externalResourceReaders: Map by lazy { (evaluatorSettings?.externalResourceReaders ?: emptyMap()) + cliOptions.externalResourceReaders } private val externalProcesses: Map by lazy { // Share ExternalReaderProcess instances between configured external resource/module readers // with the same spec. This avoids spawning multiple subprocesses if the same reader implements // both reader types and/or multiple schemes. (externalModuleReaders + externalResourceReaders).values.toSet().associateWith { ExternalReaderProcess.of(it) } } private val traceMode: TraceMode by lazy { cliOptions.traceMode ?: evaluatorSettings?.traceMode ?: TraceMode.COMPACT } private fun HttpClient.Builder.addDefaultCliCertificates() { val caCertsDir = IoUtils.getPklHomeDir().resolve("cacerts") var certsAdded = false if (Files.isDirectory(caCertsDir)) { Files.list(caCertsDir) .filter { it.isRegularFile() } .forEach { cert -> certsAdded = true addCertificates(cert) } } if (!certsAdded) { val defaultCerts = this@CliCommand.javaClass.classLoader.getResourceAsStream( "org/pkl/commons/cli/PklCARoots.pem" ) ?: throw CliException("Could not find bundled certificates") addCertificates(defaultCerts.readAllBytes()) } } /** * The HTTP client used for this command. * * To release resources held by the HTTP client in a timely manner, call [HttpClient.close]. */ val httpClient: HttpClient by lazy { with(HttpClient.builder()) { setTestPort(cliOptions.testPort) if (cliOptions.normalizedCaCertificates.isEmpty()) { addDefaultCliCertificates() } else { for (file in cliOptions.normalizedCaCertificates) addCertificates(file) } if ((proxyAddress ?: noProxy) != null) { setProxy(proxyAddress, noProxy ?: listOf()) } httpRewrites?.let(::setRewrites) // Lazy building significantly reduces execution time of commands that do minimal work. // However, it means that HTTP client initialization errors won't surface until an HTTP // request is made. buildLazily() } } protected fun moduleKeyFactories(modulePathResolver: ModulePathResolver): List { return buildList { externalModuleReaders.forEach { (key, value) -> add(ModuleKeyFactories.externalProcess(key, externalProcesses[value]!!)) } add(ModuleKeyFactories.standardLibrary) add(ModuleKeyFactories.modulePath(modulePathResolver)) add(ModuleKeyFactories.pkg) add(ModuleKeyFactories.projectpackage) addAll(ModuleKeyFactories.fromServiceProviders()) add(ModuleKeyFactories.file) add(ModuleKeyFactories.http) add(ModuleKeyFactories.genericUrl) } } private fun resourceReaders(modulePathResolver: ModulePathResolver): List { return buildList { add(ResourceReaders.environmentVariable()) add(ResourceReaders.externalProperty()) add(ResourceReaders.modulePath(modulePathResolver)) add(ResourceReaders.pkg()) add(ResourceReaders.projectpackage()) add(ResourceReaders.file()) add(ResourceReaders.http()) add(ResourceReaders.https()) externalResourceReaders.forEach { (key, value) -> add(ResourceReaders.externalProcess(key, externalProcesses[value]!!)) } } } /** * Creates an [EvaluatorBuilder] preconfigured according to [cliOptions]. To avoid resource leaks, * `ModuleKeyFactories.closeQuietly(builder.moduleKeyFactories)` must be called once the returned * builder and evaluators built by it are no longer in use. */ protected fun evaluatorBuilder(): EvaluatorBuilder { // indirectly closed by `ModuleKeyFactories.closeQuietly(builder.moduleKeyFactories)` val modulePathResolver = ModulePathResolver(modulePath) return EvaluatorBuilder.unconfigured() .setStackFrameTransformer(stackFrameTransformer) .apply { project?.let { setProjectDependencies(it.dependencies) } } .setSecurityManager(securityManager) .setHttpClient(httpClient) .setExternalProperties(externalProperties) .setEnvironmentVariables(environmentVariables) .addModuleKeyFactories(moduleKeyFactories(modulePathResolver)) .addResourceReaders(resourceReaders(modulePathResolver)) .setColor(useColor) .setLogger(Loggers.stdErr()) .setTimeout(cliOptions.timeout) .setModuleCacheDir(moduleCacheDir) .setTraceMode(traceMode) .setPowerAssertionsEnabled(cliOptions.powerAssertionsEnabled) } } ================================================ FILE: pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/CliException.kt ================================================ /* * Copyright © 2024-2025 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.commons.cli import org.pkl.commons.printStackTraceToString /** A CLI error to report back to users. */ open class CliException( /** * The error message to report back to CLI users. The message is expected to be displayed as-is * without any further enrichment. As such the message should be comprehensive and designed with * the CLI user in mind. */ message: String, /** The cause */ cause: Throwable?, /** The process exit code to use. */ val exitCode: Int = 1, ) : RuntimeException(message, cause) { constructor(message: String, exitCode: Int = 1) : this(message, null, exitCode) override fun toString(): String = message!! } /** An unexpected CLI error classified as bug. */ class CliBugException( /** The cause for the bug. */ private val theCause: Exception, /** The process exit code to use. */ exitCode: Int = 1, ) : CliException( "An unexpected error has occurred. Would you mind filing a bug report?", theCause, exitCode, ) { override fun toString(): String = "$message\n\n${theCause.printStackTraceToString()}" } ================================================ FILE: pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/CliMain.kt ================================================ /* * Copyright © 2024 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.commons.cli import java.io.PrintStream import kotlin.system.exitProcess /** Building block for CLIs. Intended to be called from a `main` method. */ fun cliMain(block: () -> Unit) { fun printError(error: Throwable, stream: PrintStream) { val message = error.toString() stream.print(message) // ensure CLI output always ends with newline if (!message.endsWith('\n')) stream.println() } // Force `native-image` to use system proxies (which does not happen with `-D`). System.setProperty("java.net.useSystemProxies", "true") try { block() } catch (e: CliTestException) { // no need to print the error, the test results will already do it exitProcess(e.exitCode) } catch (e: CliException) { printError(e, if (e.exitCode == 0) System.out else System.err) exitProcess(e.exitCode) } catch (e: Exception) { printError(CliBugException(e), System.err) exitProcess(1) } } ================================================ FILE: pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/CliTestException.kt ================================================ /* * Copyright © 2024 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.commons.cli class CliTestException(msg: String, exitCode: Int) : CliException(msg, exitCode) ================================================ FILE: pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/CliTestOptions.kt ================================================ /* * Copyright © 2024-2025 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.commons.cli import java.nio.file.Path class CliTestOptions( val junitDir: Path? = null, val overwrite: Boolean = false, val junitAggregateReports: Boolean = false, val junitAggregateSuiteName: String = "pkl-tests", ) ================================================ FILE: pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/commands/BaseCommand.kt ================================================ /* * Copyright © 2024-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.commons.cli.commands import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.core.Context import com.github.ajalt.clikt.core.context import com.github.ajalt.clikt.parameters.groups.provideDelegate abstract class BaseCommand(name: String, private val helpLink: String) : CliktCommand(name = name) { init { context { readArgumentFile = null } } abstract val helpString: String override fun help(context: Context) = helpString final override fun helpEpilog(context: Context) = "For more information, visit $helpLink" val baseOptions: BaseOptions by BaseOptions() } ================================================ FILE: pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/commands/BaseOptions.kt ================================================ /* * Copyright © 2024-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.commons.cli.commands import com.github.ajalt.clikt.completion.CompletionCandidates import com.github.ajalt.clikt.parameters.groups.OptionGroup import com.github.ajalt.clikt.parameters.options.* import com.github.ajalt.clikt.parameters.types.enum import com.github.ajalt.clikt.parameters.types.int import com.github.ajalt.clikt.parameters.types.long import com.github.ajalt.clikt.parameters.types.path import java.io.File import java.net.URI import java.net.URISyntaxException import java.nio.file.Path import java.time.Duration import java.util.regex.Pattern import org.pkl.commons.cli.CliBaseOptions import org.pkl.commons.cli.CliException import org.pkl.commons.shlex import org.pkl.core.evaluatorSettings.Color import org.pkl.core.evaluatorSettings.PklEvaluatorSettings.ExternalReader import org.pkl.core.evaluatorSettings.TraceMode import org.pkl.core.runtime.VmUtils import org.pkl.core.util.IoUtils @Suppress("MemberVisibilityCanBePrivate") class BaseOptions : OptionGroup() { companion object { /** * Parses [moduleName] into a URI. If scheme is not present, we expect that this is a file path * and encode any possibly invalid characters, and also normalize directory separators. If a * scheme is present, we expect that this is a valid URI. */ fun parseModuleName(moduleName: String): URI = when (moduleName) { "-" -> VmUtils.REPL_TEXT_URI else -> // Don't use `IoUtils.toUri` here becaus we need to normalize `\` paths to `/` on Windows. try { if (IoUtils.isUriLike(moduleName)) URI(moduleName) // Can't just use URI constructor, because URI(null, null, "C:/foo/bar", null) turns // into `URI("C", null, "/foo/bar", null)`. else if (IoUtils.isWindowsAbsolutePath(moduleName)) Path.of(moduleName).toUri() else URI(null, null, IoUtils.toNormalizedPathString(Path.of(moduleName)), null) } catch (e: URISyntaxException) { val message = buildString { append("Module URI `$moduleName` has invalid syntax (${e.reason}).") if (e.index > -1) { append("\n\n") append(moduleName) append("\n") append(" ".repeat(e.index)) append("^") } } throw CliException(message) } } fun RawOption.associateProps(): OptionWithValues, Pair, Pair> { return convert { val eq = it.indexOf('=') if (eq == -1) it to "true" else it.substring(0, eq) to it.substring(eq + 1) } .multiple() .toMap() } fun OptionWithValues.parseExternalReader( delimiter: String ): OptionWithValues< Pair?, Pair, Pair, > { return splitPair(delimiter).convert { val cmd = shlex(it.second) Pair(it.first, ExternalReader(cmd.first(), cmd.drop(1))) } } } private val defaults = CliBaseOptions() private val output = arrayOf( "json", "jsonnet", "pcf", "properties", "plist", "textproto", "xml", "yaml", "pkl-binary", ) val allowedModules: List by option( names = arrayOf("--allowed-modules"), help = "URI patterns that determine which modules can be loaded and evaluated.", ) .convert("pattern1,pattern2") { Pattern.compile(it) } .splitAll() val allowedResources: List by option( names = arrayOf("--allowed-resources"), help = "URI patterns that determine which external resources can be read.", ) .convert("pattern1,pattern2") { Pattern.compile(it) } .splitAll() val rootDir: Path? by option( names = arrayOf("--root-dir"), help = "Restricts access to file-based modules and resources to those located under the root directory.", ) .single() .path() val cacheDir: Path? by option(names = arrayOf("--cache-dir"), help = "The cache directory for storing packages.") .single() .path() val workingDir: Path by option( names = arrayOf("-w", "--working-dir"), help = "Base path that relative module paths are resolved against.", ) .single() .path() .default(defaults.normalizedWorkingDir) val properties: Map by option( names = arrayOf("-p", "--property"), metavar = "name=value", help = "External property to set (repeatable).", ) .associateProps() val color: Color by option( names = arrayOf("--color"), metavar = "when", help = "Whether to format messages in ANSI color. Possible values of are 'never', 'auto', and 'always'.", ) .enum { it.name.lowercase() } .single() .default(Color.AUTO) val noCache: Boolean by option(names = arrayOf("--no-cache"), help = "Disable caching of packages") .single() .flag(default = false) val format: String? by option( names = arrayOf("-f", "--format"), help = "Output format to generate. <${output.joinToString()}>", completionCandidates = CompletionCandidates.Fixed(output.toSet()), ) .single() val envVars: Map by option( names = arrayOf("-e", "--env-var"), metavar = "name=value", help = "Environment variable to set (repeatable).", ) .associate() val modulePath: List by option( names = arrayOf("--module-path"), metavar = "path1${File.pathSeparator}path2", help = "Directories, ZIP archives, or JAR archives to search when resolving `modulepath:` URIs.", ) .path() .splitAll(File.pathSeparator) val settings: URI? by option( names = arrayOf("--settings"), help = "Pkl settings module to use.", completionCandidates = CompletionCandidates.Path, ) .single() .convert { parseModuleName(it) } val timeout: Duration? by option( names = arrayOf("-t", "--timeout"), metavar = "number", help = "Duration, in seconds, after which evaluation of a source module will be timed out.", ) .single() .long() .convert { Duration.ofSeconds(it) } val caCertificates: List by option( names = arrayOf("--ca-certificates"), metavar = "path", help = "Only trust CA certificates from the provided file(s).", ) .path() .multiple() val proxy: URI? by option( names = arrayOf("--http-proxy"), metavar = "address", help = "Proxy to use for HTTP(S) connections.", ) .single() .convert { URI(it) } .validate { uri -> require( uri.scheme == "http" && uri.host != null && uri.path.isEmpty() && uri.userInfo == null ) { "Malformed proxy URI (expecting `http://[:]`)" } } val noProxy: List? by option( names = arrayOf("--http-no-proxy"), metavar = "pattern1,pattern", help = "Hostnames that should not be connected to via a proxy.", ) .single() .split(",") val httpRewrites: Map by option( names = arrayOf("--http-rewrite"), metavar = "from=to", help = "URL prefixes that should be rewritten.", ) .convert { it -> val uris = it.split("=", limit = 2) require(uris.size == 2) { "Rewrites must be in the form of =" } try { val (fromSpec, toSpec) = uris val fromUri = URI(fromSpec).also { IoUtils.validateRewriteRule(it) } val toUri = URI(toSpec).also { IoUtils.validateRewriteRule(it) } fromUri to toUri } catch (e: IllegalArgumentException) { fail(e.message!!) } catch (e: URISyntaxException) { val message = buildString { append("Rewrite target `${e.input}` has invalid syntax (${e.reason}).") if (e.index > -1) { append("\n\n") append(e.input) append("\n") append(" ".repeat(e.index)) append("^") } } fail(message) } } .multiple() .toMap() val externalModuleReaders: Map by option( names = arrayOf("--external-module-reader"), metavar = "=' []'", help = "External reader registrations for module URI schemes", ) .parseExternalReader("=") .multiple() .toMap() val externalResourceReaders: Map by option( names = arrayOf("--external-resource-reader"), metavar = "=' []'", help = "External reader registrations for resource URI schemes", ) .parseExternalReader("=") .multiple() .toMap() val traceMode: TraceMode? by option( names = arrayOf("--trace-mode"), metavar = "style", help = "Specifies how calls to trace() are formatted. Possible values of