mirror of
https://github.com/apple/pkl.git
synced 2026-04-24 01:08:34 +02:00
Support scheme-agnostic projects (#486)
This adds changes to support loading project dependencies in non-file based projects. The design for this feature can be found in SPICE-0005: https://github.com/apple/pkl-evolution/pull/6 Changes: * Consider all imports prefixed with `@` as dependency notation. * Bugfix: fix resolution of glob expressions in a local dependency. * Adjust pkl.Project: - Allow local dependencies from a scheme-local paths. - Disallow certain evaluator settings if not loaded as a file-based module. * Breaking API change: `ProjectDependenciesManager` constructor now requires `ModuleResolver` and `SecurityManager`.
This commit is contained in:
@@ -105,7 +105,8 @@ public class EvaluatorImpl implements Evaluator {
|
|||||||
packageResolver,
|
packageResolver,
|
||||||
projectDependencies == null
|
projectDependencies == null
|
||||||
? null
|
? null
|
||||||
: new ProjectDependenciesManager(projectDependencies)));
|
: new ProjectDependenciesManager(
|
||||||
|
projectDependencies, moduleResolver, securityManager)));
|
||||||
});
|
});
|
||||||
this.timeout = timeout;
|
this.timeout = timeout;
|
||||||
// NOTE: would probably make sense to share executor between evaluators
|
// NOTE: would probably make sense to share executor between evaluators
|
||||||
|
|||||||
@@ -1825,6 +1825,8 @@ public final class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
} catch (VmException e) {
|
} catch (VmException e) {
|
||||||
throw exceptionBuilder()
|
throw exceptionBuilder()
|
||||||
.evalError(e.getMessage(), e.getMessageArguments())
|
.evalError(e.getMessage(), e.getMessageArguments())
|
||||||
|
.withCause(e.getCause())
|
||||||
|
.withHint(e.getHint())
|
||||||
.withSourceSection(createSourceSection(importUriCtx))
|
.withSourceSection(createSourceSection(importUriCtx))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import com.oracle.truffle.api.frame.FrameDescriptor;
|
|||||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||||
import com.oracle.truffle.api.source.SourceSection;
|
import com.oracle.truffle.api.source.SourceSection;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import org.graalvm.collections.EconomicMap;
|
import org.graalvm.collections.EconomicMap;
|
||||||
import org.pkl.core.SecurityManagerException;
|
import org.pkl.core.SecurityManagerException;
|
||||||
import org.pkl.core.ast.member.SharedMemberNode;
|
import org.pkl.core.ast.member.SharedMemberNode;
|
||||||
@@ -33,6 +34,7 @@ import org.pkl.core.runtime.VmMapping;
|
|||||||
import org.pkl.core.runtime.VmObjectBuilder;
|
import org.pkl.core.runtime.VmObjectBuilder;
|
||||||
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.IoUtils;
|
||||||
import org.pkl.core.util.LateInit;
|
import org.pkl.core.util.LateInit;
|
||||||
|
|
||||||
@NodeInfo(shortName = "read*")
|
@NodeInfo(shortName = "read*")
|
||||||
@@ -73,7 +75,7 @@ public abstract class ReadGlobNode extends AbstractReadNode {
|
|||||||
var globUri = parseUri(globPattern);
|
var globUri = parseUri(globPattern);
|
||||||
var context = VmContext.get(this);
|
var context = VmContext.get(this);
|
||||||
try {
|
try {
|
||||||
var resolvedUri = currentModule.resolveUri(globUri);
|
var resolvedUri = IoUtils.resolve(context.getSecurityManager(), currentModule, globUri);
|
||||||
var reader = context.getResourceManager().getReader(resolvedUri, this);
|
var reader = context.getResourceManager().getReader(resolvedUri, this);
|
||||||
if (!reader.isGlobbable()) {
|
if (!reader.isGlobbable()) {
|
||||||
throw exceptionBuilder().evalError("cannotGlobUri", globUri, globUri.getScheme()).build();
|
throw exceptionBuilder().evalError("cannotGlobUri", globUri, globUri.getScheme()).build();
|
||||||
@@ -94,7 +96,7 @@ public abstract class ReadGlobNode extends AbstractReadNode {
|
|||||||
return cachedResult;
|
return cachedResult;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw exceptionBuilder().evalError("ioErrorResolvingGlob", globPattern).withCause(e).build();
|
throw exceptionBuilder().evalError("ioErrorResolvingGlob", globPattern).withCause(e).build();
|
||||||
} catch (SecurityManagerException | HttpClientInitException e) {
|
} catch (SecurityManagerException | HttpClientInitException | URISyntaxException e) {
|
||||||
throw exceptionBuilder().withCause(e).build();
|
throw exceptionBuilder().withCause(e).build();
|
||||||
} catch (InvalidGlobPatternException e) {
|
} catch (InvalidGlobPatternException e) {
|
||||||
throw exceptionBuilder()
|
throw exceptionBuilder()
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ import org.pkl.core.util.ErrorMessages;
|
|||||||
import org.pkl.core.util.HttpUtils;
|
import org.pkl.core.util.HttpUtils;
|
||||||
import org.pkl.core.util.IoUtils;
|
import org.pkl.core.util.IoUtils;
|
||||||
import org.pkl.core.util.Nullable;
|
import org.pkl.core.util.Nullable;
|
||||||
import org.pkl.core.util.Pair;
|
|
||||||
|
|
||||||
/** Utilities for creating and using {@link ModuleKey}s. */
|
/** Utilities for creating and using {@link ModuleKey}s. */
|
||||||
public final class ModuleKeys {
|
public final class ModuleKeys {
|
||||||
@@ -290,14 +289,18 @@ public final class ModuleKeys {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class File extends DependencyAwareModuleKey {
|
private static class File implements ModuleKey {
|
||||||
final URI uri;
|
final URI uri;
|
||||||
|
|
||||||
File(URI uri) {
|
File(URI uri) {
|
||||||
super(uri);
|
|
||||||
this.uri = uri;
|
this.uri = uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URI getUri() {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasElement(SecurityManager securityManager, URI uri)
|
public boolean hasElement(SecurityManager securityManager, URI uri)
|
||||||
throws SecurityManagerException {
|
throws SecurityManagerException {
|
||||||
@@ -329,17 +332,18 @@ public final class ModuleKeys {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Map<String, ? extends Dependency> getDependencies() {
|
public boolean isGlobbable() {
|
||||||
var projectDepsManager = VmContext.get(null).getProjectDependenciesManager();
|
return true;
|
||||||
if (projectDepsManager == null || !projectDepsManager.hasPath(Path.of(uri))) {
|
|
||||||
throw new PackageLoadError("cannotResolveDependencyNoProject");
|
|
||||||
}
|
|
||||||
return projectDepsManager.getDependencies();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected PackageLoadError cannotFindDependency(String name) {
|
public boolean isLocal() {
|
||||||
return new PackageLoadError("cannotFindDependencyInProject", name);
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasHierarchicalUris() {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -547,40 +551,29 @@ public final class ModuleKeys {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Base implementation; knows how to resolve dependencies prefixed with <code>@</code>. */
|
private abstract static class AbstractPackage implements ModuleKey {
|
||||||
private abstract static class DependencyAwareModuleKey implements ModuleKey {
|
|
||||||
|
|
||||||
protected final URI uri;
|
protected final PackageAssetUri packageAssetUri;
|
||||||
|
|
||||||
DependencyAwareModuleKey(URI uri) {
|
AbstractPackage(PackageAssetUri packageAssetUri) {
|
||||||
this.uri = uri;
|
this.packageAssetUri = packageAssetUri;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public URI getUri() {
|
|
||||||
return uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
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/foo@1.0.0#/`
|
|
||||||
return Pair.of(importPath.substring(1), "/");
|
|
||||||
}
|
|
||||||
return Pair.of(importPath.substring(1, idx), importPath.substring(idx));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Map<String, ? extends Dependency> getDependencies()
|
protected abstract Map<String, ? extends Dependency> getDependencies()
|
||||||
throws IOException, SecurityManagerException;
|
throws IOException, SecurityManagerException;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLocal() {
|
public boolean hasHierarchicalUris() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasHierarchicalUris() {
|
public boolean hasFragmentPaths() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLocal() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -589,37 +582,36 @@ public final class ModuleKeys {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private URI resolveDependencyNotation(String notation)
|
@Override
|
||||||
throws IOException, SecurityManagerException {
|
public URI getUri() {
|
||||||
var parsed = parseDependencyNotation(notation);
|
return packageAssetUri.getUri();
|
||||||
var name = parsed.getFirst();
|
|
||||||
var path = parsed.getSecond();
|
|
||||||
var dependency = getDependencies().get(name);
|
|
||||||
if (dependency == null) {
|
|
||||||
throw cannotFindDependency(name);
|
|
||||||
}
|
|
||||||
return dependency.getPackageUri().toPackageAssetUri(path).getUri();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public URI resolveUri(URI baseUri, URI importUri) throws IOException, SecurityManagerException {
|
public URI resolveUri(URI baseUri, URI importUri) throws IOException, SecurityManagerException {
|
||||||
if (importUri.isAbsolute() || !importUri.getPath().startsWith("@")) {
|
var ssp = importUri.getSchemeSpecificPart();
|
||||||
|
if (importUri.isAbsolute() || !ssp.startsWith("@")) {
|
||||||
return ModuleKey.super.resolveUri(baseUri, importUri);
|
return ModuleKey.super.resolveUri(baseUri, importUri);
|
||||||
}
|
}
|
||||||
return resolveDependencyNotation(importUri.getPath());
|
var parsed = IoUtils.parseDependencyNotation(ssp);
|
||||||
|
var name = parsed.getFirst();
|
||||||
|
var path = parsed.getSecond();
|
||||||
|
var dependency = getDependencies().get(name);
|
||||||
|
if (dependency == null) {
|
||||||
|
throw new PackageLoadError(
|
||||||
|
"cannotFindDependencyInPackage",
|
||||||
|
name,
|
||||||
|
packageAssetUri.getPackageUri().getDisplayName());
|
||||||
|
}
|
||||||
|
return dependency.getPackageUri().toPackageAssetUri(path).getUri();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract PackageLoadError cannotFindDependency(String name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Represents a module imported via the {@code package} scheme. */
|
/** Represents a module imported via the {@code package} scheme. */
|
||||||
private static class Package extends DependencyAwareModuleKey {
|
private static class Package extends AbstractPackage {
|
||||||
|
|
||||||
private final PackageAssetUri packageAssetUri;
|
|
||||||
|
|
||||||
Package(PackageAssetUri packageAssetUri) {
|
Package(PackageAssetUri packageAssetUri) {
|
||||||
super(packageAssetUri.getUri());
|
super(packageAssetUri);
|
||||||
this.packageAssetUri = packageAssetUri;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PackageResolver getPackageResolver() {
|
private PackageResolver getPackageResolver() {
|
||||||
@@ -631,6 +623,7 @@ public final class ModuleKeys {
|
|||||||
@Override
|
@Override
|
||||||
public ResolvedModuleKey resolve(SecurityManager securityManager)
|
public ResolvedModuleKey resolve(SecurityManager securityManager)
|
||||||
throws IOException, SecurityManagerException {
|
throws IOException, SecurityManagerException {
|
||||||
|
var uri = packageAssetUri.getUri();
|
||||||
securityManager.checkResolveModule(uri);
|
securityManager.checkResolveModule(uri);
|
||||||
var bytes =
|
var bytes =
|
||||||
getPackageResolver()
|
getPackageResolver()
|
||||||
@@ -654,11 +647,6 @@ public final class ModuleKeys {
|
|||||||
return getPackageResolver().hasElement(assetUri, assetUri.getPackageUri().getChecksums());
|
return getPackageResolver().hasElement(assetUri, assetUri.getPackageUri().getChecksums());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasFragmentPaths() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Map<String, ? extends Dependency> getDependencies()
|
protected Map<String, ? extends Dependency> getDependencies()
|
||||||
throws IOException, SecurityManagerException {
|
throws IOException, SecurityManagerException {
|
||||||
@@ -667,12 +655,6 @@ public final class ModuleKeys {
|
|||||||
packageAssetUri.getPackageUri(), packageAssetUri.getPackageUri().getChecksums())
|
packageAssetUri.getPackageUri(), packageAssetUri.getPackageUri().getChecksums())
|
||||||
.getDependencies();
|
.getDependencies();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected PackageLoadError cannotFindDependency(String name) {
|
|
||||||
return new PackageLoadError(
|
|
||||||
"cannotFindDependencyInPackage", name, packageAssetUri.getPackageUri().getDisplayName());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -680,15 +662,11 @@ public final class ModuleKeys {
|
|||||||
*
|
*
|
||||||
* <p>The {@code projectpackage} scheme is what project-local dependencies resolve to when
|
* <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
|
* 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 {
|
public static class ProjectPackage extends AbstractPackage {
|
||||||
|
|
||||||
private final PackageAssetUri packageAssetUri;
|
|
||||||
|
|
||||||
ProjectPackage(PackageAssetUri packageAssetUri) {
|
ProjectPackage(PackageAssetUri packageAssetUri) {
|
||||||
super(packageAssetUri.getUri());
|
super(packageAssetUri);
|
||||||
this.packageAssetUri = packageAssetUri;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PackageResolver getPackageResolver() {
|
private PackageResolver getPackageResolver() {
|
||||||
@@ -697,37 +675,36 @@ public final class ModuleKeys {
|
|||||||
return packageResolver;
|
return packageResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProjectDependenciesManager getProjectDepsResolver() {
|
private ProjectDependenciesManager getProjectDependenciesManager() {
|
||||||
var projectDepsManager = VmContext.get(null).getProjectDependenciesManager();
|
var projectDepsManager = VmContext.get(null).getProjectDependenciesManager();
|
||||||
assert projectDepsManager != null;
|
assert projectDepsManager != null;
|
||||||
return projectDepsManager;
|
return projectDepsManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
private @Nullable Path getLocalPath(Dependency dependency, PackageAssetUri packageAssetUri) {
|
private @Nullable URI getLocalUri(Dependency dependency) {
|
||||||
|
return getLocalUri(dependency, packageAssetUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable URI getLocalUri(Dependency dependency, PackageAssetUri assetUri) {
|
||||||
if (!(dependency instanceof LocalDependency localDependency)) {
|
if (!(dependency instanceof LocalDependency localDependency)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return localDependency.resolveAssetPath(
|
return localDependency.resolveAssetUri(
|
||||||
getProjectDepsResolver().getProjectDir(), packageAssetUri);
|
getProjectDependenciesManager().getProjectBaseUri(), assetUri);
|
||||||
}
|
|
||||||
|
|
||||||
private @Nullable Path getLocalPath(Dependency dependency) {
|
|
||||||
if (!(dependency instanceof LocalDependency)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return getLocalPath(dependency, packageAssetUri);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResolvedModuleKey resolve(SecurityManager securityManager)
|
public ResolvedModuleKey resolve(SecurityManager securityManager)
|
||||||
throws IOException, SecurityManagerException {
|
throws IOException, SecurityManagerException {
|
||||||
securityManager.checkResolveModule(packageAssetUri.getUri());
|
var uri = packageAssetUri.getUri();
|
||||||
|
securityManager.checkResolveModule(uri);
|
||||||
var dependency =
|
var dependency =
|
||||||
getProjectDepsResolver().getResolvedDependency(packageAssetUri.getPackageUri());
|
getProjectDependenciesManager().getResolvedDependency(packageAssetUri.getPackageUri());
|
||||||
var path = getLocalPath(dependency);
|
var local = getLocalUri(dependency);
|
||||||
if (path != null) {
|
if (local != null) {
|
||||||
securityManager.checkResolveModule(path.toUri());
|
var resolved =
|
||||||
return ResolvedModuleKeys.file(this, path.toUri(), path);
|
VmContext.get(null).getModuleResolver().resolve(local).resolve(securityManager);
|
||||||
|
return ResolvedModuleKeys.delegated(resolved, this);
|
||||||
}
|
}
|
||||||
var dep = (Dependency.RemoteDependency) dependency;
|
var dep = (Dependency.RemoteDependency) dependency;
|
||||||
assert dep.getChecksums() != null;
|
assert dep.getChecksums() != null;
|
||||||
@@ -741,11 +718,15 @@ public final class ModuleKeys {
|
|||||||
securityManager.checkResolveModule(baseUri);
|
securityManager.checkResolveModule(baseUri);
|
||||||
var packageAssetUri = PackageAssetUri.create(baseUri);
|
var packageAssetUri = PackageAssetUri.create(baseUri);
|
||||||
var dependency =
|
var dependency =
|
||||||
getProjectDepsResolver().getResolvedDependency(packageAssetUri.getPackageUri());
|
getProjectDependenciesManager().getResolvedDependency(packageAssetUri.getPackageUri());
|
||||||
var path = getLocalPath(dependency, packageAssetUri);
|
var local = getLocalUri(dependency, packageAssetUri);
|
||||||
if (path != null) {
|
if (local != null) {
|
||||||
securityManager.checkResolveModule(path.toUri());
|
var moduleKey = VmContext.get(null).getModuleResolver().resolve(local);
|
||||||
return FileResolver.listElements(path);
|
if (!moduleKey.isGlobbable()) {
|
||||||
|
throw new PackageLoadError(
|
||||||
|
"cannotResolveInLocalDependencyNotGlobbable", local.getScheme());
|
||||||
|
}
|
||||||
|
return moduleKey.listElements(securityManager, local);
|
||||||
}
|
}
|
||||||
var dep = (Dependency.RemoteDependency) dependency;
|
var dep = (Dependency.RemoteDependency) dependency;
|
||||||
assert dep.getChecksums() != null;
|
assert dep.getChecksums() != null;
|
||||||
@@ -758,42 +739,36 @@ public final class ModuleKeys {
|
|||||||
securityManager.checkResolveModule(elementUri);
|
securityManager.checkResolveModule(elementUri);
|
||||||
var packageAssetUri = PackageAssetUri.create(elementUri);
|
var packageAssetUri = PackageAssetUri.create(elementUri);
|
||||||
var dependency =
|
var dependency =
|
||||||
getProjectDepsResolver().getResolvedDependency(packageAssetUri.getPackageUri());
|
getProjectDependenciesManager().getResolvedDependency(packageAssetUri.getPackageUri());
|
||||||
var path = getLocalPath(dependency, packageAssetUri);
|
var local = getLocalUri(dependency, packageAssetUri);
|
||||||
if (path != null) {
|
if (local != null) {
|
||||||
securityManager.checkResolveModule(path.toUri());
|
var moduleKey = VmContext.get(null).getModuleResolver().resolve(local);
|
||||||
return FileResolver.hasElement(path);
|
if (!moduleKey.isGlobbable() && !moduleKey.isLocal()) {
|
||||||
|
throw new PackageLoadError(
|
||||||
|
"cannotResolveInLocalDependencyNotGlobbableNorLocal", local.getScheme());
|
||||||
|
}
|
||||||
|
return moduleKey.hasElement(securityManager, local);
|
||||||
}
|
}
|
||||||
var dep = (Dependency.RemoteDependency) dependency;
|
var dep = (Dependency.RemoteDependency) dependency;
|
||||||
assert dep.getChecksums() != null;
|
assert dep.getChecksums() != null;
|
||||||
return getPackageResolver().hasElement(packageAssetUri, dep.getChecksums());
|
return getPackageResolver().hasElement(packageAssetUri, dep.getChecksums());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasFragmentPaths() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Map<String, ? extends Dependency> getDependencies()
|
protected Map<String, ? extends Dependency> getDependencies()
|
||||||
throws IOException, SecurityManagerException {
|
throws IOException, SecurityManagerException {
|
||||||
var packageUri = packageAssetUri.getPackageUri();
|
var packageUri = packageAssetUri.getPackageUri();
|
||||||
var projectResolver = getProjectDepsResolver();
|
var projectResolver = getProjectDependenciesManager();
|
||||||
if (projectResolver.isLocalPackage(packageUri)) {
|
if (projectResolver.isLocalPackage(packageUri)) {
|
||||||
return projectResolver.getLocalPackageDependencies(packageUri);
|
return projectResolver.getLocalPackageDependencies(packageUri);
|
||||||
}
|
}
|
||||||
var dep =
|
var dep =
|
||||||
(Dependency.RemoteDependency) getProjectDepsResolver().getResolvedDependency(packageUri);
|
(Dependency.RemoteDependency)
|
||||||
|
getProjectDependenciesManager().getResolvedDependency(packageUri);
|
||||||
assert dep.getChecksums() != null;
|
assert dep.getChecksums() != null;
|
||||||
var dependencyMetadata =
|
var dependencyMetadata =
|
||||||
getPackageResolver().getDependencyMetadata(packageUri, dep.getChecksums());
|
getPackageResolver().getDependencyMetadata(packageUri, dep.getChecksums());
|
||||||
return projectResolver.getResolvedDependenciesForPackage(packageUri, dependencyMetadata);
|
return projectResolver.getResolvedDependenciesForPackage(packageUri, dependencyMetadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected PackageLoadError cannotFindDependency(String name) {
|
|
||||||
return new PackageLoadError(
|
|
||||||
"cannotFindDependencyInPackage", name, packageAssetUri.getPackageUri().getDisplayName());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,14 @@ package org.pkl.core.module;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import javax.annotation.concurrent.GuardedBy;
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
import org.graalvm.collections.EconomicMap;
|
import org.graalvm.collections.EconomicMap;
|
||||||
|
import org.pkl.core.PklBugException;
|
||||||
|
import org.pkl.core.SecurityManager;
|
||||||
|
import org.pkl.core.SecurityManagerException;
|
||||||
import org.pkl.core.packages.Dependency;
|
import org.pkl.core.packages.Dependency;
|
||||||
import org.pkl.core.packages.DependencyMetadata;
|
import org.pkl.core.packages.DependencyMetadata;
|
||||||
import org.pkl.core.packages.PackageLoadError;
|
import org.pkl.core.packages.PackageLoadError;
|
||||||
@@ -31,8 +32,10 @@ import org.pkl.core.packages.PackageUri;
|
|||||||
import org.pkl.core.project.CanonicalPackageUri;
|
import org.pkl.core.project.CanonicalPackageUri;
|
||||||
import org.pkl.core.project.DeclaredDependencies;
|
import org.pkl.core.project.DeclaredDependencies;
|
||||||
import org.pkl.core.project.ProjectDeps;
|
import org.pkl.core.project.ProjectDeps;
|
||||||
|
import org.pkl.core.runtime.ModuleResolver;
|
||||||
import org.pkl.core.runtime.VmExceptionBuilder;
|
import org.pkl.core.runtime.VmExceptionBuilder;
|
||||||
import org.pkl.core.util.EconomicMaps;
|
import org.pkl.core.util.EconomicMaps;
|
||||||
|
import org.pkl.core.util.IoUtils;
|
||||||
import org.pkl.core.util.json.Json.JsonParseException;
|
import org.pkl.core.util.json.Json.JsonParseException;
|
||||||
|
|
||||||
public final class ProjectDependenciesManager {
|
public final class ProjectDependenciesManager {
|
||||||
@@ -41,7 +44,9 @@ public final class ProjectDependenciesManager {
|
|||||||
public static final String PKL_PROJECT_DEPS_FILENAME = "PklProject.deps.json";
|
public static final String PKL_PROJECT_DEPS_FILENAME = "PklProject.deps.json";
|
||||||
|
|
||||||
private final DeclaredDependencies declaredDependencies;
|
private final DeclaredDependencies declaredDependencies;
|
||||||
private final Path projectDir;
|
private final URI projectBaseUri;
|
||||||
|
private final ModuleResolver moduleResolver;
|
||||||
|
private final SecurityManager securityManager;
|
||||||
|
|
||||||
@GuardedBy("lock")
|
@GuardedBy("lock")
|
||||||
private ProjectDeps projectDeps;
|
private ProjectDeps projectDeps;
|
||||||
@@ -59,13 +64,21 @@ public final class ProjectDependenciesManager {
|
|||||||
|
|
||||||
private final Object lock = new Object();
|
private final Object lock = new Object();
|
||||||
|
|
||||||
public ProjectDependenciesManager(DeclaredDependencies declaredDependencies) {
|
public ProjectDependenciesManager(
|
||||||
|
DeclaredDependencies declaredDependencies,
|
||||||
|
ModuleResolver moduleResolver,
|
||||||
|
SecurityManager securityManager) {
|
||||||
this.declaredDependencies = declaredDependencies;
|
this.declaredDependencies = declaredDependencies;
|
||||||
this.projectDir = Path.of(declaredDependencies.getProjectFileUri()).getParent();
|
// new URI("scheme://host/a/b/c.txt").resolve(".") == new URI("scheme://host/a/b/")
|
||||||
|
this.projectBaseUri = IoUtils.resolve(declaredDependencies.getProjectFileUri(), ".");
|
||||||
|
this.moduleResolver = moduleResolver;
|
||||||
|
this.securityManager = securityManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasPath(Path path) {
|
public boolean hasUri(URI uri) {
|
||||||
return path.startsWith(projectDir);
|
return projectBaseUri.getScheme().equals(uri.getScheme())
|
||||||
|
&& Objects.equals(projectBaseUri.getAuthority(), uri.getAuthority())
|
||||||
|
&& uri.getPath().startsWith(projectBaseUri.getPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ensureDependenciesInitialized() {
|
private void ensureDependenciesInitialized() {
|
||||||
@@ -194,30 +207,39 @@ public final class ProjectDependenciesManager {
|
|||||||
return dep;
|
return dep;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Path getProjectDir() {
|
public URI getProjectBaseUri() {
|
||||||
return projectDir;
|
return projectBaseUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Path getProjectDepsFile() {
|
public URI getProjectDepsFileUri() {
|
||||||
return projectDir.resolve(PKL_PROJECT_DEPS_FILENAME);
|
return IoUtils.resolve(projectBaseUri, PKL_PROJECT_DEPS_FILENAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public URI getProjectFileUri() {
|
||||||
|
return declaredDependencies.getProjectFileUri();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProjectDeps getProjectDeps() {
|
private ProjectDeps getProjectDeps() {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
if (projectDeps == null) {
|
if (projectDeps == null) {
|
||||||
var depsPath = getProjectDepsFile();
|
var depsUri = getProjectDepsFileUri();
|
||||||
if (!Files.exists(depsPath)) {
|
var moduleKey = moduleResolver.resolve(depsUri);
|
||||||
throw new VmExceptionBuilder()
|
|
||||||
.evalError("missingProjectDepsJson", projectDir.toUri())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
projectDeps = ProjectDeps.parse(depsPath);
|
// treat PklProject.deps.json as a module read, rather than introduce a new API.
|
||||||
} catch (IOException | URISyntaxException | JsonParseException e) {
|
var depsJson = moduleKey.resolve(securityManager).loadSource();
|
||||||
|
projectDeps = ProjectDeps.parse(depsJson);
|
||||||
|
} catch (IOException e) {
|
||||||
throw new VmExceptionBuilder()
|
throw new VmExceptionBuilder()
|
||||||
.evalError("invalidProjectDepsJson", depsPath.toUri(), e.getMessage())
|
.evalError("cannotLoadProjectDepsJson", depsUri)
|
||||||
.withCause(e)
|
.withCause(e)
|
||||||
|
.withHint(e.getMessage() != null ? e.getMessage() : ("Encountered error: " + e))
|
||||||
.build();
|
.build();
|
||||||
|
} catch (JsonParseException e) {
|
||||||
|
throw new VmExceptionBuilder()
|
||||||
|
.evalError("invalidProjectDepsJson", depsUri, e.getMessage())
|
||||||
|
.build();
|
||||||
|
} catch (SecurityManagerException e) {
|
||||||
|
throw PklBugException.unreachableCode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return projectDeps;
|
return projectDeps;
|
||||||
|
|||||||
@@ -54,6 +54,14 @@ public final class ResolvedModuleKeys {
|
|||||||
return new Virtual(original, uri, sourceText, cached);
|
return new Virtual(original, uri, sourceText, cached);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a resolved module key that behaves like {@code delegate}, except with {@code original}
|
||||||
|
* as its original module key.
|
||||||
|
*/
|
||||||
|
public static ResolvedModuleKey delegated(ResolvedModuleKey delegate, ModuleKey original) {
|
||||||
|
return new Delegated(delegate, original);
|
||||||
|
}
|
||||||
|
|
||||||
private static class File implements ResolvedModuleKey {
|
private static class File implements ResolvedModuleKey {
|
||||||
final ModuleKey original;
|
final ModuleKey original;
|
||||||
final URI uri;
|
final URI uri;
|
||||||
@@ -145,4 +153,30 @@ public final class ResolvedModuleKeys {
|
|||||||
return sourceText;
|
return sourceText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class Delegated implements ResolvedModuleKey {
|
||||||
|
|
||||||
|
private final ResolvedModuleKey delegate;
|
||||||
|
private final ModuleKey original;
|
||||||
|
|
||||||
|
Delegated(ResolvedModuleKey delegate, ModuleKey original) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.original = original;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ModuleKey getOriginal() {
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URI getUri() {
|
||||||
|
return delegate.getUri();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String loadSource() throws IOException {
|
||||||
|
return delegate.loadSource();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,11 @@
|
|||||||
*/
|
*/
|
||||||
package org.pkl.core.packages;
|
package org.pkl.core.packages;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import org.pkl.core.Version;
|
import org.pkl.core.Version;
|
||||||
|
import org.pkl.core.util.IoUtils;
|
||||||
import org.pkl.core.util.Nullable;
|
import org.pkl.core.util.Nullable;
|
||||||
|
|
||||||
public abstract class Dependency {
|
public abstract class Dependency {
|
||||||
@@ -48,10 +50,10 @@ public abstract class Dependency {
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Path resolveAssetPath(Path projectDir, PackageAssetUri packageAssetUri) {
|
public URI resolveAssetUri(URI projectBaseUri, PackageAssetUri packageAssetUri) {
|
||||||
// drop 1 to remove leading `/`
|
// drop 1 to remove leading `/`
|
||||||
var assetPath = packageAssetUri.getAssetPath().substring(1);
|
var assetPath = packageAssetUri.getAssetPath().substring(1);
|
||||||
return projectDir.resolve(path).resolve(assetPath);
|
return projectBaseUri.resolve(IoUtils.toNormalizedPathString(path.resolve(assetPath)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ package org.pkl.core.project;
|
|||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
|
import java.nio.file.FileSystemNotFoundException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -27,6 +28,7 @@ import java.util.regex.Pattern;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import org.pkl.core.Composite;
|
import org.pkl.core.Composite;
|
||||||
import org.pkl.core.Duration;
|
import org.pkl.core.Duration;
|
||||||
|
import org.pkl.core.Evaluator;
|
||||||
import org.pkl.core.EvaluatorBuilder;
|
import org.pkl.core.EvaluatorBuilder;
|
||||||
import org.pkl.core.ModuleSource;
|
import org.pkl.core.ModuleSource;
|
||||||
import org.pkl.core.PClassInfo;
|
import org.pkl.core.PClassInfo;
|
||||||
@@ -41,9 +43,11 @@ import org.pkl.core.Version;
|
|||||||
import org.pkl.core.module.ModuleKeyFactories;
|
import org.pkl.core.module.ModuleKeyFactories;
|
||||||
import org.pkl.core.packages.Checksums;
|
import org.pkl.core.packages.Checksums;
|
||||||
import org.pkl.core.packages.Dependency.RemoteDependency;
|
import org.pkl.core.packages.Dependency.RemoteDependency;
|
||||||
|
import org.pkl.core.packages.PackageLoadError;
|
||||||
import org.pkl.core.packages.PackageUri;
|
import org.pkl.core.packages.PackageUri;
|
||||||
import org.pkl.core.packages.PackageUtils;
|
import org.pkl.core.packages.PackageUtils;
|
||||||
import org.pkl.core.resource.ResourceReaders;
|
import org.pkl.core.resource.ResourceReaders;
|
||||||
|
import org.pkl.core.util.IoUtils;
|
||||||
import org.pkl.core.util.Nullable;
|
import org.pkl.core.util.Nullable;
|
||||||
|
|
||||||
/** Java representation of module {@code pkl.Project}. */
|
/** Java representation of module {@code pkl.Project}. */
|
||||||
@@ -52,8 +56,8 @@ public final class Project {
|
|||||||
private final DeclaredDependencies dependencies;
|
private final DeclaredDependencies dependencies;
|
||||||
private final EvaluatorSettings evaluatorSettings;
|
private final EvaluatorSettings evaluatorSettings;
|
||||||
private final URI projectFileUri;
|
private final URI projectFileUri;
|
||||||
private final Path projectDir;
|
private final URI projectBaseUri;
|
||||||
private final List<Path> tests;
|
private final List<URI> tests;
|
||||||
private final Map<String, Project> localProjectDependencies;
|
private final Map<String, Project> localProjectDependencies;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,10 +85,7 @@ public final class Project {
|
|||||||
.addEnvironmentVariables(envVars)
|
.addEnvironmentVariables(envVars)
|
||||||
.setTimeout(timeout)
|
.setTimeout(timeout)
|
||||||
.build()) {
|
.build()) {
|
||||||
var output = evaluator.evaluateOutputValueAs(ModuleSource.path(path), PClassInfo.Project);
|
return load(evaluator, ModuleSource.path(path));
|
||||||
return Project.parseProject(output);
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
throw new PklException(e.getMessage(), e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +104,31 @@ public final class Project {
|
|||||||
return loadFromPath(path, SecurityManagers.defaultManager, null);
|
return loadFromPath(path, SecurityManagers.defaultManager, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Loads a project from the given source. */
|
||||||
|
public static Project load(ModuleSource moduleSource) {
|
||||||
|
try (var evaluator =
|
||||||
|
EvaluatorBuilder.unconfigured()
|
||||||
|
.setSecurityManager(SecurityManagers.defaultManager)
|
||||||
|
.setStackFrameTransformer(StackFrameTransformers.defaultTransformer)
|
||||||
|
.addModuleKeyFactory(ModuleKeyFactories.standardLibrary)
|
||||||
|
.addModuleKeyFactory(ModuleKeyFactories.file)
|
||||||
|
.addModuleKeyFactory(ModuleKeyFactories.classPath(Project.class.getClassLoader()))
|
||||||
|
.addResourceReader(ResourceReaders.environmentVariable())
|
||||||
|
.addResourceReader(ResourceReaders.file())
|
||||||
|
.build()) {
|
||||||
|
return load(evaluator, moduleSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Project load(Evaluator evaluator, ModuleSource moduleSource) {
|
||||||
|
try {
|
||||||
|
var output = evaluator.evaluateOutputValueAs(moduleSource, PClassInfo.Project);
|
||||||
|
return Project.parseProject(output);
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
throw new PklException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static DeclaredDependencies parseDependencies(
|
private static DeclaredDependencies parseDependencies(
|
||||||
PObject module, URI projectFileUri, @Nullable PackageUri packageUri)
|
PObject module, URI projectFileUri, @Nullable PackageUri packageUri)
|
||||||
throws URISyntaxException {
|
throws URISyntaxException {
|
||||||
@@ -143,7 +169,7 @@ public final class Project {
|
|||||||
var pkgObj = getNullableProperty(module, "package");
|
var pkgObj = getNullableProperty(module, "package");
|
||||||
var projectFileUri = URI.create((String) module.getProperty("projectFileUri"));
|
var projectFileUri = URI.create((String) module.getProperty("projectFileUri"));
|
||||||
var dependencies = parseDependencies(module, projectFileUri, null);
|
var dependencies = parseDependencies(module, projectFileUri, null);
|
||||||
var projectDir = Path.of(projectFileUri).getParent();
|
var projectBaseUri = IoUtils.resolve(projectFileUri, ".");
|
||||||
Package pkg = null;
|
Package pkg = null;
|
||||||
if (pkgObj != null) {
|
if (pkgObj != null) {
|
||||||
pkg = parsePackage((PObject) pkgObj);
|
pkg = parsePackage((PObject) pkgObj);
|
||||||
@@ -152,12 +178,12 @@ public final class Project {
|
|||||||
getProperty(
|
getProperty(
|
||||||
module,
|
module,
|
||||||
"evaluatorSettings",
|
"evaluatorSettings",
|
||||||
(settings) -> parseEvaluatorSettings(settings, projectDir));
|
(settings) -> parseEvaluatorSettings(settings, projectBaseUri));
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
var testPathStrs = (List<String>) getProperty(module, "tests");
|
var testPathStrs = (List<String>) getProperty(module, "tests");
|
||||||
var tests =
|
var tests =
|
||||||
testPathStrs.stream()
|
testPathStrs.stream()
|
||||||
.map((it) -> projectDir.resolve(it).normalize())
|
.map((it) -> projectBaseUri.resolve(it).normalize())
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
var localProjectDependencies = parseLocalProjectDependencies(module);
|
var localProjectDependencies = parseLocalProjectDependencies(module);
|
||||||
return new Project(
|
return new Project(
|
||||||
@@ -165,7 +191,7 @@ public final class Project {
|
|||||||
dependencies,
|
dependencies,
|
||||||
evaluatorSettings,
|
evaluatorSettings,
|
||||||
projectFileUri,
|
projectFileUri,
|
||||||
projectDir,
|
projectBaseUri,
|
||||||
tests,
|
tests,
|
||||||
localProjectDependencies);
|
localProjectDependencies);
|
||||||
}
|
}
|
||||||
@@ -185,7 +211,7 @@ public final class Project {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private static EvaluatorSettings parseEvaluatorSettings(Object settings, Path projectDir) {
|
private static EvaluatorSettings parseEvaluatorSettings(Object settings, URI projectBaseUri) {
|
||||||
var pSettings = (PObject) settings;
|
var pSettings = (PObject) settings;
|
||||||
var externalProperties = getNullableProperty(pSettings, "externalProperties", Project::asMap);
|
var externalProperties = getNullableProperty(pSettings, "externalProperties", Project::asMap);
|
||||||
var env = getNullableProperty(pSettings, "env", Project::asMap);
|
var env = getNullableProperty(pSettings, "env", Project::asMap);
|
||||||
@@ -194,16 +220,18 @@ public final class Project {
|
|||||||
getNullableProperty(pSettings, "allowedResources", Project::asPatternList);
|
getNullableProperty(pSettings, "allowedResources", Project::asPatternList);
|
||||||
var noCache = (Boolean) getNullableProperty(pSettings, "noCache");
|
var noCache = (Boolean) getNullableProperty(pSettings, "noCache");
|
||||||
var modulePathStrs = (List<String>) getNullableProperty(pSettings, "modulePath");
|
var modulePathStrs = (List<String>) getNullableProperty(pSettings, "modulePath");
|
||||||
|
var timeout = (Duration) getNullableProperty(pSettings, "timeout");
|
||||||
|
|
||||||
List<Path> modulePath = null;
|
List<Path> modulePath = null;
|
||||||
if (modulePathStrs != null) {
|
if (modulePathStrs != null) {
|
||||||
modulePath =
|
modulePath =
|
||||||
modulePathStrs.stream()
|
modulePathStrs.stream()
|
||||||
.map((it) -> projectDir.resolve(it).normalize())
|
.map((it) -> resolveNullablePath(it, projectBaseUri, "modulePath"))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
var timeout = (Duration) getNullableProperty(pSettings, "timeout");
|
|
||||||
var moduleCacheDir = getNullablePath(pSettings, "moduleCacheDir", projectDir);
|
var moduleCacheDir = getNullablePath(pSettings, "moduleCacheDir", projectBaseUri);
|
||||||
var rootDir = getNullablePath(pSettings, "rootDir", projectDir);
|
var rootDir = getNullablePath(pSettings, "rootDir", projectBaseUri);
|
||||||
return new EvaluatorSettings(
|
return new EvaluatorSettings(
|
||||||
externalProperties,
|
externalProperties,
|
||||||
env,
|
env,
|
||||||
@@ -261,10 +289,28 @@ public final class Project {
|
|||||||
return new URI((String) value);
|
return new URI((String) value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a path string against projectBaseUri.
|
||||||
|
*
|
||||||
|
* @throws PackageLoadError if projectBaseUri is not a {@code file:} URI.
|
||||||
|
*/
|
||||||
|
private static @Nullable Path resolveNullablePath(
|
||||||
|
@Nullable String path, URI projectBaseUri, String propertyName) {
|
||||||
|
if (path == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Path.of(projectBaseUri).resolve(path).normalize();
|
||||||
|
} catch (FileSystemNotFoundException e) {
|
||||||
|
throw new PackageLoadError(
|
||||||
|
"relativePathPropertyDefinedByProjectFromNonFileUri", projectBaseUri, propertyName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static @Nullable Path getNullablePath(
|
private static @Nullable Path getNullablePath(
|
||||||
Composite object, String propertyName, Path projectDir) {
|
Composite object, String propertyName, URI projectBaseUri) {
|
||||||
return getNullableProperty(
|
return resolveNullablePath(
|
||||||
object, propertyName, (obj) -> projectDir.resolve((String) obj).normalize());
|
(String) getNullableProperty(object, propertyName), projectBaseUri, propertyName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@@ -309,14 +355,14 @@ public final class Project {
|
|||||||
DeclaredDependencies dependencies,
|
DeclaredDependencies dependencies,
|
||||||
EvaluatorSettings evaluatorSettings,
|
EvaluatorSettings evaluatorSettings,
|
||||||
URI projectFileUri,
|
URI projectFileUri,
|
||||||
Path projectDir,
|
URI projectBaseUri,
|
||||||
List<Path> tests,
|
List<URI> tests,
|
||||||
Map<String, Project> localProjectDependencies) {
|
Map<String, Project> localProjectDependencies) {
|
||||||
this.pkg = pkg;
|
this.pkg = pkg;
|
||||||
this.dependencies = dependencies;
|
this.dependencies = dependencies;
|
||||||
this.evaluatorSettings = evaluatorSettings;
|
this.evaluatorSettings = evaluatorSettings;
|
||||||
this.projectFileUri = projectFileUri;
|
this.projectFileUri = projectFileUri;
|
||||||
this.projectDir = projectDir;
|
this.projectBaseUri = projectBaseUri;
|
||||||
this.tests = tests;
|
this.tests = tests;
|
||||||
this.localProjectDependencies = localProjectDependencies;
|
this.localProjectDependencies = localProjectDependencies;
|
||||||
}
|
}
|
||||||
@@ -334,7 +380,16 @@ public final class Project {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<Path> getTests() {
|
public List<Path> getTests() {
|
||||||
return tests;
|
return tests.stream()
|
||||||
|
.map(
|
||||||
|
(it) -> {
|
||||||
|
try {
|
||||||
|
return Path.of(it);
|
||||||
|
} catch (FileSystemNotFoundException e) {
|
||||||
|
throw new PackageLoadError("invalidUsageOfProjectFromNonFileUri");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -366,11 +421,17 @@ public final class Project {
|
|||||||
return localProjectDependencies;
|
return localProjectDependencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public URI getProjectBaseUri() {
|
||||||
|
return projectBaseUri;
|
||||||
|
}
|
||||||
|
|
||||||
public Path getProjectDir() {
|
public Path getProjectDir() {
|
||||||
return projectDir;
|
assert projectBaseUri.getScheme().equalsIgnoreCase("file");
|
||||||
|
return Path.of(projectBaseUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class EvaluatorSettings {
|
public static class EvaluatorSettings {
|
||||||
|
|
||||||
private final @Nullable Map<String, String> externalProperties;
|
private final @Nullable Map<String, String> externalProperties;
|
||||||
private final @Nullable Map<String, String> env;
|
private final @Nullable Map<String, String> env;
|
||||||
private final @Nullable List<Pattern> allowedModules;
|
private final @Nullable List<Pattern> allowedModules;
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import org.pkl.core.SecurityManagerException;
|
|||||||
import org.pkl.core.StackFrameTransformer;
|
import org.pkl.core.StackFrameTransformer;
|
||||||
import org.pkl.core.ast.builder.ImportsAndReadsParser;
|
import org.pkl.core.ast.builder.ImportsAndReadsParser;
|
||||||
import org.pkl.core.http.HttpClient;
|
import org.pkl.core.http.HttpClient;
|
||||||
|
import org.pkl.core.module.ModuleKeyFactories;
|
||||||
import org.pkl.core.module.ModuleKeys;
|
import org.pkl.core.module.ModuleKeys;
|
||||||
import org.pkl.core.module.ProjectDependenciesManager;
|
import org.pkl.core.module.ProjectDependenciesManager;
|
||||||
import org.pkl.core.module.ResolvedModuleKeys;
|
import org.pkl.core.module.ResolvedModuleKeys;
|
||||||
@@ -55,6 +56,7 @@ import org.pkl.core.packages.DependencyMetadata;
|
|||||||
import org.pkl.core.packages.PackageLoadError;
|
import org.pkl.core.packages.PackageLoadError;
|
||||||
import org.pkl.core.packages.PackageResolver;
|
import org.pkl.core.packages.PackageResolver;
|
||||||
import org.pkl.core.packages.PackageUri;
|
import org.pkl.core.packages.PackageUri;
|
||||||
|
import org.pkl.core.runtime.ModuleResolver;
|
||||||
import org.pkl.core.runtime.VmExceptionBuilder;
|
import org.pkl.core.runtime.VmExceptionBuilder;
|
||||||
import org.pkl.core.util.ByteArrayUtils;
|
import org.pkl.core.util.ByteArrayUtils;
|
||||||
import org.pkl.core.util.ErrorMessages;
|
import org.pkl.core.util.ErrorMessages;
|
||||||
@@ -98,6 +100,7 @@ public final class ProjectPackager {
|
|||||||
private final Path workingDir;
|
private final Path workingDir;
|
||||||
private final String outputPathPattern;
|
private final String outputPathPattern;
|
||||||
private final StackFrameTransformer stackFrameTransformer;
|
private final StackFrameTransformer stackFrameTransformer;
|
||||||
|
private final SecurityManager securityManager;
|
||||||
private final PackageResolver packageResolver;
|
private final PackageResolver packageResolver;
|
||||||
private final boolean skipPublishCheck;
|
private final boolean skipPublishCheck;
|
||||||
private final Writer outputWriter;
|
private final Writer outputWriter;
|
||||||
@@ -115,6 +118,7 @@ public final class ProjectPackager {
|
|||||||
this.workingDir = workingDir;
|
this.workingDir = workingDir;
|
||||||
this.outputPathPattern = outputPathPattern;
|
this.outputPathPattern = outputPathPattern;
|
||||||
this.stackFrameTransformer = stackFrameTransformer;
|
this.stackFrameTransformer = stackFrameTransformer;
|
||||||
|
this.securityManager = securityManager;
|
||||||
// intentionally use InMemoryPackageResolver
|
// intentionally use InMemoryPackageResolver
|
||||||
this.packageResolver = PackageResolver.getInstance(securityManager, httpClient, null);
|
this.packageResolver = PackageResolver.getInstance(securityManager, httpClient, null);
|
||||||
this.skipPublishCheck = skipPublishCheck;
|
this.skipPublishCheck = skipPublishCheck;
|
||||||
@@ -226,7 +230,12 @@ public final class ProjectPackager {
|
|||||||
new HashMap<String, RemoteDependency>(
|
new HashMap<String, RemoteDependency>(
|
||||||
project.getDependencies().getLocalDependencies().size()
|
project.getDependencies().getLocalDependencies().size()
|
||||||
+ project.getDependencies().getRemoteDependencies().size());
|
+ project.getDependencies().getRemoteDependencies().size());
|
||||||
var projectDependenciesManager = new ProjectDependenciesManager(project.getDependencies());
|
// module resolver is only used for reading PklProject.deps.json, so provide one that reads
|
||||||
|
// files.
|
||||||
|
var moduleResolver = new ModuleResolver(List.of(ModuleKeyFactories.file));
|
||||||
|
var projectDependenciesManager =
|
||||||
|
new ProjectDependenciesManager(
|
||||||
|
project.getDependencies(), moduleResolver, this.securityManager);
|
||||||
for (var entry : project.getDependencies().getRemoteDependencies().entrySet()) {
|
for (var entry : project.getDependencies().getRemoteDependencies().entrySet()) {
|
||||||
var resolved =
|
var resolved =
|
||||||
(RemoteDependency)
|
(RemoteDependency)
|
||||||
|
|||||||
@@ -89,7 +89,9 @@ public class ReplServer implements AutoCloseable {
|
|||||||
var languageRef = new MutableReference<VmLanguage>(null);
|
var languageRef = new MutableReference<VmLanguage>(null);
|
||||||
packageResolver = PackageResolver.getInstance(securityManager, httpClient, moduleCacheDir);
|
packageResolver = PackageResolver.getInstance(securityManager, httpClient, moduleCacheDir);
|
||||||
projectDependenciesManager =
|
projectDependenciesManager =
|
||||||
projectDependencies == null ? null : new ProjectDependenciesManager(projectDependencies);
|
projectDependencies == null
|
||||||
|
? null
|
||||||
|
: new ProjectDependenciesManager(projectDependencies, moduleResolver, securityManager);
|
||||||
polyglotContext =
|
polyglotContext =
|
||||||
VmUtils.createContext(
|
VmUtils.createContext(
|
||||||
() -> {
|
() -> {
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import java.net.URISyntaxException;
|
|||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpResponse.BodyHandlers;
|
import java.net.http.HttpResponse.BodyHandlers;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -39,6 +38,7 @@ import org.pkl.core.packages.Dependency.LocalDependency;
|
|||||||
import org.pkl.core.packages.PackageAssetUri;
|
import org.pkl.core.packages.PackageAssetUri;
|
||||||
import org.pkl.core.packages.PackageResolver;
|
import org.pkl.core.packages.PackageResolver;
|
||||||
import org.pkl.core.runtime.VmContext;
|
import org.pkl.core.runtime.VmContext;
|
||||||
|
import org.pkl.core.runtime.VmExceptionBuilder;
|
||||||
import org.pkl.core.util.ErrorMessages;
|
import org.pkl.core.util.ErrorMessages;
|
||||||
import org.pkl.core.util.HttpUtils;
|
import org.pkl.core.util.HttpUtils;
|
||||||
import org.pkl.core.util.IoUtils;
|
import org.pkl.core.util.IoUtils;
|
||||||
@@ -486,10 +486,9 @@ public final class ResourceReaders {
|
|||||||
throws IOException, URISyntaxException, SecurityManagerException {
|
throws IOException, URISyntaxException, SecurityManagerException {
|
||||||
var assetUri = new PackageAssetUri(uri);
|
var assetUri = new PackageAssetUri(uri);
|
||||||
var dependency = getProjectDepsResolver().getResolvedDependency(assetUri.getPackageUri());
|
var dependency = getProjectDepsResolver().getResolvedDependency(assetUri.getPackageUri());
|
||||||
var path = getLocalPath(dependency, assetUri);
|
var local = getLocalUri(dependency, assetUri);
|
||||||
if (path != null) {
|
if (local != null) {
|
||||||
var bytes = Files.readAllBytes(path);
|
return VmContext.get(null).getResourceManager().read(local, null);
|
||||||
return Optional.of(new Resource(uri, bytes));
|
|
||||||
}
|
}
|
||||||
var remoteDep = (Dependency.RemoteDependency) dependency;
|
var remoteDep = (Dependency.RemoteDependency) dependency;
|
||||||
var bytes = getPackageResolver().getBytes(assetUri, true, remoteDep.getChecksums());
|
var bytes = getPackageResolver().getBytes(assetUri, true, remoteDep.getChecksums());
|
||||||
@@ -518,9 +517,15 @@ public final class ResourceReaders {
|
|||||||
var packageAssetUri = PackageAssetUri.create(baseUri);
|
var packageAssetUri = PackageAssetUri.create(baseUri);
|
||||||
var dependency =
|
var dependency =
|
||||||
getProjectDepsResolver().getResolvedDependency(packageAssetUri.getPackageUri());
|
getProjectDepsResolver().getResolvedDependency(packageAssetUri.getPackageUri());
|
||||||
var path = getLocalPath(dependency, packageAssetUri);
|
var local = getLocalUri(dependency, packageAssetUri);
|
||||||
if (path != null) {
|
if (local != null) {
|
||||||
return FileResolver.listElements(path);
|
var reader = VmContext.get(null).getResourceManager().getResourceReader(local);
|
||||||
|
if (reader == null) {
|
||||||
|
throw new VmExceptionBuilder()
|
||||||
|
.evalError("noResourceReaderRegistered", local.getScheme())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
return reader.listElements(securityManager, local);
|
||||||
}
|
}
|
||||||
var remoteDep = (Dependency.RemoteDependency) dependency;
|
var remoteDep = (Dependency.RemoteDependency) dependency;
|
||||||
return getPackageResolver()
|
return getPackageResolver()
|
||||||
@@ -534,9 +539,15 @@ public final class ResourceReaders {
|
|||||||
var packageAssetUri = PackageAssetUri.create(elementUri);
|
var packageAssetUri = PackageAssetUri.create(elementUri);
|
||||||
var dependency =
|
var dependency =
|
||||||
getProjectDepsResolver().getResolvedDependency(packageAssetUri.getPackageUri());
|
getProjectDepsResolver().getResolvedDependency(packageAssetUri.getPackageUri());
|
||||||
var path = getLocalPath(dependency, packageAssetUri);
|
var local = getLocalUri(dependency, packageAssetUri);
|
||||||
if (path != null) {
|
if (local != null) {
|
||||||
return FileResolver.hasElement(path);
|
var reader = VmContext.get(null).getResourceManager().getResourceReader(local);
|
||||||
|
if (reader == null) {
|
||||||
|
throw new VmExceptionBuilder()
|
||||||
|
.evalError("noResourceReaderRegistered", local.getScheme())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
return reader.hasElement(securityManager, local);
|
||||||
}
|
}
|
||||||
var remoteDep = (Dependency.RemoteDependency) dependency;
|
var remoteDep = (Dependency.RemoteDependency) dependency;
|
||||||
return getPackageResolver()
|
return getPackageResolver()
|
||||||
@@ -555,12 +566,12 @@ public final class ResourceReaders {
|
|||||||
return projectDepsManager;
|
return projectDepsManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
private @Nullable Path getLocalPath(Dependency dependency, PackageAssetUri packageAssetUri) {
|
private @Nullable URI getLocalUri(Dependency dependency, PackageAssetUri packageAssetUri) {
|
||||||
if (!(dependency instanceof LocalDependency localDependency)) {
|
if (!(dependency instanceof LocalDependency localDependency)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return localDependency.resolveAssetPath(
|
return localDependency.resolveAssetUri(
|
||||||
getProjectDepsResolver().getProjectDir(), packageAssetUri);
|
getProjectDepsResolver().getProjectBaseUri(), packageAssetUri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import org.pkl.core.packages.PackageLoadError;
|
|||||||
import org.pkl.core.resource.Resource;
|
import org.pkl.core.resource.Resource;
|
||||||
import org.pkl.core.resource.ResourceReader;
|
import org.pkl.core.resource.ResourceReader;
|
||||||
import org.pkl.core.stdlib.VmObjectFactory;
|
import org.pkl.core.stdlib.VmObjectFactory;
|
||||||
|
import org.pkl.core.util.Nullable;
|
||||||
|
|
||||||
public final class ResourceManager {
|
public final class ResourceManager {
|
||||||
private final Map<String, ResourceReader> resourceReaders = new HashMap<>();
|
private final Map<String, ResourceReader> resourceReaders = new HashMap<>();
|
||||||
@@ -67,17 +68,23 @@ public final class ResourceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@TruffleBoundary
|
@TruffleBoundary
|
||||||
public Optional<Object> read(URI resourceUri, Node readNode) {
|
public Optional<Object> read(URI resourceUri, @Nullable Node readNode) {
|
||||||
return resources.computeIfAbsent(
|
return resources.computeIfAbsent(
|
||||||
resourceUri.normalize(),
|
resourceUri.normalize(),
|
||||||
uri -> {
|
uri -> {
|
||||||
try {
|
try {
|
||||||
securityManager.checkReadResource(uri);
|
securityManager.checkReadResource(uri);
|
||||||
} catch (SecurityManagerException e) {
|
} catch (SecurityManagerException e) {
|
||||||
throw new VmExceptionBuilder().withCause(e).withLocation(readNode).build();
|
throw new VmExceptionBuilder().withCause(e).withOptionalLocation(readNode).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
var reader = getReader(resourceUri, readNode);
|
var reader = resourceReaders.get(uri.getScheme());
|
||||||
|
if (reader == null) {
|
||||||
|
throw new VmExceptionBuilder()
|
||||||
|
.withOptionalLocation(readNode)
|
||||||
|
.evalError("noResourceReaderRegistered", resourceUri.getScheme())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
Optional<Object> resource;
|
Optional<Object> resource;
|
||||||
try {
|
try {
|
||||||
@@ -86,16 +93,16 @@ public final class ResourceManager {
|
|||||||
throw new VmExceptionBuilder()
|
throw new VmExceptionBuilder()
|
||||||
.evalError("ioErrorReadingResource", uri)
|
.evalError("ioErrorReadingResource", uri)
|
||||||
.withCause(e)
|
.withCause(e)
|
||||||
.withLocation(readNode)
|
.withOptionalLocation(readNode)
|
||||||
.build();
|
.build();
|
||||||
} catch (URISyntaxException e) {
|
} catch (URISyntaxException e) {
|
||||||
throw new VmExceptionBuilder()
|
throw new VmExceptionBuilder()
|
||||||
.evalError("invalidResourceUri", resourceUri)
|
.evalError("invalidResourceUri", resourceUri)
|
||||||
.withHint(e.getReason())
|
.withHint(e.getReason())
|
||||||
.withLocation(readNode)
|
.withOptionalLocation(readNode)
|
||||||
.build();
|
.build();
|
||||||
} catch (SecurityManagerException | PackageLoadError | HttpClientInitException e) {
|
} catch (SecurityManagerException | PackageLoadError | HttpClientInitException e) {
|
||||||
throw new VmExceptionBuilder().withCause(e).withLocation(readNode).build();
|
throw new VmExceptionBuilder().withCause(e).withOptionalLocation(readNode).build();
|
||||||
}
|
}
|
||||||
if (resource.isEmpty()) return resource;
|
if (resource.isEmpty()) return resource;
|
||||||
|
|
||||||
@@ -108,8 +115,16 @@ public final class ResourceManager {
|
|||||||
|
|
||||||
throw new VmExceptionBuilder()
|
throw new VmExceptionBuilder()
|
||||||
.evalError("unsupportedResourceType", reader.getClass().getName(), res.getClass())
|
.evalError("unsupportedResourceType", reader.getClass().getName(), res.getClass())
|
||||||
.withLocation(readNode)
|
.withOptionalLocation(readNode)
|
||||||
.build();
|
.build();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link ResourceReader} registered to read the resource at {@code baseUri}, or {@code
|
||||||
|
* null} if there is none.
|
||||||
|
*/
|
||||||
|
public @Nullable ResourceReader getResourceReader(URI baseUri) {
|
||||||
|
return resourceReaders.get(baseUri.getScheme());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ public final class VmExceptionBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public VmExceptionBuilder withHint(String hint) {
|
public VmExceptionBuilder withHint(@Nullable String hint) {
|
||||||
this.hint = hint;
|
this.hint = hint;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import java.util.Map;
|
|||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import org.pkl.core.PklBugException;
|
||||||
import org.pkl.core.SecurityManager;
|
import org.pkl.core.SecurityManager;
|
||||||
import org.pkl.core.SecurityManagerException;
|
import org.pkl.core.SecurityManagerException;
|
||||||
import org.pkl.core.module.ModuleKey;
|
import org.pkl.core.module.ModuleKey;
|
||||||
@@ -490,7 +491,14 @@ public final class GlobResolver {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
var baseUri = enclosingModuleKey.resolveUri(enclosingUri, URI.create(basePath));
|
URI baseUri;
|
||||||
|
try {
|
||||||
|
baseUri = IoUtils.resolve(securityManager, enclosingModuleKey, URI.create(basePath));
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
// assertion: this is only thrown if the pattern starts with a triple-dot import.
|
||||||
|
// the language will throw an error if glob imports is combined with triple-dots.
|
||||||
|
throw new PklBugException(e);
|
||||||
|
}
|
||||||
|
|
||||||
resolveHierarchicalGlob(
|
resolveHierarchicalGlob(
|
||||||
securityManager,
|
securityManager,
|
||||||
|
|||||||
@@ -39,7 +39,9 @@ import org.pkl.core.Platform;
|
|||||||
import org.pkl.core.SecurityManager;
|
import org.pkl.core.SecurityManager;
|
||||||
import org.pkl.core.SecurityManagerException;
|
import org.pkl.core.SecurityManagerException;
|
||||||
import org.pkl.core.module.ModuleKey;
|
import org.pkl.core.module.ModuleKey;
|
||||||
|
import org.pkl.core.packages.PackageLoadError;
|
||||||
import org.pkl.core.runtime.ReaderBase;
|
import org.pkl.core.runtime.ReaderBase;
|
||||||
|
import org.pkl.core.runtime.VmContext;
|
||||||
import org.pkl.core.runtime.VmExceptionBuilder;
|
import org.pkl.core.runtime.VmExceptionBuilder;
|
||||||
|
|
||||||
public final class IoUtils {
|
public final class IoUtils {
|
||||||
@@ -299,29 +301,15 @@ public final class IoUtils {
|
|||||||
return resolve(baseUri, importUri);
|
return resolve(baseUri, importUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static URI resolveTripleDotImport(
|
||||||
* Resolves {@code importUri} against the module key.
|
SecurityManager securityManager, ModuleKey moduleKey, String tripleDotPath)
|
||||||
*
|
throws IOException, SecurityManagerException {
|
||||||
* <p>When {@code importUri} contains a triple-dot, it is resolved if the module key returns true
|
|
||||||
* for both {@link ModuleKey#isLocal()} and {@link ModuleKey#hasHierarchicalUris()}. Otherwise, an
|
|
||||||
* error is thrown.
|
|
||||||
*/
|
|
||||||
public static URI resolve(SecurityManager securityManager, ModuleKey moduleKey, URI importUri)
|
|
||||||
throws URISyntaxException, IOException, SecurityManagerException {
|
|
||||||
if (importUri.isAbsolute()) {
|
|
||||||
return moduleKey.resolveUri(importUri);
|
|
||||||
}
|
|
||||||
var moduleKeyUri = moduleKey.getUri();
|
var moduleKeyUri = moduleKey.getUri();
|
||||||
var tripleDotPath = parseTripleDotPath(importUri);
|
|
||||||
if (tripleDotPath == null) {
|
|
||||||
return moduleKey.resolveUri(importUri);
|
|
||||||
}
|
|
||||||
if (!moduleKey.isLocal() || !moduleKey.hasHierarchicalUris()) {
|
if (!moduleKey.isLocal() || !moduleKey.hasHierarchicalUris()) {
|
||||||
throw new VmExceptionBuilder()
|
throw new VmExceptionBuilder()
|
||||||
.evalError("cannotResolveTripleDotImports", moduleKeyUri)
|
.evalError("cannotResolveTripleDotImports", moduleKeyUri)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentPath =
|
var currentPath =
|
||||||
moduleKey.hasFragmentPaths() ? moduleKeyUri.getFragment() : moduleKeyUri.getPath();
|
moduleKey.hasFragmentPaths() ? moduleKeyUri.getFragment() : moduleKeyUri.getPath();
|
||||||
var effectiveImportPath =
|
var effectiveImportPath =
|
||||||
@@ -351,6 +339,69 @@ public final class IoUtils {
|
|||||||
throw new FileNotFoundException();
|
throw new FileNotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static 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/foo@1.0.0#/`
|
||||||
|
return Pair.of(importPath.substring(1), "/");
|
||||||
|
}
|
||||||
|
return Pair.of(importPath.substring(1, idx), importPath.substring(idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static URI resolveProjectDependency(ModuleKey moduleKey, String notation) {
|
||||||
|
var parsed = parseDependencyNotation(notation);
|
||||||
|
var name = parsed.getFirst();
|
||||||
|
var path = parsed.getSecond();
|
||||||
|
var projectDependenciesManager = VmContext.get(null).getProjectDependenciesManager();
|
||||||
|
if (!moduleKey.hasHierarchicalUris() && projectDependenciesManager != null) {
|
||||||
|
throw new PackageLoadError(
|
||||||
|
"cannotResolveDependencyWithoutHierarchicalUris",
|
||||||
|
projectDependenciesManager.getProjectFileUri());
|
||||||
|
}
|
||||||
|
if (projectDependenciesManager == null
|
||||||
|
|| !projectDependenciesManager.hasUri(moduleKey.getUri())) {
|
||||||
|
throw new PackageLoadError("cannotResolveDependencyNoProject");
|
||||||
|
}
|
||||||
|
var dependency = projectDependenciesManager.getDependencies().get(name);
|
||||||
|
if (dependency != null) {
|
||||||
|
return dependency.getPackageUri().toPackageAssetUri(path).getUri();
|
||||||
|
}
|
||||||
|
throw new PackageLoadError("cannotFindDependencyInProject", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves {@code importUri} against the module key.
|
||||||
|
*
|
||||||
|
* <p>When {@code importUri} contains a triple-dot, it is resolved if the module key returns true
|
||||||
|
* for both {@link ModuleKey#isLocal()} and {@link ModuleKey#hasHierarchicalUris()}. Otherwise, an
|
||||||
|
* error is thrown.
|
||||||
|
*
|
||||||
|
* <p>When {@code importUri} starts with a {@code @}, it is resolved if the module key supports
|
||||||
|
* dependency notation ()
|
||||||
|
*/
|
||||||
|
public static URI resolve(SecurityManager securityManager, ModuleKey moduleKey, URI importUri)
|
||||||
|
throws URISyntaxException, IOException, SecurityManagerException {
|
||||||
|
if (importUri.isAbsolute()) {
|
||||||
|
return moduleKey.resolveUri(importUri);
|
||||||
|
}
|
||||||
|
var tripleDotPath = parseTripleDotPath(importUri);
|
||||||
|
if (tripleDotPath != null) {
|
||||||
|
return resolveTripleDotImport(securityManager, moduleKey, tripleDotPath);
|
||||||
|
}
|
||||||
|
var moduleScheme = moduleKey.getUri().getScheme();
|
||||||
|
var isPackage =
|
||||||
|
moduleScheme.equalsIgnoreCase("package") || moduleScheme.equalsIgnoreCase("projectpackage");
|
||||||
|
var relativePart = importUri.getSchemeSpecificPart();
|
||||||
|
// Special-case handling of project dependencies.
|
||||||
|
// We'll allow the Package and ProjectPackage module keys to resolve dependency notation on
|
||||||
|
// their own.
|
||||||
|
if (relativePart.startsWith("@") && !isPackage) {
|
||||||
|
return resolveProjectDependency(moduleKey, relativePart);
|
||||||
|
}
|
||||||
|
return moduleKey.resolveUri(importUri);
|
||||||
|
}
|
||||||
|
|
||||||
public static URI resolve(URI baseUri, URI newUri) {
|
public static URI resolve(URI baseUri, URI newUri) {
|
||||||
if (newUri.isAbsolute()) return newUri;
|
if (newUri.isAbsolute()) return newUri;
|
||||||
|
|
||||||
|
|||||||
@@ -542,6 +542,12 @@ Cannot combine glob imports with triple-dot module URIs.
|
|||||||
cannotGlobUri=\
|
cannotGlobUri=\
|
||||||
Cannot expand glob pattern `{0}` because scheme `{1}` is not globbable.
|
Cannot expand glob pattern `{0}` because scheme `{1}` is not globbable.
|
||||||
|
|
||||||
|
cannotResolveInLocalDependencyNotGlobbable=\
|
||||||
|
Cannot resolve import in local dependency because scheme `{0}` is not globbable.
|
||||||
|
|
||||||
|
cannotResolveInLocalDependencyNotGlobbableNorLocal=\
|
||||||
|
Cannot resolve import in local dependency because scheme `{0}` is not globbable and is not local.
|
||||||
|
|
||||||
expectedAnnotationClass=\
|
expectedAnnotationClass=\
|
||||||
Expected an annotation class.
|
Expected an annotation class.
|
||||||
|
|
||||||
@@ -680,6 +686,11 @@ Cannot resolve a triple-dot import from module URI `{0}`.\n\
|
|||||||
\n\
|
\n\
|
||||||
Triple-dot imports may only be resolved by module schemes that are considered local, and have hierarchical URIs.
|
Triple-dot imports may only be resolved by module schemes that are considered local, and have hierarchical URIs.
|
||||||
|
|
||||||
|
moduleDoesNotSupportDependencies=\
|
||||||
|
Module `{0}` does not support importing dependencies.\n\
|
||||||
|
\n\
|
||||||
|
Dependencies can only be imported in modules that belong to a project, or within a package.
|
||||||
|
|
||||||
cannotHaveRelativeImport=\
|
cannotHaveRelativeImport=\
|
||||||
Module `{0}` cannot have a relative import URI.
|
Module `{0}` cannot have a relative import URI.
|
||||||
|
|
||||||
@@ -819,6 +830,9 @@ Only type unions can have a default marker (*).
|
|||||||
invalidModuleOutputValue=\
|
invalidModuleOutputValue=\
|
||||||
Expected `output.value` of module `{2}` to be of type `{0}`, but got type `{1}`.
|
Expected `output.value` of module `{2}` to be of type `{0}`, but got type `{1}`.
|
||||||
|
|
||||||
|
cannotResolveDependencyWithoutHierarchicalUris=\
|
||||||
|
Cannot import dependency because project URI `{0}` does not have a hierarchical path.
|
||||||
|
|
||||||
cannotResolveDependencyNoProject=\
|
cannotResolveDependencyNoProject=\
|
||||||
Cannot import dependency because there is no project found.\n\
|
Cannot import dependency because there is no project found.\n\
|
||||||
\n\
|
\n\
|
||||||
@@ -830,6 +844,11 @@ Cannot find a dependency named `{0}`, because it is not declared in the current
|
|||||||
\n\
|
\n\
|
||||||
To fix this, add it to the `dependencies` section of your `PklProject` file, and resolve your dependencies.
|
To fix this, add it to the `dependencies` section of your `PklProject` file, and resolve your dependencies.
|
||||||
|
|
||||||
|
cannotResolveDependencyFromReaderWithOpaqueUris=\
|
||||||
|
Cannot resolve dependencies from module reader with opaque URIs.\n\
|
||||||
|
\n\
|
||||||
|
Module reader for scheme `{0}` does not support hierarchical URIs.
|
||||||
|
|
||||||
cannotFindDependencyInPackage=\
|
cannotFindDependencyInPackage=\
|
||||||
Cannot find dependency named `{0}`, because it was not declared in package `{1}`.
|
Cannot find dependency named `{0}`, because it was not declared in package `{1}`.
|
||||||
|
|
||||||
@@ -856,10 +875,10 @@ For example, `example.com` in URI `project://example.com/my/package@1.0.0`.
|
|||||||
unexpectedChecksumInPackageUri=\
|
unexpectedChecksumInPackageUri=\
|
||||||
Did not expect to find a checksum component in this package URI.
|
Did not expect to find a checksum component in this package URI.
|
||||||
|
|
||||||
missingProjectDepsJson=\
|
cannotLoadProjectDepsJson=\
|
||||||
Cannot resolve dependency because file `PklProject.deps.json` is missing in project directory `{0}`.\n\
|
Encountered an error when attempting to load `PklProject.deps.json` at `{0}`.\n\
|
||||||
\n\
|
\n\
|
||||||
Run `pkl project resolve` to create a new set of dependencies.
|
Try running `pkl project resolve` within the project directory to create a new set of dependencies.
|
||||||
|
|
||||||
invalidProjectDepsJson=\
|
invalidProjectDepsJson=\
|
||||||
Cannot resolve dependency because file `{0}` is malformed.\n\
|
Cannot resolve dependency because file `{0}` is malformed.\n\
|
||||||
@@ -978,6 +997,16 @@ No package was declared in project `{0}`.\n\
|
|||||||
\n\
|
\n\
|
||||||
Add a `package` section to the PklProject file.
|
Add a `package` section to the PklProject file.
|
||||||
|
|
||||||
|
relativePathPropertyDefinedByProjectFromNonFileUri=\
|
||||||
|
Invalid property specified in project `{0}`\n\
|
||||||
|
\n\
|
||||||
|
Property `{1}` is only permitted in PklProject files loaded from `file:` URIs.
|
||||||
|
|
||||||
|
invalidUsageOfProjectFromNonFileUri=\
|
||||||
|
Invalid usage of project `{0}`\n\
|
||||||
|
\n\
|
||||||
|
This action can only be performed with PklProject files loaded from `file:` URIs.
|
||||||
|
|
||||||
packageTestsFailed=\
|
packageTestsFailed=\
|
||||||
Failed to create package `{0}`, because its API tests are failing.
|
Failed to create package `{0}`, because its API tests are failing.
|
||||||
|
|
||||||
|
|||||||
@@ -11,4 +11,5 @@ dependencies {
|
|||||||
uri = "package://localhost:0/badImportsWithinPackage@1.0.0"
|
uri = "package://localhost:0/badImportsWithinPackage@1.0.0"
|
||||||
}
|
}
|
||||||
["project2"] = import("../project2/PklProject")
|
["project2"] = import("../project2/PklProject")
|
||||||
|
["project6"] = import("../project6/PklProject")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,11 @@
|
|||||||
"uri": "projectpackage://localhost:0/project2@1.0.0",
|
"uri": "projectpackage://localhost:0/project2@1.0.0",
|
||||||
"path": "../project2/"
|
"path": "../project2/"
|
||||||
},
|
},
|
||||||
|
"package://localhost:12110/project6@1": {
|
||||||
|
"type": "local",
|
||||||
|
"uri": "projectpackage://localhost:12110/project6@1.0.0",
|
||||||
|
"path": "../project6/"
|
||||||
|
},
|
||||||
"package://localhost:0/badImportsWithinPackage@1": {
|
"package://localhost:0/badImportsWithinPackage@1": {
|
||||||
"type": "remote",
|
"type": "remote",
|
||||||
"uri": "projectpackage://localhost:0/badImportsWithinPackage@1.0.0",
|
"uri": "projectpackage://localhost:0/badImportsWithinPackage@1.0.0",
|
||||||
|
|||||||
@@ -34,4 +34,8 @@ examples {
|
|||||||
["glob-read absolute package uri"] {
|
["glob-read absolute package uri"] {
|
||||||
read*("package://localhost:0/birds@0.5.0#/catalog/*.pkl")
|
read*("package://localhost:0/birds@0.5.0#/catalog/*.pkl")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
["glob-import behind local project import"] {
|
||||||
|
import("@project6/children.pkl")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,11 @@
|
|||||||
"uri": "projectpackage://localhost:0/project2@1.0.0",
|
"uri": "projectpackage://localhost:0/project2@1.0.0",
|
||||||
"path": "../project2/"
|
"path": "../project2/"
|
||||||
},
|
},
|
||||||
|
"package://localhost:12110/project6@1": {
|
||||||
|
"type": "local",
|
||||||
|
"uri": "projectpackage://localhost:12110/project6@1.0.0",
|
||||||
|
"path": "../project6/"
|
||||||
|
},
|
||||||
"package://localhost:0/badImportsWithinPackage@1": {
|
"package://localhost:0/badImportsWithinPackage@1": {
|
||||||
"type": "remote",
|
"type": "remote",
|
||||||
"uri": "projectpackage://localhost:0/badImportsWithinPackage@1.0.0",
|
"uri": "projectpackage://localhost:0/badImportsWithinPackage@1.0.0",
|
||||||
|
|||||||
8
pkl-core/src/test/files/LanguageSnippetTests/input/projects/project6/PklProject
vendored
Normal file
8
pkl-core/src/test/files/LanguageSnippetTests/input/projects/project6/PklProject
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
amends "pkl:Project"
|
||||||
|
|
||||||
|
package {
|
||||||
|
name = "project6"
|
||||||
|
baseUri = "package://localhost:12110/project6"
|
||||||
|
version = "1.0.0"
|
||||||
|
packageZipUrl = "https://localhost:12110/project6/project6-\(version).zip"
|
||||||
|
}
|
||||||
4
pkl-core/src/test/files/LanguageSnippetTests/input/projects/project6/PklProject.deps.json
vendored
Normal file
4
pkl-core/src/test/files/LanguageSnippetTests/input/projects/project6/PklProject.deps.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"resolvedDependencies": {}
|
||||||
|
}
|
||||||
1
pkl-core/src/test/files/LanguageSnippetTests/input/projects/project6/children.pkl
vendored
Normal file
1
pkl-core/src/test/files/LanguageSnippetTests/input/projects/project6/children.pkl
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
children = import*("children/*.pkl")
|
||||||
1
pkl-core/src/test/files/LanguageSnippetTests/input/projects/project6/children/a.pkl
vendored
Normal file
1
pkl-core/src/test/files/LanguageSnippetTests/input/projects/project6/children/a.pkl
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name = "a"
|
||||||
1
pkl-core/src/test/files/LanguageSnippetTests/input/projects/project6/children/b.pkl
vendored
Normal file
1
pkl-core/src/test/files/LanguageSnippetTests/input/projects/project6/children/b.pkl
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name = "b"
|
||||||
1
pkl-core/src/test/files/LanguageSnippetTests/input/projects/project6/children/c.pkl
vendored
Normal file
1
pkl-core/src/test/files/LanguageSnippetTests/input/projects/project6/children/c.pkl
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name = "c"
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
–– Pkl Error ––
|
–– Pkl Error ––
|
||||||
Expected value of type `*RemoteDependency|LocalDependency`, but got a different `pkl.Project`.
|
Expected value of type `*RemoteDependency|Project(isValidLoadDependency)`, but got a different `pkl.Project`.
|
||||||
Value: new ModuleClass { package = null; tests {}; dependencies {}; evaluatorSetting...
|
Value: new ModuleClass { package = null; tests {}; dependencies {}; evaluatorSetting...
|
||||||
|
|
||||||
xxx | dependencies: Mapping<String(!contains("/")), *RemoteDependency|LocalDependency>
|
xxx | dependencies: Mapping<String(!contains("/")), *RemoteDependency|Project(isValidLoadDependency)>
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
at pkl.Project#dependencies (pkl:Project)
|
at pkl.Project#dependencies (pkl:Project)
|
||||||
|
|
||||||
* Value is not of type `LocalDependency` because:
|
* Value is not of type `Project(isValidLoadDependency)` because:
|
||||||
Type constraint `this.package != null` violated.
|
Type constraint `isValidLoadDependency` violated.
|
||||||
Value: new ModuleClass { package = null; tests {}; dependencies {}; evaluatorSetti...
|
Value: new ModuleClass { package = null; tests {}; dependencies {}; evaluatorSetti...
|
||||||
|
|
||||||
x | dependencies {
|
x | dependencies {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
–– Pkl Error ––
|
–– Pkl Error ––
|
||||||
Cannot resolve dependency because file `PklProject.deps.json` is missing in project directory `file:///$snippetsDir/input/projects/missingProjectDeps/`.
|
Encountered an error when attempting to load `PklProject.deps.json` at `file:///$snippetsDir/input/projects/missingProjectDeps/PklProject.deps.json`.
|
||||||
|
NoSuchFileException: /$snippetsDir/input/projects/missingProjectDeps/PklProject.deps.json
|
||||||
|
|
||||||
x | import "@birds/Bird.pkl"
|
x | import "@birds/Bird.pkl"
|
||||||
^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^
|
||||||
at bug (file:///$snippetsDir/input/projects/missingProjectDeps/bug.pkl)
|
at bug (file:///$snippetsDir/input/projects/missingProjectDeps/bug.pkl)
|
||||||
|
|
||||||
Run `pkl project resolve` to create a new set of dependencies.
|
Try running `pkl project resolve` within the project directory to create a new set of dependencies.
|
||||||
|
|||||||
@@ -262,4 +262,19 @@ examples {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
["glob-import behind local project import"] {
|
||||||
|
new {
|
||||||
|
children {
|
||||||
|
["children/a.pkl"] {
|
||||||
|
name = "a"
|
||||||
|
}
|
||||||
|
["children/b.pkl"] {
|
||||||
|
name = "b"
|
||||||
|
}
|
||||||
|
["children/c.pkl"] {
|
||||||
|
name = "c"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
pkl-core/src/test/files/LanguageSnippetTests/output/projects/project6/children.pcf
vendored
Normal file
11
pkl-core/src/test/files/LanguageSnippetTests/output/projects/project6/children.pcf
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
children {
|
||||||
|
["children/a.pkl"] {
|
||||||
|
name = "a"
|
||||||
|
}
|
||||||
|
["children/b.pkl"] {
|
||||||
|
name = "b"
|
||||||
|
}
|
||||||
|
["children/c.pkl"] {
|
||||||
|
name = "c"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
pkl-core/src/test/files/LanguageSnippetTests/output/projects/project6/children/a.pcf
vendored
Normal file
1
pkl-core/src/test/files/LanguageSnippetTests/output/projects/project6/children/a.pcf
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name = "a"
|
||||||
1
pkl-core/src/test/files/LanguageSnippetTests/output/projects/project6/children/b.pcf
vendored
Normal file
1
pkl-core/src/test/files/LanguageSnippetTests/output/projects/project6/children/b.pcf
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name = "b"
|
||||||
1
pkl-core/src/test/files/LanguageSnippetTests/output/projects/project6/children/c.pcf
vendored
Normal file
1
pkl-core/src/test/files/LanguageSnippetTests/output/projects/project6/children/c.pcf
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name = "c"
|
||||||
@@ -6,6 +6,7 @@ import java.net.URI
|
|||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.assertj.core.api.Assertions.assertThatCode
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.assertThrows
|
import org.junit.jupiter.api.assertThrows
|
||||||
import org.junit.jupiter.api.io.TempDir
|
import org.junit.jupiter.api.io.TempDir
|
||||||
@@ -15,7 +16,16 @@ import org.pkl.commons.writeString
|
|||||||
import org.pkl.core.ModuleSource.*
|
import org.pkl.core.ModuleSource.*
|
||||||
import org.pkl.core.util.IoUtils
|
import org.pkl.core.util.IoUtils
|
||||||
import org.junit.jupiter.api.AfterAll
|
import org.junit.jupiter.api.AfterAll
|
||||||
|
import org.pkl.commons.test.PackageServer
|
||||||
|
import org.pkl.core.module.ModuleKey
|
||||||
|
import org.pkl.core.module.ModuleKeyFactories
|
||||||
|
import org.pkl.core.module.ModuleKeyFactory
|
||||||
|
import org.pkl.core.module.ResolvedModuleKey
|
||||||
|
import org.pkl.core.project.Project
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
import java.nio.file.FileSystems
|
import java.nio.file.FileSystems
|
||||||
|
import java.util.*
|
||||||
|
import java.util.regex.Pattern
|
||||||
import kotlin.io.path.writeText
|
import kotlin.io.path.writeText
|
||||||
|
|
||||||
class EvaluatorTest {
|
class EvaluatorTest {
|
||||||
@@ -24,6 +34,28 @@ class EvaluatorTest {
|
|||||||
|
|
||||||
private const val sourceText = "name = \"pigeon\"; age = 10 + 20"
|
private const val sourceText = "name = \"pigeon\"; age = 10 + 20"
|
||||||
|
|
||||||
|
private object CustomModuleKeyFactory : ModuleKeyFactory {
|
||||||
|
override fun create(uri: URI): Optional<ModuleKey> {
|
||||||
|
return if (uri.scheme == "custom") Optional.of(CustomModuleKey(uri))
|
||||||
|
else Optional.empty<ModuleKey>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CustomModuleKey(private val uri: URI) : ModuleKey, ResolvedModuleKey {
|
||||||
|
override fun hasHierarchicalUris(): Boolean = true
|
||||||
|
|
||||||
|
override fun isGlobbable(): Boolean = false
|
||||||
|
|
||||||
|
override fun getOriginal(): ModuleKey = this
|
||||||
|
|
||||||
|
override fun getUri(): URI = uri
|
||||||
|
|
||||||
|
override fun loadSource(): String = javaClass.classLoader.getResourceAsStream(uri.path.drop(1))!!.use { it.readAllBytes().toString(
|
||||||
|
StandardCharsets.UTF_8) }
|
||||||
|
|
||||||
|
override fun resolve(securityManager: SecurityManager): ResolvedModuleKey = this
|
||||||
|
}
|
||||||
|
|
||||||
@AfterAll
|
@AfterAll
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun afterAll() {
|
fun afterAll() {
|
||||||
@@ -291,6 +323,132 @@ class EvaluatorTest {
|
|||||||
assertThat(output["bar/../bark.yml"]?.text).isEqualTo("bark: bark bark")
|
assertThat(output["bar/../bark.yml"]?.text).isEqualTo("bark: bark bark")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `project set from modulepath`(@TempDir cacheDir: Path) {
|
||||||
|
PackageServer.populateCacheDir(cacheDir)
|
||||||
|
val evaluatorBuilder = EvaluatorBuilder.preconfigured().setModuleCacheDir(cacheDir)
|
||||||
|
val project = Project.load(modulePath("/org/pkl/core/project/project5/PklProject"))
|
||||||
|
val result = evaluatorBuilder.setProjectDependencies(project.dependencies).build().use { evaluator ->
|
||||||
|
evaluator.evaluateOutputText(modulePath("/org/pkl/core/project/project5/main.pkl"))
|
||||||
|
}
|
||||||
|
assertThat(result).isEqualTo("""
|
||||||
|
prop1 {
|
||||||
|
name = "Apple"
|
||||||
|
}
|
||||||
|
prop2 {
|
||||||
|
res = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `project set from custom ModuleKeyFactory`(@TempDir cacheDir: Path) {
|
||||||
|
PackageServer.populateCacheDir(cacheDir)
|
||||||
|
val evaluatorBuilder = with(EvaluatorBuilder.preconfigured()) {
|
||||||
|
setAllowedModules(SecurityManagers.defaultAllowedModules + Pattern.compile("custom:"))
|
||||||
|
setAllowedResources(SecurityManagers.defaultAllowedResources + Pattern.compile("custom:"))
|
||||||
|
setModuleCacheDir(cacheDir)
|
||||||
|
setModuleKeyFactories(
|
||||||
|
listOf(
|
||||||
|
CustomModuleKeyFactory,
|
||||||
|
ModuleKeyFactories.standardLibrary,
|
||||||
|
ModuleKeyFactories.pkg,
|
||||||
|
ModuleKeyFactories.projectpackage,
|
||||||
|
ModuleKeyFactories.file
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val project = evaluatorBuilder.build().use { Project.load(it, uri("custom:/org/pkl/core/project/project5/PklProject")) }
|
||||||
|
|
||||||
|
val evaluator = evaluatorBuilder.setProjectDependencies(project.dependencies).build()
|
||||||
|
val output = evaluator.use { it.evaluateOutputText(uri("custom:/org/pkl/core/project/project5/main.pkl")) }
|
||||||
|
assertThat(output)
|
||||||
|
.isEqualTo(
|
||||||
|
"""
|
||||||
|
prop1 {
|
||||||
|
name = "Apple"
|
||||||
|
}
|
||||||
|
prop2 {
|
||||||
|
res = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
.trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `project base path set to non-hierarchical scheme`() {
|
||||||
|
class FooBarModuleKey(val moduleUri: URI) : ModuleKey, ResolvedModuleKey {
|
||||||
|
override fun hasHierarchicalUris(): Boolean = false
|
||||||
|
override fun isGlobbable(): Boolean = false
|
||||||
|
override fun getOriginal(): ModuleKey = this
|
||||||
|
override fun getUri(): URI = moduleUri
|
||||||
|
override fun loadSource(): String =
|
||||||
|
if (uri.schemeSpecificPart.endsWith("PklProject")) {
|
||||||
|
"""
|
||||||
|
amends "pkl:Project"
|
||||||
|
""".trimIndent()
|
||||||
|
} else """
|
||||||
|
birds = import("@birds/catalog/Ostritch.pkl")
|
||||||
|
""".trimIndent()
|
||||||
|
override fun resolve(securityManager: SecurityManager): ResolvedModuleKey {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val fooBayModuleKeyFactory = ModuleKeyFactory { uri ->
|
||||||
|
if (uri.scheme == "foobar") Optional.of(FooBarModuleKey(uri))
|
||||||
|
else Optional.empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
val evaluatorBuilder = with(EvaluatorBuilder.preconfigured()) {
|
||||||
|
setAllowedModules(SecurityManagers.defaultAllowedModules + Pattern.compile("foobar:"))
|
||||||
|
setAllowedResources(SecurityManagers.defaultAllowedResources + Pattern.compile("foobar:"))
|
||||||
|
setModuleKeyFactories(
|
||||||
|
listOf(
|
||||||
|
fooBayModuleKeyFactory,
|
||||||
|
ModuleKeyFactories.standardLibrary,
|
||||||
|
ModuleKeyFactories.pkg,
|
||||||
|
ModuleKeyFactories.projectpackage,
|
||||||
|
ModuleKeyFactories.file
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val project = evaluatorBuilder.build().use { Project.load(it, uri("foobar:foo/PklProject")) }
|
||||||
|
val evaluator = evaluatorBuilder.setProjectDependencies(project.dependencies).build()
|
||||||
|
assertThatCode { evaluator.use { it.evaluateOutputText(uri("foobar:baz")) } }
|
||||||
|
.hasMessageContaining("Cannot import dependency because project URI `foobar:foo/PklProject` does not have a hierarchical path.")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `cannot glob import in local dependency from modulepath`(@TempDir cacheDir: Path) {
|
||||||
|
PackageServer.populateCacheDir(cacheDir)
|
||||||
|
val evaluatorBuilder = EvaluatorBuilder.preconfigured().setModuleCacheDir(cacheDir)
|
||||||
|
val project = Project.load(modulePath("/org/pkl/core/project/project6/PklProject"))
|
||||||
|
evaluatorBuilder.setProjectDependencies(project.dependencies).build().use { evaluator ->
|
||||||
|
assertThatCode {
|
||||||
|
evaluator.evaluateOutputText(modulePath("/org/pkl/core/project/project6/globWithinDependency.pkl"))
|
||||||
|
}.hasMessageContaining("""
|
||||||
|
Cannot resolve import in local dependency because scheme `modulepath` is not globbable.
|
||||||
|
|
||||||
|
1 | res = import*("*.pkl")
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
""".trimIndent())
|
||||||
|
assertThatCode {
|
||||||
|
evaluator.evaluateOutputText(modulePath("/org/pkl/core/project/project6/globIntoDependency.pkl"))
|
||||||
|
}.hasMessageContaining("""
|
||||||
|
–– Pkl Error ––
|
||||||
|
Cannot resolve import in local dependency because scheme `modulepath` is not globbable.
|
||||||
|
|
||||||
|
1 | import* "@project7/*.pkl" as proj7Files
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun checkModule(module: PModule) {
|
private fun checkModule(module: PModule) {
|
||||||
assertThat(module.properties.size).isEqualTo(2)
|
assertThat(module.properties.size).isEqualTo(2)
|
||||||
assertThat(module.getProperty("name")).isEqualTo("pigeon")
|
assertThat(module.getProperty("name")).isEqualTo("pigeon")
|
||||||
|
|||||||
@@ -38,10 +38,15 @@ abstract class AbstractLanguageSnippetTestsEngine : InputOutputTestEngine() {
|
|||||||
internal val selection: String = ""
|
internal val selection: String = ""
|
||||||
|
|
||||||
protected val packageServer: PackageServer = PackageServer()
|
protected val packageServer: PackageServer = PackageServer()
|
||||||
|
|
||||||
override val includedTests: List<Regex> = listOf(Regex(".*$selection\\.pkl"))
|
override val includedTests: List<Regex> = listOf(Regex(".*$selection\\.pkl"))
|
||||||
|
|
||||||
override val excludedTests: List<Regex> = listOf(Regex(".*/native/.*"))
|
override val excludedTests: List<Regex> = buildList {
|
||||||
|
add(Regex(".*/native/.*"))
|
||||||
|
if (IoUtils.isWindows()) {
|
||||||
|
addAll(windowsExcludedTests)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override val inputDir: Path = snippetsDir.resolve("input")
|
override val inputDir: Path = snippetsDir.resolve("input")
|
||||||
|
|
||||||
@@ -68,7 +73,12 @@ abstract class AbstractLanguageSnippetTestsEngine : InputOutputTestEngine() {
|
|||||||
packageServer.close()
|
packageServer.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun String.stripFilePaths() = replace(snippetsDir.toUri().toString(), "file:///\$snippetsDir/")
|
private val replacement by lazy {
|
||||||
|
if (snippetsDir.root.toString() != "/") "\$snippetsDir" else "/\$snippetsDir"
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun String.stripFilePaths(): String =
|
||||||
|
replace(IoUtils.toNormalizedPathString(snippetsDir), replacement)
|
||||||
|
|
||||||
protected fun String.stripLineNumbers() = replace(lineNumberRegex) { result ->
|
protected fun String.stripLineNumbers() = replace(lineNumberRegex) { result ->
|
||||||
// replace line number with equivalent number of 'x' characters to keep formatting intact
|
// replace line number with equivalent number of 'x' characters to keep formatting intact
|
||||||
@@ -80,7 +90,7 @@ abstract class AbstractLanguageSnippetTestsEngine : InputOutputTestEngine() {
|
|||||||
// can't think of a better solution right now
|
// can't think of a better solution right now
|
||||||
protected fun String.stripVersionCheckErrorMessage() =
|
protected fun String.stripVersionCheckErrorMessage() =
|
||||||
replace("Pkl version is ${Release.current().version()}", "Pkl version is xxx")
|
replace("Pkl version is ${Release.current().version()}", "Pkl version is xxx")
|
||||||
|
|
||||||
protected fun String.stripStdlibLocationSha(): String =
|
protected fun String.stripStdlibLocationSha(): String =
|
||||||
replace("https://github.com/apple/pkl/blob/${Release.current().commitId()}/stdlib/", "https://github.com/apple/pkl/blob/\$commitId/stdlib/")
|
replace("https://github.com/apple/pkl/blob/${Release.current().commitId()}/stdlib/", "https://github.com/apple/pkl/blob/\$commitId/stdlib/")
|
||||||
|
|
||||||
@@ -261,7 +271,12 @@ class AlpineLanguageSnippetTestsEngine : AbstractNativeLanguageSnippetTestsEngin
|
|||||||
override val testClass: KClass<*> = AlpineLanguageSnippetTests::class
|
override val testClass: KClass<*> = AlpineLanguageSnippetTests::class
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// error message contains different file path on Windows
|
||||||
|
private val windowsExcludedTests get() = listOf(Regex(".*missingProjectDeps/bug\\.pkl"))
|
||||||
|
|
||||||
class WindowsLanguageSnippetTestsEngine : AbstractNativeLanguageSnippetTestsEngine() {
|
class WindowsLanguageSnippetTestsEngine : AbstractNativeLanguageSnippetTestsEngine() {
|
||||||
override val pklExecutablePath: Path = PklExecutablePaths.windowsAmd64
|
override val pklExecutablePath: Path = PklExecutablePaths.windowsAmd64
|
||||||
override val testClass: KClass<*> = WindowsLanguageSnippetTests::class
|
override val testClass: KClass<*> = WindowsLanguageSnippetTests::class
|
||||||
|
override val excludedTests: List<Regex>
|
||||||
|
get() = super.excludedTests + windowsExcludedTests
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
package org.pkl.core.project
|
package org.pkl.core.project
|
||||||
|
|
||||||
import org.pkl.commons.test.PackageServer
|
|
||||||
import org.pkl.commons.writeString
|
|
||||||
import org.pkl.core.*
|
|
||||||
import org.pkl.core.packages.PackageUri
|
|
||||||
import org.pkl.core.project.Project.EvaluatorSettings
|
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.assertj.core.api.Assertions.assertThatCode
|
import org.assertj.core.api.Assertions.assertThatCode
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.io.TempDir
|
import org.junit.jupiter.api.io.TempDir
|
||||||
import org.pkl.commons.test.FileTestUtils
|
import org.pkl.commons.test.FileTestUtils
|
||||||
|
import org.pkl.commons.test.PackageServer
|
||||||
|
import org.pkl.commons.writeString
|
||||||
|
import org.pkl.core.*
|
||||||
import org.pkl.core.http.HttpClient
|
import org.pkl.core.http.HttpClient
|
||||||
|
import org.pkl.core.packages.PackageUri
|
||||||
|
import org.pkl.core.project.Project.EvaluatorSettings
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
amends "pkl:Project"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
["fruit"] {
|
||||||
|
uri = "package://localhost:0/fruit@1.0.5"
|
||||||
|
}
|
||||||
|
["project4"] = import("../project4/PklProject")
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"resolvedDependencies": {
|
||||||
|
"package://localhost:0/fruit@1": {
|
||||||
|
"type": "remote",
|
||||||
|
"uri": "projectpackage://localhost:0/fruit@1.0.5",
|
||||||
|
"checksums": {
|
||||||
|
"sha256": "$skipChecksumVerification"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"package://localhost:0/project4@1": {
|
||||||
|
"type": "local",
|
||||||
|
"uri": "projectpackage://localhost:0/project4@1.0.0",
|
||||||
|
"path": "../project4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import "@fruit/catalog/apple.pkl"
|
||||||
|
import "@project4/module1.pkl"
|
||||||
|
|
||||||
|
prop1 = apple
|
||||||
|
prop2 = module1
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
amends "pkl:Project"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
["project7"] = import("../project7/PklProject")
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"resolvedDependencies": {
|
||||||
|
"package://localhost:0/project7@1": {
|
||||||
|
"type": "local",
|
||||||
|
"uri": "projectpackage://localhost:0/project7@1.0.0",
|
||||||
|
"path": "../project7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import* "@project7/*.pkl" as proj7Files
|
||||||
|
|
||||||
|
res = proj7Files
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import "@project7/main.pkl"
|
||||||
|
|
||||||
|
res = main.res
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
amends "pkl:Project"
|
||||||
|
|
||||||
|
package {
|
||||||
|
name = "project7"
|
||||||
|
version = "1.0.0"
|
||||||
|
packageZipUrl = "https://bogus.value"
|
||||||
|
baseUri = "package://localhost:0/project7"
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
res = import*("*.pkl")
|
||||||
@@ -478,7 +478,7 @@ class EmbeddedExecutorTest {
|
|||||||
)
|
)
|
||||||
val result = executor.evaluatePath(pklFile) {
|
val result = executor.evaluatePath(pklFile) {
|
||||||
allowedModules("file:", "package:", "projectpackage:", "https:")
|
allowedModules("file:", "package:", "projectpackage:", "https:")
|
||||||
allowedResources("prop:", "package:", "projectpackage:", "https:")
|
allowedResources("file:", "prop:", "package:", "projectpackage:", "https:")
|
||||||
moduleCacheDir(cacheDir)
|
moduleCacheDir(cacheDir)
|
||||||
projectDir(projectDir)
|
projectDir(projectDir)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,18 +91,17 @@ package: Package?
|
|||||||
/// ```
|
/// ```
|
||||||
tests: Listing<String>(isDistinct)
|
tests: Listing<String>(isDistinct)
|
||||||
|
|
||||||
/// Tells if the project is a file-based module named `PklProject`.
|
/// Tells if the project is a local module named `PklProject`, is not self, and has a [package] section
|
||||||
local isLocalPklProject = (it: Project) ->
|
local isValidLoadDependency = (it: Project) ->
|
||||||
it.projectFileUri.startsWith("file:") && it.projectFileUri.endsWith("/PklProject")
|
isUriLocal(projectFileUri, it.projectFileUri)
|
||||||
|
&& it.projectFileUri.endsWith("/PklProject")
|
||||||
|
&& it != module
|
||||||
|
&& it.package != null
|
||||||
|
|
||||||
/// A local dependency is another [Project] that is local to the file system.
|
const local function isUriLocal(uri1: Uri, uri2: Uri): Boolean =
|
||||||
///
|
// This is an imperfect check; should also check that the URIs have the same authority.
|
||||||
/// To declare, use `import("path/to/PklProject")`
|
// We should improve this if/when there is a URI library in the stdlib.
|
||||||
typealias LocalDependency = Project(
|
uri1.substring(0, uri1.indexOf(":")) == uri2.substring(0, uri2.indexOf(":"))
|
||||||
isLocalPklProject,
|
|
||||||
this != module,
|
|
||||||
this.package != null
|
|
||||||
)
|
|
||||||
|
|
||||||
/// The dependencies of this project.
|
/// The dependencies of this project.
|
||||||
///
|
///
|
||||||
@@ -176,19 +175,31 @@ typealias LocalDependency = Project(
|
|||||||
/// 1. Gather all dependencies, both direct and transitive.
|
/// 1. Gather all dependencies, both direct and transitive.
|
||||||
/// 2. For each package's major version, determine the highest declared minor version.
|
/// 2. For each package's major version, determine the highest declared minor version.
|
||||||
/// 3. Write each resolved dependency to sibling file `PklProject.deps.json`.
|
/// 3. Write each resolved dependency to sibling file `PklProject.deps.json`.
|
||||||
dependencies: Mapping<String(!contains("/")), *RemoteDependency|LocalDependency>
|
dependencies: Mapping<String(!contains("/")), *RemoteDependency|Project(isValidLoadDependency)>
|
||||||
|
|
||||||
|
local isFileBasedProject = projectFileUri.startsWith("file:")
|
||||||
|
|
||||||
/// If set, controls the base evaluator settings when running the evaluator.
|
/// If set, controls the base evaluator settings when running the evaluator.
|
||||||
///
|
///
|
||||||
/// These settings influence the behavior of the evaluator when running the `pkl eval`, `pkl test`,
|
/// These settings influence the behavior of the evaluator when running the `pkl eval`, `pkl test`,
|
||||||
/// and `pkl repl` CLI commands.
|
/// and `pkl repl` CLI commands.
|
||||||
/// Note that command line flags passed to the CLI will override any settings defined here.
|
/// Command line flags passed to the CLI will override any settings defined here.
|
||||||
///
|
///
|
||||||
/// Other integrations can possibly ignore these evaluator settings.
|
/// Other integrations can possibly ignore these evaluator settings.
|
||||||
///
|
///
|
||||||
/// Evaluator settings do not get published as part of a package.
|
/// Evaluator settings do not get published as part of a package.
|
||||||
/// It is not possible for a package dependency to influence the evaluator settings of a project.
|
/// It is not possible for a package dependency to influence the evaluator settings of a project.
|
||||||
evaluatorSettings: EvaluatorSettings
|
///
|
||||||
|
/// The following values can only be set if this is a file-based project.
|
||||||
|
///
|
||||||
|
/// - [modulePath][EvaluatorSettings.modulePath]
|
||||||
|
/// - [rootDir][EvaluatorSettings.rootDir]
|
||||||
|
/// - [moduleCacheDir][EvaluatorSettings.moduleCacheDir]
|
||||||
|
evaluatorSettings: EvaluatorSettings(
|
||||||
|
(modulePath != null).implies(isFileBasedProject),
|
||||||
|
(rootDir != null).implies(isFileBasedProject),
|
||||||
|
(moduleCacheDir != null).implies(isFileBasedProject)
|
||||||
|
)
|
||||||
|
|
||||||
/// The URI of the PklProject file.
|
/// The URI of the PklProject file.
|
||||||
///
|
///
|
||||||
|
|||||||
Reference in New Issue
Block a user