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 scheme-agnostic projects #486

Merged
merged 4 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion pkl-core/src/main/java/org/pkl/core/EvaluatorImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ public EvaluatorImpl(
packageResolver,
projectDependencies == null
? null
: new ProjectDependenciesManager(projectDependencies)));
: new ProjectDependenciesManager(
projectDependencies, moduleResolver, securityManager)));
});
this.timeout = timeout;
// NOTE: would probably make sense to share executor between evaluators
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1825,6 +1825,8 @@ private URI resolveImport(String importUri, StringConstantContext importUriCtx)
} catch (VmException e) {
throw exceptionBuilder()
.evalError(e.getMessage(), e.getMessageArguments())
.withCause(e.getCause())
.withHint(e.getHint())
.withSourceSection(createSourceSection(importUriCtx))
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.source.SourceSection;
import java.io.IOException;
import java.net.URISyntaxException;
import org.graalvm.collections.EconomicMap;
import org.pkl.core.SecurityManagerException;
import org.pkl.core.ast.member.SharedMemberNode;
Expand All @@ -33,6 +34,7 @@
import org.pkl.core.runtime.VmObjectBuilder;
import org.pkl.core.util.GlobResolver;
import org.pkl.core.util.GlobResolver.InvalidGlobPatternException;
import org.pkl.core.util.IoUtils;
import org.pkl.core.util.LateInit;

@NodeInfo(shortName = "read*")
Expand Down Expand Up @@ -73,7 +75,7 @@ public Object read(String globPattern) {
var globUri = parseUri(globPattern);
var context = VmContext.get(this);
try {
var resolvedUri = currentModule.resolveUri(globUri);
var resolvedUri = IoUtils.resolve(context.getSecurityManager(), currentModule, globUri);
var reader = context.getResourceManager().getReader(resolvedUri, this);
if (!reader.isGlobbable()) {
throw exceptionBuilder().evalError("cannotGlobUri", globUri, globUri.getScheme()).build();
Expand All @@ -94,7 +96,7 @@ public Object read(String globPattern) {
return cachedResult;
} catch (IOException e) {
throw exceptionBuilder().evalError("ioErrorResolvingGlob", globPattern).withCause(e).build();
} catch (SecurityManagerException | HttpClientInitException e) {
} catch (SecurityManagerException | HttpClientInitException | URISyntaxException e) {
throw exceptionBuilder().withCause(e).build();
} catch (InvalidGlobPatternException e) {
throw exceptionBuilder()
Expand Down
191 changes: 83 additions & 108 deletions pkl-core/src/main/java/org/pkl/core/module/ModuleKeys.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
import org.pkl.core.util.HttpUtils;
import org.pkl.core.util.IoUtils;
import org.pkl.core.util.Nullable;
import org.pkl.core.util.Pair;

/** Utilities for creating and using {@link ModuleKey}s. */
public final class ModuleKeys {
Expand Down Expand Up @@ -290,14 +289,18 @@ public String loadSource() throws IOException {
}
}

private static class File extends DependencyAwareModuleKey {
private static class File implements ModuleKey {
final URI uri;

File(URI uri) {
super(uri);
this.uri = uri;
}

@Override
public URI getUri() {
return uri;
}

@Override
public boolean hasElement(SecurityManager securityManager, URI uri)
throws SecurityManagerException {
Expand Down Expand Up @@ -329,17 +332,18 @@ public ResolvedModuleKey resolve(SecurityManager securityManager)
}

@Override
protected Map<String, ? extends Dependency> getDependencies() {
var projectDepsManager = VmContext.get(null).getProjectDependenciesManager();
if (projectDepsManager == null || !projectDepsManager.hasPath(Path.of(uri))) {
throw new PackageLoadError("cannotResolveDependencyNoProject");
}
return projectDepsManager.getDependencies();
public boolean isGlobbable() {
return true;
}

@Override
public boolean isLocal() {
return true;
}

@Override
protected PackageLoadError cannotFindDependency(String name) {
return new PackageLoadError("cannotFindDependencyInProject", name);
public boolean hasHierarchicalUris() {
return true;
}
}

Expand Down Expand Up @@ -547,79 +551,67 @@ public ResolvedModuleKey resolve(SecurityManager securityManager)
}
}

/** Base implementation; knows how to resolve dependencies prefixed with <code>@</code>. */
private abstract static class DependencyAwareModuleKey implements ModuleKey {
private abstract static class AbstractPackage implements ModuleKey {

protected final URI uri;
protected final PackageAssetUri packageAssetUri;

DependencyAwareModuleKey(URI uri) {
this.uri = uri;
AbstractPackage(PackageAssetUri packageAssetUri) {
this.packageAssetUri = packageAssetUri;
}

protected abstract Map<String, ? extends Dependency> getDependencies()
throws IOException, SecurityManagerException;

@Override
public URI getUri() {
return uri;
public boolean hasHierarchicalUris() {
return true;
}

protected Pair<String, String> parseDependencyNotation(String importPath) {
var idx = importPath.indexOf('/');
if (idx == -1) {
// treat named dependency without a subpath as the root path.
// i.e. resolve to `@foo` to `package://example.com/[email protected]#/`
return Pair.of(importPath.substring(1), "/");
}
return Pair.of(importPath.substring(1, idx), importPath.substring(idx));
@Override
public boolean hasFragmentPaths() {
return true;
}

protected abstract Map<String, ? extends Dependency> getDependencies()
throws IOException, SecurityManagerException;

@Override
public boolean isLocal() {
return true;
}

@Override
public boolean hasHierarchicalUris() {
public boolean isGlobbable() {
return true;
}

@Override
public boolean isGlobbable() {
return true;
public URI getUri() {
return packageAssetUri.getUri();
}

private URI resolveDependencyNotation(String notation)
throws IOException, SecurityManagerException {
var parsed = parseDependencyNotation(notation);
@Override
public URI resolveUri(URI baseUri, URI importUri) throws IOException, SecurityManagerException {
var ssp = importUri.getSchemeSpecificPart();
if (importUri.isAbsolute() || !ssp.startsWith("@")) {
return ModuleKey.super.resolveUri(baseUri, importUri);
}
var parsed = IoUtils.parseDependencyNotation(ssp);
var name = parsed.getFirst();
var path = parsed.getSecond();
var dependency = getDependencies().get(name);
if (dependency == null) {
throw cannotFindDependency(name);
throw new PackageLoadError(
"cannotFindDependencyInPackage",
name,
packageAssetUri.getPackageUri().getDisplayName());
}
return dependency.getPackageUri().toPackageAssetUri(path).getUri();
}

@Override
public URI resolveUri(URI baseUri, URI importUri) throws IOException, SecurityManagerException {
if (importUri.isAbsolute() || !importUri.getPath().startsWith("@")) {
return ModuleKey.super.resolveUri(baseUri, importUri);
}
return resolveDependencyNotation(importUri.getPath());
}

protected abstract PackageLoadError cannotFindDependency(String name);
}

/** Represents a module imported via the {@code package} scheme. */
private static class Package extends DependencyAwareModuleKey {

private final PackageAssetUri packageAssetUri;
private static class Package extends AbstractPackage {

Package(PackageAssetUri packageAssetUri) {
super(packageAssetUri.getUri());
this.packageAssetUri = packageAssetUri;
super(packageAssetUri);
}

private PackageResolver getPackageResolver() {
Expand All @@ -631,6 +623,7 @@ private PackageResolver getPackageResolver() {
@Override
public ResolvedModuleKey resolve(SecurityManager securityManager)
throws IOException, SecurityManagerException {
var uri = packageAssetUri.getUri();
securityManager.checkResolveModule(uri);
var bytes =
getPackageResolver()
Expand All @@ -654,11 +647,6 @@ public boolean hasElement(SecurityManager securityManager, URI elementUri)
return getPackageResolver().hasElement(assetUri, assetUri.getPackageUri().getChecksums());
}

@Override
public boolean hasFragmentPaths() {
return true;
}

@Override
protected Map<String, ? extends Dependency> getDependencies()
throws IOException, SecurityManagerException {
Expand All @@ -667,28 +655,18 @@ public boolean hasFragmentPaths() {
packageAssetUri.getPackageUri(), packageAssetUri.getPackageUri().getChecksums())
.getDependencies();
}

@Override
protected PackageLoadError cannotFindDependency(String name) {
return new PackageLoadError(
"cannotFindDependencyInPackage", name, packageAssetUri.getPackageUri().getDisplayName());
}
}

/**
* Represents a module imported via the {@code projectpackage} scheme.
*
* <p>The {@code projectpackage} scheme is what project-local dependencies resolve to when
* imported using dependency notation (for example, {@code import "@foo/bar.pkl"}). This scheme is
* an internal implementation detail, and we do not expect a project to declare this.
* an internal implementation detail, and we do not expect a module to declare this.
*/
private static class ProjectPackage extends DependencyAwareModuleKey {

private final PackageAssetUri packageAssetUri;

public static class ProjectPackage extends AbstractPackage {
ProjectPackage(PackageAssetUri packageAssetUri) {
super(packageAssetUri.getUri());
this.packageAssetUri = packageAssetUri;
super(packageAssetUri);
}

private PackageResolver getPackageResolver() {
Expand All @@ -697,37 +675,36 @@ private PackageResolver getPackageResolver() {
return packageResolver;
}

private ProjectDependenciesManager getProjectDepsResolver() {
private ProjectDependenciesManager getProjectDependenciesManager() {
var projectDepsManager = VmContext.get(null).getProjectDependenciesManager();
assert projectDepsManager != null;
return projectDepsManager;
}

private @Nullable Path getLocalPath(Dependency dependency, PackageAssetUri packageAssetUri) {
if (!(dependency instanceof LocalDependency localDependency)) {
return null;
}
return localDependency.resolveAssetPath(
getProjectDepsResolver().getProjectDir(), packageAssetUri);
private @Nullable URI getLocalUri(Dependency dependency) {
return getLocalUri(dependency, packageAssetUri);
}

private @Nullable Path getLocalPath(Dependency dependency) {
if (!(dependency instanceof LocalDependency)) {
private @Nullable URI getLocalUri(Dependency dependency, PackageAssetUri assetUri) {
if (!(dependency instanceof LocalDependency localDependency)) {
return null;
}
return getLocalPath(dependency, packageAssetUri);
return localDependency.resolveAssetUri(
getProjectDependenciesManager().getProjectBaseUri(), assetUri);
}

@Override
public ResolvedModuleKey resolve(SecurityManager securityManager)
throws IOException, SecurityManagerException {
securityManager.checkResolveModule(packageAssetUri.getUri());
var uri = packageAssetUri.getUri();
securityManager.checkResolveModule(uri);
var dependency =
getProjectDepsResolver().getResolvedDependency(packageAssetUri.getPackageUri());
var path = getLocalPath(dependency);
if (path != null) {
securityManager.checkResolveModule(path.toUri());
return ResolvedModuleKeys.file(this, path.toUri(), path);
getProjectDependenciesManager().getResolvedDependency(packageAssetUri.getPackageUri());
var local = getLocalUri(dependency);
if (local != null) {
var resolved =
VmContext.get(null).getModuleResolver().resolve(local).resolve(securityManager);
return ResolvedModuleKeys.delegated(resolved, this);
}
var dep = (Dependency.RemoteDependency) dependency;
assert dep.getChecksums() != null;
Expand All @@ -741,11 +718,15 @@ public List<PathElement> listElements(SecurityManager securityManager, URI baseU
securityManager.checkResolveModule(baseUri);
var packageAssetUri = PackageAssetUri.create(baseUri);
var dependency =
getProjectDepsResolver().getResolvedDependency(packageAssetUri.getPackageUri());
var path = getLocalPath(dependency, packageAssetUri);
if (path != null) {
securityManager.checkResolveModule(path.toUri());
return FileResolver.listElements(path);
getProjectDependenciesManager().getResolvedDependency(packageAssetUri.getPackageUri());
var local = getLocalUri(dependency, packageAssetUri);
if (local != null) {
var moduleKey = VmContext.get(null).getModuleResolver().resolve(local);
if (!moduleKey.isGlobbable()) {
throw new PackageLoadError(
"cannotResolveInLocalDependencyNotGlobbable", local.getScheme());
}
return moduleKey.listElements(securityManager, local);
}
var dep = (Dependency.RemoteDependency) dependency;
assert dep.getChecksums() != null;
Expand All @@ -758,42 +739,36 @@ public boolean hasElement(SecurityManager securityManager, URI elementUri)
securityManager.checkResolveModule(elementUri);
var packageAssetUri = PackageAssetUri.create(elementUri);
var dependency =
getProjectDepsResolver().getResolvedDependency(packageAssetUri.getPackageUri());
var path = getLocalPath(dependency, packageAssetUri);
if (path != null) {
securityManager.checkResolveModule(path.toUri());
return FileResolver.hasElement(path);
getProjectDependenciesManager().getResolvedDependency(packageAssetUri.getPackageUri());
var local = getLocalUri(dependency, packageAssetUri);
if (local != null) {
var moduleKey = VmContext.get(null).getModuleResolver().resolve(local);
if (!moduleKey.isGlobbable() && !moduleKey.isLocal()) {
throw new PackageLoadError(
"cannotResolveInLocalDependencyNotGlobbableNorLocal", local.getScheme());
}
return moduleKey.hasElement(securityManager, local);
}
var dep = (Dependency.RemoteDependency) dependency;
assert dep.getChecksums() != null;
return getPackageResolver().hasElement(packageAssetUri, dep.getChecksums());
}

@Override
public boolean hasFragmentPaths() {
return true;
}

@Override
protected Map<String, ? extends Dependency> getDependencies()
throws IOException, SecurityManagerException {
var packageUri = packageAssetUri.getPackageUri();
var projectResolver = getProjectDepsResolver();
var projectResolver = getProjectDependenciesManager();
if (projectResolver.isLocalPackage(packageUri)) {
return projectResolver.getLocalPackageDependencies(packageUri);
}
var dep =
(Dependency.RemoteDependency) getProjectDepsResolver().getResolvedDependency(packageUri);
(Dependency.RemoteDependency)
getProjectDependenciesManager().getResolvedDependency(packageUri);
assert dep.getChecksums() != null;
var dependencyMetadata =
getPackageResolver().getDependencyMetadata(packageUri, dep.getChecksums());
return projectResolver.getResolvedDependenciesForPackage(packageUri, dependencyMetadata);
}

@Override
protected PackageLoadError cannotFindDependency(String name) {
return new PackageLoadError(
"cannotFindDependencyInPackage", name, packageAssetUri.getPackageUri().getDisplayName());
}
}
}