diff --git a/bench/gradle.lockfile b/bench/gradle.lockfile index 536ff5cc..75232702 100644 --- a/bench/gradle.lockfile +++ b/bench/gradle.lockfile @@ -2,12 +2,12 @@ # Manual edits can break the build and are not advised. # This file is expected to be part of source control. com.tunnelvisionlabs:antlr4-runtime:4.9.0=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath -net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath 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=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata -org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.graalvm.compiler:compiler:23.0.2=graal org.graalvm.sdk:graal-sdk:23.0.2=graal,jmh,jmhRuntimeClasspath,truffle org.graalvm.truffle:truffle-api:23.0.2=graal,jmh,jmhRuntimeClasspath,truffle diff --git a/docs/gradle.lockfile b/docs/gradle.lockfile index 75e1a066..0b041bcb 100644 --- a/docs/gradle.lockfile +++ b/docs/gradle.lockfile @@ -3,10 +3,10 @@ # This file is expected to be part of source control. com.tunnelvisionlabs:antlr4-runtime:4.9.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath io.leangen.geantyref:geantyref:1.3.15=testRuntimeClasspath -net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata -org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.graalvm.sdk:graal-sdk:23.0.2=testRuntimeClasspath org.graalvm.truffle:truffle-api:23.0.2=testRuntimeClasspath org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath diff --git a/docs/modules/bindings-specification/pages/message-passing-api.adoc b/docs/modules/bindings-specification/pages/message-passing-api.adoc index 7071a0d3..b4b54aeb 100644 --- a/docs/modules/bindings-specification/pages/message-passing-api.adoc +++ b/docs/modules/bindings-specification/pages/message-passing-api.adoc @@ -121,6 +121,9 @@ outputFormat: String? /// The project dependency settings. project: Project? +/// Configuration of outgoing HTTP requests. +http: Http? + class ClientResourceReader { /// The URI scheme this reader is responsible for reading. scheme: String @@ -175,6 +178,54 @@ class Project { dependencies: Mapping } +/// Settings that control how Pkl talks to HTTP(S) servers. +class Http { + /// Configuration of the HTTP proxy to use. + /// + /// If [null], uses the operating system's proxy configuration. + proxy: Proxy? +} + +/// 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) +} + class RemoteDependency { type: "remote" diff --git a/docs/modules/pkl-cli/pages/index.adoc b/docs/modules/pkl-cli/pages/index.adoc index 47313ea6..7a93aef0 100644 --- a/docs/modules/pkl-cli/pages/index.adoc +++ b/docs/modules/pkl-cli/pages/index.adoc @@ -829,3 +829,18 @@ These certificates can be overridden via either of the two options: 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 Proxy + +When making HTTP(S) requests, Pkl can use a proxy. +By default, no proxy is configured. +A proxy can be configured as follows: + +- Using `--proxy `. + The URI must (currently) have scheme `http`, and may not contain anything other than a host and port. +- Using `--no-proxy `. + The given (comma separated list of) hosts bypass the proxy. + Hosts can be specified by domain name (in which case all subdomains also bypass the proxy), IP addresses, or IP ranges (using link:https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation[CIDR notation]). + 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. diff --git a/docs/modules/pkl-cli/partials/cli-common-options.adoc b/docs/modules/pkl-cli/partials/cli-common-options.adoc index 1198eb94..45a02118 100644 --- a/docs/modules/pkl-cli/partials/cli-common-options.adoc +++ b/docs/modules/pkl-cli/partials/cli-common-options.adoc @@ -122,3 +122,21 @@ 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]. ==== + +.--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. +==== + +.--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]. +==== diff --git a/docs/modules/pkl-gradle/partials/gradle-common-properties.adoc b/docs/modules/pkl-gradle/partials/gradle-common-properties.adoc index c1498786..5d269346 100644 --- a/docs/modules/pkl-gradle/partials/gradle-common-properties.adoc +++ b/docs/modules/pkl-gradle/partials/gradle-common-properties.adoc @@ -83,3 +83,21 @@ 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: `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: `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]. +==== diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e0d2e7a6..d4c9b30b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -47,6 +47,7 @@ slf4j = "1.+" # Breaking change in snakeYaml 2.6 (removing DumpSettingsBuilder::setScalarResolver), so pin to 2.5 snakeYaml = "2.5" spotlessPlugin = "6.11.0" +wiremock = "3.+" [libraries] # ordered alphabetically antlr = { group = "com.tunnelvisionlabs", name = "antlr4", version.ref = "antlr" } @@ -94,6 +95,7 @@ spotlessPlugin = { group = "com.diffplug.spotless", name = "spotless-plugin-grad 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" } +wiremock = { group = "org.wiremock", name = "wiremock", version.ref = "wiremock" } [plugins] # ordered alphabetically checksum = { id = "org.gradle.crypto.checksum", version.ref = "checksumPlugin" } diff --git a/pkl-certs/gradle.lockfile b/pkl-certs/gradle.lockfile index 55677eec..a0e766fa 100644 --- a/pkl-certs/gradle.lockfile +++ b/pkl-certs/gradle.lockfile @@ -1,10 +1,10 @@ # 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. -net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata -org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath diff --git a/pkl-cli/gradle.lockfile b/pkl-cli/gradle.lockfile index 0f2c555b..e53e5780 100644 --- a/pkl-cli/gradle.lockfile +++ b/pkl-cli/gradle.lockfile @@ -1,13 +1,59 @@ # 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.8.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +com.fasterxml.jackson.core:jackson-annotations:2.17.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +com.fasterxml.jackson.core:jackson-core:2.17.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +com.fasterxml.jackson.core:jackson-databind:2.17.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.17.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +com.fasterxml.jackson:jackson-bom:2.17.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.github.ajalt.clikt:clikt-jvm:3.5.1=compileClasspath,default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.clikt:clikt:3.5.1=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +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.26.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +com.google.guava:failureaccess:1.0.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +com.google.guava:guava:33.2.0-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 +com.jayway.jsonpath:json-path:2.9.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +com.networknt:json-schema-validator:1.4.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.tunnelvisionlabs:antlr4-runtime:4.9.0=default,runtimeClasspath,testRuntimeClasspath -net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +commons-fileupload:commons-fileupload:1.5=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +commons-io:commons-io:2.11.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +net.javacrumbs.json-unit:json-unit-core:2.38.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +net.minidev:accessors-smart:2.5.1=testRuntimeClasspath +net.minidev:json-smart:2.5.1=testRuntimeClasspath +net.sf.jopt-simple:jopt-simple:5.0.4=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.apache.httpcomponents.client5:httpclient5:5.3.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.apache.httpcomponents.core5:httpcore5-h2:5.2.4=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.apache.httpcomponents.core5:httpcore5:5.2.4=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata -org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.checkerframework:checker-qual:3.42.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.eclipse.jetty.http2:http2-common:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.eclipse.jetty.http2:http2-hpack:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.eclipse.jetty.http2:http2-server:11.0.20=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.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.eclipse.jetty:jetty-alpn-java-client:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.eclipse.jetty:jetty-alpn-java-server:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.eclipse.jetty:jetty-alpn-server:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.eclipse.jetty:jetty-bom:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.eclipse.jetty:jetty-client:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.eclipse.jetty:jetty-http:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.eclipse.jetty:jetty-io:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.eclipse.jetty:jetty-proxy:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.eclipse.jetty:jetty-security:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.eclipse.jetty:jetty-server:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.eclipse.jetty:jetty-servlet:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.eclipse.jetty:jetty-servlets:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.eclipse.jetty:jetty-util:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.eclipse.jetty:jetty-webapp:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.eclipse.jetty:jetty-xml:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.fusesource.jansi:jansi:2.4.0=default org.fusesource.jansi:jansi:2.4.1=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.graalvm.compiler:compiler:23.0.2=compileClasspath,compileOnlyDependenciesMetadata @@ -17,6 +63,8 @@ org.graalvm.nativeimage:pointsto:23.0.2=compileClasspath,compileOnlyDependencies org.graalvm.nativeimage:svm:23.0.2=compileClasspath,compileOnlyDependenciesMetadata org.graalvm.sdk:graal-sdk:23.0.2=compileClasspath,compileOnlyDependenciesMetadata,default,runtimeClasspath,testRuntimeClasspath org.graalvm.truffle:truffle-api:23.0.2=compileClasspath,compileOnlyDependenciesMetadata,default,runtimeClasspath,testRuntimeClasspath +org.hamcrest:hamcrest-core:2.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.hamcrest:hamcrest:2.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath @@ -32,14 +80,10 @@ org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10=apiDependenciesMetadata,compileCl org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains:annotations:13.0=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -org.jline:jline-native:3.23.0=default -org.jline:jline-native:3.23.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -org.jline:jline-reader:3.23.0=default -org.jline:jline-reader:3.23.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -org.jline:jline-terminal-jansi:3.23.0=default -org.jline:jline-terminal-jansi:3.23.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -org.jline:jline-terminal:3.23.0=default -org.jline:jline-terminal:3.23.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jline:jline-native:3.23.0=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jline:jline-reader:3.23.0=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jline:jline-terminal-jansi:3.23.0=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jline:jline-terminal:3.23.0=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.jupiter:junit-jupiter-api:5.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit.jupiter:junit-jupiter-engine:5.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit.jupiter:junit-jupiter-params:5.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath @@ -49,5 +93,12 @@ org.junit:junit-bom:5.10.2=testCompileClasspath,testImplementationDependenciesMe org.msgpack:msgpack-core:0.9.0=default,runtimeClasspath,testRuntimeClasspath org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.organicdesign:Paguro:3.10.3=default,runtimeClasspath,testRuntimeClasspath +org.slf4j:slf4j-api:2.0.11=testRuntimeClasspath +org.slf4j:slf4j-api:2.0.9=testCompileClasspath,testImplementationDependenciesMetadata org.snakeyaml:snakeyaml-engine:2.5=default,runtimeClasspath,testRuntimeClasspath +org.wiremock:wiremock:3.6.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.xmlunit:xmlunit-core:2.10.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.xmlunit:xmlunit-legacy:2.10.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.xmlunit:xmlunit-placeholders:2.10.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.yaml:snakeyaml:2.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath empty=annotationProcessor,archives,compile,intransitiveDependenciesMetadata,javaExecutable,kotlinCompilerPluginClasspath,kotlinNativeCompilerPluginClasspath,kotlinScriptDef,kotlinScriptDefExtensions,runtime,runtimeOnlyDependenciesMetadata,shadow,signatures,sourcesJar,stagedAlpineLinuxAmd64Executable,stagedLinuxAarch64Executable,stagedLinuxAmd64Executable,stagedMacAarch64Executable,stagedMacAmd64Executable,testAnnotationProcessor,testApiDependenciesMetadata,testCompile,testCompileOnly,testCompileOnlyDependenciesMetadata,testIntransitiveDependenciesMetadata,testKotlinScriptDef,testKotlinScriptDefExtensions,testRuntime diff --git a/pkl-cli/pkl-cli.gradle.kts b/pkl-cli/pkl-cli.gradle.kts index 17b5aeac..cc80cfd6 100644 --- a/pkl-cli/pkl-cli.gradle.kts +++ b/pkl-cli/pkl-cli.gradle.kts @@ -56,6 +56,7 @@ dependencies { } testImplementation(projects.pklCommonsTest) + testImplementation(libs.wiremock) fun executableDir(name: String) = files(layout.buildDirectory.dir("executable/$name")) stagedMacAmd64Executable(executableDir("pkl-macos-amd64")) diff --git a/pkl-cli/src/main/kotlin/org/pkl/cli/CliServer.kt b/pkl-cli/src/main/kotlin/org/pkl/cli/CliServer.kt index 126616b9..3035802b 100644 --- a/pkl-cli/src/main/kotlin/org/pkl/cli/CliServer.kt +++ b/pkl-cli/src/main/kotlin/org/pkl/cli/CliServer.kt @@ -25,7 +25,7 @@ import org.pkl.server.Server class CliServer(options: CliBaseOptions) : CliCommand(options) { override fun doRun() = try { - val server = Server(MessageTransports.stream(System.`in`, System.out), httpClient) + val server = Server(MessageTransports.stream(System.`in`, System.out)) server.use { it.start() } } catch (e: ProtocolException) { throw CliException(e.message!!) diff --git a/pkl-cli/src/test/kotlin/org/pkl/cli/CliEvaluatorTest.kt b/pkl-cli/src/test/kotlin/org/pkl/cli/CliEvaluatorTest.kt index 44c9aa12..34b273d0 100644 --- a/pkl-cli/src/test/kotlin/org/pkl/cli/CliEvaluatorTest.kt +++ b/pkl-cli/src/test/kotlin/org/pkl/cli/CliEvaluatorTest.kt @@ -15,17 +15,23 @@ */ 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.StringReader import java.io.StringWriter +import java.net.ServerSocket import java.net.URI 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.AfterAll import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.condition.DisabledOnOs @@ -40,8 +46,10 @@ import org.pkl.commons.cli.CliException import org.pkl.commons.test.FileTestUtils import org.pkl.commons.test.PackageServer import org.pkl.core.OutputFormat +import org.pkl.core.SecurityManagers import org.pkl.core.util.IoUtils +@WireMockTest(httpsEnabled = true, proxyMode = true) class CliEvaluatorTest { companion object { private val defaultContents = @@ -1262,6 +1270,196 @@ result = someLib.x .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")), + proxyAddress = 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")), + proxyAddress = URI(wwRuntimeInfo.httpBaseUrl), + noProxy = 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/Ostritch.pkl")), + noCache = true, + proxyAddress = URI(wwRuntimeInfo.httpBaseUrl), + caCertificates = listOf(FileTestUtils.selfSignedCertificate), + allowedModules = SecurityManagers.defaultAllowedModules + Pattern.compile("http:") + ) + ) + val output = evalToConsole(options) + assertThat(output) + .isEqualTo( + """ + name = "Ostritch" + + 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") + } + private fun evalModuleThatImportsPackage(certsFile: Path, testPort: Int = -1) { val moduleUri = writePklFile( diff --git a/pkl-codegen-java/gradle.lockfile b/pkl-codegen-java/gradle.lockfile index 7b9c09f1..ac71e00a 100644 --- a/pkl-codegen-java/gradle.lockfile +++ b/pkl-codegen-java/gradle.lockfile @@ -6,10 +6,10 @@ com.github.ajalt.clikt:clikt:3.5.1=apiDependenciesMetadata,compileClasspath,defa com.squareup:javapoet:1.13.0=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.tunnelvisionlabs:antlr4-runtime:4.9.0=default,runtimeClasspath,testRuntimeClasspath io.leangen.geantyref:geantyref:1.3.15=testRuntimeClasspath -net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata -org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.graalvm.sdk:graal-sdk:23.0.2=default,runtimeClasspath,testRuntimeClasspath org.graalvm.truffle:truffle-api:23.0.2=default,runtimeClasspath,testRuntimeClasspath org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath diff --git a/pkl-codegen-kotlin/gradle.lockfile b/pkl-codegen-kotlin/gradle.lockfile index a6e18388..4aee4cca 100644 --- a/pkl-codegen-kotlin/gradle.lockfile +++ b/pkl-codegen-kotlin/gradle.lockfile @@ -6,10 +6,10 @@ com.github.ajalt.clikt:clikt:3.5.1=apiDependenciesMetadata,compileClasspath,defa com.squareup:kotlinpoet:1.6.0=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.tunnelvisionlabs:antlr4-runtime:4.9.0=default,runtimeClasspath,testRuntimeClasspath io.leangen.geantyref:geantyref:1.3.15=testRuntimeClasspath -net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,testRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata -org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.graalvm.sdk:graal-sdk:23.0.2=default,runtimeClasspath,testRuntimeClasspath org.graalvm.truffle:truffle-api:23.0.2=default,runtimeClasspath,testRuntimeClasspath org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata diff --git a/pkl-commons-cli/gradle.lockfile b/pkl-commons-cli/gradle.lockfile index 0f03c440..b59c74b9 100644 --- a/pkl-commons-cli/gradle.lockfile +++ b/pkl-commons-cli/gradle.lockfile @@ -4,10 +4,10 @@ com.github.ajalt.clikt:clikt-jvm:3.5.1=compileClasspath,default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.ajalt.clikt:clikt:3.5.1=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.tunnelvisionlabs:antlr4-runtime:4.9.0=default,runtimeClasspath,testRuntimeClasspath -net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata -org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.graalvm.sdk:graal-sdk:23.0.2=default,runtimeClasspath,testRuntimeClasspath org.graalvm.truffle:truffle-api:23.0.2=default,runtimeClasspath,testRuntimeClasspath org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath diff --git a/pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/CliBaseOptions.kt b/pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/CliBaseOptions.kt index b4a330bd..adb63151 100644 --- a/pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/CliBaseOptions.kt +++ b/pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/CliBaseOptions.kt @@ -20,7 +20,6 @@ import java.nio.file.Files import java.nio.file.Path import java.time.Duration import java.util.regex.Pattern -import org.pkl.core.http.HttpClient import org.pkl.core.module.ProjectDependenciesManager import org.pkl.core.util.IoUtils @@ -129,6 +128,12 @@ data class CliBaseOptions( * `~/.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 proxyAddress: URI? = null, + + /** Hostnames, IP addresses, or CIDR blocks to not proxy. */ + val noProxy: List? = null, ) { companion object { @@ -177,24 +182,4 @@ data class CliBaseOptions( /** [caCertificates] after normalization. */ val normalizedCaCertificates: List = caCertificates.map(normalizedWorkingDir::resolve) - - /** - * The HTTP client shared between CLI commands created with this [CliBaseOptions] instance. - * - * To release the resources held by the HTTP client in a timely manner, call its `close()` method. - */ - val httpClient: HttpClient by lazy { - with(HttpClient.builder()) { - setTestPort(testPort) - if (normalizedCaCertificates.isEmpty()) { - addDefaultCliCertificates() - } else { - for (file in normalizedCaCertificates) addCertificates(file) - } - // 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() - } - } } diff --git a/pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/CliCommand.kt b/pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/CliCommand.kt index 2e078d5a..054c6722 100644 --- a/pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/CliCommand.kt +++ b/pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/CliCommand.kt @@ -18,6 +18,7 @@ package org.pkl.commons.cli import java.nio.file.Path import java.util.regex.Pattern import org.pkl.core.* +import org.pkl.core.evaluatorSettings.PklEvaluatorSettings import org.pkl.core.http.HttpClient import org.pkl.core.module.ModuleKeyFactories import org.pkl.core.module.ModuleKeyFactory @@ -35,6 +36,9 @@ abstract class CliCommand(protected val cliOptions: CliBaseOptions) { if (cliOptions.testMode) { IoUtils.setTestMode() } + + proxyAddress?.let(IoUtils::setSystemProxy) + try { doRun() } catch (e: PklException) { @@ -97,42 +101,44 @@ abstract class CliCommand(protected val cliOptions: CliBaseOptions) { ) } - private val projectSettings: Project.EvaluatorSettings? by lazy { - if (cliOptions.omitProjectSettings) null else project?.settings + private val evaluatorSettings: PklEvaluatorSettings? by lazy { + if (cliOptions.omitProjectSettings) null else project?.evaluatorSettings } protected val allowedModules: List by lazy { cliOptions.allowedModules - ?: projectSettings?.allowedModules ?: SecurityManagers.defaultAllowedModules + ?: evaluatorSettings?.allowedModules ?: SecurityManagers.defaultAllowedModules } protected val allowedResources: List by lazy { cliOptions.allowedResources - ?: projectSettings?.allowedResources ?: SecurityManagers.defaultAllowedResources + ?: evaluatorSettings?.allowedResources ?: SecurityManagers.defaultAllowedResources } - protected val rootDir: Path? by lazy { cliOptions.normalizedRootDir ?: projectSettings?.rootDir } + protected val rootDir: Path? by lazy { + cliOptions.normalizedRootDir ?: evaluatorSettings?.rootDir + } protected val environmentVariables: Map by lazy { - cliOptions.environmentVariables ?: projectSettings?.env ?: System.getenv() + cliOptions.environmentVariables ?: evaluatorSettings?.env ?: System.getenv() } protected val externalProperties: Map by lazy { - cliOptions.externalProperties ?: projectSettings?.externalProperties ?: emptyMap() + cliOptions.externalProperties ?: evaluatorSettings?.externalProperties ?: emptyMap() } protected val moduleCacheDir: Path? by lazy { if (cliOptions.noCache) null else cliOptions.normalizedModuleCacheDir - ?: projectSettings?.let { settings -> - if (settings.isNoCache == true) null else settings.moduleCacheDir + ?: evaluatorSettings?.let { settings -> + if (settings.noCache == true) null else settings.moduleCacheDir } ?: IoUtils.getDefaultModuleCacheDir() } protected val modulePath: List by lazy { - cliOptions.normalizedModulePath ?: projectSettings?.modulePath ?: emptyList() + cliOptions.normalizedModulePath ?: evaluatorSettings?.modulePath ?: emptyList() } protected val stackFrameTransformer: StackFrameTransformer by lazy { @@ -152,9 +158,36 @@ abstract class CliCommand(protected val cliOptions: CliBaseOptions) { ) } - // share HTTP client with other commands with the same cliOptions - protected val httpClient: HttpClient - get() = cliOptions.httpClient + private val proxyAddress = + cliOptions.proxyAddress + ?: project?.evaluatorSettings?.http?.proxy?.address ?: settings.http?.proxy?.address + + private val noProxy = + cliOptions.noProxy + ?: project?.evaluatorSettings?.http?.proxy?.noProxy ?: settings.http?.proxy?.noProxy + + /** + * 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()) + } + // 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 { diff --git a/pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/CliMain.kt b/pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/CliMain.kt index 7e4b4a3e..c586a6cb 100644 --- a/pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/CliMain.kt +++ b/pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/CliMain.kt @@ -27,6 +27,9 @@ fun cliMain(block: () -> Unit) { 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) { diff --git a/pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/commands/BaseOptions.kt b/pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/commands/BaseOptions.kt index 038d76d7..bfff2291 100644 --- a/pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/commands/BaseOptions.kt +++ b/pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/commands/BaseOptions.kt @@ -172,6 +172,32 @@ class BaseOptions : OptionGroup() { .path() .multiple() + @Suppress("HttpUrlsUsage") + val proxy: URI? by + option( + names = arrayOf("--proxy"), + metavar = "
", + 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("--no-proxy"), + metavar = "", + help = "Hostnames that should not be connected to via a proxy." + ) + .single() + .split(",") + // hidden option used by native tests private val testPort: Int by option(names = arrayOf("--test-port"), help = "Internal test option", hidden = true) @@ -202,7 +228,9 @@ class BaseOptions : OptionGroup() { testPort = testPort, omitProjectSettings = projectOptions?.omitProjectSettings ?: false, noProject = projectOptions?.noProject ?: false, - caCertificates = caCertificates + caCertificates = caCertificates, + proxyAddress = proxy, + noProxy = noProxy ?: emptyList() ) } } diff --git a/pkl-commons-test/gradle.lockfile b/pkl-commons-test/gradle.lockfile index fd334765..bcf786ff 100644 --- a/pkl-commons-test/gradle.lockfile +++ b/pkl-commons-test/gradle.lockfile @@ -2,11 +2,11 @@ # Manual edits can break the build and are not advised. # This file is expected to be part of source control. net.bytebuddy:byte-buddy:1.12.21=default -net.bytebuddy:byte-buddy:1.14.11=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.14.16=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.apiguardian:apiguardian-api:1.1.2=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata org.assertj:assertj-core:3.24.2=default -org.assertj:assertj-core:3.25.3=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.assertj:assertj-core:3.26.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath diff --git a/pkl-commons/gradle.lockfile b/pkl-commons/gradle.lockfile index 1fc8ca33..3208f915 100644 --- a/pkl-commons/gradle.lockfile +++ b/pkl-commons/gradle.lockfile @@ -1,10 +1,10 @@ # 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. -net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata -org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath diff --git a/pkl-config-java/gradle.lockfile b/pkl-config-java/gradle.lockfile index 1947ec92..b5b7a592 100644 --- a/pkl-config-java/gradle.lockfile +++ b/pkl-config-java/gradle.lockfile @@ -8,10 +8,10 @@ com.tunnelvisionlabs:antlr4-runtime:4.9.0=default,pklCodegenJava,runtimeClasspat io.leangen.geantyref:geantyref:1.3.14=default io.leangen.geantyref:geantyref:1.3.15=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath javax.inject:javax.inject:1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata -org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.graalvm.sdk:graal-sdk:23.0.2=default,pklCodegenJava,runtimeClasspath,testRuntimeClasspath org.graalvm.truffle:truffle-api:23.0.2=default,pklCodegenJava,runtimeClasspath,testRuntimeClasspath org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath diff --git a/pkl-config-kotlin/gradle.lockfile b/pkl-config-kotlin/gradle.lockfile index 1246d8a4..9c811290 100644 --- a/pkl-config-kotlin/gradle.lockfile +++ b/pkl-config-kotlin/gradle.lockfile @@ -7,10 +7,10 @@ com.squareup:kotlinpoet:1.6.0=pklCodegenKotlin com.tunnelvisionlabs:antlr4-runtime:4.9.0=default,pklCodegenKotlin,pklConfigJava,runtimeClasspath,testRuntimeClasspath io.leangen.geantyref:geantyref:1.3.14=default io.leangen.geantyref:geantyref:1.3.15=pklConfigJava,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata -org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.graalvm.sdk:graal-sdk:23.0.2=default,pklCodegenKotlin,pklConfigJava,runtimeClasspath,testRuntimeClasspath org.graalvm.truffle:truffle-api:23.0.2=default,pklCodegenKotlin,pklConfigJava,runtimeClasspath,testRuntimeClasspath org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath diff --git a/pkl-core/gradle.lockfile b/pkl-core/gradle.lockfile index a0d2b9fd..5e741839 100644 --- a/pkl-core/gradle.lockfile +++ b/pkl-core/gradle.lockfile @@ -6,13 +6,13 @@ com.squareup:javapoet:1.13.0=generatorCompileClasspath,generatorImplementationDe com.tunnelvisionlabs:antlr4-annotations:4.9.0=antlr com.tunnelvisionlabs:antlr4-runtime:4.9.0=antlr,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.tunnelvisionlabs:antlr4:4.9.0=antlr -net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.abego.treelayout:org.abego.treelayout.core:1.0.1=antlr org.antlr:ST4:4.3=antlr org.antlr:antlr-runtime:3.5.2=antlr org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata -org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.graalvm.sdk:graal-sdk:23.0.2=compileClasspath,default,generatorCompileClasspath,generatorImplementationDependenciesMetadata,generatorRuntimeClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.graalvm.truffle:truffle-api:23.0.2=compileClasspath,default,generatorCompileClasspath,generatorImplementationDependenciesMetadata,generatorRuntimeClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.graalvm.truffle:truffle-dsl-processor:23.0.2=annotationProcessor diff --git a/pkl-core/src/main/java/org/pkl/core/EvaluatorBuilder.java b/pkl-core/src/main/java/org/pkl/core/EvaluatorBuilder.java index 3c1d06b8..52c4511a 100644 --- a/pkl-core/src/main/java/org/pkl/core/EvaluatorBuilder.java +++ b/pkl-core/src/main/java/org/pkl/core/EvaluatorBuilder.java @@ -440,39 +440,39 @@ public final class EvaluatorBuilder { */ public EvaluatorBuilder applyFromProject(Project project) { this.dependencies = project.getDependencies(); - var settings = project.getSettings(); + var settings = project.getEvaluatorSettings(); if (securityManager != null) { throw new IllegalStateException( "Cannot call both `setSecurityManager` and `setProject`, because both define security manager settings. Call `setProjectOnly` if the security manager is desired."); } - if (settings.getAllowedModules() != null) { - setAllowedModules(settings.getAllowedModules()); + if (settings.allowedModules() != null) { + setAllowedModules(settings.allowedModules()); } - if (settings.getAllowedResources() != null) { - setAllowedResources(settings.getAllowedResources()); + if (settings.allowedResources() != null) { + setAllowedResources(settings.allowedResources()); } - if (settings.getExternalProperties() != null) { - setExternalProperties(settings.getExternalProperties()); + if (settings.externalProperties() != null) { + setExternalProperties(settings.externalProperties()); } - if (settings.getEnv() != null) { - setEnvironmentVariables(settings.getEnv()); + if (settings.env() != null) { + setEnvironmentVariables(settings.env()); } - if (settings.getTimeout() != null) { - setTimeout(settings.getTimeout().toJavaDuration()); + if (settings.timeout() != null) { + setTimeout(settings.timeout().toJavaDuration()); } - if (settings.getModulePath() != null) { + if (settings.modulePath() != null) { // indirectly closed by `ModuleKeyFactories.closeQuietly(builder.moduleKeyFactories)` - var modulePathResolver = new ModulePathResolver(settings.getModulePath()); + var modulePathResolver = new ModulePathResolver(settings.modulePath()); addResourceReader(ResourceReaders.modulePath(modulePathResolver)); addModuleKeyFactory(ModuleKeyFactories.modulePath(modulePathResolver)); } - if (settings.getRootDir() != null) { - setRootDir(settings.getRootDir()); + if (settings.rootDir() != null) { + setRootDir(settings.rootDir()); } - if (Boolean.TRUE.equals(settings.isNoCache())) { + if (Boolean.TRUE.equals(settings.noCache())) { setModuleCacheDir(null); - } else if (settings.getModuleCacheDir() != null) { - setModuleCacheDir(settings.getModuleCacheDir()); + } else if (settings.moduleCacheDir() != null) { + setModuleCacheDir(settings.moduleCacheDir()); } return this; } diff --git a/pkl-core/src/main/java/org/pkl/core/StackFrameTransformers.java b/pkl-core/src/main/java/org/pkl/core/StackFrameTransformers.java index f2111285..2122cfa1 100644 --- a/pkl-core/src/main/java/org/pkl/core/StackFrameTransformers.java +++ b/pkl-core/src/main/java/org/pkl/core/StackFrameTransformers.java @@ -108,7 +108,7 @@ public final class StackFrameTransformers { public static StackFrameTransformer createDefault(PklSettings settings) { return defaultTransformer // order is relevant - .andThen(convertFilePathToUriScheme(settings.getEditor().getUrlScheme())); + .andThen(convertFilePathToUriScheme(settings.editor().urlScheme())); } private static StackFrameTransformer loadFromServiceProviders() { diff --git a/pkl-core/src/main/java/org/pkl/core/evaluatorSettings/PklEvaluatorSettings.java b/pkl-core/src/main/java/org/pkl/core/evaluatorSettings/PklEvaluatorSettings.java new file mode 100644 index 00000000..20e15cd8 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/evaluatorSettings/PklEvaluatorSettings.java @@ -0,0 +1,191 @@ +/** + * 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.evaluatorSettings; + +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.regex.Pattern; +import org.pkl.core.Duration; +import org.pkl.core.PNull; +import org.pkl.core.PObject; +import org.pkl.core.PklBugException; +import org.pkl.core.PklException; +import org.pkl.core.Value; +import org.pkl.core.util.ErrorMessages; +import org.pkl.core.util.Nullable; + +/** Java version of {@code pkl.EvaluatorSettings}. */ +public record PklEvaluatorSettings( + @Nullable Map externalProperties, + @Nullable Map env, + @Nullable List allowedModules, + @Nullable List allowedResources, + @Nullable Boolean noCache, + @Nullable Path moduleCacheDir, + @Nullable List modulePath, + @Nullable Duration timeout, + @Nullable Path rootDir, + @Nullable Http http) { + + /** Initializes a {@link PklEvaluatorSettings} from a raw object representation. */ + @SuppressWarnings("unchecked") + public static PklEvaluatorSettings parse( + Value input, BiFunction pathNormalizer) { + if (!(input instanceof PObject pSettings)) { + throw PklBugException.unreachableCode(); + } + + var moduleCacheDirStr = (String) pSettings.get("moduleCacheDir"); + var moduleCacheDir = + moduleCacheDirStr == null + ? null + : pathNormalizer.apply(moduleCacheDirStr, "moduleCacheDir"); + + var allowedModulesStrs = (List) pSettings.get("allowedModules"); + var allowedModules = + allowedModulesStrs == null + ? null + : allowedModulesStrs.stream().map(Pattern::compile).toList(); + + var allowedResourcesStrs = (List) pSettings.get("allowedResources"); + var allowedResources = + allowedResourcesStrs == null + ? null + : allowedResourcesStrs.stream().map(Pattern::compile).toList(); + + var modulePathStrs = (List) pSettings.get("modulePath"); + var modulePath = + modulePathStrs == null + ? null + : modulePathStrs.stream().map(it -> pathNormalizer.apply(it, "modulePath")).toList(); + + var rootDirStr = (String) pSettings.get("rootDir"); + var rootDir = rootDirStr == null ? null : pathNormalizer.apply(rootDirStr, "rootDir"); + + return new PklEvaluatorSettings( + (Map) pSettings.get("externalProperties"), + (Map) pSettings.get("env"), + allowedModules, + allowedResources, + (Boolean) pSettings.get("noCache"), + moduleCacheDir, + modulePath, + (Duration) pSettings.get("timeout"), + rootDir, + Http.parse((Value) pSettings.get("http"))); + } + + public record Http(@Nullable Proxy proxy) { + public static final Http DEFAULT = new Http(null); + + public static @Nullable Http parse(@Nullable Value input) { + if (input == null || input instanceof PNull) { + return null; + } else if (input instanceof PObject http) { + var proxy = Proxy.parse((Value) http.getProperty("proxy")); + return proxy == null ? DEFAULT : new Http(proxy); + } else { + throw PklBugException.unreachableCode(); + } + } + } + + public record Proxy(@Nullable URI address, @Nullable List noProxy) { + public static Proxy create(@Nullable String address, @Nullable List noProxy) { + URI addressUri; + try { + addressUri = address == null ? null : new URI(address); + } catch (URISyntaxException e) { + throw new PklException(ErrorMessages.create("invalidUri", address)); + } + return new Proxy(addressUri, noProxy); + } + + @SuppressWarnings("unchecked") + public static @Nullable Proxy parse(Value input) { + if (input instanceof PNull) { + return null; + } else if (input instanceof PObject proxy) { + var address = (String) proxy.get("address"); + var noProxy = (List) proxy.get("noProxy"); + return create(address, noProxy); + } else { + throw PklBugException.unreachableCode(); + } + } + } + + private boolean arePatternsEqual( + @Nullable List thesePatterns, @Nullable List thosePatterns) { + if (thesePatterns == null) { + return thosePatterns == null; + } + if (thosePatterns == null || thesePatterns.size() != thosePatterns.size()) { + return false; + } + for (var i = 0; i < thesePatterns.size(); i++) { + if (!thesePatterns.get(i).pattern().equals(thosePatterns.get(i).pattern())) { + return false; + } + } + return true; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PklEvaluatorSettings that)) { + return false; + } + + return Objects.equals(externalProperties, that.externalProperties) + && Objects.equals(env, that.env) + && arePatternsEqual(allowedModules, that.allowedModules) + && arePatternsEqual(allowedResources, that.allowedResources) + && Objects.equals(noCache, that.noCache) + && Objects.equals(moduleCacheDir, that.moduleCacheDir) + && Objects.equals(timeout, that.timeout) + && Objects.equals(rootDir, that.rootDir) + && Objects.equals(http, that.http); + } + + private int hashPatterns(@Nullable List patterns) { + if (patterns == null) { + return 0; + } + var ret = 1; + for (var pattern : patterns) { + ret = 31 * ret + pattern.pattern().hashCode(); + } + return ret; + } + + @Override + public int hashCode() { + var result = + Objects.hash(externalProperties, env, noCache, moduleCacheDir, timeout, rootDir, http); + result = 31 * result + hashPatterns(allowedModules); + result = 31 * result + hashPatterns(allowedResources); + return result; + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/evaluatorSettings/package-info.java b/pkl-core/src/main/java/org/pkl/core/evaluatorSettings/package-info.java new file mode 100644 index 00000000..26c9b633 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/evaluatorSettings/package-info.java @@ -0,0 +1,4 @@ +@NonnullByDefault +package org.pkl.core.evaluatorSettings; + +import org.pkl.core.util.NonnullByDefault; diff --git a/pkl-core/src/main/java/org/pkl/core/http/HttpClient.java b/pkl-core/src/main/java/org/pkl/core/http/HttpClient.java index 2863d0e3..1d26106b 100644 --- a/pkl-core/src/main/java/org/pkl/core/http/HttpClient.java +++ b/pkl-core/src/main/java/org/pkl/core/http/HttpClient.java @@ -21,7 +21,9 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.net.http.HttpTimeoutException; import java.nio.file.Path; +import java.util.List; import javax.net.ssl.SSLContext; +import org.pkl.core.util.Nullable; /** * An HTTP client. @@ -36,6 +38,7 @@ import javax.net.ssl.SSLContext; public interface HttpClient extends AutoCloseable { /** A builder of {@linkplain HttpClient HTTP clients}. */ + @SuppressWarnings("unused") interface Builder { /** * Sets the {@code User-Agent} header. @@ -116,6 +119,32 @@ public interface HttpClient extends AutoCloseable { */ Builder setTestPort(int port); + /** + * Sets the proxy selector to use when establishing connections. + * + *

