Add analyze imports libs (SPICE-0001) (#695)

This adds a new feature to build a dependency graph of Pkl programs, following the SPICE outlined in https://github.com/apple/pkl-evolution/pull/2.

It adds:
* CLI command `pkl analyze imports`
* Java API `org.pkl.core.Analyzer`
* Pkl stdlib module `pkl:analyze`
* pkl-gradle extension `analyze`

In addition, it also changes the Gradle plugin such that `transitiveModules` is by default computed from the import graph.
This commit is contained in:
Daniel Chao
2024-10-23 14:36:57 -07:00
committed by GitHub
parent eb3891b21f
commit ce25cb8ef0
53 changed files with 2054 additions and 53 deletions
@@ -0,0 +1 @@
import "b.pkl"
@@ -0,0 +1 @@
import "cyclicalB.pkl"
@@ -0,0 +1 @@
import "cyclicalA.pkl"
@@ -0,0 +1 @@
import* "[ab].pkl"
@@ -0,0 +1,33 @@
amends "../snippetTest.pkl"
import "pkl:analyze"
import "pkl:reflect"
import ".../input-helper/analyze/a.pkl"
import ".../input-helper/analyze/cyclicalA.pkl"
import ".../input-helper/analyze/globImport.pkl"
examples {
["basic"] {
analyze.importGraph(Set(reflect.Module(a).uri))
}
["cycles"] {
analyze.importGraph(Set(reflect.Module(cyclicalA).uri))
}
["globs"] {
analyze.importGraph(Set(reflect.Module(globImport).uri))
}
["packages"] {
analyze.importGraph(Set("package://localhost:0/birds@0.5.0#/Bird.pkl"))
}
}
output {
renderer {
// mimick result of `pkl analyze imports` CLI command
converters {
[Map] = (it) -> it.toMapping()
[Set] = (it) -> it.toListing()
}
}
}
@@ -0,0 +1,3 @@
import "pkl:analyze"
result = analyze.importGraph(Set("http://localhost:0/foo.pkl"))
@@ -0,0 +1,3 @@
import "pkl:analyze"
result = analyze.importGraph(Set("foo <>"))
@@ -0,0 +1,3 @@
import "pkl:analyze"
result = analyze.importGraph(Set("foo.pkl"))
@@ -0,0 +1,79 @@
examples {
["basic"] {
new {
imports {
["file:///$snippetsDir/input-helper/analyze/a.pkl"] {
new {
uri = "file:///$snippetsDir/input-helper/analyze/b.pkl"
}
}
["file:///$snippetsDir/input-helper/analyze/b.pkl"] {}
}
resolvedImports {
["file:///$snippetsDir/input-helper/analyze/a.pkl"] = "file:///$snippetsDir/input-helper/analyze/a.pkl"
["file:///$snippetsDir/input-helper/analyze/b.pkl"] = "file:///$snippetsDir/input-helper/analyze/b.pkl"
}
}
}
["cycles"] {
new {
imports {
["file:///$snippetsDir/input-helper/analyze/cyclicalA.pkl"] {
new {
uri = "file:///$snippetsDir/input-helper/analyze/cyclicalB.pkl"
}
}
["file:///$snippetsDir/input-helper/analyze/cyclicalB.pkl"] {
new {
uri = "file:///$snippetsDir/input-helper/analyze/cyclicalA.pkl"
}
}
}
resolvedImports {
["file:///$snippetsDir/input-helper/analyze/cyclicalA.pkl"] = "file:///$snippetsDir/input-helper/analyze/cyclicalA.pkl"
["file:///$snippetsDir/input-helper/analyze/cyclicalB.pkl"] = "file:///$snippetsDir/input-helper/analyze/cyclicalB.pkl"
}
}
}
["globs"] {
new {
imports {
["file:///$snippetsDir/input-helper/analyze/a.pkl"] {
new {
uri = "file:///$snippetsDir/input-helper/analyze/b.pkl"
}
}
["file:///$snippetsDir/input-helper/analyze/b.pkl"] {}
["file:///$snippetsDir/input-helper/analyze/globImport.pkl"] {
new {
uri = "file:///$snippetsDir/input-helper/analyze/a.pkl"
}
new {
uri = "file:///$snippetsDir/input-helper/analyze/b.pkl"
}
}
}
resolvedImports {
["file:///$snippetsDir/input-helper/analyze/a.pkl"] = "file:///$snippetsDir/input-helper/analyze/a.pkl"
["file:///$snippetsDir/input-helper/analyze/b.pkl"] = "file:///$snippetsDir/input-helper/analyze/b.pkl"
["file:///$snippetsDir/input-helper/analyze/globImport.pkl"] = "file:///$snippetsDir/input-helper/analyze/globImport.pkl"
}
}
}
["packages"] {
new {
imports {
["package://localhost:0/birds@0.5.0#/Bird.pkl"] {
new {
uri = "package://localhost:0/fruit@1.0.5#/Fruit.pkl"
}
}
["package://localhost:0/fruit@1.0.5#/Fruit.pkl"] {}
}
resolvedImports {
["package://localhost:0/birds@0.5.0#/Bird.pkl"] = "package://localhost:0/birds@0.5.0#/Bird.pkl"
["package://localhost:0/fruit@1.0.5#/Fruit.pkl"] = "package://localhost:0/fruit@1.0.5#/Fruit.pkl"
}
}
}
}
@@ -0,0 +1,10 @@
–– Pkl Error ––
HTTP/1.1 header parser received no bytes
x | result = analyze.importGraph(Set("http://localhost:0/foo.pkl"))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at analyzeInvalidHttpModule#result (file:///$snippetsDir/input/errors/analyzeInvalidHttpModule.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
@@ -0,0 +1,12 @@
–– Pkl Error ––
Module URI `foo <>` has invalid syntax.
x | result = analyze.importGraph(Set("foo <>"))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at analyzeInvalidModuleUri#result (file:///$snippetsDir/input/errors/analyzeInvalidModuleUri.pkl)
Illegal character in path at index 3: foo <>
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
@@ -0,0 +1,10 @@
–– Pkl Error ––
Cannot analyze relative module URI `foo.pkl`.
x | result = analyze.importGraph(Set("foo.pkl"))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at analyzeRelativeModuleUri#result (file:///$snippetsDir/input/errors/analyzeRelativeModuleUri.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
@@ -6,6 +6,7 @@ x | import "pkl:nonExisting"
at cannotFindStdLibModule#nonExisting (file:///$snippetsDir/input/errors/cannotFindStdLibModule.pkl)
Available standard library modules:
pkl:analyze
pkl:base
pkl:Benchmark
pkl:DocPackageInfo