Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
267 changes: 132 additions & 135 deletions pkl-gradle/src/main/java/org/pkl/gradle/PklPlugin.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import java.io.File;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Optional;
Expand All @@ -35,20 +34,15 @@ public abstract class AnalyzeImportsTask extends ModulesTask {
@Input
public abstract Property<String> getOutputFormat();

private final Provider<CliImportAnalyzer> cliImportAnalyzerProvider =
getProviders()
.provider(
() ->
new CliImportAnalyzer(
new CliImportAnalyzerOptions(
getCliBaseOptions(),
mapAndGetOrNull(getOutputFile(), it -> it.getAsFile().toPath()),
mapAndGetOrNull(getOutputFormat(), it -> it))));

@Override
protected void doRunTask() {
//noinspection ResultOfMethodCallIgnored
getOutputs().getPreviousOutputFiles().forEach(File::delete);
cliImportAnalyzerProvider.get().run();
new CliImportAnalyzer(
new CliImportAnalyzerOptions(
getCliBaseOptions(),
mapAndGetOrNull(getOutputFile(), it -> it.getAsFile().toPath()),
mapAndGetOrNull(getOutputFormat(), it -> it)))
.run();
}
}
78 changes: 45 additions & 33 deletions pkl-gradle/src/main/java/org/pkl/gradle/task/BasePklTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,21 @@ public Provider<URI> getSettingsModuleUri() {
.map((Transformer<@Nullable URI, Object>) object -> object instanceof URI uri ? uri : null);
}

/**
* The working directory for the task, used as the base for relative path resolution. This
* replaces direct access to {@code project.getProjectDir()} at execution time to support the
* Gradle configuration cache.
*/
@Internal
public abstract DirectoryProperty getWorkingDir();

// Exposed as a task input via workingDirPath so that a change to the project directory
// invalidates the task without tracking the directory's contents.
@Input
public Provider<String> getWorkingDirPath() {
return getWorkingDir().map(it -> it.getAsFile().getAbsolutePath());
}

