Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Projects loaded from arbitrary URIs #21

Merged
merged 2 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions DEVELOPMENT.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
:uri-pkl-repo: https://github.com/apple/pkl

= Pkl Go Development Guide

== Debugging the Pkl Server

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@HT154 Does this work generally for debugging Pkl? I was just finding myself yesterday a bit annoyed at not being able to put breakpoints in Pkl sources.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For debugging "regular" pkl eval invocations, you don't need to fuss with this, you can do it straight from intellij by finding the entrypoint (pkl-cli/src/main/kotlin/org/pkl/cli/Main.kt) and starting to run/debug from the main function. You can also modify the run configuration in that menu to pass CLI args, env vars, etc. as needed.

The approach documented here does work for general debugging as well, but it's required for debugging pkl server because the host process (eg. via pkl-go) currently must spawn the pkl process.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's very cool, thanks Josh!


The pkl-go evaluator API runs `pkl server` as a subprocess, which presents obstacles for directly debugging the server process.
It is possible to

*Debug Stub Setup*

. Create a file named `debugpkl`
. Mark the file executable with `chmod +x debugpkl`
. Populate the file with this content (note: the path to the executable will depend on where the Pkl repo is cloned):

[,shell]
----
#!/bin/sh

exec java -agentlib:jdwp=transport=dt_socket,server=n,address=localhost:5005,suspend=y -jar /path/to/pkl/pkl-cli/build/executable/jpkl "$@"
----


*IntelliJ IDEA Setup:*

. Open the {uri-pkl-repo}[pkl] project
. Build `jpkl` by running `./gradlew javaExecutable` so it is available to the script defined above
. Run > Edit Configurations...
. Add a new "Remote JVM Debug" configuration
. Provide a name, eg. `debugpkl`
. Under "Configuration", select the "Listen to remote JVM" debugger mode
. Enable "Auto restart"
. Ensure Host is "localhost" and Port is "5005"

*Usage*

. Configure pkl-go to use `debugpkl` as the server executable: `export PKL_EXEC=./debugpkl`
. Optionally, turn on extra debug output: `export PKL_DEBUG=1`
. Define breakpoints as desired in the Pkl codebase using IntelliJ
. In IntelliJ, start debugging the "Remote JVM Debug" configuration defined above
. Execute the process using pkl-go

When the Pkl server execution reaches a defined breakpoint, it will pause and activate the debugger in IntelliJ.
35 changes: 20 additions & 15 deletions pkl/evaluator_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
package pkl

import (
"fmt"
"io/fs"
"os"
"path"
Expand Down Expand Up @@ -56,10 +55,16 @@ type EvaluatorOptions struct {
// - `"yaml"`
OutputFormat string

// AllowedModules is the URI patterns that determine which modules can be loaded and evaluated.
// AllowedModules defines URI patterns that determine which modules are permitted to be loaded and evaluated.
// Patterns are regular expressions in the dialect understood by [java.util.regex.Pattern].
//
// [java.util.regex.Pattern]: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/regex/Pattern.html
AllowedModules []string

// AllowedResources is the URI patterns that determine which resources can be loaded and evaluated.
// AllowedResources defines URI patterns that determine which resources are permitted to be loaded and evaluated.
// Patterns are regular expressions in the dialect understood by [java.util.regex.Pattern].
//
// [java.util.regex.Pattern]: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/regex/Pattern.html
AllowedResources []string

// ResourceReaders are the resource readers to be used by the evaluator.
Expand All @@ -78,7 +83,7 @@ type EvaluatorOptions struct {
// Attempting to read past the root directory is an error.
RootDir string

// ProjectDir is the project directory for the evaluator.
// ProjectFileURI is the project directory for the evaluator.
//
// Setting this determines how Pkl resolves dependency notation imports.
// It causes Pkl to look for the resolved dependencies relative to this directory,
Expand All @@ -97,13 +102,13 @@ type EvaluatorOptions struct {
//
// To emulate the CLI's `--project-dir` flag, create an evaluator with NewProjectEvaluator,
// or EvaluatorManager.NewProjectEvaluator.
ProjectDir string
ProjectBaseURI string

// DeclaredProjectDepenedencies is set of dependencies available to modules within ProjectDir.
// DeclaredProjectDepenedencies is set of dependencies available to modules within ProjectBaseURI.
//
// When importing dependencies, a PklProject.deps.json file must exist within ProjectDir
// When importing dependencies, a PklProject.deps.json file must exist within ProjectBaseURI
// that contains the project's resolved dependencies.
DeclaredProjectDepenedencies *ProjectDependencies
DeclaredProjectDependencies *ProjectDependencies
}

type ProjectRemoteDependency struct {
Expand Down Expand Up @@ -201,12 +206,12 @@ func (e *EvaluatorOptions) toMessage() *msgapi.CreateEvaluator {
}

func (e *EvaluatorOptions) project() *msgapi.ProjectOrDependency {
if e.ProjectDir == "" {
if e.ProjectBaseURI == "" {
return nil
}
return &msgapi.ProjectOrDependency{
ProjectFileUri: fmt.Sprintf("file://%s/PklProject", e.ProjectDir),
Dependencies: e.DeclaredProjectDepenedencies.toMessage(),
ProjectFileUri: e.ProjectBaseURI + "/PklProject",
Dependencies: e.DeclaredProjectDependencies.toMessage(),
}
}

Expand Down Expand Up @@ -257,7 +262,7 @@ var WithDefaultCacheDir = func(opts *EvaluatorOptions) {
var WithResourceReader = func(reader ResourceReader) func(opts *EvaluatorOptions) {
return func(opts *EvaluatorOptions) {
opts.ResourceReaders = append(opts.ResourceReaders, reader)
opts.AllowedResources = append(opts.AllowedResources, reader.Scheme())
opts.AllowedResources = append(opts.AllowedResources, reader.Scheme()+":")
}
}

Expand All @@ -266,7 +271,7 @@ var WithResourceReader = func(reader ResourceReader) func(opts *EvaluatorOptions
var WithModuleReader = func(reader ModuleReader) func(opts *EvaluatorOptions) {
return func(opts *EvaluatorOptions) {
opts.ModuleReaders = append(opts.ModuleReaders, reader)
opts.AllowedModules = append(opts.AllowedModules, reader.Scheme())
opts.AllowedModules = append(opts.AllowedModules, reader.Scheme()+":")
}
}

Expand Down Expand Up @@ -320,8 +325,8 @@ var WithProjectEvaluatorSettings = func(project *Project) func(opts *EvaluatorOp
// WithProjectDependencies configures the evaluator with dependencies from the specified project.
var WithProjectDependencies = func(project *Project) func(opts *EvaluatorOptions) {
return func(opts *EvaluatorOptions) {
opts.ProjectDir = strings.TrimPrefix(strings.TrimSuffix(project.ProjectFileUri, "/PklProject"), "file://")
opts.DeclaredProjectDepenedencies = project.Dependencies()
opts.ProjectBaseURI = strings.TrimSuffix(project.ProjectFileUri, "/PklProject")
opts.DeclaredProjectDependencies = project.Dependencies()
}
}

Expand Down
1 change: 1 addition & 0 deletions pkl/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
// Reader is the base implementation shared by a ResourceReader and a ModuleReader.
type Reader interface {
// Scheme returns the scheme part of the URL that this reader can read.
// The value should be the URI scheme up to (not including) ":"
Scheme() string

// IsGlobbable tells if this reader supports globbing via Pkl's `import*` and `glob*` keywords
Expand Down