Defaults to: {@link java.net.ProxySelector#getDefault()}. + */ + Builder setProxySelector(java.net.ProxySelector proxySelector); + + /** + * Configures HTTP connections to connect to the provided proxy address. + * + *

The provided {@code proxyAddress} must have scheme http, not contain userInfo, and not + * have a path segment. + * + *

If {@code proxyAddress} is {@code null}, uses the proxy address provided by {@link + * java.net.ProxySelector#getDefault()}. + * + *

NOTE: Due to a limitation in the + * JDK, this does not configure the proxy server used for certificate revocation checking. + * To configure the certificate revocation checker, the result of {@link + * java.net.ProxySelector#getDefault} needs to be changed either by setting system properties, + * or via {@link java.net.ProxySelector#setDefault}. + * + * @throws IllegalArgumentException if `proxyAddress` is invalid. + */ + Builder setProxy(@Nullable URI proxyAddress, List noProxy); + /** * Creates a new {@code HttpClient} from the current state of this builder. * diff --git a/pkl-core/src/main/java/org/pkl/core/http/HttpClientBuilder.java b/pkl-core/src/main/java/org/pkl/core/http/HttpClientBuilder.java index b488fb1b..2a99b17d 100644 --- a/pkl-core/src/main/java/org/pkl/core/http/HttpClientBuilder.java +++ b/pkl-core/src/main/java/org/pkl/core/http/HttpClientBuilder.java @@ -16,6 +16,7 @@ package org.pkl.core.http; import java.io.IOException; +import java.net.ProxySelector; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Files; @@ -25,6 +26,7 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; import org.pkl.core.Release; +import org.pkl.core.http.HttpClient.Builder; import org.pkl.core.util.ErrorMessages; import org.pkl.core.util.IoUtils; @@ -36,6 +38,7 @@ final class HttpClientBuilder implements HttpClient.Builder { private final List certificateFiles = new ArrayList<>(); private final List certificateUris = new ArrayList<>(); private int testPort = -1; + private ProxySelector proxySelector; HttpClientBuilder() { this(IoUtils.getPklHomeDir().resolve("cacerts")); @@ -109,6 +112,17 @@ final class HttpClientBuilder implements HttpClient.Builder { return this; } + public HttpClient.Builder setProxySelector(ProxySelector proxySelector) { + this.proxySelector = proxySelector; + return this; + } + + @Override + public Builder setProxy(URI proxyAddress, List noProxy) { + this.proxySelector = new org.pkl.core.http.ProxySelector(proxyAddress, noProxy); + return this; + } + @Override public HttpClient build() { return doBuild().get(); @@ -123,8 +137,11 @@ final class HttpClientBuilder implements HttpClient.Builder { // make defensive copies because Supplier may get called after builder was mutated var certificateFiles = List.copyOf(this.certificateFiles); var certificateUris = List.copyOf(this.certificateUris); + var proxySelector = + this.proxySelector != null ? this.proxySelector : java.net.ProxySelector.getDefault(); return () -> { - var jdkClient = new JdkHttpClient(certificateFiles, certificateUris, connectTimeout); + var jdkClient = + new JdkHttpClient(certificateFiles, certificateUris, connectTimeout, proxySelector); return new RequestRewritingClient(userAgent, requestTimeout, testPort, jdkClient); }; } diff --git a/pkl-core/src/main/java/org/pkl/core/http/JdkHttpClient.java b/pkl-core/src/main/java/org/pkl/core/http/JdkHttpClient.java index 46a64e3e..64406096 100644 --- a/pkl-core/src/main/java/org/pkl/core/http/JdkHttpClient.java +++ b/pkl-core/src/main/java/org/pkl/core/http/JdkHttpClient.java @@ -77,11 +77,16 @@ final class JdkHttpClient implements HttpClient { closeMethod = result; } - JdkHttpClient(List certificateFiles, List certificateUris, Duration connectTimeout) { + JdkHttpClient( + List certificateFiles, + List certificateUris, + Duration connectTimeout, + java.net.ProxySelector proxySelector) { underlying = java.net.http.HttpClient.newBuilder() .sslContext(createSslContext(certificateFiles, certificateUris)) .connectTimeout(connectTimeout) + .proxy(proxySelector) .followRedirects(Redirect.NORMAL) .build(); } diff --git a/pkl-core/src/main/java/org/pkl/core/http/NoProxyRule.java b/pkl-core/src/main/java/org/pkl/core/http/NoProxyRule.java new file mode 100644 index 00000000..da12f6f8 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/http/NoProxyRule.java @@ -0,0 +1,215 @@ +/** + * 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.http; + +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.URI; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.util.regex.Pattern; +import org.pkl.core.util.Nullable; + +/** + * Represents a noproxy entry. + * + *

Follows the rules described in Standardizing + * {@code no_proxy} + */ +final class NoProxyRule { + private static final String portString = "(?::(?\\d{1,5}))?"; + private static final String cidrString = "(?:/(?\\d{1,3}))?"; + private static final String ipv4AddressString = + "(?[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})"; + private static final Pattern ipv4Address = Pattern.compile("^" + ipv4AddressString + "$"); + private static final Pattern ipv4AddressOrCidr = + Pattern.compile("^" + ipv4AddressString + cidrString + portString + "$"); + private static final String ipv6AddressString = + "(?(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?::[0-9a-fA-F]{1,4}){1,6}|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(?::[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]+|::(?:ffff(:0{1,4})?:)?(?:(?:25[0-5]|(2[0-4]|1?[0-9])?[0-9])\\.){3}(?:25[0-5]|(?:2[0-4]|1?[0-9])?[0-9])|(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1?[0-9])?[0-9])\\.){3}(?:25[0-5]|(?:2[0-4]|1?[0-9])?[0-9]))"; + private static final Pattern ipv6AddressOrCidr = + Pattern.compile( + "^(?\\[)?" + ipv6AddressString + cidrString + "(?])?" + portString + "$"); + private static final Pattern hostnamePattern = + Pattern.compile("^\\.?(?[^:]+)" + portString + "$"); + + private @Nullable Integer ipv4 = null; + private @Nullable Integer ipv4Mask = null; + private @Nullable BigInteger ipv6 = null; + private @Nullable BigInteger ipv6Mask = null; + private @Nullable String hostname = null; + private int port = 0; + private boolean allNoProxy = false; + + public NoProxyRule(String repr) { + if (repr.equals("*")) { + allNoProxy = true; + return; + } + var ipv4Matcher = ipv4AddressOrCidr.matcher(repr); + if (ipv4Matcher.matches()) { + var ipAddress = ipv4Matcher.group("host"); + ipv4 = parseIpv4(ipAddress); + if (ipv4Matcher.group("cidr") != null) { + var prefixLength = Integer.parseInt(ipv4Matcher.group("cidr")); + if (prefixLength > 32) { + // best-effort (don't fail on invalid cidrs). + hostname = repr; + } + ipv4Mask = 0xffffffff << (32 - prefixLength); + } + if (ipv4Matcher.group("port") != null) { + port = Integer.parseInt(ipv4Matcher.group("port")); + } + return; + } + var ipv6Matcher = ipv6AddressOrCidr.matcher(repr); + if (ipv6Matcher.matches()) { + var ipAddress = ipv6Matcher.group("host"); + ipv6 = parseIpv6(ipAddress); + if (ipv6Matcher.group("cidr") != null) { + var maskBuffer = ByteBuffer.allocate(16).putLong(-1L).putLong(-1L); + var prefixLength = Integer.parseInt(ipv6Matcher.group("cidr")); + if (prefixLength > 128) { + // best-effort (don't fail on invalid cidrs). + hostname = repr; + return; + } + ipv6Mask = new BigInteger(1, maskBuffer.array()).not().shiftRight(prefixLength); + } + if (ipv6Matcher.group("port") != null) { + port = Integer.parseInt(ipv6Matcher.group("port")); + } + return; + } + var hostnameMatcher = hostnamePattern.matcher(repr); + if (hostnameMatcher.matches()) { + hostname = hostnameMatcher.group("host"); + if (hostnameMatcher.group("port") != null) { + port = Integer.parseInt(hostnameMatcher.group("port")); + } + return; + } + throw new RuntimeException("Failed to parse hostname in no-proxy rule: " + repr); + } + + public boolean matches(URI uri) { + if (allNoProxy) { + return true; + } + if (!hostMatches(uri)) { + return false; + } + if (port == 0) { + return true; + } + var thatPort = uri.getPort(); + if (thatPort == -1) { + thatPort = + switch (uri.getScheme()) { + case "http" -> 80; + case "https" -> 443; + default -> -1; + }; + } + return port == thatPort; + } + + /** Tells if the provided URI should not be proxied according to the rules described. */ + public boolean hostMatches(URI uri) { + if (allNoProxy) { + return true; + } + var host = uri.getHost(); + if (host == null) { + return false; + } + if (host.equalsIgnoreCase(hostname)) { + return true; + } + if (hostname != null && endsWithIgnoreCase(host, "." + hostname)) { + return true; + } + return ipV6Matches(uri.getHost()) || ipV4Matches(uri.getHost()); + } + + private boolean endsWithIgnoreCase(String str, String suffix) { + var len = suffix.length(); + return str.regionMatches(true, str.length() - len, suffix, 0, len); + } + + private boolean ipV4Matches(String hostname) { + if (ipv4 == null) { + return false; + } + if (!ipv4Address.matcher(hostname).matches()) { + return false; + } + var address = parseIpv4(hostname); + if (ipv4.equals(address)) { + return true; + } + if (ipv4Mask != null) { + return (ipv4 & ipv4Mask) == (address & ipv4Mask); + } + return false; + } + + private boolean ipV6Matches(String hostname) { + if (ipv6 == null) { + return false; + } + if (!hostname.startsWith("[") && !hostname.endsWith("]")) { + return false; + } + var ipv6Repr = hostname.substring(1, hostname.length() - 1); + // According to RFC3986, square brackets can _only_ surround IPV6 addresses, so it should be + // safe to straight up parse it. + // + var address = parseIpv6(ipv6Repr); + if (ipv6.equals(address)) { + return true; + } + if (ipv6Mask != null) { + return ipv6.and(ipv6Mask).equals(address.and(ipv6Mask)); + } + return false; + } + + private BigInteger parseIpv6(String repr) { + try { + var inet = Inet6Address.getByName(repr); + var byteArr = inet.getAddress(); + return new BigInteger(1, byteArr); + } catch (UnknownHostException e) { + // should never happen; `repr` is an IPV6 literal. + throw new RuntimeException( + "Received unexpected UnknownHostException during parsing IPV6 literal", e); + } + } + + private int parseIpv4(String repr) { + try { + var inet = Inet4Address.getByName(repr); + return ByteBuffer.wrap(inet.getAddress()).getInt(); + } catch (UnknownHostException e) { + // should never happen; `repr` is an IPV4 literal. + throw new RuntimeException( + "Received unexpected UnknownHostException during parsing IPV4 literal", e); + } + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/http/ProxySelector.java b/pkl-core/src/main/java/org/pkl/core/http/ProxySelector.java new file mode 100644 index 00000000..891c6a11 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/http/ProxySelector.java @@ -0,0 +1,78 @@ +/** + * 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.http; + +import com.oracle.truffle.api.nodes.ExplodeLoop; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.SocketAddress; +import java.net.URI; +import java.util.List; +import org.pkl.core.util.ErrorMessages; +import org.pkl.core.util.Nullable; + +final class ProxySelector extends java.net.ProxySelector { + + public static final List NO_PROXY = List.of(Proxy.NO_PROXY); + + private final @Nullable List myProxy; + private final List noProxyRules; + private final @Nullable java.net.ProxySelector delegate; + + ProxySelector(@Nullable URI proxyAddress, List noProxyRules) { + this.noProxyRules = noProxyRules.stream().map(NoProxyRule::new).toList(); + if (proxyAddress == null) { + this.delegate = java.net.ProxySelector.getDefault(); + this.myProxy = null; + } else { + if (!proxyAddress.getScheme().equalsIgnoreCase("http") + || proxyAddress.getHost() == null + || !proxyAddress.getPath().isEmpty() + || proxyAddress.getUserInfo() != null) { + throw new IllegalArgumentException( + ErrorMessages.create("malformedProxyAddress", proxyAddress)); + } + this.delegate = null; + var port = proxyAddress.getPort(); + if (port == -1) { + port = 80; + } + this.myProxy = + List.of(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyAddress.getHost(), port))); + } + } + + @Override + @ExplodeLoop + public List select(URI uri) { + for (var proxyRule : noProxyRules) { + if (proxyRule.matches(uri)) { + return NO_PROXY; + } + } + if (delegate != null) { + return delegate.select(uri); + } + assert myProxy != null; + return myProxy; + } + + @Override + public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { + /* ignore */ + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/project/Project.java b/pkl-core/src/main/java/org/pkl/core/project/Project.java index 554cb8aa..395e5d66 100644 --- a/pkl-core/src/main/java/org/pkl/core/project/Project.java +++ b/pkl-core/src/main/java/org/pkl/core/project/Project.java @@ -39,7 +39,9 @@ import org.pkl.core.SecurityManager; import org.pkl.core.SecurityManagers; import org.pkl.core.StackFrameTransformer; import org.pkl.core.StackFrameTransformers; +import org.pkl.core.Value; import org.pkl.core.Version; +import org.pkl.core.evaluatorSettings.PklEvaluatorSettings; import org.pkl.core.module.ModuleKeyFactories; import org.pkl.core.packages.Checksums; import org.pkl.core.packages.Dependency.RemoteDependency; @@ -54,7 +56,7 @@ import org.pkl.core.util.Nullable; public final class Project { private final @Nullable Package pkg; private final DeclaredDependencies dependencies; - private final EvaluatorSettings evaluatorSettings; + private final PklEvaluatorSettings evaluatorSettings; private final URI projectFileUri; private final URI projectBaseUri; private final List tests; @@ -178,7 +180,9 @@ public final class Project { getProperty( module, "evaluatorSettings", - (settings) -> parseEvaluatorSettings(settings, projectBaseUri)); + (settings) -> + PklEvaluatorSettings.parse( + (Value) settings, (it, name) -> resolveNullablePath(it, projectBaseUri, name))); @SuppressWarnings("unchecked") var testPathStrs = (List) getProperty(module, "tests"); var tests = @@ -210,51 +214,6 @@ public final class Project { return result; } - @SuppressWarnings("unchecked") - private static EvaluatorSettings parseEvaluatorSettings(Object settings, URI projectBaseUri) { - var pSettings = (PObject) settings; - var externalProperties = getNullableProperty(pSettings, "externalProperties", Project::asMap); - var env = getNullableProperty(pSettings, "env", Project::asMap); - var allowedModules = getNullableProperty(pSettings, "allowedModules", Project::asPatternList); - var allowedResources = - getNullableProperty(pSettings, "allowedResources", Project::asPatternList); - var noCache = (Boolean) getNullableProperty(pSettings, "noCache"); - var modulePathStrs = (List) getNullableProperty(pSettings, "modulePath"); - var timeout = (Duration) getNullableProperty(pSettings, "timeout"); - - List modulePath = null; - if (modulePathStrs != null) { - modulePath = - modulePathStrs.stream() - .map((it) -> resolveNullablePath(it, projectBaseUri, "modulePath")) - .collect(Collectors.toList()); - } - - var moduleCacheDir = getNullablePath(pSettings, "moduleCacheDir", projectBaseUri); - var rootDir = getNullablePath(pSettings, "rootDir", projectBaseUri); - return new EvaluatorSettings( - externalProperties, - env, - allowedModules, - allowedResources, - noCache, - moduleCacheDir, - modulePath, - timeout, - rootDir); - } - - @SuppressWarnings("unchecked") - private static Map asMap(Object t) { - assert t instanceof Map; - return (Map) t; - } - - @SuppressWarnings("unchecked") - private static List asPatternList(Object t) { - return ((List) t).stream().map(Pattern::compile).collect(Collectors.toList()); - } - private static Object getProperty(PObject settings, String propertyName) { return settings.getProperty(propertyName); } @@ -307,12 +266,6 @@ public final class Project { } } - private static @Nullable Path getNullablePath( - Composite object, String propertyName, URI projectBaseUri) { - return resolveNullablePath( - (String) getNullableProperty(object, propertyName), projectBaseUri, propertyName); - } - @SuppressWarnings("unchecked") private static Package parsePackage(PObject pObj) throws URISyntaxException { var name = (String) pObj.getProperty("name"); @@ -353,7 +306,7 @@ public final class Project { private Project( @Nullable Package pkg, DeclaredDependencies dependencies, - EvaluatorSettings evaluatorSettings, + PklEvaluatorSettings evaluatorSettings, URI projectFileUri, URI projectBaseUri, List tests, @@ -371,7 +324,13 @@ public final class Project { return pkg; } + /** Use {@link org.pkl.core.project.Project#getEvaluatorSettings()} instead. */ + @Deprecated(forRemoval = true) public EvaluatorSettings getSettings() { + return new EvaluatorSettings(evaluatorSettings); + } + + public PklEvaluatorSettings getEvaluatorSettings() { return evaluatorSettings; } @@ -430,17 +389,13 @@ public final class Project { return Path.of(projectBaseUri); } + @Deprecated(forRemoval = true) public static class EvaluatorSettings { + private final PklEvaluatorSettings delegate; - private final @Nullable Map externalProperties; - private final @Nullable Map env; - private final @Nullable List allowedModules; - private final @Nullable List allowedResources; - private final @Nullable Boolean noCache; - private final @Nullable Path moduleCacheDir; - private final @Nullable List modulePath; - private final @Nullable Duration timeout; - private final @Nullable Path rootDir; + public EvaluatorSettings(PklEvaluatorSettings delegate) { + this.delegate = delegate; + } public EvaluatorSettings( @Nullable Map externalProperties, @@ -452,81 +407,63 @@ public final class Project { @Nullable List modulePath, @Nullable Duration timeout, @Nullable Path rootDir) { - this.externalProperties = externalProperties; - this.env = env; - this.allowedModules = allowedModules; - this.allowedResources = allowedResources; - this.noCache = noCache; - this.moduleCacheDir = moduleCacheDir; - this.modulePath = modulePath; - this.timeout = timeout; - this.rootDir = rootDir; + this.delegate = + new PklEvaluatorSettings( + externalProperties, + env, + allowedModules, + allowedResources, + noCache, + moduleCacheDir, + modulePath, + timeout, + rootDir, + null); } + @Deprecated(forRemoval = true) public @Nullable Map getExternalProperties() { - return externalProperties; + return delegate.externalProperties(); } + @Deprecated(forRemoval = true) public @Nullable Map getEnv() { - return env; + return delegate.env(); } + @Deprecated(forRemoval = true) public @Nullable List getAllowedModules() { - return allowedModules; + return delegate.allowedModules(); } + @Deprecated(forRemoval = true) public @Nullable List getAllowedResources() { - return allowedResources; + return delegate.allowedResources(); } + @Deprecated(forRemoval = true) public @Nullable Boolean isNoCache() { - return noCache; + return delegate.noCache(); } + @Deprecated(forRemoval = true) public @Nullable List getModulePath() { - return modulePath; + return delegate.modulePath(); } + @Deprecated(forRemoval = true) public @Nullable Duration getTimeout() { - return timeout; + return delegate.timeout(); } + @Deprecated(forRemoval = true) public @Nullable Path getModuleCacheDir() { - return moduleCacheDir; + return delegate.moduleCacheDir(); } + @Deprecated(forRemoval = true) public @Nullable Path getRootDir() { - return rootDir; - } - - private boolean arePatternsEqual( - @Nullable List myPattern, @Nullable List thatPattern) { - if (myPattern == null) { - return thatPattern == null; - } - if (thatPattern == null) { - return false; - } - if (myPattern.size() != thatPattern.size()) { - return false; - } - for (var i = 0; i < myPattern.size(); i++) { - if (!myPattern.get(i).pattern().equals(thatPattern.get(i).pattern())) { - return false; - } - } - return true; - } - - private int hashPatterns(@Nullable List patterns) { - if (patterns == null) { - return 0; - } - var ret = 1; - for (var pattern : patterns) { - ret = 31 * ret + pattern.pattern().hashCode(); - } - return ret; + return delegate.rootDir(); } @Override @@ -534,52 +471,37 @@ public final class Project { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { - return false; - } - EvaluatorSettings that = (EvaluatorSettings) o; - return Objects.equals(externalProperties, that.externalProperties) - && Objects.equals(env, that.env) - && arePatternsEqual(allowedModules, that.allowedModules) - && arePatternsEqual(allowedResources, that.allowedResources) - && Objects.equals(noCache, that.noCache) - && Objects.equals(moduleCacheDir, that.moduleCacheDir) - && Objects.equals(modulePath, that.modulePath) - && Objects.equals(timeout, that.timeout) - && Objects.equals(rootDir, that.rootDir); + return o != null + && getClass() == o.getClass() + && Objects.equals(delegate, ((EvaluatorSettings) o).delegate); } @Override public int hashCode() { - var result = - Objects.hash( - externalProperties, env, noCache, moduleCacheDir, modulePath, timeout, rootDir); - result = 31 * result + hashPatterns(allowedModules); - result = 31 * result + hashPatterns(allowedResources); - return result; + return delegate.hashCode(); } @Override public String toString() { return "EvaluatorSettings{" + "externalProperties=" - + externalProperties + + delegate.externalProperties() + ", env=" - + env + + delegate.env() + ", allowedModules=" - + allowedModules + + delegate.allowedModules() + ", allowedResources=" - + allowedResources + + delegate.allowedResources() + ", noCache=" - + noCache + + delegate.noCache() + ", moduleCacheDir=" - + moduleCacheDir + + delegate.moduleCacheDir() + ", modulePath=" - + modulePath + + delegate.modulePath() + ", timeout=" - + timeout + + delegate.timeout() + ", rootDir=" - + rootDir + + delegate.rootDir() + '}'; } } diff --git a/pkl-core/src/main/java/org/pkl/core/settings/PklSettings.java b/pkl-core/src/main/java/org/pkl/core/settings/PklSettings.java index f718b53c..9e0b9a2b 100644 --- a/pkl-core/src/main/java/org/pkl/core/settings/PklSettings.java +++ b/pkl-core/src/main/java/org/pkl/core/settings/PklSettings.java @@ -20,9 +20,11 @@ import java.nio.file.Path; import java.util.List; import java.util.regex.Pattern; import org.pkl.core.*; +import org.pkl.core.evaluatorSettings.PklEvaluatorSettings; import org.pkl.core.module.ModuleKeyFactories; import org.pkl.core.resource.ResourceReaders; import org.pkl.core.runtime.VmEvalException; +import org.pkl.core.runtime.VmExceptionBuilder; import org.pkl.core.util.IoUtils; import org.pkl.core.util.Nullable; @@ -32,19 +34,13 @@ import org.pkl.core.util.Nullable; * {@code load} methods. */ // keep in sync with stdlib/settings.pkl -public final class PklSettings { +public record PklSettings(Editor editor, @Nullable PklEvaluatorSettings.Http http) { private static final List ALLOWED_MODULES = List.of(Pattern.compile("pkl:"), Pattern.compile("file:")); private static final List ALLOWED_RESOURCES = List.of(Pattern.compile("env:"), Pattern.compile("file:")); - private final Editor editor; - - public PklSettings(Editor editor) { - this.editor = editor; - } - /** * Loads the user settings file ({@literal ~/.pkl/settings.pkl}). If this file does not exist, * returns default settings defined by module {@literal pkl.settings}. @@ -56,7 +52,9 @@ public final class PklSettings { /** For testing only. */ static PklSettings loadFromPklHomeDir(Path pklHomeDir) throws VmEvalException { var path = pklHomeDir.resolve("settings.pkl"); - return Files.exists(path) ? load(ModuleSource.path(path)) : new PklSettings(Editor.SYSTEM); + return Files.exists(path) + ? load(ModuleSource.path(path)) + : new PklSettings(Editor.SYSTEM, null); } /** Loads a settings file from the given path. */ @@ -73,46 +71,34 @@ public final class PklSettings { .addEnvironmentVariables(System.getenv()) .build()) { var module = evaluator.evaluateOutputValueAs(moduleSource, PClassInfo.Settings); - return parseSettings(module); + return parseSettings(module, moduleSource); } } - private static PklSettings parseSettings(PObject module) throws VmEvalException { - // can't use object mapping in pkl-core, so map manually - var editor = (PObject) module.getProperty("editor"); - var urlScheme = (String) editor.getProperty("urlScheme"); - return new PklSettings(new Editor(urlScheme)); + private static PklSettings parseSettings(PObject module, ModuleSource location) + throws VmEvalException { + + if (!(module.getPropertyOrNull("editor") instanceof PObject pObject) + || !(pObject.getPropertyOrNull("urlScheme") instanceof String str)) { + throw new VmExceptionBuilder().evalError("invalidSettingsFile", location.getUri()).build(); + } + var editor = new Editor(str); + var httpSettings = PklEvaluatorSettings.Http.parse((Value) module.getProperty("http")); + return new PklSettings(editor, httpSettings); } - /** Returns the editor for viewing and editing Pkl files. */ + /** + * Returns the editor for viewing and editing Pkl files. + * + *

This method is deprecated, use {@link #editor()} instead. + */ + @Deprecated(forRemoval = true) public Editor getEditor() { return editor; } - @Override - public boolean equals(@Nullable Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - var that = (PklSettings) o; - - return editor.equals(that.editor); - } - - @Override - public int hashCode() { - return editor.hashCode(); - } - - @Override - public String toString() { - return "PklSettings{" + "editor=" + editor + '}'; - } - /** An editor for viewing and editing Pkl files. */ - public static final class Editor { - private final String urlScheme; - + public record Editor(String urlScheme) { /** The editor associated with {@code file:} URLs ending in {@code .pkl}. */ public static final Editor SYSTEM = new Editor("%{url}, line %{line}"); @@ -134,37 +120,15 @@ public final class PklSettings { /** The Visual Studio Code editor. */ public static final Editor VS_CODE = new Editor("vscode://file/%{path}:%{line}:%{column}"); - /** Constructs an editor. */ - public Editor(String urlScheme) { - this.urlScheme = urlScheme; - } - /** * Returns the URL scheme for opening files in this editor. The following placeholders are * supported: {@code %{url}}, {@code %{path}}, {@code %{line}}, {@code %{column}}. + * + *

This method is deprecated; use {@link #urlScheme()} instead. */ + @Deprecated(forRemoval = true) public String getUrlScheme() { return urlScheme; } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - var editor = (Editor) o; - - return urlScheme.equals(editor.urlScheme); - } - - @Override - public int hashCode() { - return urlScheme.hashCode(); - } - - @Override - public String toString() { - return "Editor{" + "urlScheme='" + urlScheme + '\'' + '}'; - } } } diff --git a/pkl-core/src/main/java/org/pkl/core/util/IoUtils.java b/pkl-core/src/main/java/org/pkl/core/util/IoUtils.java index 5153adf3..96ee80b7 100644 --- a/pkl-core/src/main/java/org/pkl/core/util/IoUtils.java +++ b/pkl-core/src/main/java/org/pkl/core/util/IoUtils.java @@ -540,6 +540,18 @@ public final class IoUtils { System.setProperty("org.pkl.testMode", "true"); } + public static void setSystemProxy(URI proxyAddress) { + // Set HTTP proxy settings to configure the certificate revocation checker, because + // there is no other way to configure it. (see https://bugs.openjdk.org/browse/JDK-8256409) + // + // This only influences the behavior of the revocation checker. + // Otherwise, proxying is handled by [ProxySelector]. + System.setProperty("http.proxyHost", proxyAddress.getHost()); + System.setProperty( + "http.proxyPort", + proxyAddress.getPort() == -1 ? "80" : String.valueOf(proxyAddress.getPort())); + } + public static @Nullable String parseTripleDotPath(URI importUri) throws URISyntaxException { var importScheme = importUri.getScheme(); if (importScheme != null) return null; diff --git a/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties b/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties index 562dd44d..98b7ea1b 100644 --- a/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties +++ b/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties @@ -1053,3 +1053,7 @@ Certificates can only be loaded from `jar:` or `file:` URLs, but got:\n\ cannotFindBuiltInCertificates=\ Cannot find Pkl's trusted CA certificates on the class path.\n\ To fix this problem, add dependendy `org.pkl:pkl-certs`. + +# suppress inspection "HttpUrlsUsage" +malformedProxyAddress=\ +Malformed proxy URI (expecting `http://[:]`): `{0}`. diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/cannotFindStdLibModule.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/cannotFindStdLibModule.err index 00e174c8..19a3834d 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/cannotFindStdLibModule.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/cannotFindStdLibModule.err @@ -10,6 +10,7 @@ pkl:base pkl:Benchmark pkl:DocPackageInfo pkl:DocsiteInfo +pkl:EvaluatorSettings pkl:json pkl:jsonnet pkl:math diff --git a/pkl-core/src/test/kotlin/org/pkl/core/http/NoProxyRuleTest.kt b/pkl-core/src/test/kotlin/org/pkl/core/http/NoProxyRuleTest.kt new file mode 100644 index 00000000..5c165a26 --- /dev/null +++ b/pkl-core/src/test/kotlin/org/pkl/core/http/NoProxyRuleTest.kt @@ -0,0 +1,146 @@ +package org.pkl.core.http + +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import java.net.URI + +@Suppress("HttpUrlsUsage") +class NoProxyRuleTest { + @Test + fun wildcard() { + val noProxyRule = NoProxyRule("*") + assertTrue(noProxyRule.matches(URI("https://foo.com"))) + assertTrue(noProxyRule.matches(URI("https://bar.com"))) + assertTrue(noProxyRule.matches(URI("https://foo:5000"))) + } + + @Test + fun `hostname matching`() { + val noProxyRule = NoProxyRule("foo.com") + assertTrue(noProxyRule.matches(URI("https://foo.com"))) + assertTrue(noProxyRule.matches(URI("http://foo.com"))) + assertTrue(noProxyRule.matches(URI("https://foo.com:5000"))) + assertTrue(noProxyRule.matches(URI("https://FOO.COM"))) + assertTrue(noProxyRule.matches(URI("https://bar.foo.com"))) + assertFalse(noProxyRule.matches(URI("https://bar.foo.com.bar"))) + assertFalse(noProxyRule.matches(URI("https://bar.foocom"))) + assertFalse(noProxyRule.matches(URI("https://fooo.com"))) + assertFalse(noProxyRule.matches(URI("https://ooofoo.com"))) + assertFalse(noProxyRule.matches(URI("pkl:foo.com"))) + } + + @Test + fun `hostname matching, leading dot`() { + val noProxyRule = NoProxyRule(".foo.com") + assertTrue(noProxyRule.matches(URI("https://foo.com"))) + assertTrue(noProxyRule.matches(URI("http://foo.com"))) + assertTrue(noProxyRule.matches(URI("https://foo.com:5000"))) + assertTrue(noProxyRule.matches(URI("https://FOO.COM"))) + assertTrue(noProxyRule.matches(URI("https://bar.foo.com"))) + assertFalse(noProxyRule.matches(URI("https://bar.foo.com.bar"))) + assertFalse(noProxyRule.matches(URI("https://bar.foocom"))) + assertFalse(noProxyRule.matches(URI("https://fooo.com"))) + assertFalse(noProxyRule.matches(URI("https://ooofoo.com"))) + assertFalse(noProxyRule.matches(URI("pkl:foo.com"))) + } + + @Test + fun `hostname matching, with port`() { + val noProxyRule = NoProxyRule("foo.com:5000") + assertTrue(noProxyRule.matches(URI("https://foo.com:5000"))) + assertFalse(noProxyRule.matches(URI("https://foo.com"))) + assertFalse(noProxyRule.matches(URI("https://foo.com:3000"))) + } + + @Test + fun `ipv4 address literal matching`() { + val noProxyRule = NoProxyRule("192.168.1.1") + assertTrue(noProxyRule.matches(URI("http://192.168.1.1:5000"))) + assertTrue(noProxyRule.matches(URI("http://192.168.1.1"))) + assertTrue(noProxyRule.matches(URI("https://192.168.1.1"))) + assertFalse(noProxyRule.matches(URI("https://192.168.1.0"))) + assertFalse(noProxyRule.matches(URI("https://192.168.1.0:5000"))) + } + + @Test + fun `ipv4 address literal matching, with port`() { + val noProxyRule = NoProxyRule("192.168.1.1:5000") + assertTrue(noProxyRule.matches(URI("http://192.168.1.1:5000"))) + assertTrue(noProxyRule.matches(URI("https://192.168.1.1:5000"))) + assertFalse(noProxyRule.matches(URI("http://192.168.1.1"))) + assertFalse(noProxyRule.matches(URI("https://192.168.1.1"))) + assertFalse(noProxyRule.matches(URI("http://192.168.1.1:3000"))) + assertFalse(noProxyRule.matches(URI("https://192.168.1.1:3000"))) + } + + @Test + fun `ipv6 address literal matching`() { + val noProxyRule = NoProxyRule("::1") + assertTrue(noProxyRule.matches(URI("http://[::1]"))) + assertTrue(noProxyRule.matches(URI("http://[::1]:5000"))) + assertTrue(noProxyRule.matches(URI("https://[::1]"))) + assertTrue(noProxyRule.matches(URI("https://[0000:0000:0000:0000:0000:0000:0000:0001]"))) + assertFalse(noProxyRule.matches(URI("https://[::2]"))) + assertFalse(noProxyRule.matches(URI("https://[::2]:5000"))) + } + + @Test + fun `ipv6 address literal matching, with port`() { + val noProxyRule = NoProxyRule("[::1]:5000") + assertTrue(noProxyRule.matches(URI("http://[::1]:5000"))) + assertTrue(noProxyRule.matches(URI("https://[0000:0000:0000:0000:0000:0000:0000:0001]:5000"))) + assertFalse(noProxyRule.matches(URI("http://[::1]"))) + assertFalse(noProxyRule.matches(URI("https://[::1]"))) + assertFalse(noProxyRule.matches(URI("https://[::2]"))) + assertFalse(noProxyRule.matches(URI("https://[::2]:5000"))) + } + + @Test + fun `ipv4 port from protocol`() { + val noProxyRuleHttp = NoProxyRule("192.168.1.1:80") + assertTrue(noProxyRuleHttp.matches(URI("http://192.168.1.1"))) + assertTrue(noProxyRuleHttp.matches(URI("http://192.168.1.1:80"))) + assertTrue(noProxyRuleHttp.matches(URI("https://192.168.1.1:80"))) + assertFalse(noProxyRuleHttp.matches(URI("https://192.168.1.1"))) + assertFalse(noProxyRuleHttp.matches(URI("https://192.168.1.1:5000"))) + + val noProxyRuleHttps = NoProxyRule("192.168.1.1:443") + assertTrue(noProxyRuleHttps.matches(URI("https://192.168.1.1"))) + assertTrue(noProxyRuleHttps.matches(URI("http://192.168.1.1:443"))) + assertFalse(noProxyRuleHttps.matches(URI("http://192.168.1.1"))) + assertFalse(noProxyRuleHttps.matches(URI("https://192.168.1.1:80"))) + } + + @Test + fun `ipv4 cidr block matching`() { + val noProxyRule1 = NoProxyRule("10.0.0.0/16") + assertTrue(noProxyRule1.matches(URI("https://10.0.0.0"))) + assertTrue(noProxyRule1.matches(URI("https://10.0.255.255"))) + assertTrue(noProxyRule1.matches(URI("https://10.0.255.255:5000"))) + assertFalse(noProxyRule1.matches(URI("https://10.1.0.0"))) + assertFalse(noProxyRule1.matches(URI("https://11.0.0.0"))) + assertFalse(noProxyRule1.matches(URI("https://9.255.255.255"))) + assertFalse(noProxyRule1.matches(URI("https://9.255.255.255:5000"))) + + val noProxyRule2 = NoProxyRule("10.0.0.0/32") + assertTrue(noProxyRule2.matches(URI("https://10.0.0.0"))) + assertTrue(noProxyRule2.matches(URI("https://10.0.0.0:5000"))) + assertFalse(noProxyRule2.matches(URI("https://10.0.0.1"))) + assertFalse(noProxyRule2.matches(URI("https://9.255.255.55:5000"))) + } + + @Test + fun `ipv6 cidr block matching`() { + val noProxyRule1 = NoProxyRule("1000::ff/32") + assertTrue(noProxyRule1.matches(URI("https://[1000::]"))) + assertTrue(noProxyRule1.matches(URI("https://[1000:0:ffff:ffff:ffff:ffff:ffff:ffff]"))) + assertFalse(noProxyRule1.matches(URI("https://[999::]"))) + assertFalse(noProxyRule1.matches(URI("https://[1000:1::]"))) + + val noProxyRule2 = NoProxyRule("1000::ff/128") + assertTrue(noProxyRule2.matches(URI("https://[1000::ff]"))) + assertFalse(noProxyRule2.matches(URI("https://[999::]"))) + assertFalse(noProxyRule2.matches(URI("https://[1001::]"))) + } +} diff --git a/pkl-core/src/test/kotlin/org/pkl/core/http/RequestCapturingClient.kt b/pkl-core/src/test/kotlin/org/pkl/core/http/RequestCapturingClient.kt index e03854d6..a5aee9cb 100644 --- a/pkl-core/src/test/kotlin/org/pkl/core/http/RequestCapturingClient.kt +++ b/pkl-core/src/test/kotlin/org/pkl/core/http/RequestCapturingClient.kt @@ -6,7 +6,7 @@ import java.net.http.HttpResponse class RequestCapturingClient : HttpClient { lateinit var request: HttpRequest - + override fun send( request: HttpRequest, responseBodyHandler: HttpResponse.BodyHandler diff --git a/pkl-core/src/test/kotlin/org/pkl/core/project/ProjectTest.kt b/pkl-core/src/test/kotlin/org/pkl/core/project/ProjectTest.kt index da8c8521..258a3a01 100644 --- a/pkl-core/src/test/kotlin/org/pkl/core/project/ProjectTest.kt +++ b/pkl-core/src/test/kotlin/org/pkl/core/project/ProjectTest.kt @@ -10,7 +10,7 @@ import org.pkl.commons.writeString import org.pkl.core.* import org.pkl.core.http.HttpClient import org.pkl.core.packages.PackageUri -import org.pkl.core.project.Project.EvaluatorSettings +import org.pkl.core.evaluatorSettings.PklEvaluatorSettings import java.net.URI import java.nio.file.Path import java.util.regex.Pattern @@ -40,7 +40,7 @@ class ProjectTest { listOf(Path.of("apiTest1.pkl"), Path.of("apiTest2.pkl")), listOf("PklProject", "PklProject.deps.json", ".**", "*.exe") ) - val expectedSettings = EvaluatorSettings( + val expectedSettings = PklEvaluatorSettings( mapOf("two" to "2"), mapOf("one" to "1"), listOf("foo:", "bar:").map(Pattern::compile), @@ -52,7 +52,8 @@ class ProjectTest { path.resolve("modulepath2/") ), Duration.ofMinutes(5.0), - path + path, + null ) projectPath.writeString(""" amends "pkl:Project" @@ -116,7 +117,7 @@ class ProjectTest { """.trimIndent()) val project = Project.loadFromPath(projectPath) assertThat(project.`package`).isEqualTo(expectedPackage) - assertThat(project.settings).isEqualTo(expectedSettings) + assertThat(project.evaluatorSettings).isEqualTo(expectedSettings) assertThat(project.tests).isEqualTo(listOf(path.resolve("test1.pkl"), path.resolve("test2.pkl"))) } diff --git a/pkl-core/src/test/kotlin/org/pkl/core/settings/PklSettingsTest.kt b/pkl-core/src/test/kotlin/org/pkl/core/settings/PklSettingsTest.kt index b388e539..fc6e9f31 100644 --- a/pkl-core/src/test/kotlin/org/pkl/core/settings/PklSettingsTest.kt +++ b/pkl-core/src/test/kotlin/org/pkl/core/settings/PklSettingsTest.kt @@ -4,13 +4,18 @@ 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.assertThrows import org.junit.jupiter.api.io.TempDir import org.pkl.commons.createParentDirectories import org.pkl.commons.writeString import org.pkl.core.Evaluator import org.pkl.core.ModuleSource import org.pkl.core.PObject +import org.pkl.core.StackFrameTransformers +import org.pkl.core.evaluatorSettings.PklEvaluatorSettings +import org.pkl.core.runtime.VmException import org.pkl.core.settings.PklSettings.Editor +import java.net.URI class PklSettingsTest { @Test @@ -25,7 +30,61 @@ class PklSettingsTest { ) val settings = PklSettings.loadFromPklHomeDir(tempDir) - assertThat(settings).isEqualTo(PklSettings(Editor.SUBLIME)) + assertThat(settings).isEqualTo(PklSettings(Editor.SUBLIME, null)) + } + + @Test + fun `load user settings with http`(@TempDir tempDir: Path) { + val settingsPath = tempDir.resolve("settings.pkl") + settingsPath.createParentDirectories() + settingsPath.writeString( + """ + amends "pkl:settings" + http { + proxy { + address = "http://localhost:8080" + noProxy { + "example.com" + "pkg.pkl-lang.org" + } + } + } + """.trimIndent() + ) + + val settings = PklSettings.loadFromPklHomeDir(tempDir) + val expectedHttp = PklEvaluatorSettings.Http( + PklEvaluatorSettings.Proxy( + URI("http://localhost:8080"), + listOf("example.com", "pkg.pkl-lang.org") + ) + ) + assertThat(settings).isEqualTo(PklSettings(Editor.SYSTEM, expectedHttp)) + } + + @Test + fun `load user settings with http, but no noProxy`(@TempDir tempDir: Path) { + val settingsPath = tempDir.resolve("settings.pkl") + settingsPath.createParentDirectories() + settingsPath.writeString( + """ + amends "pkl:settings" + http { + proxy { + address = "http://localhost:8080" + } + } + """.trimIndent() + ) + + val settings = PklSettings.loadFromPklHomeDir(tempDir) + val expectedHttp = PklEvaluatorSettings.Http( + PklEvaluatorSettings.Proxy( + URI("http://localhost:8080"), + listOf(), + ) + ) + assertThat(settings).isEqualTo(PklSettings(Editor.SYSTEM, expectedHttp)) } @Test @@ -39,7 +98,7 @@ class PklSettingsTest { ) val settings = PklSettings.load(ModuleSource.path(settingsPath)) - assertThat(settings).isEqualTo(PklSettings(Editor.IDEA)) + assertThat(settings).isEqualTo(PklSettings(Editor.IDEA, null)) } @Test @@ -76,6 +135,6 @@ class PklSettingsTest { } private fun checkEquals(expected: Editor, actual: PObject) { - assertThat(actual.getProperty("urlScheme") as String).isEqualTo(expected.urlScheme) + assertThat(actual.getProperty("urlScheme") as String).isEqualTo(expected.urlScheme()) } } diff --git a/pkl-doc/gradle.lockfile b/pkl-doc/gradle.lockfile index 513eff55..2e644be8 100644 --- a/pkl-doc/gradle.lockfile +++ b/pkl-doc/gradle.lockfile @@ -11,14 +11,14 @@ com.google.guava:guava:32.1.1-jre=testCompileClasspath,testImplementationDepende com.google.j2objc:j2objc-annotations:2.8=testCompileClasspath,testImplementationDependenciesMetadata com.google.jimfs:jimfs:1.3.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.ibm.icu:icu4j:58.2=validator -com.ibm.icu:icu4j:71.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +com.ibm.icu:icu4j:72.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath com.shapesecurity:salvation:2.7.2=validator com.tunnelvisionlabs:antlr4-runtime:4.9.0=default,runtimeClasspath,testRuntimeClasspath commons-codec:commons-codec:1.10=validator commons-io:commons-io:2.4=validator commons-logging:commons-logging:1.2=validator isorelax:isorelax:20030108=validator -net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath net.sf.saxon:Saxon-HE:9.6.0-4=validator nu.validator:cssvalidator:1.0.8=validator @@ -32,7 +32,7 @@ org.apache.httpcomponents:httpcore:4.4=validator org.apache.logging.log4j:log4j-1.2-api:2.17.1=validator org.apache.logging.log4j:log4j-api:2.17.1=validator org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata -org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.checkerframework:checker-qual:3.33.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.commonmark:commonmark-ext-gfm-tables:0.21.0=default org.commonmark:commonmark-ext-gfm-tables:0.22.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath diff --git a/pkl-executor/gradle.lockfile b/pkl-executor/gradle.lockfile index f07f8e93..1d68115d 100644 --- a/pkl-executor/gradle.lockfile +++ b/pkl-executor/gradle.lockfile @@ -2,10 +2,10 @@ # Manual edits can break the build and are not advised. # This file is expected to be part of source control. com.tunnelvisionlabs:antlr4-runtime:4.9.0=testRuntimeClasspath -net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata -org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.graalvm.sdk:graal-sdk:23.0.2=testRuntimeClasspath org.graalvm.truffle:truffle-api:23.0.2=testRuntimeClasspath org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath diff --git a/pkl-executor/src/test/kotlin/org/pkl/executor/EmbeddedExecutorTest.kt b/pkl-executor/src/test/kotlin/org/pkl/executor/EmbeddedExecutorTest.kt index 54338fa2..3932a0f8 100644 --- a/pkl-executor/src/test/kotlin/org/pkl/executor/EmbeddedExecutorTest.kt +++ b/pkl-executor/src/test/kotlin/org/pkl/executor/EmbeddedExecutorTest.kt @@ -41,7 +41,7 @@ class EmbeddedExecutorTest { } override fun toString(): String = name } - + companion object { @JvmStatic private val allTestExecutors: List by lazy { diff --git a/pkl-gradle/gradle.lockfile b/pkl-gradle/gradle.lockfile index 7ca45f9d..e53f409d 100644 --- a/pkl-gradle/gradle.lockfile +++ b/pkl-gradle/gradle.lockfile @@ -3,10 +3,10 @@ # This file is expected to be part of source control. com.github.ajalt.clikt:clikt-jvm:3.5.1=compileClasspath com.github.ajalt.clikt:clikt:3.5.1=compileClasspath,compileOnlyDependenciesMetadata -net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata -org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath diff --git a/pkl-gradle/src/main/java/org/pkl/gradle/PklPlugin.java b/pkl-gradle/src/main/java/org/pkl/gradle/PklPlugin.java index a3ec76fa..e4d50fcd 100644 --- a/pkl-gradle/src/main/java/org/pkl/gradle/PklPlugin.java +++ b/pkl-gradle/src/main/java/org/pkl/gradle/PklPlugin.java @@ -281,6 +281,8 @@ public class PklPlugin implements Plugin { spec.getNoCache().convention(false); spec.getTestPort().convention(-1); + + spec.getNoProxy().convention(List.of()); } private void configureCodeGenSpec(CodeGenSpec spec) { @@ -424,6 +426,8 @@ public class PklPlugin implements Plugin { task.getModuleCacheDir().set(spec.getModuleCacheDir()); task.getEvalTimeout().set(spec.getEvalTimeout()); task.getTestPort().set(spec.getTestPort()); + task.getProxyAddress().set(spec.getProxyAddress()); + task.getNoProxy().set(spec.getNoProxy()); } private void configureModulesTask(T task, S spec) { diff --git a/pkl-gradle/src/main/java/org/pkl/gradle/spec/BasePklSpec.java b/pkl-gradle/src/main/java/org/pkl/gradle/spec/BasePklSpec.java index eda8403c..fab2aa1a 100644 --- a/pkl-gradle/src/main/java/org/pkl/gradle/spec/BasePklSpec.java +++ b/pkl-gradle/src/main/java/org/pkl/gradle/spec/BasePklSpec.java @@ -15,6 +15,7 @@ */ package org.pkl.gradle.spec; +import java.net.URI; import java.time.Duration; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.DirectoryProperty; @@ -50,4 +51,8 @@ public interface BasePklSpec { Property getEvalTimeout(); Property getTestPort(); + + Property getProxyAddress(); + + ListProperty getNoProxy(); } diff --git a/pkl-gradle/src/main/java/org/pkl/gradle/task/BasePklTask.java b/pkl-gradle/src/main/java/org/pkl/gradle/task/BasePklTask.java index b47608b5..9894000c 100644 --- a/pkl-gradle/src/main/java/org/pkl/gradle/task/BasePklTask.java +++ b/pkl-gradle/src/main/java/org/pkl/gradle/task/BasePklTask.java @@ -125,6 +125,14 @@ public abstract class BasePklTask extends DefaultTask { @Optional public abstract Property getTestPort(); + @Input + @Optional + public abstract Property getProxyAddress(); + + @Input + @Optional + public abstract ListProperty getNoProxy(); + @TaskAction public void runTask() { doRunTask(); @@ -156,7 +164,9 @@ public abstract class BasePklTask extends DefaultTask { false, false, getTestPort().getOrElse(-1), - Collections.emptyList()); + Collections.emptyList(), + getProxyAddress().getOrNull(), + getNoProxy().getOrElse(List.of())); } return cachedOptions; } diff --git a/pkl-gradle/src/main/java/org/pkl/gradle/task/ModulesTask.java b/pkl-gradle/src/main/java/org/pkl/gradle/task/ModulesTask.java index 8717a280..97421cfc 100644 --- a/pkl-gradle/src/main/java/org/pkl/gradle/task/ModulesTask.java +++ b/pkl-gradle/src/main/java/org/pkl/gradle/task/ModulesTask.java @@ -178,7 +178,9 @@ public abstract class ModulesTask extends BasePklTask { getNoProject().getOrElse(false), false, getTestPort().getOrElse(-1), - Collections.emptyList()); + Collections.emptyList(), + null, + List.of()); } return cachedOptions; } diff --git a/pkl-server/gradle.lockfile b/pkl-server/gradle.lockfile index 21a97dfd..b3a77fa7 100644 --- a/pkl-server/gradle.lockfile +++ b/pkl-server/gradle.lockfile @@ -2,10 +2,10 @@ # Manual edits can break the build and are not advised. # This file is expected to be part of source control. com.tunnelvisionlabs:antlr4-runtime:4.9.0=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata -org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.graalvm.sdk:graal-sdk:23.0.2=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.graalvm.truffle:truffle-api:23.0.2=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath diff --git a/pkl-server/src/main/kotlin/org/pkl/server/Message.kt b/pkl-server/src/main/kotlin/org/pkl/server/Message.kt index 6203b304..530000bb 100644 --- a/pkl-server/src/main/kotlin/org/pkl/server/Message.kt +++ b/pkl-server/src/main/kotlin/org/pkl/server/Message.kt @@ -20,6 +20,7 @@ import java.nio.file.Path import java.time.Duration import java.util.* import java.util.regex.Pattern +import org.pkl.core.evaluatorSettings.PklEvaluatorSettings.* import org.pkl.core.module.PathElement import org.pkl.core.packages.Checksums @@ -123,6 +124,7 @@ data class CreateEvaluatorRequest( val cacheDir: Path?, val outputFormat: String?, val project: Project?, + val http: Http?, ) : ClientRequestMessage() { override val type = MessageType.CREATE_EVALUATOR_REQUEST diff --git a/pkl-server/src/main/kotlin/org/pkl/server/MessagePackDecoder.kt b/pkl-server/src/main/kotlin/org/pkl/server/MessagePackDecoder.kt index 34d84f26..3cee911c 100644 --- a/pkl-server/src/main/kotlin/org/pkl/server/MessagePackDecoder.kt +++ b/pkl-server/src/main/kotlin/org/pkl/server/MessagePackDecoder.kt @@ -23,6 +23,7 @@ import org.msgpack.core.MessageTypeException import org.msgpack.core.MessageUnpacker import org.msgpack.value.Value import org.msgpack.value.impl.ImmutableStringValueImpl +import org.pkl.core.evaluatorSettings.PklEvaluatorSettings import org.pkl.core.module.PathElement import org.pkl.core.packages.Checksums @@ -50,8 +51,8 @@ internal class MessagePackDecoder(private val unpacker: MessageUnpacker) : Messa allowedModules = map.unpackStringListOrNull("allowedModules")?.map(Pattern::compile), allowedResources = map.unpackStringListOrNull("allowedResources")?.map(Pattern::compile), - clientModuleReaders = map.unpackModuleReaderSpec("clientModuleReaders"), - clientResourceReaders = map.unpackResourceReaderSpec("clientResourceReaders"), + clientModuleReaders = map.unpackModuleReaderSpec(), + clientResourceReaders = map.unpackResourceReaderSpec(), modulePaths = map.unpackStringListOrNull("modulePaths")?.map(Path::of), env = map.unpackStringMapOrNull("env"), properties = map.unpackStringMapOrNull("properties"), @@ -59,7 +60,8 @@ internal class MessagePackDecoder(private val unpacker: MessageUnpacker) : Messa rootDir = map.unpackStringOrNull("rootDir")?.let(Path::of), cacheDir = map.unpackStringOrNull("cacheDir")?.let(Path::of), outputFormat = map.unpackStringOrNull("outputFormat"), - project = map.unpackProject("project") + project = map.unpackProject(), + http = map.unpackHttp(), ) } MessageType.CREATE_EVALUATOR_RESPONSE.code -> { @@ -221,8 +223,8 @@ internal class MessagePackDecoder(private val unpacker: MessageUnpacker) : Messa PathElement(map.unpackString("name"), map.unpackBoolean("isDirectory")) } - private fun Map.unpackModuleReaderSpec(name: String): List? { - val keys = getNullable(name) ?: return null + private fun Map.unpackModuleReaderSpec(): List? { + val keys = getNullable("clientModuleReaders") ?: return null return keys.asArrayValue().toList().map { value -> val readerMap = value.asMapValue().map() ModuleReaderSpec( @@ -234,8 +236,8 @@ internal class MessagePackDecoder(private val unpacker: MessageUnpacker) : Messa } } - private fun Map.unpackResourceReaderSpec(name: String): List { - val keys = getNullable(name) ?: return emptyList() + private fun Map.unpackResourceReaderSpec(): List { + val keys = getNullable("clientResourceReaders") ?: return emptyList() return keys.asArrayValue().toList().map { value -> val readerMap = value.asMapValue().map() ResourceReaderSpec( @@ -246,13 +248,26 @@ internal class MessagePackDecoder(private val unpacker: MessageUnpacker) : Messa } } - private fun Map.unpackProject(name: String): Project? { - val projMap = getNullable(name)?.asMapValue()?.map() ?: return null + private fun Map.unpackProject(): Project? { + val projMap = getNullable("project")?.asMapValue()?.map() ?: return null val projectFileUri = URI(projMap.unpackString("projectFileUri")) val dependencies = projMap.unpackDependencies("dependencies") return Project(projectFileUri, null, dependencies) } + private fun Map.unpackHttp(): PklEvaluatorSettings.Http? { + val httpMap = getNullable("http")?.asMapValue()?.map() ?: return null + val proxy = httpMap.unpackProxy() + return PklEvaluatorSettings.Http(proxy) + } + + private fun Map.unpackProxy(): PklEvaluatorSettings.Proxy? { + val proxyMap = getNullable("proxy")?.asMapValue()?.map() ?: return null + val address = proxyMap.unpackString("address") + val noProxy = proxyMap.unpackStringListOrNull("noProxy") + return PklEvaluatorSettings.Proxy.create(address, noProxy) + } + private fun Map.unpackDependencies(name: String): Map { val mapValue = get(name).asMapValue().map() return mapValue.entries.associate { (key, value) -> diff --git a/pkl-server/src/main/kotlin/org/pkl/server/Server.kt b/pkl-server/src/main/kotlin/org/pkl/server/Server.kt index f703d199..3b078130 100644 --- a/pkl-server/src/main/kotlin/org/pkl/server/Server.kt +++ b/pkl-server/src/main/kotlin/org/pkl/server/Server.kt @@ -29,9 +29,9 @@ import org.pkl.core.packages.PackageUri import org.pkl.core.project.DeclaredDependencies import org.pkl.core.resource.ResourceReader import org.pkl.core.resource.ResourceReaders +import org.pkl.core.util.IoUtils -class Server(private val transport: MessageTransport, private val httpClient: HttpClient) : - AutoCloseable { +class Server(private val transport: MessageTransport) : AutoCloseable { private val evaluators: MutableMap = ConcurrentHashMap() // https://github.com/jano7/executor would be the perfect executor here @@ -162,6 +162,14 @@ class Server(private val transport: MessageTransport, private val httpClient: Ht val properties = message.properties ?: emptyMap() val timeout = message.timeout val cacheDir = message.cacheDir + val http = + with(HttpClient.builder()) { + message.http?.proxy?.let { proxy -> + setProxy(proxy.address, message.http.proxy?.noProxy ?: listOf()) + proxy.address?.let(IoUtils::setSystemProxy) + } + buildLazily() + } val dependencies = message.project?.let { proj -> buildDeclaredDependencies(proj.projectFileUri, proj.dependencies, null) @@ -175,7 +183,7 @@ class Server(private val transport: MessageTransport, private val httpClient: Ht SecurityManagers.defaultTrustLevels, rootDir ), - httpClient, + http, ClientLogger(evaluatorId, transport), createModuleKeyFactories(message, evaluatorId, resolver), createResourceReaders(message, evaluatorId, resolver), diff --git a/pkl-server/src/test/kotlin/org/pkl/server/AbstractServerTest.kt b/pkl-server/src/test/kotlin/org/pkl/server/AbstractServerTest.kt index 3fc0b6b4..f1246199 100644 --- a/pkl-server/src/test/kotlin/org/pkl/server/AbstractServerTest.kt +++ b/pkl-server/src/test/kotlin/org/pkl/server/AbstractServerTest.kt @@ -1029,7 +1029,8 @@ abstract class AbstractServerTest { rootDir = null, cacheDir = cacheDir, outputFormat = null, - project = project + project = project, + http = null, ) send(message) diff --git a/pkl-server/src/test/kotlin/org/pkl/server/JvmServerTest.kt b/pkl-server/src/test/kotlin/org/pkl/server/JvmServerTest.kt index 2702f603..cfdc8b32 100644 --- a/pkl-server/src/test/kotlin/org/pkl/server/JvmServerTest.kt +++ b/pkl-server/src/test/kotlin/org/pkl/server/JvmServerTest.kt @@ -19,7 +19,6 @@ import java.io.PipedInputStream import java.io.PipedOutputStream import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach -import org.pkl.core.http.HttpClient class JvmServerTest : AbstractServerTest() { private val transports: Pair = run { @@ -35,7 +34,7 @@ class JvmServerTest : AbstractServerTest() { } override val client: TestTransport = TestTransport(transports.first) - private val server: Server = Server(transports.second, HttpClient.dummyClient()) + private val server: Server = Server(transports.second) @BeforeEach fun beforeEach() { diff --git a/pkl-server/src/test/kotlin/org/pkl/server/MessagePackCodecTest.kt b/pkl-server/src/test/kotlin/org/pkl/server/MessagePackCodecTest.kt index a49f6112..1e511bd9 100644 --- a/pkl-server/src/test/kotlin/org/pkl/server/MessagePackCodecTest.kt +++ b/pkl-server/src/test/kotlin/org/pkl/server/MessagePackCodecTest.kt @@ -112,7 +112,8 @@ class MessagePackCodecTest { "baz" to RemoteDependency(URI("package://localhost:0/baz@1.1.0"), Checksums("abc123")) ) - ) + ), + http = null, ) ) } diff --git a/pkl-tools/gradle.lockfile b/pkl-tools/gradle.lockfile index 68cf077a..e7aa3758 100644 --- a/pkl-tools/gradle.lockfile +++ b/pkl-tools/gradle.lockfile @@ -28,14 +28,10 @@ org.jetbrains.kotlinx:kotlinx-serialization-core:1.5.1=default,runtimeClasspath, org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.5.1=default,runtimeClasspath,testRuntimeClasspath org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1=default,runtimeClasspath,testRuntimeClasspath org.jetbrains:annotations:13.0=compileClasspath,default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.jline:jline-native:3.23.0=default -org.jline:jline-native:3.23.0=runtimeClasspath,testRuntimeClasspath -org.jline:jline-reader:3.23.0=default -org.jline:jline-reader:3.23.0=runtimeClasspath,testRuntimeClasspath -org.jline:jline-terminal-jansi:3.23.0=default -org.jline:jline-terminal-jansi:3.23.0=runtimeClasspath,testRuntimeClasspath -org.jline:jline-terminal:3.23.0=default -org.jline:jline-terminal:3.23.0=runtimeClasspath,testRuntimeClasspath +org.jline:jline-native:3.23.0=default,runtimeClasspath,testRuntimeClasspath +org.jline:jline-reader:3.23.0=default,runtimeClasspath,testRuntimeClasspath +org.jline:jline-terminal-jansi:3.23.0=default,runtimeClasspath,testRuntimeClasspath +org.jline:jline-terminal:3.23.0=default,runtimeClasspath,testRuntimeClasspath org.msgpack:msgpack-core:0.9.0=default,runtimeClasspath,testRuntimeClasspath org.organicdesign:Paguro:3.10.3=default,runtimeClasspath,testRuntimeClasspath org.snakeyaml:snakeyaml-engine:2.5=default,runtimeClasspath,testRuntimeClasspath diff --git a/stdlib/EvaluatorSettings.pkl b/stdlib/EvaluatorSettings.pkl new file mode 100644 index 00000000..8a941e08 --- /dev/null +++ b/stdlib/EvaluatorSettings.pkl @@ -0,0 +1,140 @@ +//===----------------------------------------------------------------------===// +// 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. +//===----------------------------------------------------------------------===// + +/// Common settings for Pkl's own evaluator. +@ModuleInfo { minPklVersion = "0.26.0" } +@Since { version = "0.26.0" } +module pkl.EvaluatorSettings + +/// The external properties available to Pkl, read using the `prop:` scheme. +externalProperties: Mapping? + +/// The environment variables available to Pkl, read using the `env:` scheme. +/// +/// Example: +/// ``` +/// env { +/// ["IS_PROD"] = "true" +/// } +/// ``` +env: Mapping? + +/// The set of module URI patterns that can be imported. +/// +/// Each element is a regular expression pattern that is tested against a module import. +/// +/// Modules are imported either through an amends or extends clause, an import clause, or an +/// import expression. +/// +/// Example: +/// ``` +/// allowedModules { +/// "https:" +/// "file:" +/// "package:" +/// "projectpackage:" +/// } +/// ``` +allowedModules: Listing? + +/// The set of resource URI patterns that can be imported. +/// +/// Each element is a regular expression pattern that is tested against a resource read. +/// +/// Example: +/// ``` +/// allowedResources { +/// "https:" +/// "file:" +/// "package:" +/// "projectpackage:" +/// "env:" +/// "prop:" +/// } +/// ``` +allowedResources: Listing? + +/// Disables the file system cache for `package:` modules. +/// +/// When caching is disabled, packages are loaded over the network and stored in memory. +noCache: Boolean? + +/// A collection of jars, zips, or directories to be placed into the module path. +/// +/// Module path modules and resources may be read and imported using the `modulepath:` scheme. +modulePath: Listing? + +/// 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. +timeout: Duration? + +/// The directory where `package:` modules are cached. +moduleCacheDir: String? + +/// Restricts access to file-based modules and resources to those located under this directory. +rootDir: String? + +/// Configuration of outgoing HTTP requests. +http: Http? + +/// Settings that control how Pkl talks to HTTP(S) servers. +class Http { + /// Configuration of the HTTP proxy to use. + /// + /// If [null], uses the operating system's proxy configuration. + proxy: Proxy? +} + +/// 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) +} diff --git a/stdlib/Project.pkl b/stdlib/Project.pkl index 7b12509d..fe4571a0 100644 --- a/stdlib/Project.pkl +++ b/stdlib/Project.pkl @@ -67,6 +67,7 @@ @ModuleInfo { minPklVersion = "0.26.0" } module pkl.Project +import "pkl:EvaluatorSettings" as EvaluatorSettingsModule import "pkl:Project" import "pkl:reflect" import "pkl:semver" @@ -195,7 +196,9 @@ local isFileBasedProject = projectFileUri.startsWith("file:") /// - [modulePath][EvaluatorSettings.modulePath] /// - [rootDir][EvaluatorSettings.rootDir] /// - [moduleCacheDir][EvaluatorSettings.moduleCacheDir] -evaluatorSettings: EvaluatorSettings( +/// +/// For each of these, relative paths are resolved against the project's enclosing directory. +evaluatorSettings: EvaluatorSettingsModule( (modulePath != null).implies(isFileBasedProject), (rootDir != null).implies(isFileBasedProject), (moduleCacheDir != null).implies(isFileBasedProject) @@ -403,82 +406,8 @@ class Package { fixed uri: PackageUri = "\(baseUri)@\(version)" } -class EvaluatorSettings { - /// The external properties available to Pkl, read using the `prop:` scheme. - externalProperties: Mapping? - - /// The environment variables available to Pkl, read using the `env:` scheme. - /// - /// Example: - /// ``` - /// env { - /// ["IS_PROD"] = "true" - /// } - /// ``` - env: Mapping? - - /// The set of module URI patterns that can be imported. - /// - /// Each element is a regular expression pattern that is tested against a module import. - /// - /// Modules are imported either through an amends or extends clause, an import clause, or an - /// import expression. - /// - /// Example: - /// ``` - /// allowedModules { - /// "https:" - /// "file:" - /// "package:" - /// "projectpackage:" - /// } - /// ``` - allowedModules: Listing? - - /// The set of resource URI patterns that can be imported. - /// - /// Each element is a regular expression pattern that is tested against a resource read. - /// - /// Example: - /// ``` - /// allowedResources { - /// "https:" - /// "file:" - /// "package:" - /// "projectpackage:" - /// "env:" - /// "prop:" - /// } - /// ``` - allowedResources: Listing? - - /// Disables the file system cache for `package:` modules. - /// - /// When caching is disabled, packages are loaded over the network and stored in memory. - noCache: Boolean? - - /// A collection of jars, zips, or directories to be placed into the module path. - /// - /// Module path modules and resources may be read and imported using the `modulepath:` scheme. - /// - /// Relative paths are resolved against PklProject's enclosing directory. - modulePath: Listing? - - /// 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. - timeout: Duration? - - /// The directory where `package:` modules are cached. - /// - /// Relative paths are resolved against PklProject's enclosing directory. - moduleCacheDir: String? - - /// Restricts access to file-based modules and resources to those located under this directory. - /// - /// Relative paths are resolved against PklProject's enclosing directory. - rootDir: String? -} +@Deprecated { since = "0.26.0"; replaceWith = "EvaluatorSettingsModule" } +typealias EvaluatorSettings = EvaluatorSettingsModule /// Common software licenses in the [SPDX License List](https://spdx.org/licenses/). typealias CommonSpdxLicenseIdentifier = diff --git a/stdlib/settings.pkl b/stdlib/settings.pkl index 44d3d8b0..f87d6240 100644 --- a/stdlib/settings.pkl +++ b/stdlib/settings.pkl @@ -22,9 +22,15 @@ @ModuleInfo { minPklVersion = "0.26.0" } module pkl.settings +import "pkl:EvaluatorSettings" + /// The editor for viewing and editing Pkl files. editor: Editor = System +/// Settings for controlling how Pkl makes HTTP(S) requests. +@Since { version = "0.26.0" } +http: EvaluatorSettings.Http? + /// The editor associated with `file:` URLs ending in `.pkl`. hidden System: Editor = new { urlScheme = "%{url}, line %{line}"