mirror of
https://github.com/apple/pkl.git
synced 2026-04-17 05:59:46 +02:00
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:
120
pkl-core/src/main/java/org/pkl/core/Analyzer.java
Normal file
120
pkl-core/src/main/java/org/pkl/core/Analyzer.java
Normal file
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.pkl.core.http.HttpClient;
|
||||
import org.pkl.core.http.HttpClientInitException;
|
||||
import org.pkl.core.module.ModuleKeyFactory;
|
||||
import org.pkl.core.module.ProjectDependenciesManager;
|
||||
import org.pkl.core.packages.PackageLoadError;
|
||||
import org.pkl.core.packages.PackageResolver;
|
||||
import org.pkl.core.project.DeclaredDependencies;
|
||||
import org.pkl.core.runtime.ModuleResolver;
|
||||
import org.pkl.core.runtime.ResourceManager;
|
||||
import org.pkl.core.runtime.VmContext;
|
||||
import org.pkl.core.runtime.VmException;
|
||||
import org.pkl.core.runtime.VmImportAnalyzer;
|
||||
import org.pkl.core.runtime.VmUtils;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
/** Utility library for static analysis of Pkl programs. */
|
||||
public class Analyzer {
|
||||
private final StackFrameTransformer transformer;
|
||||
private final SecurityManager securityManager;
|
||||
private final @Nullable Path moduleCacheDir;
|
||||
private final @Nullable DeclaredDependencies projectDependencies;
|
||||
private final ModuleResolver moduleResolver;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
public Analyzer(
|
||||
StackFrameTransformer transformer,
|
||||
SecurityManager securityManager,
|
||||
Collection<ModuleKeyFactory> moduleKeyFactories,
|
||||
@Nullable Path moduleCacheDir,
|
||||
@Nullable DeclaredDependencies projectDependencies,
|
||||
HttpClient httpClient) {
|
||||
this.transformer = transformer;
|
||||
this.securityManager = securityManager;
|
||||
this.moduleCacheDir = moduleCacheDir;
|
||||
this.projectDependencies = projectDependencies;
|
||||
this.moduleResolver = new ModuleResolver(moduleKeyFactories);
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a graph of imports from the provided source modules.
|
||||
*
|
||||
* <p>For details, see {@link ImportGraph}.
|
||||
*/
|
||||
public ImportGraph importGraph(URI... sources) {
|
||||
var context = createContext();
|
||||
try {
|
||||
context.enter();
|
||||
var vmContext = VmContext.get(null);
|
||||
return VmImportAnalyzer.analyze(sources, vmContext);
|
||||
} catch (SecurityManagerException
|
||||
| IOException
|
||||
| URISyntaxException
|
||||
| PackageLoadError
|
||||
| HttpClientInitException e) {
|
||||
throw new PklException(e.getMessage(), e);
|
||||
} catch (PklException err) {
|
||||
throw err;
|
||||
} catch (VmException err) {
|
||||
throw err.toPklException(transformer);
|
||||
} catch (Exception e) {
|
||||
throw new PklBugException(e);
|
||||
} finally {
|
||||
context.leave();
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
private Context createContext() {
|
||||
var packageResolver =
|
||||
PackageResolver.getInstance(
|
||||
securityManager, HttpClient.builder().buildLazily(), moduleCacheDir);
|
||||
return VmUtils.createContext(
|
||||
() -> {
|
||||
VmContext vmContext = VmContext.get(null);
|
||||
vmContext.initialize(
|
||||
new VmContext.Holder(
|
||||
transformer,
|
||||
securityManager,
|
||||
httpClient,
|
||||
moduleResolver,
|
||||
new ResourceManager(securityManager, List.of()),
|
||||
Loggers.stdErr(),
|
||||
Map.of(),
|
||||
Map.of(),
|
||||
moduleCacheDir,
|
||||
null,
|
||||
packageResolver,
|
||||
projectDependencies == null
|
||||
? null
|
||||
: new ProjectDependenciesManager(
|
||||
projectDependencies, moduleResolver, securityManager)));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -433,6 +433,10 @@ public final class EvaluatorBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public @Nullable DeclaredDependencies getProjectDependencies() {
|
||||
return this.dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a project, sets its dependencies, and also applies any evaluator settings if set.
|
||||
*
|
||||
|
||||
111
pkl-core/src/main/java/org/pkl/core/ImportGraph.java
Normal file
111
pkl-core/src/main/java/org/pkl/core/ImportGraph.java
Normal file
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
import org.pkl.core.util.json.Json;
|
||||
import org.pkl.core.util.json.Json.FormatException;
|
||||
import org.pkl.core.util.json.Json.JsArray;
|
||||
import org.pkl.core.util.json.Json.JsObject;
|
||||
import org.pkl.core.util.json.Json.JsonParseException;
|
||||
import org.pkl.core.util.json.Json.MappingException;
|
||||
|
||||
/**
|
||||
* Java representation of {@code pkl.analyze#ImportGraph}.
|
||||
*
|
||||
* @param imports The graph of imports declared within the program.
|
||||
* <p>Each key is a module inside the program, and each value is the module URIs declared as
|
||||
* imports inside that module. The set of all dependent modules within a program is the set of
|
||||
* keys in this map.
|
||||
* @param resolvedImports A mapping of a module's in-language URI, and the URI that it resolves to.
|
||||
* <p>For example, a local package dependency is represented with scheme {@code
|
||||
* projectpackage:}, and (typically) resolves to a {@code file:} scheme.
|
||||
*/
|
||||
public record ImportGraph(Map<URI, Set<Import>> imports, Map<URI, URI> resolvedImports) {
|
||||
/**
|
||||
* Java representation of {@code pkl.analyze#Import}.
|
||||
*
|
||||
* @param uri The absolute URI of the import.
|
||||
*/
|
||||
public record Import(URI uri) implements Comparable<Import> {
|
||||
@Override
|
||||
public int compareTo(Import o) {
|
||||
return uri.compareTo(o.uri());
|
||||
}
|
||||
}
|
||||
|
||||
/** Parses the provided JSON into an import graph. */
|
||||
public static ImportGraph parseFromJson(String input) throws JsonParseException {
|
||||
var parsed = Json.parseObject(input);
|
||||
var imports = parseImports(parsed.getObject("imports"));
|
||||
var resolvedImports = parseResolvedImports(parsed.getObject("resolvedImports"));
|
||||
return new ImportGraph(imports, resolvedImports);
|
||||
}
|
||||
|
||||
private static Map<URI, Set<Import>> parseImports(Json.JsObject jsObject)
|
||||
throws JsonParseException {
|
||||
var ret = new TreeMap<URI, Set<Import>>();
|
||||
for (var entry : jsObject.entrySet()) {
|
||||
try {
|
||||
var key = new URI(entry.getKey());
|
||||
var value = entry.getValue();
|
||||
var set = new TreeSet<Import>();
|
||||
if (!(value instanceof JsArray array)) {
|
||||
throw new FormatException("array", value.getClass());
|
||||
}
|
||||
for (var elem : array) {
|
||||
if (!(elem instanceof JsObject importObj)) {
|
||||
throw new FormatException("object", elem.getClass());
|
||||
}
|
||||
set.add(parseImport(importObj));
|
||||
}
|
||||
ret.put(key, set);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new MappingException(entry.getKey(), e);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static ImportGraph.Import parseImport(Json.JsObject jsObject) throws JsonParseException {
|
||||
var uri = jsObject.getURI("uri");
|
||||
return new Import(uri);
|
||||
}
|
||||
|
||||
private static Map<URI, URI> parseResolvedImports(Json.JsObject jsObject)
|
||||
throws JsonParseException {
|
||||
var ret = new TreeMap<URI, URI>();
|
||||
for (var entry : jsObject.entrySet()) {
|
||||
try {
|
||||
var key = new URI(entry.getKey());
|
||||
var value = entry.getValue();
|
||||
if (!(value instanceof String str)) {
|
||||
throw new FormatException("string", value.getClass());
|
||||
}
|
||||
var valueUri = new URI(str);
|
||||
ret.put(key, valueUri);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new MappingException(entry.getKey(), e);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -21,9 +21,12 @@ import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.pkl.core.ast.builder.ImportsAndReadsParser.Entry;
|
||||
import org.pkl.core.module.ModuleKey;
|
||||
import org.pkl.core.module.ResolvedModuleKey;
|
||||
import org.pkl.core.parser.LexParseException;
|
||||
import org.pkl.core.parser.Parser;
|
||||
import org.pkl.core.parser.antlr.PklLexer;
|
||||
import org.pkl.core.parser.antlr.PklParser.ImportClauseContext;
|
||||
import org.pkl.core.parser.antlr.PklParser.ImportExprContext;
|
||||
import org.pkl.core.parser.antlr.PklParser.ModuleExtendsOrAmendsClauseContext;
|
||||
@@ -31,8 +34,8 @@ import org.pkl.core.parser.antlr.PklParser.ReadExprContext;
|
||||
import org.pkl.core.parser.antlr.PklParser.SingleLineStringLiteralContext;
|
||||
import org.pkl.core.runtime.VmExceptionBuilder;
|
||||
import org.pkl.core.runtime.VmUtils;
|
||||
import org.pkl.core.util.IoUtils;
|
||||
import org.pkl.core.util.Nullable;
|
||||
import org.pkl.core.util.Pair;
|
||||
|
||||
/**
|
||||
* Collects module uris and resource uris imported within a module.
|
||||
@@ -46,17 +49,29 @@ import org.pkl.core.util.Pair;
|
||||
* <li>read expressions
|
||||
* </ul>
|
||||
*/
|
||||
public final class ImportsAndReadsParser
|
||||
extends AbstractAstBuilder<@Nullable List<Pair<String, SourceSection>>> {
|
||||
public class ImportsAndReadsParser extends AbstractAstBuilder<@Nullable List<Entry>> {
|
||||
|
||||
public record Entry(
|
||||
boolean isModule,
|
||||
boolean isGlob,
|
||||
boolean isExtends,
|
||||
boolean isAmends,
|
||||
String stringValue,
|
||||
SourceSection sourceSection) {}
|
||||
|
||||
/** Parses a module, and collects all imports and reads. */
|
||||
public static @Nullable List<Pair<String, SourceSection>> parse(
|
||||
public static @Nullable List<Entry> parse(
|
||||
ModuleKey moduleKey, ResolvedModuleKey resolvedModuleKey) throws IOException {
|
||||
var parser = new Parser();
|
||||
var text = resolvedModuleKey.loadSource();
|
||||
var source = VmUtils.createSource(moduleKey, text);
|
||||
var importListParser = new ImportsAndReadsParser(source);
|
||||
return parser.parseModule(text).accept(importListParser);
|
||||
try {
|
||||
return parser.parseModule(text).accept(importListParser);
|
||||
} catch (LexParseException e) {
|
||||
var moduleName = IoUtils.inferModuleName(moduleKey);
|
||||
throw VmUtils.toVmException(e, source, moduleName);
|
||||
}
|
||||
}
|
||||
|
||||
public ImportsAndReadsParser(Source source) {
|
||||
@@ -69,29 +84,35 @@ public final class ImportsAndReadsParser
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pair<String, SourceSection>> visitModuleExtendsOrAmendsClause(
|
||||
public @Nullable List<Entry> visitModuleExtendsOrAmendsClause(
|
||||
ModuleExtendsOrAmendsClauseContext ctx) {
|
||||
var importStr = doVisitSingleLineConstantStringPart(ctx.stringConstant().ts);
|
||||
var sourceSection = createSourceSection(ctx.stringConstant());
|
||||
return Collections.singletonList(Pair.of(importStr, sourceSection));
|
||||
return Collections.singletonList(
|
||||
new Entry(
|
||||
true, false, ctx.EXTENDS() != null, ctx.AMENDS() != null, importStr, sourceSection));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pair<String, SourceSection>> visitImportClause(ImportClauseContext ctx) {
|
||||
public List<Entry> visitImportClause(ImportClauseContext ctx) {
|
||||
var importStr = doVisitSingleLineConstantStringPart(ctx.stringConstant().ts);
|
||||
var sourceSection = createSourceSection(ctx.stringConstant());
|
||||
return Collections.singletonList(Pair.of(importStr, sourceSection));
|
||||
return Collections.singletonList(
|
||||
new Entry(
|
||||
true, ctx.t.getType() == PklLexer.IMPORT_GLOB, false, false, importStr, sourceSection));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pair<String, SourceSection>> visitImportExpr(ImportExprContext ctx) {
|
||||
public List<Entry> visitImportExpr(ImportExprContext ctx) {
|
||||
var importStr = doVisitSingleLineConstantStringPart(ctx.stringConstant().ts);
|
||||
var sourceSection = createSourceSection(ctx.stringConstant());
|
||||
return Collections.singletonList(Pair.of(importStr, sourceSection));
|
||||
return Collections.singletonList(
|
||||
new Entry(
|
||||
true, ctx.t.getType() == PklLexer.IMPORT_GLOB, false, false, importStr, sourceSection));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pair<String, SourceSection>> visitReadExpr(ReadExprContext ctx) {
|
||||
public List<Entry> visitReadExpr(ReadExprContext ctx) {
|
||||
var expr = ctx.expr();
|
||||
if (!(expr instanceof SingleLineStringLiteralContext slCtx)) {
|
||||
return Collections.emptyList();
|
||||
@@ -111,20 +132,26 @@ public final class ImportsAndReadsParser
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Collections.singletonList(Pair.of(importString, createSourceSection(slCtx)));
|
||||
return Collections.singletonList(
|
||||
new Entry(
|
||||
false,
|
||||
ctx.t.getType() == PklLexer.READ_GLOB,
|
||||
false,
|
||||
false,
|
||||
importString,
|
||||
createSourceSection(slCtx)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable List<Pair<String, SourceSection>> aggregateResult(
|
||||
@Nullable List<Pair<String, SourceSection>> aggregate,
|
||||
@Nullable List<Pair<String, SourceSection>> nextResult) {
|
||||
protected @Nullable List<Entry> aggregateResult(
|
||||
@Nullable List<Entry> aggregate, @Nullable List<Entry> nextResult) {
|
||||
if (aggregate == null || aggregate.isEmpty()) {
|
||||
return nextResult;
|
||||
}
|
||||
if (nextResult == null || nextResult.isEmpty()) {
|
||||
return aggregate;
|
||||
}
|
||||
var ret = new ArrayList<Pair<String, SourceSection>>(aggregate.size() + nextResult.size());
|
||||
var ret = new ArrayList<Entry>(aggregate.size() + nextResult.size());
|
||||
ret.addAll(aggregate);
|
||||
ret.addAll(nextResult);
|
||||
return ret;
|
||||
|
||||
@@ -199,6 +199,10 @@ public final class ProjectDependenciesManager {
|
||||
}
|
||||
}
|
||||
|
||||
public DeclaredDependencies getDeclaredDependencies() {
|
||||
return declaredDependencies;
|
||||
}
|
||||
|
||||
public Dependency getResolvedDependency(PackageUri packageUri) {
|
||||
var dep = getProjectDeps().get(CanonicalPackageUri.fromPackageUri(packageUri));
|
||||
if (dep == null) {
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package org.pkl.core.project;
|
||||
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
@@ -64,7 +63,6 @@ import org.pkl.core.util.GlobResolver;
|
||||
import org.pkl.core.util.GlobResolver.InvalidGlobPatternException;
|
||||
import org.pkl.core.util.IoUtils;
|
||||
import org.pkl.core.util.Nullable;
|
||||
import org.pkl.core.util.Pair;
|
||||
|
||||
/**
|
||||
* Given a list of project directories, prepares artifacts to be published as a package.
|
||||
@@ -397,8 +395,8 @@ public final class ProjectPackager {
|
||||
return;
|
||||
}
|
||||
for (var importContext : imports) {
|
||||
var importStr = importContext.first;
|
||||
var sourceSection = importContext.second;
|
||||
var importStr = importContext.stringValue();
|
||||
var sourceSection = importContext.sourceSection();
|
||||
if (isAbsoluteImport(importStr)) {
|
||||
continue;
|
||||
}
|
||||
@@ -440,7 +438,7 @@ public final class ProjectPackager {
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable List<Pair<String, SourceSection>> getImportsAndReads(Path pklModulePath) {
|
||||
private @Nullable List<ImportsAndReadsParser.Entry> getImportsAndReads(Path pklModulePath) {
|
||||
try {
|
||||
var moduleKey = ModuleKeys.file(pklModulePath.toUri());
|
||||
var resolvedModuleKey = ResolvedModuleKeys.file(moduleKey, moduleKey.getUri(), pklModulePath);
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 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.runtime;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import java.net.URI;
|
||||
|
||||
public class AnalyzeModule extends StdLibModule {
|
||||
private static final VmTyped instance = VmUtils.createEmptyModule();
|
||||
|
||||
static {
|
||||
loadModule(URI.create("pkl:analyze"), instance);
|
||||
}
|
||||
|
||||
public static VmTyped getModule() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static VmClass getImportGraphClass() {
|
||||
return AnalyzeModule.ImportGraphClass.instance;
|
||||
}
|
||||
|
||||
public static VmClass getImportClass() {
|
||||
return AnalyzeModule.ImportClass.instance;
|
||||
}
|
||||
|
||||
private static final class ImportGraphClass {
|
||||
static final VmClass instance = loadClass("ImportGraph");
|
||||
}
|
||||
|
||||
private static final class ImportClass {
|
||||
static final VmClass instance = loadClass("Import");
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
private static VmClass loadClass(String className) {
|
||||
var theModule = getModule();
|
||||
return (VmClass) VmUtils.readMember(theModule, Identifier.get(className));
|
||||
}
|
||||
}
|
||||
@@ -85,6 +85,8 @@ public final class ModuleCache {
|
||||
// some standard library modules are cached as static singletons
|
||||
// and hence aren't parsed/initialized anew for every evaluator
|
||||
switch (moduleName) {
|
||||
case "analyze":
|
||||
return AnalyzeModule.getModule();
|
||||
case "base":
|
||||
// always needed
|
||||
return BaseModule.getModule();
|
||||
|
||||
@@ -94,6 +94,7 @@ public abstract class VmException extends AbstractTruffleException {
|
||||
public enum Kind {
|
||||
EVAL_ERROR,
|
||||
UNDEFINED_VALUE,
|
||||
WRAPPED,
|
||||
BUG
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ public final class VmExceptionBuilder {
|
||||
|
||||
private @Nullable Object receiver;
|
||||
private @Nullable Map<CallTarget, StackFrame> insertedStackFrames;
|
||||
private VmException wrappedException;
|
||||
|
||||
public static class MultilineValue {
|
||||
private final Iterable<?> lines;
|
||||
@@ -332,6 +333,12 @@ public final class VmExceptionBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public VmExceptionBuilder wrapping(VmException nestedException) {
|
||||
this.wrappedException = nestedException;
|
||||
this.kind = VmException.Kind.WRAPPED;
|
||||
return this;
|
||||
}
|
||||
|
||||
public VmExceptionBuilder withInsertedStackFrames(
|
||||
Map<CallTarget, StackFrame> insertedStackFrames) {
|
||||
this.insertedStackFrames = insertedStackFrames;
|
||||
@@ -383,6 +390,19 @@ public final class VmExceptionBuilder {
|
||||
memberName,
|
||||
hint,
|
||||
effectiveInsertedStackFrames);
|
||||
case WRAPPED ->
|
||||
new VmWrappedEvalException(
|
||||
message,
|
||||
cause,
|
||||
isExternalMessage,
|
||||
messageArguments,
|
||||
programValues,
|
||||
location,
|
||||
sourceSection,
|
||||
memberName,
|
||||
hint,
|
||||
effectiveInsertedStackFrames,
|
||||
wrappedException);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.stream.Collectors;
|
||||
import org.pkl.core.Release;
|
||||
import org.pkl.core.util.ErrorMessages;
|
||||
import org.pkl.core.util.Nullable;
|
||||
@@ -46,7 +47,7 @@ public final class VmExceptionRenderer {
|
||||
if (exception instanceof VmBugException bugException) {
|
||||
renderBugException(bugException, builder);
|
||||
} else {
|
||||
renderException(exception, builder);
|
||||
renderException(exception, builder, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,13 +67,13 @@ public final class VmExceptionRenderer {
|
||||
.replaceAll("\\+", "%20"));
|
||||
|
||||
builder.append("\n\n");
|
||||
renderException(exception, builder);
|
||||
renderException(exception, builder, true);
|
||||
builder.append('\n').append(Release.current().versionInfo()).append("\n\n");
|
||||
|
||||
exceptionToReport.printStackTrace(new PrintWriter(new StringBuilderWriter(builder)));
|
||||
}
|
||||
|
||||
private void renderException(VmException exception, StringBuilder builder) {
|
||||
private void renderException(VmException exception, StringBuilder builder, boolean withHeader) {
|
||||
var header = "–– Pkl Error ––";
|
||||
|
||||
String message;
|
||||
@@ -94,7 +95,16 @@ public final class VmExceptionRenderer {
|
||||
message = exception.getMessage();
|
||||
}
|
||||
|
||||
builder.append(header).append('\n').append(message).append('\n');
|
||||
if (withHeader) {
|
||||
builder.append(header).append('\n');
|
||||
}
|
||||
builder.append(message).append('\n');
|
||||
|
||||
if (exception instanceof VmWrappedEvalException vmWrappedEvalException) {
|
||||
var sb = new StringBuilder();
|
||||
renderException(vmWrappedEvalException.getWrappedException(), sb, false);
|
||||
hint = sb.toString().lines().map((it) -> ">\t" + it).collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
// include cause's message unless it's the same as this exception's message
|
||||
if (exception.getCause() != null) {
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* 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.runtime;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
import org.pkl.core.ImportGraph;
|
||||
import org.pkl.core.ImportGraph.Import;
|
||||
import org.pkl.core.SecurityManager;
|
||||
import org.pkl.core.SecurityManagerException;
|
||||
import org.pkl.core.ast.builder.ImportsAndReadsParser;
|
||||
import org.pkl.core.ast.builder.ImportsAndReadsParser.Entry;
|
||||
import org.pkl.core.util.GlobResolver;
|
||||
import org.pkl.core.util.GlobResolver.InvalidGlobPatternException;
|
||||
import org.pkl.core.util.GlobResolver.ResolvedGlobElement;
|
||||
import org.pkl.core.util.IoUtils;
|
||||
|
||||
public class VmImportAnalyzer {
|
||||
@TruffleBoundary
|
||||
public static ImportGraph analyze(URI[] moduleUris, VmContext context)
|
||||
throws IOException, URISyntaxException, SecurityManagerException {
|
||||
var imports = new TreeMap<URI, Set<ImportGraph.Import>>();
|
||||
var resolvedImports = new TreeMap<URI, URI>();
|
||||
for (var moduleUri : moduleUris) {
|
||||
analyzeSingle(moduleUri, context, imports, resolvedImports);
|
||||
}
|
||||
return new ImportGraph(imports, resolvedImports);
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
private static void analyzeSingle(
|
||||
URI moduleUri,
|
||||
VmContext context,
|
||||
Map<URI, Set<ImportGraph.Import>> imports,
|
||||
Map<URI, URI> resolvedImports)
|
||||
throws IOException, URISyntaxException, SecurityManagerException {
|
||||
var moduleResolver = context.getModuleResolver();
|
||||
var securityManager = context.getSecurityManager();
|
||||
var importsInModule = collectImports(moduleUri, moduleResolver, securityManager);
|
||||
|
||||
imports.put(moduleUri, importsInModule);
|
||||
resolvedImports.put(
|
||||
moduleUri, moduleResolver.resolve(moduleUri).resolve(securityManager).getUri());
|
||||
for (var imprt : importsInModule) {
|
||||
if (imports.containsKey(imprt.uri())) {
|
||||
continue;
|
||||
}
|
||||
analyzeSingle(imprt.uri(), context, imports, resolvedImports);
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<ImportGraph.Import> collectImports(
|
||||
URI moduleUri, ModuleResolver moduleResolver, SecurityManager securityManager)
|
||||
throws IOException, URISyntaxException, SecurityManagerException {
|
||||
var moduleKey = moduleResolver.resolve(moduleUri);
|
||||
var resolvedModuleKey = moduleKey.resolve(securityManager);
|
||||
List<Entry> importsAndReads;
|
||||
try {
|
||||
importsAndReads = ImportsAndReadsParser.parse(moduleKey, resolvedModuleKey);
|
||||
} catch (VmException err) {
|
||||
throw new VmExceptionBuilder()
|
||||
.evalError("cannotAnalyzeBecauseSyntaxError", moduleKey.getUri())
|
||||
.wrapping(err)
|
||||
.build();
|
||||
}
|
||||
if (importsAndReads == null) {
|
||||
return Set.of();
|
||||
}
|
||||
var result = new TreeSet<ImportGraph.Import>();
|
||||
for (var entry : importsAndReads) {
|
||||
if (!entry.isModule()) {
|
||||
continue;
|
||||
}
|
||||
if (entry.isGlob()) {
|
||||
var theModuleKey =
|
||||
moduleResolver.resolve(moduleKey.resolveUri(IoUtils.toUri(entry.stringValue())));
|
||||
try {
|
||||
var elements =
|
||||
GlobResolver.resolveGlob(
|
||||
securityManager,
|
||||
theModuleKey,
|
||||
moduleKey,
|
||||
moduleKey.getUri(),
|
||||
entry.stringValue());
|
||||
var globImports =
|
||||
elements.values().stream()
|
||||
.map(ResolvedGlobElement::getUri)
|
||||
.map(ImportGraph.Import::new)
|
||||
.toList();
|
||||
result.addAll(globImports);
|
||||
} catch (InvalidGlobPatternException e) {
|
||||
throw new VmExceptionBuilder()
|
||||
.evalError("invalidGlobPattern", entry.stringValue())
|
||||
.withSourceSection(entry.sourceSection())
|
||||
.build();
|
||||
}
|
||||
} else {
|
||||
var resolvedUri =
|
||||
IoUtils.resolve(securityManager, moduleKey, IoUtils.toUri(entry.stringValue()));
|
||||
result.add(new Import(resolvedUri));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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.runtime;
|
||||
|
||||
import com.oracle.truffle.api.CallTarget;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.pkl.core.StackFrame;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
public class VmWrappedEvalException extends VmEvalException {
|
||||
|
||||
private final VmException wrappedException;
|
||||
|
||||
public VmWrappedEvalException(
|
||||
String message,
|
||||
@Nullable Throwable cause,
|
||||
boolean isExternalMessage,
|
||||
Object[] messageArguments,
|
||||
List<ProgramValue> programValues,
|
||||
@Nullable Node location,
|
||||
@Nullable SourceSection sourceSection,
|
||||
@Nullable String memberName,
|
||||
@Nullable String hint,
|
||||
Map<CallTarget, StackFrame> insertedStackFrames,
|
||||
VmException wrappedException) {
|
||||
super(
|
||||
message,
|
||||
cause,
|
||||
isExternalMessage,
|
||||
messageArguments,
|
||||
programValues,
|
||||
location,
|
||||
sourceSection,
|
||||
memberName,
|
||||
hint,
|
||||
insertedStackFrames);
|
||||
this.wrappedException = wrappedException;
|
||||
}
|
||||
|
||||
public VmException getWrappedException() {
|
||||
return wrappedException;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 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.stdlib.analyze;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import org.pkl.core.ImportGraph;
|
||||
import org.pkl.core.ImportGraph.Import;
|
||||
import org.pkl.core.SecurityManagerException;
|
||||
import org.pkl.core.packages.PackageLoadError;
|
||||
import org.pkl.core.runtime.AnalyzeModule;
|
||||
import org.pkl.core.runtime.VmContext;
|
||||
import org.pkl.core.runtime.VmImportAnalyzer;
|
||||
import org.pkl.core.runtime.VmMap;
|
||||
import org.pkl.core.runtime.VmSet;
|
||||
import org.pkl.core.runtime.VmTyped;
|
||||
import org.pkl.core.stdlib.ExternalMethod1Node;
|
||||
import org.pkl.core.stdlib.VmObjectFactory;
|
||||
|
||||
public final class AnalyzeNodes {
|
||||
private AnalyzeNodes() {}
|
||||
|
||||
private static VmObjectFactory<Import> importFactory =
|
||||
new VmObjectFactory<Import>(AnalyzeModule::getImportClass)
|
||||
.addStringProperty("uri", (it) -> it.uri().toString());
|
||||
|
||||
private static VmObjectFactory<ImportGraph> importGraphFactory =
|
||||
new VmObjectFactory<ImportGraph>(AnalyzeModule::getImportGraphClass)
|
||||
.addMapProperty(
|
||||
"imports",
|
||||
graph -> {
|
||||
var builder = VmMap.builder();
|
||||
for (var entry : graph.imports().entrySet()) {
|
||||
var vmSetBuilder = VmSet.EMPTY.builder();
|
||||
for (var imprt : entry.getValue()) {
|
||||
vmSetBuilder.add(importFactory.create(imprt));
|
||||
}
|
||||
builder.add(entry.getKey().toString(), vmSetBuilder.build());
|
||||
}
|
||||
return builder.build();
|
||||
})
|
||||
.addMapProperty(
|
||||
"resolvedImports",
|
||||
graph -> {
|
||||
var builder = VmMap.builder();
|
||||
for (var entry : graph.resolvedImports().entrySet()) {
|
||||
builder.add(entry.getKey().toString(), entry.getValue().toString());
|
||||
}
|
||||
return builder.build();
|
||||
});
|
||||
|
||||
public abstract static class importGraph extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected Object eval(@SuppressWarnings("unused") VmTyped self, VmSet moduleUris) {
|
||||
var uris = new URI[moduleUris.getLength()];
|
||||
var idx = 0;
|
||||
for (var moduleUri : moduleUris) {
|
||||
URI uri;
|
||||
try {
|
||||
uri = new URI((String) moduleUri);
|
||||
} catch (URISyntaxException e) {
|
||||
throw exceptionBuilder()
|
||||
.evalError("invalidModuleUri", moduleUri)
|
||||
.withHint(e.getMessage())
|
||||
.build();
|
||||
}
|
||||
if (!uri.isAbsolute()) {
|
||||
throw exceptionBuilder().evalError("cannotAnalyzeRelativeModuleUri", moduleUri).build();
|
||||
}
|
||||
uris[idx] = uri;
|
||||
idx++;
|
||||
}
|
||||
var context = VmContext.get(this);
|
||||
try {
|
||||
var results = VmImportAnalyzer.analyze(uris, context);
|
||||
return importGraphFactory.create(results);
|
||||
} catch (IOException | URISyntaxException | SecurityManagerException | PackageLoadError e) {
|
||||
throw exceptionBuilder().withCause(e).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
@NonnullByDefault
|
||||
package org.pkl.core.stdlib.analyze;
|
||||
|
||||
import org.pkl.core.util.NonnullByDefault;
|
||||
@@ -635,6 +635,9 @@ Expected an exception, but none was thrown.
|
||||
cannotEvaluateRelativeModuleUri=\
|
||||
Cannot evaluate relative module URI `{0}`.
|
||||
|
||||
cannotAnalyzeRelativeModuleUri=\
|
||||
Cannot analyze relative module URI `{0}`.
|
||||
|
||||
invalidModuleUri=\
|
||||
Module URI `{0}` has invalid syntax.
|
||||
|
||||
@@ -1060,3 +1063,6 @@ To fix this problem, add dependendy `org.pkl:pkl-certs`.
|
||||
# suppress inspection "HttpUrlsUsage"
|
||||
malformedProxyAddress=\
|
||||
Malformed proxy URI (expecting `http://<host>[:<port>]`): `{0}`.
|
||||
|
||||
cannotAnalyzeBecauseSyntaxError=\
|
||||
Found a syntax error when parsing module `{0}`.
|
||||
|
||||
Reference in New Issue
Block a user