Allow referring to remote project dependencies on the CLI with @-notation (#1434)

This commit is contained in:
Jen Basch
2026-02-23 08:52:56 -08:00
committed by GitHub
parent 77395a86f4
commit 2ec0baad53
16 changed files with 212 additions and 33 deletions

View File

@@ -175,11 +175,19 @@ data class CliBaseOptions(
/** [rootDir] after normalization. */
val normalizedRootDir: Path? = rootDir?.let(normalizedWorkingDir::resolve)
/** The effective project directory, if exists. */
val normalizedProjectFile: Path? by lazy {
projectDir?.resolve(ProjectDependenciesManager.PKL_PROJECT_FILENAME)
?: normalizedWorkingDir.getProjectFile(rootDir)
}
/** [sourceModules] after normalization. */
val normalizedSourceModules: List<URI> =
sourceModules
.map { uri ->
if (uri.isAbsolute) uri else IoUtils.resolve(normalizedWorkingDir.toUri(), uri)
if (uri.isAbsolute) uri
else if (uri.path.startsWith("@") && !noProject && normalizedProjectFile != null) uri
else IoUtils.resolve(normalizedWorkingDir.toUri(), uri)
}
// sort modules to make cli output independent of source module order
.sorted()
@@ -195,12 +203,6 @@ data class CliBaseOptions(
/** [moduleCacheDir] after normalization. */
val normalizedModuleCacheDir: Path? = moduleCacheDir?.let(normalizedWorkingDir::resolve)
/** The effective project directory, if exists. */
val normalizedProjectFile: Path? by lazy {
projectDir?.resolve(ProjectDependenciesManager.PKL_PROJECT_FILENAME)
?: normalizedWorkingDir.getProjectFile(rootDir)
}
/** [caCertificates] after normalization. */
val normalizedCaCertificates: List<Path> = caCertificates.map(normalizedWorkingDir::resolve)
}

View File

@@ -86,6 +86,35 @@ 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> =
if (project == null) cliOptions.normalizedSourceModules
else cliOptions.normalizedSourceModules.map(::resolveModuleUri)
protected fun loadProject(projectFile: Path): Project {
val securityManager =
SecurityManagers.standard(

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,9 +17,14 @@ package org.pkl.commons.cli.commands
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.Context
import com.github.ajalt.clikt.core.context
import com.github.ajalt.clikt.parameters.groups.provideDelegate
abstract class BaseCommand(name: String, private val helpLink: String) : CliktCommand(name = name) {
init {
context { readArgumentFile = null }
}
abstract val helpString: String
override fun help(context: Context) = helpString

View File

@@ -0,0 +1,25 @@
/*
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.commons.cli.commands
import com.github.ajalt.clikt.core.NoOpCliktCommand
import com.github.ajalt.clikt.core.context
open class NoOpCommand(name: String? = null) : NoOpCliktCommand(name) {
init {
context { readArgumentFile = null }
}
}