Document public module APIs (#1813)
This commit is contained in:
1 parent
6811abf11c
commit
8399f11186
7 files changed
+104
-2
No files matched your search
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,13 +26,29 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.pkl.core.util.IoUtils;
|
||||
|
||||
/** Utilities for inspecting file-system paths while resolving module glob imports. */
|
||||
public final class FileResolver {
|
||||
private FileResolver() {}
|
||||
|
||||
/**
|
||||
* Returns the immediate, non-symbolic-link children of the directory identified by {@code
|
||||
* baseUri}.
|
||||
*
|
||||
* <p>Returns an empty list if {@code baseUri} does not exist or does not identify a directory.
|
||||
*
|
||||
* @throws IOException if the directory cannot be read
|
||||
*/
|
||||
public static List<PathElement> listElements(URI baseUri) throws IOException {
|
||||
return listElements(IoUtils.pathOf(baseUri));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the immediate, non-symbolic-link children of {@code path}.
|
||||
*
|
||||
* <p>Returns an empty list if {@code path} does not exist or is not a directory.
|
||||
*
|
||||
* @throws IOException if the directory cannot be read
|
||||
*/
|
||||
public static List<PathElement> listElements(Path path) throws IOException {
|
||||
try (var stream = Files.newDirectoryStream(path)) {
|
||||
var ret = new ArrayList<PathElement>();
|
||||
@@ -49,10 +65,12 @@ public final class FileResolver {
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns whether the file-system path identified by {@code elementUri} exists. */
|
||||
public static boolean hasElement(URI elementUri) {
|
||||
return Files.exists(IoUtils.pathOf(elementUri));
|
||||
}
|
||||
|
||||
/** Returns whether {@code path} exists. */
|
||||
public static boolean hasElement(Path path) {
|
||||
return Files.exists(path);
|
||||
}
|
||||
|
||||
@@ -57,10 +57,19 @@ public final class ModulePathResolver implements AutoCloseable {
|
||||
|
||||
private static final ModulePathResolver EMPTY = new ModulePathResolver(Collections.emptyList());
|
||||
|
||||
/** Returns a resolver whose module path contains no entries. */
|
||||
public static ModulePathResolver empty() {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a resolver for the given module path entries.
|
||||
*
|
||||
* <p>Each entry can be a directory, JAR file, or ZIP file. Entries are searched in iteration
|
||||
* order; if more than one entry contains the same path, the first entry wins.
|
||||
*
|
||||
* @param modulePath the module path entries to search
|
||||
*/
|
||||
public ModulePathResolver(Iterable<Path> modulePath) {
|
||||
this.modulePath = modulePath;
|
||||
}
|
||||
@@ -103,6 +112,13 @@ public final class ModulePathResolver implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a {@code modulepath:} URI to the path containing its source.
|
||||
*
|
||||
* @throws FileNotFoundException if the module path contains no matching file
|
||||
* @throws IOException if a module path entry cannot be read
|
||||
* @throws IllegalStateException if this resolver has been closed
|
||||
*/
|
||||
public Path resolve(URI uri) throws IOException {
|
||||
var modulePath = getModulePath(uri);
|
||||
var result = getFileCache().get(modulePath);
|
||||
@@ -111,6 +127,12 @@ public final class ModulePathResolver implements AutoCloseable {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the module path contains the element identified by {@code elementUri}.
|
||||
*
|
||||
* @throws UncheckedIOException if a module path entry cannot be read
|
||||
* @throws IllegalStateException if this resolver has been closed
|
||||
*/
|
||||
public boolean hasElement(URI elementUri) {
|
||||
var path = elementUri.getPath();
|
||||
try {
|
||||
@@ -121,6 +143,11 @@ public final class ModulePathResolver implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes file systems opened for JAR and ZIP entries.
|
||||
*
|
||||
* <p>Calling this method more than once has no effect.
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
synchronized (lock) {
|
||||
|
||||
@@ -24,7 +24,9 @@ import org.graalvm.collections.EconomicMap;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.pkl.core.util.EconomicMaps;
|
||||
|
||||
/** Describes a child of a hierarchical module or resource path. */
|
||||
public class PathElement {
|
||||
/** Orders files before directories, then orders elements lexicographically by name. */
|
||||
public static final Comparator<PathElement> comparator =
|
||||
(o1, o2) -> {
|
||||
if (o1.isDirectory && !o2.isDirectory) {
|
||||
@@ -39,19 +41,23 @@ public class PathElement {
|
||||
|
||||
private final boolean isDirectory;
|
||||
|
||||
/** Creates an element whose type cannot be inspected and is therefore treated as a file. */
|
||||
public static PathElement opaque(String name) {
|
||||
return new PathElement(name, false);
|
||||
}
|
||||
|
||||
/** Creates an element with the given name and directory status. */
|
||||
public PathElement(String name, boolean isDirectory) {
|
||||
this.name = name;
|
||||
this.isDirectory = isDirectory;
|
||||
}
|
||||
|
||||
/** Returns this element's name relative to its parent. */
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/** Returns an equivalent element with {@code name}, or this element if its name is unchanged. */
|
||||
public PathElement withName(String name) {
|
||||
if (name.equals(this.name)) {
|
||||
return this;
|
||||
@@ -59,6 +65,7 @@ public class PathElement {
|
||||
return new PathElement(name, isDirectory);
|
||||
}
|
||||
|
||||
/** Returns whether this element represents a directory. */
|
||||
public boolean isDirectory() {
|
||||
return isDirectory;
|
||||
}
|
||||
@@ -80,13 +87,19 @@ public class PathElement {
|
||||
return "PathElement{" + "name='" + name + '\'' + ", isDirectory=" + isDirectory + '}';
|
||||
}
|
||||
|
||||
/** A path element that stores its descendants as a tree. */
|
||||
public static final class TreePathElement extends PathElement {
|
||||
private final EconomicMap<String, TreePathElement> children = EconomicMaps.create();
|
||||
|
||||
/** Creates a tree element with the given name and directory status. */
|
||||
public TreePathElement(String name, boolean isDirectory) {
|
||||
super(name, isDirectory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds {@code child} unless a child named {@code name} already exists, and returns the stored
|
||||
* child.
|
||||
*/
|
||||
public TreePathElement putIfAbsent(String name, TreePathElement child) {
|
||||
children.putIfAbsent(name, child);
|
||||
return children.get(name);
|
||||
@@ -113,10 +126,12 @@ public class PathElement {
|
||||
return getElement(Path.of(basePath));
|
||||
}
|
||||
|
||||
/** Returns this element's children, keyed by name. */
|
||||
public EconomicMap<String, TreePathElement> getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
||||
/** Returns a snapshot of this element's child values. */
|
||||
public List<PathElement> getChildrenValues() {
|
||||
var ret = new ArrayList<PathElement>(children.size());
|
||||
for (var elem : EconomicMaps.getValues(children)) {
|
||||
|
||||
@@ -40,9 +40,12 @@ import org.pkl.core.util.EconomicMaps;
|
||||
import org.pkl.core.util.IoUtils;
|
||||
import org.pkl.core.util.json.Json.JsonParseException;
|
||||
|
||||
/** Resolves a project's declared package dependencies against its lock file. */
|
||||
public final class ProjectDependenciesManager {
|
||||
/** The conventional name of a Pkl project file. */
|
||||
public static final String PKL_PROJECT_FILENAME = "PklProject";
|
||||
|
||||
/** The conventional name of a Pkl project's dependency lock file. */
|
||||
public static final String PKL_PROJECT_DEPS_FILENAME = "PklProject.deps.json";
|
||||
|
||||
private final DeclaredDependencies declaredDependencies;
|
||||
@@ -66,6 +69,13 @@ public final class ProjectDependenciesManager {
|
||||
|
||||
private final Object lock = new Object();
|
||||
|
||||
/**
|
||||
* Creates a dependency manager for a project.
|
||||
*
|
||||
* @param declaredDependencies the dependencies declared by the project
|
||||
* @param moduleResolver the resolver used to load the project's dependency lock file
|
||||
* @param securityManager the security manager used when loading the dependency lock file
|
||||
*/
|
||||
public ProjectDependenciesManager(
|
||||
DeclaredDependencies declaredDependencies,
|
||||
ModuleResolver moduleResolver,
|
||||
@@ -77,6 +87,7 @@ public final class ProjectDependenciesManager {
|
||||
this.securityManager = securityManager;
|
||||
}
|
||||
|
||||
/** Returns whether {@code uri} is located within this project. */
|
||||
public boolean hasUri(URI uri) {
|
||||
return projectBaseUri.getScheme().equals(uri.getScheme())
|
||||
&& Objects.equals(projectBaseUri.getAuthority(), uri.getAuthority())
|
||||
@@ -164,6 +175,7 @@ public final class ProjectDependenciesManager {
|
||||
}
|
||||
|
||||
// `ensureDependenciesInitialized` makes `myDependencies` safe to access
|
||||
/** Returns the project's direct dependencies, keyed by their declared names. */
|
||||
@SuppressWarnings({"FieldAccessNotGuarded", "GuardedBy"})
|
||||
public Map<String, Dependency> getDependencies() {
|
||||
ensureDependenciesInitialized();
|
||||
@@ -172,6 +184,7 @@ public final class ProjectDependenciesManager {
|
||||
}
|
||||
|
||||
// `ensureDependenciesInitialized` makes `localPackageDependencies` safe to access
|
||||
/** Returns whether {@code packageUri} identifies a local dependency of this project. */
|
||||
@SuppressWarnings({"FieldAccessNotGuarded", "GuardedBy"})
|
||||
public boolean isLocalPackage(PackageUri packageUri) {
|
||||
ensureDependenciesInitialized();
|
||||
@@ -179,6 +192,7 @@ public final class ProjectDependenciesManager {
|
||||
}
|
||||
|
||||
// `ensureDependenciesInitialized` makes `localPackageDependencies` safe to access
|
||||
/** Returns the dependencies of the local package identified by {@code packageUri}. */
|
||||
@SuppressWarnings({"FieldAccessNotGuarded", "GuardedBy"})
|
||||
public Map<String, Dependency> getLocalPackageDependencies(PackageUri packageUri) {
|
||||
ensureDependenciesInitialized();
|
||||
@@ -188,6 +202,13 @@ public final class ProjectDependenciesManager {
|
||||
return dep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a package's declared dependencies against this project's lock file.
|
||||
*
|
||||
* @param packageUri the package whose dependencies are being resolved
|
||||
* @param dependencyMetadata the package's published dependency metadata
|
||||
* @return dependencies keyed by their names in the package metadata
|
||||
*/
|
||||
public Map<String, Dependency> getResolvedDependenciesForPackage(
|
||||
PackageUri packageUri, DependencyMetadata dependencyMetadata) {
|
||||
synchronized (lock) {
|
||||
@@ -217,10 +238,12 @@ public final class ProjectDependenciesManager {
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns this project's declared dependencies. */
|
||||
public DeclaredDependencies getDeclaredDependencies() {
|
||||
return declaredDependencies;
|
||||
}
|
||||
|
||||
/** Returns the locked dependency matching {@code packageUri}. */
|
||||
public Dependency getResolvedDependency(PackageUri packageUri) {
|
||||
var dep = getProjectDeps().get(CanonicalPackageUri.fromPackageUri(packageUri));
|
||||
if (dep == null) {
|
||||
@@ -229,14 +252,17 @@ public final class ProjectDependenciesManager {
|
||||
return dep;
|
||||
}
|
||||
|
||||
/** Returns the base URI used to resolve paths within this project. */
|
||||
public URI getProjectBaseUri() {
|
||||
return projectBaseUri;
|
||||
}
|
||||
|
||||
/** Returns the URI of this project's dependency lock file. */
|
||||
public URI getProjectDepsFileUri() {
|
||||
return IoUtils.resolve(projectBaseUri, PKL_PROJECT_DEPS_FILENAME);
|
||||
}
|
||||
|
||||
/** Returns the URI of this project's project file. */
|
||||
public URI getProjectFileUri() {
|
||||
return declaredDependencies.projectFileUri();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,6 +21,7 @@ import org.pkl.core.SecurityManager;
|
||||
|
||||
/** SPI for identifying a resolved module and loading its source code. */
|
||||
public interface ResolvedModuleKey {
|
||||
/** Returns the unresolved module key from which this key was resolved. */
|
||||
ModuleKey getOriginal();
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,6 +34,7 @@ public final class ResolvedModuleKeys {
|
||||
* loaded from that file path and cached using the given URI as cache key.
|
||||
*
|
||||
* @param nofollow if true, the file will be opened with {@link LinkOption#NOFOLLOW_LINKS}.
|
||||
* @return a resolved module key backed by {@code path}
|
||||
*/
|
||||
public static ResolvedModuleKey file(ModuleKey original, URI uri, Path path, boolean nofollow) {
|
||||
return new FileKey(original, uri, path, nofollow);
|
||||
@@ -42,6 +43,8 @@ public final class ResolvedModuleKeys {
|
||||
/**
|
||||
* Creates a resolved module key backed by the given file path. The resulting module will be
|
||||
* loaded from that file path and cached using the given URI as cache key.
|
||||
*
|
||||
* @return a resolved module key backed by {@code path}
|
||||
*/
|
||||
public static ResolvedModuleKey file(ModuleKey original, URI uri, Path path) {
|
||||
return new FileKey(original, uri, path, false);
|
||||
@@ -50,6 +53,8 @@ public final class ResolvedModuleKeys {
|
||||
/**
|
||||
* Creates a resolved module key backed by the given URL. The resulting module will be loaded from
|
||||
* that URL and cached using the given URI as cache key.
|
||||
*
|
||||
* @return a resolved module key backed by {@code url}
|
||||
*/
|
||||
public static ResolvedModuleKey url(ModuleKey original, URI uri, URL url) {
|
||||
return new Url(original, uri, url);
|
||||
@@ -58,6 +63,8 @@ public final class ResolvedModuleKeys {
|
||||
/**
|
||||
* Creates a resolved module key backed by the given source code. If {@code cached} is {@code
|
||||
* true}, the resulting module will be cached using the given URI as cache key.
|
||||
*
|
||||
* @return a resolved module key backed by {@code sourceText}
|
||||
*/
|
||||
public static ResolvedModuleKey virtual(
|
||||
ModuleKey original, URI uri, String sourceText, boolean cached) {
|
||||
@@ -67,6 +74,8 @@ public final class ResolvedModuleKeys {
|
||||
/**
|
||||
* Creates a resolved module key that behaves like {@code delegate}, except with {@code original}
|
||||
* as its original module key.
|
||||
*
|
||||
* @return a resolved module key backed by {@code delegate}
|
||||
*/
|
||||
public static ResolvedModuleKey delegated(ResolvedModuleKey delegate, ModuleKey original) {
|
||||
return new Delegated(delegate, original);
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* APIs for resolving Pkl module URIs and loading module source code.
|
||||
*
|
||||
* <p>Custom module schemes can be implemented with {@link org.pkl.core.module.ModuleKeyFactory},
|
||||
* {@link org.pkl.core.module.ModuleKey}, and {@link org.pkl.core.module.ResolvedModuleKey}.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.pkl.core.module;
|
||||
|
||||
|
||||
Reference in new issue
Block a user