mirror of
https://github.com/apple/pkl.git
synced 2026-04-24 17:28:37 +02:00
Improve handling of errors when analysis fails (#948)
Modules that cannot be loaded for one reason or another now cause a Pkl Exception, with the offending import highlighted as part of the stack trace.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -17,7 +17,6 @@ package org.pkl.core;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -78,7 +77,6 @@ public class Analyzer {
|
|||||||
return VmImportAnalyzer.analyze(sources, vmContext);
|
return VmImportAnalyzer.analyze(sources, vmContext);
|
||||||
} catch (SecurityManagerException
|
} catch (SecurityManagerException
|
||||||
| IOException
|
| IOException
|
||||||
| URISyntaxException
|
|
||||||
| PackageLoadError
|
| PackageLoadError
|
||||||
| HttpClientInitException e) {
|
| HttpClientInitException e) {
|
||||||
throw new PklException(e.getMessage(), e);
|
throw new PklException(e.getMessage(), e);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -16,9 +16,12 @@
|
|||||||
package org.pkl.core.runtime;
|
package org.pkl.core.runtime;
|
||||||
|
|
||||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
|
import java.nio.file.NoSuchFileException;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -31,22 +34,22 @@ import org.pkl.core.SecurityManagerException;
|
|||||||
import org.pkl.core.ast.builder.ImportsAndReadsParser;
|
import org.pkl.core.ast.builder.ImportsAndReadsParser;
|
||||||
import org.pkl.core.ast.builder.ImportsAndReadsParser.Entry;
|
import org.pkl.core.ast.builder.ImportsAndReadsParser.Entry;
|
||||||
import org.pkl.core.externalreader.ExternalReaderProcessException;
|
import org.pkl.core.externalreader.ExternalReaderProcessException;
|
||||||
|
import org.pkl.core.module.ResolvedModuleKey;
|
||||||
|
import org.pkl.core.packages.PackageLoadError;
|
||||||
import org.pkl.core.util.GlobResolver;
|
import org.pkl.core.util.GlobResolver;
|
||||||
import org.pkl.core.util.GlobResolver.InvalidGlobPatternException;
|
import org.pkl.core.util.GlobResolver.InvalidGlobPatternException;
|
||||||
import org.pkl.core.util.GlobResolver.ResolvedGlobElement;
|
|
||||||
import org.pkl.core.util.IoUtils;
|
import org.pkl.core.util.IoUtils;
|
||||||
|
|
||||||
public class VmImportAnalyzer {
|
public class VmImportAnalyzer {
|
||||||
@TruffleBoundary
|
@TruffleBoundary
|
||||||
public static ImportGraph analyze(URI[] moduleUris, VmContext context)
|
public static ImportGraph analyze(URI[] moduleUris, VmContext context)
|
||||||
throws IOException,
|
throws IOException, SecurityManagerException {
|
||||||
URISyntaxException,
|
|
||||||
SecurityManagerException,
|
|
||||||
ExternalReaderProcessException {
|
|
||||||
var imports = new TreeMap<URI, Set<ImportGraph.Import>>();
|
var imports = new TreeMap<URI, Set<ImportGraph.Import>>();
|
||||||
var resolvedImports = new TreeMap<URI, URI>();
|
var resolvedImports = new TreeMap<URI, URI>();
|
||||||
for (var moduleUri : moduleUris) {
|
for (var moduleUri : moduleUris) {
|
||||||
analyzeSingle(moduleUri, context, imports, resolvedImports);
|
var moduleKey = context.getModuleResolver().resolve(moduleUri);
|
||||||
|
var resolvedModuleKey = moduleKey.resolve(context.getSecurityManager());
|
||||||
|
analyzeSingle(moduleUri, resolvedModuleKey, context, imports, resolvedImports);
|
||||||
}
|
}
|
||||||
return new ImportGraph(imports, resolvedImports);
|
return new ImportGraph(imports, resolvedImports);
|
||||||
}
|
}
|
||||||
@@ -54,37 +57,33 @@ public class VmImportAnalyzer {
|
|||||||
@TruffleBoundary
|
@TruffleBoundary
|
||||||
private static void analyzeSingle(
|
private static void analyzeSingle(
|
||||||
URI moduleUri,
|
URI moduleUri,
|
||||||
|
ResolvedModuleKey resolvedModuleKey,
|
||||||
VmContext context,
|
VmContext context,
|
||||||
Map<URI, Set<ImportGraph.Import>> imports,
|
Map<URI, Set<ImportGraph.Import>> imports,
|
||||||
Map<URI, URI> resolvedImports)
|
Map<URI, URI> resolvedImports) {
|
||||||
throws IOException,
|
|
||||||
URISyntaxException,
|
|
||||||
SecurityManagerException,
|
|
||||||
ExternalReaderProcessException {
|
|
||||||
var moduleResolver = context.getModuleResolver();
|
var moduleResolver = context.getModuleResolver();
|
||||||
var securityManager = context.getSecurityManager();
|
var securityManager = context.getSecurityManager();
|
||||||
var importsInModule = collectImports(moduleUri, moduleResolver, securityManager);
|
var collectedImports = collectImports(resolvedModuleKey, moduleResolver, securityManager);
|
||||||
|
var importsInModule = new TreeSet<Import>();
|
||||||
|
for (var imprt : collectedImports) {
|
||||||
|
importsInModule.add(imprt.toImport());
|
||||||
|
}
|
||||||
imports.put(moduleUri, importsInModule);
|
imports.put(moduleUri, importsInModule);
|
||||||
resolvedImports.put(
|
resolvedImports.put(moduleUri, resolvedModuleKey.getUri());
|
||||||
moduleUri, moduleResolver.resolve(moduleUri).resolve(securityManager).getUri());
|
for (var imprt : collectedImports) {
|
||||||
for (var imprt : importsInModule) {
|
if (imports.containsKey(imprt.moduleUri)) {
|
||||||
if (imports.containsKey(imprt.uri())) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
analyzeSingle(imprt.uri(), context, imports, resolvedImports);
|
analyzeSingle(imprt.moduleUri, imprt.resolvedModuleKey, context, imports, resolvedImports);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<ImportGraph.Import> collectImports(
|
private static Set<ImportEntry> collectImports(
|
||||||
URI moduleUri, ModuleResolver moduleResolver, SecurityManager securityManager)
|
ResolvedModuleKey resolvedModuleKey,
|
||||||
throws IOException,
|
ModuleResolver moduleResolver,
|
||||||
URISyntaxException,
|
SecurityManager securityManager) {
|
||||||
SecurityManagerException,
|
|
||||||
ExternalReaderProcessException {
|
|
||||||
var moduleKey = moduleResolver.resolve(moduleUri);
|
|
||||||
var resolvedModuleKey = moduleKey.resolve(securityManager);
|
|
||||||
List<Entry> importsAndReads;
|
List<Entry> importsAndReads;
|
||||||
|
var moduleKey = resolvedModuleKey.getOriginal();
|
||||||
try {
|
try {
|
||||||
importsAndReads = ImportsAndReadsParser.parse(moduleKey, resolvedModuleKey);
|
importsAndReads = ImportsAndReadsParser.parse(moduleKey, resolvedModuleKey);
|
||||||
} catch (VmException err) {
|
} catch (VmException err) {
|
||||||
@@ -92,19 +91,21 @@ public class VmImportAnalyzer {
|
|||||||
.evalError("cannotAnalyzeBecauseSyntaxError", moduleKey.getUri())
|
.evalError("cannotAnalyzeBecauseSyntaxError", moduleKey.getUri())
|
||||||
.wrapping(err)
|
.wrapping(err)
|
||||||
.build();
|
.build();
|
||||||
|
} catch (IOException err) {
|
||||||
|
throw new VmExceptionBuilder().evalError("ioErrorLoadingModule", moduleKey.getUri()).build();
|
||||||
}
|
}
|
||||||
if (importsAndReads == null) {
|
if (importsAndReads == null) {
|
||||||
return Set.of();
|
return Set.of();
|
||||||
}
|
}
|
||||||
var result = new TreeSet<ImportGraph.Import>();
|
var result = new HashSet<ImportEntry>();
|
||||||
for (var entry : importsAndReads) {
|
for (var entry : importsAndReads) {
|
||||||
if (!entry.isModule()) {
|
if (!entry.isModule()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (entry.isGlob()) {
|
try {
|
||||||
var theModuleKey =
|
if (entry.isGlob()) {
|
||||||
moduleResolver.resolve(moduleKey.resolveUri(IoUtils.toUri(entry.stringValue())));
|
var theModuleKey =
|
||||||
try {
|
moduleResolver.resolve(moduleKey.resolveUri(IoUtils.toUri(entry.stringValue())));
|
||||||
var elements =
|
var elements =
|
||||||
GlobResolver.resolveGlob(
|
GlobResolver.resolveGlob(
|
||||||
securityManager,
|
securityManager,
|
||||||
@@ -112,24 +113,60 @@ public class VmImportAnalyzer {
|
|||||||
moduleKey,
|
moduleKey,
|
||||||
moduleKey.getUri(),
|
moduleKey.getUri(),
|
||||||
entry.stringValue());
|
entry.stringValue());
|
||||||
var globImports =
|
for (var globElement : elements.values()) {
|
||||||
elements.values().stream()
|
var moduleUri = globElement.uri();
|
||||||
.map(ResolvedGlobElement::uri)
|
var mk = moduleResolver.resolve(moduleUri);
|
||||||
.map(ImportGraph.Import::new)
|
var rmk = mk.resolve(securityManager);
|
||||||
.toList();
|
result.add(new ImportEntry(moduleUri, rmk));
|
||||||
result.addAll(globImports);
|
}
|
||||||
} catch (InvalidGlobPatternException e) {
|
} else {
|
||||||
throw new VmExceptionBuilder()
|
var resolvedUri =
|
||||||
.evalError("invalidGlobPattern", entry.stringValue())
|
IoUtils.resolve(securityManager, moduleKey, IoUtils.toUri(entry.stringValue()));
|
||||||
.withSourceSection(entry.sourceSection())
|
var mKey = moduleResolver.resolve(resolvedUri);
|
||||||
.build();
|
var rmk = mKey.resolve(securityManager);
|
||||||
|
result.add(new ImportEntry(resolvedUri, rmk));
|
||||||
}
|
}
|
||||||
} else {
|
} catch (InvalidGlobPatternException e) {
|
||||||
var resolvedUri =
|
throw new VmExceptionBuilder()
|
||||||
IoUtils.resolve(securityManager, moduleKey, IoUtils.toUri(entry.stringValue()));
|
.evalError("invalidGlobPattern", entry.stringValue())
|
||||||
result.add(new Import(resolvedUri));
|
.withSourceSection(entry.sourceSection())
|
||||||
|
.build();
|
||||||
|
} catch (FileNotFoundException | NoSuchFileException e) {
|
||||||
|
throw new VmExceptionBuilder()
|
||||||
|
.evalError("cannotFindModule", entry.stringValue())
|
||||||
|
.withSourceSection(entry.sourceSection())
|
||||||
|
.build();
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
throw new VmExceptionBuilder()
|
||||||
|
.evalError("invalidModuleUri", entry.stringValue())
|
||||||
|
.withHint(e.getReason())
|
||||||
|
.withSourceSection(entry.sourceSection())
|
||||||
|
.build();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new VmExceptionBuilder()
|
||||||
|
.evalError("ioErrorLoadingModule", entry.stringValue())
|
||||||
|
.withCause(e)
|
||||||
|
.withSourceSection(entry.sourceSection())
|
||||||
|
.build();
|
||||||
|
} catch (SecurityManagerException | PackageLoadError e) {
|
||||||
|
throw new VmExceptionBuilder()
|
||||||
|
.withSourceSection(entry.sourceSection())
|
||||||
|
.withCause(e)
|
||||||
|
.build();
|
||||||
|
} catch (ExternalReaderProcessException e) {
|
||||||
|
throw new VmExceptionBuilder()
|
||||||
|
.withSourceSection(entry.sourceSection())
|
||||||
|
.evalError("externalReaderFailure")
|
||||||
|
.withCause(e)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record ImportEntry(URI moduleUri, ResolvedModuleKey resolvedModuleKey) {
|
||||||
|
private Import toImport() {
|
||||||
|
return new Import(resolvedModuleKey.getOriginal().getUri());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -23,7 +23,6 @@ import java.net.URISyntaxException;
|
|||||||
import org.pkl.core.ImportGraph;
|
import org.pkl.core.ImportGraph;
|
||||||
import org.pkl.core.ImportGraph.Import;
|
import org.pkl.core.ImportGraph.Import;
|
||||||
import org.pkl.core.SecurityManagerException;
|
import org.pkl.core.SecurityManagerException;
|
||||||
import org.pkl.core.externalreader.ExternalReaderProcessException;
|
|
||||||
import org.pkl.core.packages.PackageLoadError;
|
import org.pkl.core.packages.PackageLoadError;
|
||||||
import org.pkl.core.runtime.AnalyzeModule;
|
import org.pkl.core.runtime.AnalyzeModule;
|
||||||
import org.pkl.core.runtime.VmContext;
|
import org.pkl.core.runtime.VmContext;
|
||||||
@@ -92,11 +91,7 @@ public final class AnalyzeNodes {
|
|||||||
try {
|
try {
|
||||||
var results = VmImportAnalyzer.analyze(uris, context);
|
var results = VmImportAnalyzer.analyze(uris, context);
|
||||||
return importGraphFactory.create(results);
|
return importGraphFactory.create(results);
|
||||||
} catch (IOException
|
} catch (IOException | SecurityManagerException | PackageLoadError e) {
|
||||||
| URISyntaxException
|
|
||||||
| SecurityManagerException
|
|
||||||
| PackageLoadError
|
|
||||||
| ExternalReaderProcessException e) {
|
|
||||||
throw exceptionBuilder().withCause(e).build();
|
throw exceptionBuilder().withCause(e).build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
pkl-core/src/test/files/LanguageSnippetTests/input-helper/analyze/cannotFindModule.pkl
vendored
Normal file
1
pkl-core/src/test/files/LanguageSnippetTests/input-helper/analyze/cannotFindModule.pkl
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
import "bogusModule.pkl"
|
||||||
1
pkl-core/src/test/files/LanguageSnippetTests/input-helper/analyze/invalidGlob.pkl
vendored
Normal file
1
pkl-core/src/test/files/LanguageSnippetTests/input-helper/analyze/invalidGlob.pkl
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
import* ".../**.pkl"
|
||||||
5
pkl-core/src/test/files/LanguageSnippetTests/input/errors/analyzeImportsCannotFindModule.pkl
vendored
Normal file
5
pkl-core/src/test/files/LanguageSnippetTests/input/errors/analyzeImportsCannotFindModule.pkl
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import "pkl:analyze"
|
||||||
|
import "pkl:reflect"
|
||||||
|
import ".../input-helper/analyze/cannotFindModule.pkl"
|
||||||
|
|
||||||
|
res = analyze.importGraph(Set(reflect.Module(cannotFindModule).uri))
|
||||||
5
pkl-core/src/test/files/LanguageSnippetTests/input/errors/analyzeImportsInvalidGlob.pkl
vendored
Normal file
5
pkl-core/src/test/files/LanguageSnippetTests/input/errors/analyzeImportsInvalidGlob.pkl
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import "pkl:analyze"
|
||||||
|
import "pkl:reflect"
|
||||||
|
import ".../input-helper/analyze/invalidGlob.pkl"
|
||||||
|
|
||||||
|
res = analyze.importGraph(Set(reflect.Module(invalidGlob).uri))
|
||||||
14
pkl-core/src/test/files/LanguageSnippetTests/output/errors/analyzeImportsCannotFindModule.err
vendored
Normal file
14
pkl-core/src/test/files/LanguageSnippetTests/output/errors/analyzeImportsCannotFindModule.err
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
–– Pkl Error ––
|
||||||
|
Cannot find module `bogusModule.pkl`.
|
||||||
|
|
||||||
|
x | import "bogusModule.pkl"
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
at pkl.analyze#importGraph (file:///$snippetsDir/input-helper/analyze/cannotFindModule.pkl)
|
||||||
|
|
||||||
|
x | res = analyze.importGraph(Set(reflect.Module(cannotFindModule).uri))
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
at analyzeImportsCannotFindModule#res (file:///$snippetsDir/input/errors/analyzeImportsCannotFindModule.pkl)
|
||||||
|
|
||||||
|
xxx | text = renderer.renderDocument(value)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
at pkl.base#Module.output.text (pkl:base)
|
||||||
14
pkl-core/src/test/files/LanguageSnippetTests/output/errors/analyzeImportsInvalidGlob.err
vendored
Normal file
14
pkl-core/src/test/files/LanguageSnippetTests/output/errors/analyzeImportsInvalidGlob.err
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
–– Pkl Error ––
|
||||||
|
Cannot combine glob imports with triple-dot module URIs.
|
||||||
|
|
||||||
|
x | import* ".../**.pkl"
|
||||||
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
at analyzeImportsInvalidGlob#invalidGlob (file:///$snippetsDir/input-helper/analyze/invalidGlob.pkl)
|
||||||
|
|
||||||
|
x | res = analyze.importGraph(Set(reflect.Module(invalidGlob).uri))
|
||||||
|
^^^^^^^^^^^
|
||||||
|
at analyzeImportsInvalidGlob#res (file:///$snippetsDir/input/errors/analyzeImportsInvalidGlob.pkl)
|
||||||
|
|
||||||
|
xxx | text = renderer.renderDocument(value)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
at pkl.base#Module.output.text (pkl:base)
|
||||||
Reference in New Issue
Block a user