Improve handling of evaling dependency notation URIs (#1595)

This commit is contained in:
Daniel Chao
2026-05-15 15:51:09 -07:00
committed by GitHub
parent 3ad1cb3645
commit a7a64acbac
17 changed files with 123 additions and 156 deletions
@@ -190,7 +190,7 @@ data class CliBaseOptions(
sourceModules
.map { uri ->
if (uri.isAbsolute) uri
else if (uri.path.startsWith("@") && !noProject && normalizedProjectFile != null) uri
else if (uri.path.startsWith("@")) uri
else IoUtils.resolve(normalizedWorkingDir.toUri(), uri)
}
// sort modules to make cli output independent of source module order
@@ -86,36 +86,6 @@ abstract class CliCommand(protected val cliOptions: CliBaseOptions) {
}
}
protected fun resolveModuleUri(uri: URI): URI =
if (uri.isAbsolute) uri
else { // must be @dep/mod.pkl notation!!
if (!uri.path.startsWith('@'))
throw CliBugException(
RuntimeException("tried to resolve project URI `$uri` with no @ prefix")
)
if (project == null)
throw CliBugException(
RuntimeException("tried to resolve project URI `$uri` with no project present")
)
val dep = uri.path.substringBefore('/').drop(1)
val path = uri.path.dropWhile { it != '/' }
if (path.isEmpty()) throw CliException("Invalid project dependency URI `$uri`.")
val remoteDep =
project!!.dependencies.remoteDependencies()[dep]
?: if (project!!.dependencies.localDependencies().containsKey(dep))
throw CliException(
"Only remote project dependencies may be referenced using @-notation. Dependency `@$dep` is a local dependency."
)
else throw CliException("Project does not contain dependency `@$dep`.")
remoteDep.packageUri.toPackageAssetUri(path).uri
}
protected val resolvedSourceModules: List<URI> by lazy {
if (project == null) cliOptions.normalizedSourceModules
else cliOptions.normalizedSourceModules.map(::resolveModuleUri)
}
protected fun loadProject(projectFile: Path): Project {
val securityManager =
SecurityManagers.standard(
@@ -0,0 +1,5 @@
amends "pkl:Project"
dependencies {
["fruit"] { uri = "package://localhost:0/fruit@1.1.0" }
}
@@ -0,0 +1,12 @@
{
"schemaVersion": 1,
"resolvedDependencies": {
"package://localhost:0/fruit@1": {
"type": "remote",
"uri": "projectpackage://localhost:0/fruit@1.1.0",
"checksums": {
"sha256": "$skipChecksumVerification"
}
}
}
}
@@ -17,13 +17,10 @@ package org.pkl.commons.cli
import com.github.ajalt.clikt.core.parse
import com.github.ajalt.clikt.parameters.groups.provideDelegate
import java.net.URI
import java.nio.file.Path
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.writeText
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.condition.DisabledOnJre
import org.junit.jupiter.api.condition.JRE
import org.junit.jupiter.api.io.TempDir
@@ -49,7 +46,6 @@ class CliCommandTest {
class CliTest(options: CliBaseOptions) : CliCommand(options) {
override fun doRun() = Unit
val myResolvedSourceModules = resolvedSourceModules
val myAllowedModules = allowedModules
val myAllowedResources = allowedResources
val myRootDir = rootDir
@@ -98,114 +94,6 @@ class CliCommandTest {
)
}
@Test
fun `@-notation package URIs - treated as relative paths when no project present`(
@TempDir tempDir: Path
) {
cmd.parse(arrayOf("--working-dir=$tempDir"))
val opts = cmd.baseOptions.baseOptions(listOf(URI("@foo/bar.pkl")), testMode = true)
val cliTest = CliTest(opts)
assertThat(cliTest.myResolvedSourceModules)
.isEqualTo(listOf(tempDir.toUri().resolve("@foo/bar.pkl")))
}
@Test
fun `@-notation package URIs - empty paths are rejected`(@TempDir tempDir: Path) {
tempDir
.resolve("PklProject")
.writeText(
"""
amends "pkl:Project"
"""
.trimIndent()
)
cmd.parse(arrayOf("--working-dir=$tempDir"))
val opts = cmd.baseOptions.baseOptions(listOf(URI("@no.slash")), testMode = true)
val exc = assertThrows<CliException> { CliTest(opts) }
assertThat(exc.message).isEqualTo("Invalid project dependency URI `@no.slash`.")
}
@Test
fun `@-notation package URIs - missing dependencies are rejected`(@TempDir tempDir: Path) {
tempDir
.resolve("PklProject")
.writeText(
"""
amends "pkl:Project"
"""
.trimIndent()
)
cmd.parse(arrayOf("--working-dir=$tempDir"))
val opts = cmd.baseOptions.baseOptions(listOf(URI("@foo/bar.pkl")), testMode = true)
val exc = assertThrows<CliException> { CliTest(opts) }
assertThat(exc.message).isEqualTo("Project does not contain dependency `@foo`.")
}
@Test
fun `@-notation package URIs - local dependencies are rejected`(
@TempDir tempDir: Path,
@TempDir depDir: Path,
) {
depDir
.resolve("PklProject")
.writeText(
"""
amends "pkl:Project"
package {
name = "foo"
baseUri = "package://example.com/foo"
version = "0.0.1"
packageZipUrl = "https://example.com/foo@\(version).zip"
}
"""
.trimIndent()
)
tempDir
.resolve("PklProject")
.writeText(
"""
amends "pkl:Project"
dependencies {
["foo"] = import("${depDir.toUri().resolve("PklProject")}")
}
"""
.trimIndent()
)
cmd.parse(arrayOf("--working-dir=$tempDir"))
val opts = cmd.baseOptions.baseOptions(listOf(URI("@foo/bar.pkl")), testMode = true)
val exc = assertThrows<CliException> { CliTest(opts) }
assertThat(exc.message)
.isEqualTo(
"Only remote project dependencies may be referenced using @-notation. Dependency `@foo` is a local dependency."
)
}
@Test
fun `@-notation package URIs - remote dependencies are resolved`(@TempDir tempDir: Path) {
tempDir
.resolve("PklProject")
.writeText(
"""
amends "pkl:Project"
dependencies {
["foo"] {
uri = "package://example.com/foo@1.2.3"
}
}
"""
.trimIndent()
)
cmd.parse(arrayOf("--working-dir=$tempDir"))
val opts = cmd.baseOptions.baseOptions(listOf(URI("@foo/bar.pkl")), testMode = true)
val cliTest = CliTest(opts)
assertThat(cliTest.myResolvedSourceModules)
.isEqualTo(listOf(tempDir.toUri().resolve("package://example.com/foo@1.2.3#/bar.pkl")))
}
val projectWithAllEvaluatorSettings =
"""
amends "pkl:Project"