// Exposed as a task input via evalRootDirPath, because we only need to depend
// on this directory's path and not on its contents.
@Internal
Expand Down Expand Up @@ -174,42 +189,39 @@ public void runTask() {

protected abstract void doRunTask();

protected @Nullable CliBaseOptions __cachedOptions;

// Must be called during task execution time only.
// Note: CliBaseOptions is intentionally not cached — caching would require holding a reference
// across the configuration/execution boundary, which is incompatible with the Gradle
// configuration cache. The cost of constructing this object per-invocation is negligible.
@Internal
protected CliBaseOptions getCliBaseOptions() {
if (__cachedOptions == null) {
__cachedOptions =
new CliBaseOptions(
getSourceModulesAsUris(),
patternsFromStrings(getAllowedModules().get()),
patternsFromStrings(getAllowedResources().get()),
getEnvironmentVariables().get(),
getExternalProperties().get(),
parseModulePath(),
getProject().getProjectDir().toPath(),
mapAndGetOrNull(getEvalRootDirPath(), Paths::get),
mapAndGetOrNull(getSettingsModule(), PluginUtils::parseModuleNotationToUri),
null,
getEvalTimeout().getOrNull(),
mapAndGetOrNull(getModuleCacheDir(), it1 -> it1.getAsFile().toPath()),
getColor().getOrElse(false) ? Color.ALWAYS : Color.NEVER,
getNoCache().getOrElse(false),
false,
false,
false,
getTestPort().getOrElse(-1),
Collections.emptyList(),
getHttpProxy().getOrNull(),
getHttpNoProxy().getOrElse(List.of()),
getHttpRewrites().getOrNull(),
Map.of(),
Map.of(),
null,
getPowerAssertions().getOrElse(false));
}
return __cachedOptions;
return new CliBaseOptions(
getSourceModulesAsUris(),
patternsFromStrings(getAllowedModules().get()),
patternsFromStrings(getAllowedResources().get()),
getEnvironmentVariables().get(),
getExternalProperties().get(),
parseModulePath(),
getWorkingDir().get().getAsFile().toPath(),
mapAndGetOrNull(getEvalRootDirPath(), Paths::get),
mapAndGetOrNull(getSettingsModule(), PluginUtils::parseModuleNotationToUri),
null,
getEvalTimeout().getOrNull(),
mapAndGetOrNull(getModuleCacheDir(), it1 -> it1.getAsFile().toPath()),
getColor().getOrElse(false) ? Color.ALWAYS : Color.NEVER,
getNoCache().getOrElse(false),
false,
false,
false,
getTestPort().getOrElse(-1),
Collections.emptyList(),
getHttpProxy().getOrNull(),
getHttpNoProxy().getOrElse(List.of()),
getHttpRewrites().getOrNull(),
Map.of(),
Map.of(),
null,
getPowerAssertions().getOrElse(false));
}

@Internal
Expand Down
32 changes: 15 additions & 17 deletions pkl-gradle/src/main/java/org/pkl/gradle/task/EvalTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Internal;
Expand Down Expand Up @@ -56,35 +55,34 @@ public abstract class EvalTask extends ModulesTask {
@Optional
public abstract Property<String> getExpression();

private final Provider<CliEvaluator> cliEvaluator =
getProviders()
.provider(
() ->
new CliEvaluator(
new CliEvaluatorOptions(
getCliBaseOptions(),
getOutputFile().get().getAsFile().getAbsolutePath(),
getOutputFormat().get(),
getModuleOutputSeparator().get(),
mapAndGetOrNull(
getMultipleFileOutputDir(), it -> it.getAsFile().getAbsolutePath()),
getExpression().getOrNull())));
private CliEvaluator createCliEvaluator() {
return new CliEvaluator(
new CliEvaluatorOptions(
getCliBaseOptions(),
getOutputFile().get().getAsFile().getAbsolutePath(),
getOutputFormat().get(),
getModuleOutputSeparator().get(),
mapAndGetOrNull(getMultipleFileOutputDir(), it -> it.getAsFile().getAbsolutePath()),
getExpression().getOrNull()));
}

@SuppressWarnings("unused")
@OutputFiles
@Optional
public FileCollection getEffectiveOutputFiles() {
return getObjects()
.fileCollection()
.from(cliEvaluator.map(e -> nullToEmpty(e.getOutputFiles())));
.from(getProviders().provider(() -> nullToEmpty(createCliEvaluator().getOutputFiles())));
}

@OutputDirectories
@Optional
public FileCollection getEffectiveOutputDirs() {
return getObjects()
.fileCollection()
.from(cliEvaluator.map(e -> nullToEmpty(e.getOutputDirectories())));
.from(
getProviders()
.provider(() -> nullToEmpty(createCliEvaluator().getOutputDirectories())));
}

private static <T> Set<T> nullToEmpty(@Nullable Set<T> set) {
Expand All @@ -95,6 +93,6 @@ private static <T> Set<T> nullToEmpty(@Nullable Set<T> set) {
protected void doRunTask() {
//noinspection ResultOfMethodCallIgnored
getOutputs().getPreviousOutputFiles().forEach(File::delete);
cliEvaluator.get().run();
createCliEvaluator().run();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ protected void doRunTask() {
new CliJavaCodeGenerator(
new CliJavaCodeGeneratorOptions(
getCliBaseOptions(),
getProject().file(getOutputDir()).toPath(),
getOutputDir().get().getAsFile().toPath(),
getIndent().get(),
getAddGeneratedAnnotation().get(),
getGenerateGetters().get(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ protected void doRunTask() {
new CliKotlinCodeGenerator(
new CliKotlinCodeGeneratorOptions(
getCliBaseOptions(),
getProject().file(getOutputDir()).toPath(),
getOutputDir().get().getAsFile().toPath(),
getIndent().get(),
getGenerateKdoc().get(),
getGenerateSpringBootConfig().get(),
Expand Down
68 changes: 30 additions & 38 deletions pkl-gradle/src/main/java/org/pkl/gradle/task/ModulesTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -56,21 +55,17 @@ public abstract class ModulesTask extends BasePklTask {
@PathSensitive(PathSensitivity.ABSOLUTE)
public abstract ListProperty<File> getTransitiveModules();

private final Map<List<Object>, Pair<List<File>, List<URI>>> parsedSourceModulesCache =
new HashMap<>();

// Used for input tracking purposes only.
@Internal
public Provider<Pair<List<File>, List<URI>>> getParsedSourceModules() {
return getSourceModules()
.map(it -> parsedSourceModulesCache.computeIfAbsent(it, this::splitFilesAndUris));
return getSourceModules().map(this::splitFilesAndUris);
}

// We use @InputFiles and FileCollection here to ensure that file contents are tracked.
@InputFiles
@PathSensitive(PathSensitivity.ABSOLUTE)
public FileCollection getSourceModuleFiles() {
return getProject().files(getParsedSourceModules().map(it -> it.first));
return getObjects().fileCollection().from(getParsedSourceModules().map(it -> it.first));
}

// We use @Input and just a list value because we can only track the URIs themselves
Expand Down Expand Up @@ -144,37 +139,34 @@ public void runTask() {

@Internal
@Override
// See BasePklTask.getCliBaseOptions() for why caching is intentionally omitted.
protected CliBaseOptions getCliBaseOptions() {
if (__cachedOptions == null) {
__cachedOptions =
new CliBaseOptions(
getSourceModulesAsUris(),
patternsFromStrings(getAllowedModules().get()),
patternsFromStrings(getAllowedResources().get()),
getEnvironmentVariables().get(),
getExternalProperties().get(),
parseModulePath(),
getProject().getProjectDir().toPath(),
mapAndGetOrNull(getEvalRootDirPath(), Paths::get),
mapAndGetOrNull(getSettingsModule(), PluginUtils::parseModuleNotationToUri),
getProjectDir().isPresent() ? getProjectDir().get().getAsFile().toPath() : null,
getEvalTimeout().getOrNull(),
mapAndGetOrNull(getModuleCacheDir(), it1 -> it1.getAsFile().toPath()),
getColor().getOrElse(false) ? Color.ALWAYS : Color.NEVER,
getNoCache().getOrElse(false),
getOmitProjectSettings().getOrElse(false),
getNoProject().getOrElse(false),
false,
getTestPort().getOrElse(-1),
Collections.emptyList(),
null,
List.of(),
getHttpRewrites().getOrNull(),
Map.of(),
Map.of(),
null,
getPowerAssertions().getOrElse(false));
}
return __cachedOptions;
return new CliBaseOptions(
getSourceModulesAsUris(),
patternsFromStrings(getAllowedModules().get()),
patternsFromStrings(getAllowedResources().get()),
getEnvironmentVariables().get(),
getExternalProperties().get(),
parseModulePath(),
getWorkingDir().get().getAsFile().toPath(),
mapAndGetOrNull(getEvalRootDirPath(), Paths::get),
mapAndGetOrNull(getSettingsModule(), PluginUtils::parseModuleNotationToUri),
getProjectDir().isPresent() ? getProjectDir().get().getAsFile().toPath() : null,
getEvalTimeout().getOrNull(),
mapAndGetOrNull(getModuleCacheDir(), it1 -> it1.getAsFile().toPath()),
getColor().getOrElse(false) ? Color.ALWAYS : Color.NEVER,
getNoCache().getOrElse(false),
getOmitProjectSettings().getOrElse(false),
getNoProject().getOrElse(false),
false,
getTestPort().getOrElse(-1),
Collections.emptyList(),
null,
List.of(),
getHttpRewrites().getOrNull(),
Map.of(),
Map.of(),
null,
getPowerAssertions().getOrElse(false));
}
}
37 changes: 36 additions & 1 deletion pkl-gradle/src/main/java/org/pkl/gradle/utils/PluginUtils.java
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -19,11 +19,16 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.file.FileSystemLocation;
import org.gradle.api.file.RegularFile;
import org.pkl.core.ImportGraph;
import org.pkl.core.util.IoUtils;

public class PluginUtils {
Expand Down Expand Up @@ -125,4 +130,34 @@ public static URI parseModuleNotationToUri(Object m) {
var parsed1 = PluginUtils.parseModuleNotation(m);
return parsedModuleNotationToUri(parsed1);
}

/**
* Parses the list of file-scheme transitive imports from the JSON output file produced by an
* analyze imports task. Returns an empty list if the file does not exist. Throws a {@link
* RuntimeException} if the file exists but cannot be read or parsed, so that upstream errors are
* not silently lost.
*
* <p>The automatically-created {@code GatherImports} tasks always write JSON, so this method
* assumes JSON format and should only be called for those tasks.
*
* @param outputFile the output file produced by the analyze imports task
* @return the list of file-based transitive import paths
*/
public static List<File> parseTransitiveFiles(RegularFile outputFile) {
if (!outputFile.getAsFile().exists()) {
return Collections.emptyList();
}
try {
var contents = Files.readString(outputFile.getAsFile().toPath());
var importGraph = ImportGraph.parseFromJson(contents);
var imports = importGraph.resolvedImports().values();
return imports.stream()
.filter(it -> it.getScheme().equalsIgnoreCase("file"))
.map(File::new)
.toList();
} catch (Exception e) {
throw new RuntimeException(
"Failed to parse transitive imports from " + outputFile.getAsFile(), e);
}
}
}
Loading
Loading