mirror of
https://github.com/apple/pkl.git
synced 2026-06-07 14:22:49 +02:00
Initial commit
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright © 2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.gradle;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.NamedDomainObjectContainer;
|
||||
import org.gradle.api.tasks.Nested;
|
||||
import org.pkl.gradle.spec.EvalSpec;
|
||||
import org.pkl.gradle.spec.JavaCodeGenSpec;
|
||||
import org.pkl.gradle.spec.KotlinCodeGenSpec;
|
||||
import org.pkl.gradle.spec.PkldocSpec;
|
||||
import org.pkl.gradle.spec.TestSpec;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public interface PklExtension {
|
||||
NamedDomainObjectContainer<EvalSpec> getEvaluators();
|
||||
|
||||
NamedDomainObjectContainer<JavaCodeGenSpec> getJavaCodeGenerators();
|
||||
|
||||
NamedDomainObjectContainer<KotlinCodeGenSpec> getKotlinCodeGenerators();
|
||||
|
||||
NamedDomainObjectContainer<PkldocSpec> getPkldocGenerators();
|
||||
|
||||
NamedDomainObjectContainer<TestSpec> getTests();
|
||||
|
||||
@Nested
|
||||
PklProjectCommands getProject();
|
||||
|
||||
default void evaluators(Action<? super NamedDomainObjectContainer<EvalSpec>> action) {
|
||||
action.execute(getEvaluators());
|
||||
}
|
||||
|
||||
default void javaCodeGenerators(
|
||||
Action<? super NamedDomainObjectContainer<JavaCodeGenSpec>> action) {
|
||||
action.execute(getJavaCodeGenerators());
|
||||
}
|
||||
|
||||
default void kotlinCodeGenerators(
|
||||
Action<? super NamedDomainObjectContainer<KotlinCodeGenSpec>> action) {
|
||||
action.execute(getKotlinCodeGenerators());
|
||||
}
|
||||
|
||||
default void pkldocGenerators(Action<? super NamedDomainObjectContainer<PkldocSpec>> action) {
|
||||
action.execute(getPkldocGenerators());
|
||||
}
|
||||
|
||||
default void tests(Action<? super NamedDomainObjectContainer<TestSpec>> action) {
|
||||
action.execute(getTests());
|
||||
}
|
||||
|
||||
default void project(Action<? super PklProjectCommands> action) {
|
||||
action.execute(getProject());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,519 @@
|
||||
/**
|
||||
* Copyright © 2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.gradle;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.NamedDomainObjectContainer;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.file.SourceDirectorySet;
|
||||
import org.gradle.api.plugins.Convention;
|
||||
import org.gradle.api.provider.Provider;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.SourceSetContainer;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.language.base.plugins.LifecycleBasePlugin;
|
||||
import org.gradle.plugins.ide.idea.model.IdeaModel;
|
||||
import org.gradle.util.GradleVersion;
|
||||
import org.pkl.cli.CliEvaluatorOptions;
|
||||
import org.pkl.core.util.IoUtils;
|
||||
import org.pkl.core.util.LateInit;
|
||||
import org.pkl.gradle.spec.BasePklSpec;
|
||||
import org.pkl.gradle.spec.CodeGenSpec;
|
||||
import org.pkl.gradle.spec.EvalSpec;
|
||||
import org.pkl.gradle.spec.JavaCodeGenSpec;
|
||||
import org.pkl.gradle.spec.KotlinCodeGenSpec;
|
||||
import org.pkl.gradle.spec.ModulesSpec;
|
||||
import org.pkl.gradle.spec.PkldocSpec;
|
||||
import org.pkl.gradle.spec.ProjectPackageSpec;
|
||||
import org.pkl.gradle.spec.ProjectResolveSpec;
|
||||
import org.pkl.gradle.spec.TestSpec;
|
||||
import org.pkl.gradle.task.BasePklTask;
|
||||
import org.pkl.gradle.task.CodeGenTask;
|
||||
import org.pkl.gradle.task.EvalTask;
|
||||
import org.pkl.gradle.task.JavaCodeGenTask;
|
||||
import org.pkl.gradle.task.KotlinCodeGenTask;
|
||||
import org.pkl.gradle.task.ModulesTask;
|
||||
import org.pkl.gradle.task.PkldocTask;
|
||||
import org.pkl.gradle.task.ProjectPackageTask;
|
||||
import org.pkl.gradle.task.ProjectResolveTask;
|
||||
import org.pkl.gradle.task.TestTask;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class PklPlugin implements Plugin<Project> {
|
||||
|
||||
private static final String MIN_GRADLE_VERSION = "7.2";
|
||||
|
||||
@LateInit private Project project;
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
this.project = project;
|
||||
|
||||
if (GradleVersion.current().compareTo(GradleVersion.version(MIN_GRADLE_VERSION)) < 0) {
|
||||
throw new GradleException(
|
||||
String.format("Plugin `org.pkl` requires Gradle %s or higher.", MIN_GRADLE_VERSION));
|
||||
}
|
||||
|
||||
var extension = project.getExtensions().create("pkl", PklExtension.class);
|
||||
configureExtension(extension);
|
||||
}
|
||||
|
||||
private void configureExtension(PklExtension extension) {
|
||||
configureEvalTasks(extension.getEvaluators());
|
||||
configureJavaCodeGenTasks(extension.getJavaCodeGenerators());
|
||||
configureKotlinCodeGenTasks(extension.getKotlinCodeGenerators());
|
||||
configurePkldocTasks(extension.getPkldocGenerators());
|
||||
configureTestTasks(extension.getTests());
|
||||
configureProjectPackageTasks(extension.getProject().getPackagers());
|
||||
configureProjectResolveTasks(extension.getProject().getResolvers());
|
||||
}
|
||||
|
||||
private void configureProjectPackageTasks(NamedDomainObjectContainer<ProjectPackageSpec> specs) {
|
||||
specs.all(
|
||||
spec -> {
|
||||
configureBaseSpec(spec);
|
||||
spec.getOutputPath()
|
||||
.convention(project.getLayout().getBuildDirectory().dir("generated/pkl/packages"));
|
||||
spec.getOverwrite().convention(false);
|
||||
var packageTask = createTask(ProjectPackageTask.class, spec);
|
||||
packageTask.configure(
|
||||
task -> {
|
||||
task.getProjectDirectories().from(spec.getProjectDirectories());
|
||||
task.getOutputPath().set(spec.getOutputPath());
|
||||
task.getSkipPublishCheck().set(spec.getSkipPublishCheck());
|
||||
task.getJunitReportsDir().set(spec.getJunitReportsDir());
|
||||
task.getOverwrite().set(spec.getOverwrite());
|
||||
});
|
||||
project
|
||||
.getPluginManager()
|
||||
.withPlugin(
|
||||
"base",
|
||||
appliedPlugin ->
|
||||
project
|
||||
.getTasks()
|
||||
.named(
|
||||
LifecycleBasePlugin.BUILD_TASK_NAME,
|
||||
it -> it.dependsOn(packageTask)));
|
||||
});
|
||||
}
|
||||
|
||||
private void configureProjectResolveTasks(NamedDomainObjectContainer<ProjectResolveSpec> specs) {
|
||||
specs.all(
|
||||
spec -> {
|
||||
configureBaseSpec(spec);
|
||||
var resolveTask = createTask(ProjectResolveTask.class, spec);
|
||||
resolveTask.configure(
|
||||
task -> task.getProjectDirectories().from(spec.getProjectDirectories()));
|
||||
});
|
||||
}
|
||||
|
||||
private void configureEvalTasks(NamedDomainObjectContainer<EvalSpec> specs) {
|
||||
specs.all(
|
||||
spec -> {
|
||||
configureBaseSpec(spec);
|
||||
spec.getOutputFile()
|
||||
.convention(
|
||||
project
|
||||
.getLayout()
|
||||
.getProjectDirectory()
|
||||
// %{moduleDir} is resolved relatively to the working directory,
|
||||
// and the working directory is set to the project directory,
|
||||
// so this path works correctly.
|
||||
.file("%{moduleDir}/%{moduleName}.%{outputFormat}"));
|
||||
spec.getOutputFormat().convention("pcf");
|
||||
spec.getModuleOutputSeparator()
|
||||
.convention(CliEvaluatorOptions.Companion.getDefaults().getModuleOutputSeparator());
|
||||
spec.getExpression()
|
||||
.convention(CliEvaluatorOptions.Companion.getDefaults().getExpression());
|
||||
|
||||
createModulesTask(EvalTask.class, spec)
|
||||
.configure(
|
||||
task -> {
|
||||
task.getOutputFile().set(spec.getOutputFile());
|
||||
task.getOutputFormat().set(spec.getOutputFormat());
|
||||
task.getModuleOutputSeparator().set(spec.getModuleOutputSeparator());
|
||||
task.getMultipleFileOutputDir().set(spec.getMultipleFileOutputDir());
|
||||
task.getExpression().set(spec.getExpression());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void configureJavaCodeGenTasks(NamedDomainObjectContainer<JavaCodeGenSpec> specs) {
|
||||
specs.all(
|
||||
spec -> {
|
||||
configureBaseSpec(spec);
|
||||
configureCodeGenSpec(spec);
|
||||
|
||||
spec.getGenerateGetters().convention(false);
|
||||
spec.getGenerateJavadoc().convention(false);
|
||||
|
||||
createModulesTask(JavaCodeGenTask.class, spec)
|
||||
.configure(
|
||||
task -> {
|
||||
configureCodeGenTask(task, spec);
|
||||
task.getGenerateGetters().set(spec.getGenerateGetters());
|
||||
task.getGenerateJavadoc().set(spec.getGenerateJavadoc());
|
||||
task.getParamsAnnotation().set(spec.getParamsAnnotation());
|
||||
task.getNonNullAnnotation().set(spec.getNonNullAnnotation());
|
||||
});
|
||||
});
|
||||
|
||||
project.afterEvaluate(
|
||||
prj ->
|
||||
specs.all(
|
||||
spec -> {
|
||||
configureIdeaModule(spec);
|
||||
configureCodeGenSpecSourceDirectories(
|
||||
spec, "java", s -> Optional.of(s.getJava()));
|
||||
}));
|
||||
}
|
||||
|
||||
private void configureKotlinCodeGenTasks(NamedDomainObjectContainer<KotlinCodeGenSpec> specs) {
|
||||
specs.all(
|
||||
spec -> {
|
||||
configureBaseSpec(spec);
|
||||
configureCodeGenSpec(spec);
|
||||
|
||||
spec.getGenerateKdoc().convention(false);
|
||||
|
||||
createModulesTask(KotlinCodeGenTask.class, spec)
|
||||
.configure(
|
||||
task -> {
|
||||
configureCodeGenTask(task, spec);
|
||||
task.getGenerateKdoc().set(spec.getGenerateKdoc());
|
||||
});
|
||||
});
|
||||
|
||||
project.afterEvaluate(
|
||||
prj ->
|
||||
specs.all(
|
||||
spec -> {
|
||||
configureIdeaModule(spec);
|
||||
configureCodeGenSpecSourceDirectories(
|
||||
spec, "kotlin", this::getKotlinSourceDirectorySet);
|
||||
}));
|
||||
}
|
||||
|
||||
private void configurePkldocTasks(NamedDomainObjectContainer<PkldocSpec> specs) {
|
||||
specs.all(
|
||||
spec -> {
|
||||
configureBaseSpec(spec);
|
||||
|
||||
spec.getOutputDir()
|
||||
.convention(
|
||||
project
|
||||
.getLayout()
|
||||
.getBuildDirectory()
|
||||
.map(it -> it.dir("pkldoc").dir(spec.getName())));
|
||||
|
||||
createModulesTask(PkldocTask.class, spec)
|
||||
.configure(task -> task.getOutputDir().set(spec.getOutputDir()));
|
||||
});
|
||||
}
|
||||
|
||||
private void configureTestTasks(NamedDomainObjectContainer<TestSpec> specs) {
|
||||
specs.all(
|
||||
spec -> {
|
||||
configureBaseSpec(spec);
|
||||
|
||||
spec.getOverwrite().convention(false);
|
||||
|
||||
var testTask = createModulesTask(TestTask.class, spec);
|
||||
testTask.configure(
|
||||
task -> {
|
||||
task.getJunitReportsDir().set(spec.getJunitReportsDir());
|
||||
task.getOverwrite().set(spec.getOverwrite());
|
||||
});
|
||||
|
||||
project
|
||||
.getPluginManager()
|
||||
.withPlugin(
|
||||
"base",
|
||||
appliedPlugin ->
|
||||
project
|
||||
.getTasks()
|
||||
.named(
|
||||
LifecycleBasePlugin.CHECK_TASK_NAME,
|
||||
checkTask -> checkTask.dependsOn(testTask)));
|
||||
});
|
||||
}
|
||||
|
||||
private void configureBaseSpec(BasePklSpec spec) {
|
||||
spec.getAllowedModules()
|
||||
.convention(
|
||||
List.of(
|
||||
"repl:", "file:", "modulepath:", "https:", "pkl:", "package:", "projectpackage:"));
|
||||
|
||||
spec.getAllowedResources()
|
||||
.convention(List.of("env:", "prop:", "file:", "modulepath:", "https:", "package:"));
|
||||
|
||||
spec.getEvalRootDir().convention(project.getRootProject().getLayout().getProjectDirectory());
|
||||
|
||||
// Defaulting to OS env vars is bad for reproducibility and cachability.
|
||||
// Hence, this spec defaults to empty env vars, which is consistent with other Gradle tasks
|
||||
// (e.g., Test) but inconsistent with the Pkl CLI.
|
||||
// Therefore, we don't set any initial value for the environmentVariables property.
|
||||
|
||||
// Not using `convention()` to allow the user to unset this property, disabling the cache.
|
||||
spec.getModuleCacheDir().set(IoUtils.getDefaultModuleCacheDir().toFile());
|
||||
|
||||
spec.getNoCache().convention(false);
|
||||
}
|
||||
|
||||
private void configureCodeGenSpec(CodeGenSpec spec) {
|
||||
spec.getOutputDir()
|
||||
.convention(
|
||||
project
|
||||
.getLayout()
|
||||
.getBuildDirectory()
|
||||
.map(it -> it.dir("generated").dir("pkl").dir(spec.getName())));
|
||||
|
||||
spec.getSourceSet()
|
||||
.convention(
|
||||
project
|
||||
.getProviders()
|
||||
.provider(
|
||||
() -> {
|
||||
var sourceSets = project.getExtensions().findByType(SourceSetContainer.class);
|
||||
if (sourceSets == null) {
|
||||
return null;
|
||||
}
|
||||
return sourceSets.findByName(SourceSet.MAIN_SOURCE_SET_NAME);
|
||||
}));
|
||||
|
||||
spec.getIndent().convention(" ");
|
||||
|
||||
spec.getGenerateSpringBootConfig().convention(false);
|
||||
|
||||
spec.getImplementSerializable().convention(false);
|
||||
|
||||
configureCodeGenSpecModulePath(spec);
|
||||
}
|
||||
|
||||
private void configureCodeGenSpecModulePath(CodeGenSpec spec) {
|
||||
// Set module path to all of the configured resources source directories and the compile
|
||||
// classpath to find Pkl modules that are classpath resources.
|
||||
// Compilation classpath should be correct (vs. runtime classpath) if the library author
|
||||
// properly declares upstream libraries as `api` dependencies.
|
||||
// We must not use the processResources task as an input here, because it would introduce
|
||||
// a circular dependency (because codegen also generates a resources directory).
|
||||
//
|
||||
// Also note that in this case, we are NOT setting a dependency from the
|
||||
// spec.getModulePath() file collection to the sourceSet.getResources().getSourceDirectories()
|
||||
// file collection. Doing that would make spec.getModulePath() propagate a dependency
|
||||
// on the tasks which contribute to sourceSet.getResources().getSourceDirectories(),
|
||||
// and one of them is our own codegen task, which would result in a circular dependency.
|
||||
// Refer to configureCodeGenSpecSourceDirectories for logic which links the codegen task
|
||||
// to sourceSet.getResources().getSourceDirectories().
|
||||
|
||||
var modulePath = project.files();
|
||||
modulePath
|
||||
.from(getResourceSourceDirectoriesExceptSpecOutput(spec))
|
||||
// This technically breaks the dependency on compile classpath builder tasks,
|
||||
// however, compile classpath on a source set is always a plain configuration which
|
||||
// has no builder tasks, so this is not an issue.
|
||||
.from(spec.getSourceSet().map(SourceSet::getCompileClasspath));
|
||||
spec.getModulePath().from(modulePath);
|
||||
}
|
||||
|
||||
private Provider<Set<File>> getResourceSourceDirectoriesExceptSpecOutput(CodeGenSpec spec) {
|
||||
// Intentionally break the dependency on source set's resources source directory set
|
||||
// builder tasks by using `getFiles()` instead of `FileCollection`
|
||||
// returned by `getSourceDirectories()`.
|
||||
// Additionally, we must exclude our own output, to avoid creating circular dependencies
|
||||
// at runtime which invalidate task execution cache.
|
||||
return spec.getSourceSet()
|
||||
.flatMap(
|
||||
sourceSet ->
|
||||
spec.getOutputDir()
|
||||
.map(
|
||||
specOutputDir ->
|
||||
sourceSet
|
||||
.getResources()
|
||||
.getSourceDirectories()
|
||||
.filter(
|
||||
f ->
|
||||
!f.getAbsolutePath()
|
||||
.startsWith(
|
||||
specOutputDir.getAsFile().getAbsolutePath()))
|
||||
.getFiles()));
|
||||
}
|
||||
|
||||
// Must be called from Project.afterEvaluate() only, because this method depends
|
||||
// on user-provided configuration not accessible with lazy configuration.
|
||||
private void configureCodeGenSpecSourceDirectories(
|
||||
CodeGenSpec spec,
|
||||
String languageName,
|
||||
Function<? super SourceSet, ? extends Optional<SourceDirectorySet>>
|
||||
extractSourceDirectorySet) {
|
||||
var task = project.getTasks().named(spec.getName(), CodeGenTask.class);
|
||||
var sourceSet = spec.getSourceSet().get();
|
||||
extractSourceDirectorySet
|
||||
.apply(sourceSet)
|
||||
.ifPresentOrElse(
|
||||
dirSet -> dirSet.srcDir(task.flatMap(t -> t.getOutputDir().dir(languageName))),
|
||||
() ->
|
||||
project
|
||||
.getLogger()
|
||||
.debug(
|
||||
"Source directory set for language {} is not available, "
|
||||
+ "will not add task {} as its dependency",
|
||||
languageName,
|
||||
task.getName()));
|
||||
sourceSet.getResources().srcDir(task.flatMap(t -> t.getOutputDir().dir("resources")));
|
||||
}
|
||||
|
||||
// Must be called from Project.afterEvaluate() only, because this method depends
|
||||
// on user-provided configuration not accessible with lazy configuration.
|
||||
private void configureIdeaModule(CodeGenSpec spec) {
|
||||
project
|
||||
.getPluginManager()
|
||||
.withPlugin(
|
||||
"idea",
|
||||
plugin -> {
|
||||
var module = project.getExtensions().getByType(IdeaModel.class).getModule();
|
||||
var outputDir = spec.getOutputDir().get().getAsFile();
|
||||
module.getGeneratedSourceDirs().add(outputDir);
|
||||
if (spec.getSourceSet().get().getName().toLowerCase().contains("test")) {
|
||||
module.setTestSourceDirs(append(module.getTestSourceDirs(), outputDir));
|
||||
} else {
|
||||
module.setSourceDirs(append(module.getSourceDirs(), outputDir));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void configureCodeGenTask(CodeGenTask task, CodeGenSpec spec) {
|
||||
task.getIndent().set(spec.getIndent());
|
||||
task.getOutputDir().set(spec.getOutputDir());
|
||||
task.getGenerateSpringBootConfig().set(spec.getGenerateSpringBootConfig());
|
||||
task.getImplementSerializable().set(spec.getImplementSerializable());
|
||||
}
|
||||
|
||||
private <T extends BasePklTask, S extends BasePklSpec> void configureBaseTask(T task, S spec) {
|
||||
task.getAllowedModules().set(spec.getAllowedModules());
|
||||
task.getAllowedResources().set(spec.getAllowedResources());
|
||||
task.getEnvironmentVariables().set(spec.getEnvironmentVariables());
|
||||
task.getExternalProperties().set(spec.getExternalProperties());
|
||||
task.getModulePath().from(spec.getModulePath());
|
||||
task.getSettingsModule().set(spec.getSettingsModule());
|
||||
task.getEvalRootDir().set(spec.getEvalRootDir());
|
||||
task.getNoCache().set(spec.getNoCache());
|
||||
task.getModuleCacheDir().set(spec.getModuleCacheDir());
|
||||
task.getEvalTimeout().set(spec.getEvalTimeout());
|
||||
}
|
||||
|
||||
private <T extends ModulesTask, S extends ModulesSpec> void configureModulesTask(T task, S spec) {
|
||||
configureBaseTask(task, spec);
|
||||
task.getSourceModules().set(spec.getSourceModules());
|
||||
task.getTransitiveModules().from(spec.getTransitiveModules());
|
||||
task.getNoProject().set(spec.getNoProject());
|
||||
task.getProjectDir().set(spec.getProjectDir());
|
||||
task.getOmitProjectSettings().set(spec.getOmitProjectSettings());
|
||||
}
|
||||
|
||||
private <T extends ModulesTask> TaskProvider<T> createModulesTask(
|
||||
Class<T> taskClass, ModulesSpec spec) {
|
||||
return project
|
||||
.getTasks()
|
||||
.register(spec.getName(), taskClass, task -> configureModulesTask(task, spec));
|
||||
}
|
||||
|
||||
private <T extends BasePklTask> TaskProvider<T> createTask(Class<T> taskClass, BasePklSpec spec) {
|
||||
return project
|
||||
.getTasks()
|
||||
.register(spec.getName(), taskClass, task -> configureBaseTask(task, spec));
|
||||
}
|
||||
|
||||
private <T> Set<T> append(Set<? extends T> set1, T element) {
|
||||
Set<T> result = new LinkedHashSet<>(set1.size() + 1);
|
||||
result.addAll(set1);
|
||||
result.add(element);
|
||||
return result;
|
||||
}
|
||||
|
||||
private Optional<SourceDirectorySet> getKotlinSourceDirectorySet(SourceSet sourceSet) {
|
||||
// First, try loading it as an extension - 1.8+ version of Kotlin plugin does this.
|
||||
var kotlinExtension = sourceSet.getExtensions().findByName("kotlin");
|
||||
if (kotlinExtension instanceof SourceDirectorySet) {
|
||||
return Optional.of((SourceDirectorySet) kotlinExtension);
|
||||
}
|
||||
|
||||
// Otherwise, try to load it as a convention. First, we attempt to get the convention
|
||||
// object of the source set via the HasConvention.getConvention() method.
|
||||
// We don't use the HasConvention interface directly as it is deprecated.
|
||||
// Then, we extract the `kotlin` plugin from the convention, which "provides"
|
||||
// the additional properties for the source set. This plugin has a method named
|
||||
// `getKotlin` whose return type is a source directory set, so we use reflection
|
||||
// to call it too.
|
||||
// Basically, this is equivalent to calling `sourceSet.kotlin`, where `kotlin` is a property
|
||||
// contributed by a plugin also named `kotlin`.
|
||||
// This part of logic can be removed once we stop supporting Kotlin plugin with version
|
||||
// less than 1.8.x.
|
||||
try {
|
||||
var getConventionMethod = sourceSet.getClass().getMethod("getConvention");
|
||||
var convention = getConventionMethod.invoke(sourceSet);
|
||||
if (convention instanceof Convention) {
|
||||
var kotlinSourceSet = ((Convention) convention).getPlugins().get("kotlin");
|
||||
if (kotlinSourceSet == null) {
|
||||
project
|
||||
.getLogger()
|
||||
.debug(
|
||||
"Cannot obtain Kotlin source directory set of source set [{}], "
|
||||
+ "it does not have the `kotlin` convention plugin",
|
||||
sourceSet.getName());
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
var getKotlinMethod = kotlinSourceSet.getClass().getMethod("getKotlin");
|
||||
var kotlinSourceDirectorySet = getKotlinMethod.invoke(kotlinSourceSet);
|
||||
if (kotlinSourceDirectorySet instanceof SourceDirectorySet) {
|
||||
return Optional.of((SourceDirectorySet) kotlinSourceDirectorySet);
|
||||
}
|
||||
|
||||
project
|
||||
.getLogger()
|
||||
.debug(
|
||||
"Cannot obtain Kotlin source directory set, sourceSets.{}.kotlin is of wrong type",
|
||||
sourceSet.getName());
|
||||
} else {
|
||||
project
|
||||
.getLogger()
|
||||
.debug(
|
||||
"Cannot obtain Kotlin source directory set, sourceSets.{}.convention "
|
||||
+ "returned unexpected type",
|
||||
sourceSet.getName());
|
||||
}
|
||||
|
||||
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||
project
|
||||
.getLogger()
|
||||
.debug(
|
||||
"Cannot obtain Kotlin source directory set of source set [{}] via a convention",
|
||||
sourceSet.getName(),
|
||||
e);
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright © 2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.gradle;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.NamedDomainObjectContainer;
|
||||
import org.pkl.gradle.spec.ProjectPackageSpec;
|
||||
import org.pkl.gradle.spec.ProjectResolveSpec;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public interface PklProjectCommands {
|
||||
NamedDomainObjectContainer<ProjectPackageSpec> getPackagers();
|
||||
|
||||
NamedDomainObjectContainer<ProjectResolveSpec> getResolvers();
|
||||
|
||||
default void packagers(Action<? super NamedDomainObjectContainer<ProjectPackageSpec>> action) {
|
||||
action.execute(getPackagers());
|
||||
}
|
||||
|
||||
default void resolvers(Action<? super NamedDomainObjectContainer<ProjectResolveSpec>> action) {
|
||||
action.execute(getResolvers());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
@NonnullByDefault
|
||||
package org.pkl.gradle;
|
||||
|
||||
import org.pkl.core.util.NonnullByDefault;
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright © 2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.gradle.spec;
|
||||
|
||||
import java.time.Duration;
|
||||
import org.gradle.api.file.ConfigurableFileCollection;
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
import org.gradle.api.provider.ListProperty;
|
||||
import org.gradle.api.provider.MapProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
|
||||
/** Configuration options shared between plugin features. Documented in user manual. */
|
||||
public interface BasePklSpec {
|
||||
String getName();
|
||||
|
||||
ConfigurableFileCollection getTransitiveModules();
|
||||
|
||||
ListProperty<String> getAllowedModules();
|
||||
|
||||
ListProperty<String> getAllowedResources();
|
||||
|
||||
MapProperty<String, String> getEnvironmentVariables();
|
||||
|
||||
MapProperty<String, String> getExternalProperties();
|
||||
|
||||
ConfigurableFileCollection getModulePath();
|
||||
|
||||
Property<Object> getSettingsModule();
|
||||
|
||||
DirectoryProperty getEvalRootDir();
|
||||
|
||||
DirectoryProperty getModuleCacheDir();
|
||||
|
||||
Property<Boolean> getNoCache();
|
||||
|
||||
// use same type (Duration) as Gradle's `Task.timeout`
|
||||
Property<Duration> getEvalTimeout();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright © 2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.gradle.spec;
|
||||
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
|
||||
/** Configuration options shared between code generators. Documented in user manual. */
|
||||
public interface CodeGenSpec extends ModulesSpec {
|
||||
DirectoryProperty getOutputDir();
|
||||
|
||||
Property<SourceSet> getSourceSet();
|
||||
|
||||
Property<String> getIndent();
|
||||
|
||||
Property<Boolean> getGenerateSpringBootConfig();
|
||||
|
||||
Property<Boolean> getImplementSerializable();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright © 2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.gradle.spec;
|
||||
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
|
||||
/** Configuration options for evaluators. Documented in user manual. */
|
||||
public interface EvalSpec extends ModulesSpec {
|
||||
RegularFileProperty getOutputFile();
|
||||
|
||||
Property<String> getOutputFormat();
|
||||
|
||||
Property<String> getModuleOutputSeparator();
|
||||
|
||||
DirectoryProperty getMultipleFileOutputDir();
|
||||
|
||||
Property<String> getExpression();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright © 2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.gradle.spec;
|
||||
|
||||
import org.gradle.api.provider.Property;
|
||||
|
||||
/** Configuration options for Java code generators. Documented in user manual. */
|
||||
public interface JavaCodeGenSpec extends CodeGenSpec {
|
||||
Property<Boolean> getGenerateGetters();
|
||||
|
||||
Property<Boolean> getGenerateJavadoc();
|
||||
|
||||
Property<String> getParamsAnnotation();
|
||||
|
||||
Property<String> getNonNullAnnotation();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright © 2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.gradle.spec;
|
||||
|
||||
import org.gradle.api.provider.Property;
|
||||
|
||||
/** Configuration options for Kotlin code generators. Documented in user manual. */
|
||||
public interface KotlinCodeGenSpec extends CodeGenSpec {
|
||||
Property<Boolean> getGenerateKdoc();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright © 2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.gradle.spec;
|
||||
|
||||
import org.gradle.api.file.ConfigurableFileCollection;
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
import org.gradle.api.provider.ListProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
|
||||
public interface ModulesSpec extends BasePklSpec {
|
||||
ListProperty<Object> getSourceModules();
|
||||
|
||||
ConfigurableFileCollection getTransitiveModules();
|
||||
|
||||
DirectoryProperty getProjectDir();
|
||||
|
||||
Property<Boolean> getOmitProjectSettings();
|
||||
|
||||
Property<Boolean> getNoProject();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright © 2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.gradle.spec;
|
||||
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
|
||||
/** Configuration options for Pkldoc generators. Documented in user manual. */
|
||||
public interface PkldocSpec extends ModulesSpec {
|
||||
DirectoryProperty getOutputDir();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright © 2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.gradle.spec;
|
||||
|
||||
import org.gradle.api.file.ConfigurableFileCollection;
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
|
||||
public interface ProjectPackageSpec extends BasePklSpec {
|
||||
ConfigurableFileCollection getProjectDirectories();
|
||||
|
||||
DirectoryProperty getOutputPath();
|
||||
|
||||
DirectoryProperty getJunitReportsDir();
|
||||
|
||||
Property<Boolean> getOverwrite();
|
||||
|
||||
Property<Boolean> getSkipPublishCheck();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright © 2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.gradle.spec;
|
||||
|
||||
import org.gradle.api.file.ConfigurableFileCollection;
|
||||
|
||||
public interface ProjectResolveSpec extends BasePklSpec {
|
||||
ConfigurableFileCollection getProjectDirectories();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright © 2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.gradle.spec;
|
||||
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
|
||||
public interface TestSpec extends ModulesSpec {
|
||||
DirectoryProperty getJunitReportsDir();
|
||||
|
||||
Property<Boolean> getOverwrite();
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
@NonnullByDefault
|
||||
package org.pkl.gradle.spec;
|
||||
|
||||
import org.pkl.core.util.NonnullByDefault;
|
||||
@@ -0,0 +1,278 @@
|
||||
/**
|
||||
* Copyright © 2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.gradle.task;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.InvalidUserDataException;
|
||||
import org.gradle.api.file.ConfigurableFileCollection;
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
import org.gradle.api.file.FileSystemLocation;
|
||||
import org.gradle.api.provider.ListProperty;
|
||||
import org.gradle.api.provider.MapProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.provider.Provider;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.InputFile;
|
||||
import org.gradle.api.tasks.InputFiles;
|
||||
import org.gradle.api.tasks.Internal;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
import org.pkl.commons.cli.CliBaseOptions;
|
||||
import org.pkl.core.util.IoUtils;
|
||||
import org.pkl.core.util.LateInit;
|
||||
import org.pkl.core.util.Nullable;
|
||||
|
||||
public abstract class BasePklTask extends DefaultTask {
|
||||
@Input
|
||||
public abstract ListProperty<String> getAllowedModules();
|
||||
|
||||
@Input
|
||||
public abstract ListProperty<String> getAllowedResources();
|
||||
|
||||
@Input
|
||||
public abstract MapProperty<String, String> getEnvironmentVariables();
|
||||
|
||||
@Input
|
||||
public abstract MapProperty<String, String> getExternalProperties();
|
||||
|
||||
@InputFiles
|
||||
public abstract ConfigurableFileCollection getModulePath();
|
||||
|
||||
@Internal
|
||||
public abstract Property<Object> getSettingsModule();
|
||||
|
||||
@Internal
|
||||
public Provider<Object> getParsedSettingsModule() {
|
||||
return getSettingsModule().map(this::parseModuleNotation);
|
||||
}
|
||||
|
||||
@InputFile
|
||||
@Optional
|
||||
public Provider<File> getSettingsModuleFile() {
|
||||
return getParsedSettingsModule()
|
||||
.map(
|
||||
it -> {
|
||||
if (it instanceof File) {
|
||||
return (File) it;
|
||||
}
|
||||
//noinspection DataFlowIssue
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
public Provider<URI> getSettingsModuleUri() {
|
||||
return getParsedSettingsModule()
|
||||
.map(
|
||||
it -> {
|
||||
if (it instanceof URI) {
|
||||
return (URI) it;
|
||||
}
|
||||
//noinspection DataFlowIssue
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
// Exposed as a task input via evalRootDirPath, because we only need to depend
|
||||
// on this directory's path and not on its contents.
|
||||
@Internal
|
||||
public abstract DirectoryProperty getEvalRootDir();
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
public Provider<String> getEvalRootDirPath() {
|
||||
return getEvalRootDir().map(it -> it.getAsFile().getAbsolutePath());
|
||||
}
|
||||
|
||||
// This is not a task input because it doesn't affect task output but only performance.
|
||||
@Internal
|
||||
public abstract DirectoryProperty getModuleCacheDir();
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
public abstract Property<Boolean> getNoCache();
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
public abstract Property<Duration> getEvalTimeout();
|
||||
|
||||
@TaskAction
|
||||
public void runTask() {
|
||||
doRunTask();
|
||||
}
|
||||
|
||||
protected abstract void doRunTask();
|
||||
|
||||
@LateInit protected CliBaseOptions cachedOptions;
|
||||
|
||||
@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(), this::parseModuleNotationToUri),
|
||||
null,
|
||||
getEvalTimeout().getOrNull(),
|
||||
mapAndGetOrNull(getModuleCacheDir(), it1 -> it1.getAsFile().toPath()),
|
||||
getNoCache().getOrElse(false),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
Collections.emptyList());
|
||||
}
|
||||
return cachedOptions;
|
||||
}
|
||||
|
||||
@Internal
|
||||
protected List<URI> getSourceModulesAsUris() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
protected List<Path> parseModulePath() {
|
||||
return getModulePath().getFiles().stream().map(File::toPath).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the specified source module notation into a "parsed" notation which is then used for
|
||||
* input path tracking and as an argument for the CLI API.
|
||||
*
|
||||
* <p>This method accepts the following input types:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link URI} - used as is.
|
||||
* <li>{@link File} - used as is.
|
||||
* <li>{@link Path} - converted to a {@link File}. This conversion may fail because not all
|
||||
* {@link Path}s point to the local file system.
|
||||
* <li>{@link URL} - converted to a {@link URI}. This conversion may fail because {@link URL}
|
||||
* allows for URLs which are not compliant URIs.
|
||||
* <li>{@link CharSequence} - first, converted to a string. If this string is "URI-like" (see
|
||||
* {@link IoUtils#isUriLike(String)}), then we attempt to parse it as a {@link URI}, which
|
||||
* may fail. Otherwise, we attempt to parse it as a {@link Path}, which is then converted to
|
||||
* a {@link File} (both of these operations may fail).
|
||||
* <li>{@link FileSystemLocation} - converted to a {@link File} via the {@link
|
||||
* FileSystemLocation#getAsFile()} method.
|
||||
* </ul>
|
||||
*
|
||||
* In case the returned value is determined to be a {@link URI}, then this URI is first checked
|
||||
* for whether its scheme is {@code file}, like {@code file:///example/path}. In such case, this
|
||||
* method returns a {@link File} corresponding to the file path in the URI. Otherwise, a {@link
|
||||
* URI} instance is returned.
|
||||
*
|
||||
* @throws InvalidUserDataException In case the input is none of the types described above, or
|
||||
* when the underlying value cannot be parsed correctly.
|
||||
*/
|
||||
protected Object parseModuleNotation(Object m) {
|
||||
if (m instanceof URI) {
|
||||
var u = (URI) m;
|
||||
if ("file".equals(u.getScheme())) {
|
||||
return new File(u.getPath());
|
||||
}
|
||||
return u;
|
||||
} else if (m instanceof File) {
|
||||
return m;
|
||||
} else if (m instanceof Path) {
|
||||
try {
|
||||
return ((Path) m).toFile();
|
||||
} catch (UnsupportedOperationException e) {
|
||||
throw new InvalidUserDataException("Failed to parse Pkl module file path: " + m, e);
|
||||
}
|
||||
} else if (m instanceof URL) {
|
||||
try {
|
||||
return parseModuleNotation(((URL) m).toURI());
|
||||
} catch (URISyntaxException e) {
|
||||
throw new InvalidUserDataException("Failed to parse Pkl module URI: " + m, e);
|
||||
}
|
||||
} else if (m instanceof CharSequence) {
|
||||
var s = m.toString();
|
||||
if (IoUtils.isUriLike(s)) {
|
||||
try {
|
||||
return parseModuleNotation(IoUtils.toUri(s));
|
||||
} catch (URISyntaxException e) {
|
||||
throw new InvalidUserDataException("Failed to parse Pkl module URI: " + s, e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return Paths.get(s).toFile();
|
||||
} catch (InvalidPathException | UnsupportedOperationException e) {
|
||||
throw new InvalidUserDataException("Failed to parse Pkl module file path: " + s, e);
|
||||
}
|
||||
}
|
||||
} else if (m instanceof FileSystemLocation) {
|
||||
return ((FileSystemLocation) m).getAsFile();
|
||||
} else {
|
||||
throw new InvalidUserDataException(
|
||||
"Unsupported value of type " + m.getClass() + " used as a module path: " + m);
|
||||
}
|
||||
}
|
||||
|
||||
protected URI parseModuleNotationToUri(Object m) {
|
||||
var parsed1 = parseModuleNotation(m);
|
||||
return parsedModuleNotationToUri(parsed1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts either a file or a URI to a URI. We convert a file to a URI via the {@link
|
||||
* IoUtils#createUri(String)} because other ways of conversion can make relative paths into
|
||||
* absolute URIs, which may break module loading.
|
||||
*/
|
||||
private URI parsedModuleNotationToUri(Object m) {
|
||||
if (m instanceof File) {
|
||||
var f = (File) m;
|
||||
return IoUtils.createUri(f.getPath());
|
||||
} else if (m instanceof URI) {
|
||||
return (URI) m;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid parsed module notation: " + m);
|
||||
}
|
||||
|
||||
protected List<Pattern> patternsFromStrings(List<String> patterns) {
|
||||
return patterns.stream().map(Pattern::compile).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to {@code provider.map(it -> f.apply(it)).getOrNull()}.
|
||||
*
|
||||
* <p>This function is necessary because in some cases doing {@code
|
||||
* someProvider.map(...).getOrNull()} may trigger validation errors inside Gradle, when {@code
|
||||
* someProvider} is derived from a property.
|
||||
*/
|
||||
protected <T, U> @Nullable U mapAndGetOrNull(Provider<T> provider, Function<T, U> f) {
|
||||
@Nullable T value = provider.getOrNull();
|
||||
return value == null ? null : f.apply(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright © 2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.gradle.task;
|
||||
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.OutputDirectory;
|
||||
|
||||
public abstract class CodeGenTask extends ModulesTask {
|
||||
@OutputDirectory
|
||||
public abstract DirectoryProperty getOutputDir();
|
||||
|
||||
@Input
|
||||
public abstract Property<String> getIndent();
|
||||
|
||||
@Input
|
||||
public abstract Property<Boolean> getGenerateSpringBootConfig();
|
||||
|
||||
@Input
|
||||
public abstract Property<Boolean> getImplementSerializable();
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright © 2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.gradle.task;
|
||||
|
||||
import java.io.File;
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.Internal;
|
||||
import org.gradle.api.tasks.UntrackedTask;
|
||||
import org.pkl.cli.CliEvaluator;
|
||||
import org.pkl.cli.CliEvaluatorOptions;
|
||||
|
||||
@UntrackedTask(because = "Output file names are known only after execution")
|
||||
public abstract class EvalTask extends ModulesTask {
|
||||
@Internal
|
||||
public abstract RegularFileProperty getOutputFile();
|
||||
|
||||
@Internal
|
||||
public abstract Property<String> getOutputFormat();
|
||||
|
||||
@Internal
|
||||
public abstract Property<String> getModuleOutputSeparator();
|
||||
|
||||
@Internal
|
||||
public abstract DirectoryProperty getMultipleFileOutputDir();
|
||||
|
||||
@Internal
|
||||
public abstract Property<String> getExpression();
|
||||
|
||||
@Override
|
||||
protected void doRunTask() {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
getOutputs().getPreviousOutputFiles().forEach(File::delete);
|
||||
|
||||
new CliEvaluator(
|
||||
new CliEvaluatorOptions(
|
||||
getCliBaseOptions(),
|
||||
getOutputFile().get().getAsFile().getAbsolutePath(),
|
||||
getOutputFormat().get(),
|
||||
getModuleOutputSeparator().get(),
|
||||
mapAndGetOrNull(getMultipleFileOutputDir(), it -> it.getAsFile().getAbsolutePath()),
|
||||
getExpression().get()))
|
||||
.run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright © 2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.gradle.task;
|
||||
|
||||
import java.io.File;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.pkl.codegen.java.CliJavaCodeGenerator;
|
||||
import org.pkl.codegen.java.CliJavaCodeGeneratorOptions;
|
||||
|
||||
public abstract class JavaCodeGenTask extends CodeGenTask {
|
||||
@Input
|
||||
public abstract Property<Boolean> getGenerateGetters();
|
||||
|
||||
@Input
|
||||
public abstract Property<Boolean> getGenerateJavadoc();
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
public abstract Property<String> getParamsAnnotation();
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
public abstract Property<String> getNonNullAnnotation();
|
||||
|
||||
@Override
|
||||
protected void doRunTask() {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
getOutputs().getPreviousOutputFiles().forEach(File::delete);
|
||||
|
||||
new CliJavaCodeGenerator(
|
||||
new CliJavaCodeGeneratorOptions(
|
||||
getCliBaseOptions(),
|
||||
getProject().file(getOutputDir()).toPath(),
|
||||
getIndent().get(),
|
||||
getGenerateGetters().get(),
|
||||
getGenerateJavadoc().get(),
|
||||
getGenerateSpringBootConfig().get(),
|
||||
getParamsAnnotation().getOrNull(),
|
||||
getNonNullAnnotation().getOrNull(),
|
||||
getImplementSerializable().get()))
|
||||
.run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright © 2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.gradle.task;
|
||||
|
||||
import java.io.File;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.pkl.codegen.kotlin.CliKotlinCodeGenerator;
|
||||
import org.pkl.codegen.kotlin.CliKotlinCodeGeneratorOptions;
|
||||
|
||||
public abstract class KotlinCodeGenTask extends CodeGenTask {
|
||||
@Input
|
||||
public abstract Property<Boolean> getGenerateKdoc();
|
||||
|
||||
@Override
|
||||
protected void doRunTask() {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
getOutputs().getPreviousOutputFiles().forEach(File::delete);
|
||||
|
||||
new CliKotlinCodeGenerator(
|
||||
new CliKotlinCodeGeneratorOptions(
|
||||
getCliBaseOptions(),
|
||||
getProject().file(getOutputDir()).toPath(),
|
||||
getIndent().get(),
|
||||
getGenerateKdoc().get(),
|
||||
getGenerateSpringBootConfig().get(),
|
||||
getImplementSerializable().get()))
|
||||
.run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* Copyright © 2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.gradle.task;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
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;
|
||||
import org.gradle.api.InvalidUserDataException;
|
||||
import org.gradle.api.file.ConfigurableFileCollection;
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.provider.ListProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.provider.Provider;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.InputFiles;
|
||||
import org.gradle.api.tasks.Internal;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
import org.pkl.commons.cli.CliBaseOptions;
|
||||
import org.pkl.core.util.IoUtils;
|
||||
import org.pkl.core.util.Pair;
|
||||
|
||||
public abstract class ModulesTask extends BasePklTask {
|
||||
// We expose the contents of this property as task inputs via the sourceModuleFiles
|
||||
// and sourceModuleUris properties. We cannot use two separate properties because
|
||||
// the order of source modules matters for the CLI API invocation, so it must be
|
||||
// a single collection.
|
||||
@Internal
|
||||
public abstract ListProperty<Object> getSourceModules();
|
||||
|
||||
@InputFiles
|
||||
public abstract ConfigurableFileCollection 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));
|
||||
}
|
||||
|
||||
// We use @InputFiles and FileCollection here to ensure that file contents are tracked.
|
||||
@InputFiles
|
||||
public FileCollection getSourceModuleFiles() {
|
||||
return getProject().files(getParsedSourceModules().map(it -> it.first));
|
||||
}
|
||||
|
||||
// We use @Input and just a list value because we can only track the URIs themselves
|
||||
// but not their contents.
|
||||
@Input
|
||||
public Provider<List<URI>> getSourceModuleUris() {
|
||||
return getParsedSourceModules().map(it -> it.second);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sourceModules property as a list of URIs.
|
||||
*
|
||||
* <p>This method ensures that the order of source modules in the sourceModules property is
|
||||
* preserved all the way to the CLI API invocation.
|
||||
*/
|
||||
@Internal
|
||||
@Override
|
||||
protected List<URI> getSourceModulesAsUris() {
|
||||
return getSourceModules().get().stream()
|
||||
.map(this::parseModuleNotationToUri)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// Exposed as a task input via getProjectDirPath, because we only need to depend
|
||||
// on this directory's path and not on its contents.
|
||||
@Internal
|
||||
public abstract DirectoryProperty getProjectDir();
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
public Provider<String> getProjectDirPath() {
|
||||
return getProjectDir().map(it -> it.getAsFile().getAbsolutePath());
|
||||
}
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
public abstract Property<Boolean> getOmitProjectSettings();
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
public abstract Property<Boolean> getNoProject();
|
||||
|
||||
/**
|
||||
* A source module can be either a file or a URI. Files can be tracked, so this method splits a
|
||||
* collection of module notations (which can be strings, URIs, URLs, Files or Paths) into a list
|
||||
* of files (for content-based tracking) and URIs (for simple value-based tracking). These lists
|
||||
* are then exposed as separate read-only properties to make Gradle track them as proper inputs.
|
||||
*/
|
||||
private Pair<List<File>, List<URI>> splitFilesAndUris(List<Object> modules) {
|
||||
var files = new ArrayList<File>();
|
||||
var uris = new ArrayList<URI>();
|
||||
for (var m : modules) {
|
||||
var parsed = parseModuleNotation(m);
|
||||
if (parsed instanceof File) {
|
||||
files.add((File) parsed);
|
||||
} else if (parsed instanceof URI) {
|
||||
uris.add((URI) parsed);
|
||||
}
|
||||
}
|
||||
return Pair.of(files, uris);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts either a file or a URI to a URI. We convert a file to a URI via the {@link
|
||||
* IoUtils#createUri(String)} because other ways of conversion can make relative paths into
|
||||
* absolute URIs, which may break module loading.
|
||||
*/
|
||||
private URI parsedModuleNotationToUri(Object m) {
|
||||
if (m instanceof File) {
|
||||
var f = (File) m;
|
||||
return IoUtils.createUri(f.getPath());
|
||||
} else if (m instanceof URI) {
|
||||
return (URI) m;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid parsed module notation: " + m);
|
||||
}
|
||||
|
||||
protected URI parseModuleNotationToUri(Object m) {
|
||||
var parsed1 = parseModuleNotation(m);
|
||||
return parsedModuleNotationToUri(parsed1);
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
@Override
|
||||
public void runTask() {
|
||||
if (getCliBaseOptions().getNormalizedSourceModules().isEmpty()) {
|
||||
throw new InvalidUserDataException("No source modules specified.");
|
||||
}
|
||||
doRunTask();
|
||||
}
|
||||
|
||||
@Internal
|
||||
@Override
|
||||
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(), this::parseModuleNotationToUri),
|
||||
getProjectDir().isPresent() ? getProjectDir().get().getAsFile().toPath() : null,
|
||||
getEvalTimeout().getOrNull(),
|
||||
mapAndGetOrNull(getModuleCacheDir(), it1 -> it1.getAsFile().toPath()),
|
||||
getNoCache().getOrElse(false),
|
||||
getOmitProjectSettings().getOrElse(false),
|
||||
getNoProject().getOrElse(false),
|
||||
false,
|
||||
Collections.emptyList());
|
||||
}
|
||||
return cachedOptions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright © 2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.gradle.task;
|
||||
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
import org.gradle.api.tasks.OutputDirectory;
|
||||
import org.pkl.doc.CliDocGenerator;
|
||||
import org.pkl.doc.CliDocGeneratorOptions;
|
||||
|
||||
public abstract class PkldocTask extends ModulesTask {
|
||||
@OutputDirectory
|
||||
public abstract DirectoryProperty getOutputDir();
|
||||
|
||||
@Override
|
||||
protected void doRunTask() {
|
||||
new CliDocGenerator(
|
||||
new CliDocGeneratorOptions(
|
||||
getCliBaseOptions(), getOutputDir().get().getAsFile().toPath()))
|
||||
.run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Copyright © 2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.gradle.task;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.file.Path;
|
||||
import java.util.stream.Collectors;
|
||||
import org.gradle.api.InvalidUserDataException;
|
||||
import org.gradle.api.file.ConfigurableFileCollection;
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.InputFiles;
|
||||
import org.gradle.api.tasks.Internal;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.OutputDirectory;
|
||||
import org.gradle.api.tasks.UntrackedTask;
|
||||
import org.pkl.cli.CliProjectPackager;
|
||||
import org.pkl.commons.cli.CliTestOptions;
|
||||
|
||||
@UntrackedTask(because = "Output names are known only after execution")
|
||||
public abstract class ProjectPackageTask extends BasePklTask {
|
||||
@InputFiles
|
||||
public abstract ConfigurableFileCollection getProjectDirectories();
|
||||
|
||||
@Internal
|
||||
public abstract DirectoryProperty getOutputPath();
|
||||
|
||||
@Optional
|
||||
@OutputDirectory
|
||||
public abstract DirectoryProperty getJunitReportsDir();
|
||||
|
||||
@Input
|
||||
public abstract Property<Boolean> getOverwrite();
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
public abstract Property<Boolean> getSkipPublishCheck();
|
||||
|
||||
@Override
|
||||
protected void doRunTask() {
|
||||
var projectDirectories =
|
||||
getProjectDirectories().getFiles().stream()
|
||||
.map(it -> Path.of(it.getAbsolutePath()))
|
||||
.collect(Collectors.toList());
|
||||
if (projectDirectories.isEmpty()) {
|
||||
throw new InvalidUserDataException("No project directories specified.");
|
||||
}
|
||||
new CliProjectPackager(
|
||||
getCliBaseOptions(),
|
||||
projectDirectories,
|
||||
new CliTestOptions(
|
||||
mapAndGetOrNull(getJunitReportsDir(), it -> it.getAsFile().toPath()),
|
||||
getOverwrite().get()),
|
||||
getOutputPath().get().getAsFile().getAbsolutePath(),
|
||||
getSkipPublishCheck().getOrElse(false),
|
||||
new PrintWriter(System.out),
|
||||
new PrintWriter(System.err))
|
||||
.run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright © 2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.gradle.task;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import org.gradle.api.InvalidUserDataException;
|
||||
import org.gradle.api.file.ConfigurableFileCollection;
|
||||
import org.gradle.api.provider.Provider;
|
||||
import org.gradle.api.tasks.InputFiles;
|
||||
import org.gradle.api.tasks.Internal;
|
||||
import org.pkl.cli.CliProjectResolver;
|
||||
|
||||
public abstract class ProjectResolveTask extends BasePklTask {
|
||||
@Internal
|
||||
public abstract ConfigurableFileCollection getProjectDirectories();
|
||||
|
||||
// Only the `PklProject` files matter for creating PklProject.deps.json files.
|
||||
// Otherwise, these tasks can be considered up to date.
|
||||
@InputFiles
|
||||
public Provider<List<File>> getProjectPklFiles() {
|
||||
return getProjectDirectories()
|
||||
.getElements()
|
||||
.map(
|
||||
(files) ->
|
||||
files.stream()
|
||||
.map((it) -> it.getAsFile().toPath().resolve("PklProject").toFile())
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRunTask() {
|
||||
var projectDirectories =
|
||||
getProjectDirectories().getFiles().stream()
|
||||
.map(it -> Path.of(it.getAbsolutePath()))
|
||||
.collect(Collectors.toList());
|
||||
if (projectDirectories.isEmpty()) {
|
||||
throw new InvalidUserDataException("No project directories specified.");
|
||||
}
|
||||
new CliProjectResolver(
|
||||
getCliBaseOptions(),
|
||||
projectDirectories,
|
||||
new PrintWriter(System.out),
|
||||
new PrintWriter(System.err))
|
||||
.run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright © 2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.gradle.task;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.OutputDirectory;
|
||||
import org.pkl.cli.CliTestRunner;
|
||||
import org.pkl.commons.cli.CliTestOptions;
|
||||
|
||||
public abstract class TestTask extends ModulesTask {
|
||||
@Optional
|
||||
@OutputDirectory
|
||||
public abstract DirectoryProperty getJunitReportsDir();
|
||||
|
||||
@Input
|
||||
public abstract Property<Boolean> getOverwrite();
|
||||
|
||||
@Override
|
||||
protected void doRunTask() {
|
||||
new CliTestRunner(
|
||||
getCliBaseOptions(),
|
||||
new CliTestOptions(
|
||||
mapAndGetOrNull(getJunitReportsDir(), it -> it.getAsFile().toPath()),
|
||||
getOverwrite().get()),
|
||||
new PrintWriter(System.out),
|
||||
new PrintWriter(System.err))
|
||||
.run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
@NonnullByDefault
|
||||
package org.pkl.gradle.task;
|
||||
|
||||
import org.pkl.core.util.NonnullByDefault;
|
||||
@@ -0,0 +1,69 @@
|
||||
package org.pkl.gradle
|
||||
|
||||
import org.pkl.commons.createParentDirectories
|
||||
import org.pkl.commons.readString
|
||||
import org.pkl.commons.writeString
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.gradle.testkit.runner.BuildResult
|
||||
import org.gradle.testkit.runner.GradleRunner
|
||||
import org.gradle.testkit.runner.UnexpectedBuildFailure
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import java.net.URI
|
||||
import java.nio.file.Path
|
||||
|
||||
abstract class AbstractTest {
|
||||
private val gradleVersion: String? = System.getProperty("testGradleVersion")
|
||||
|
||||
private val gradleDistributionUrl: String? = System.getProperty("testGradleDistributionUrl")
|
||||
|
||||
@TempDir
|
||||
protected lateinit var testProjectDir: Path
|
||||
|
||||
protected fun runTask(
|
||||
taskName: String,
|
||||
expectFailure: Boolean = false
|
||||
): BuildResult {
|
||||
|
||||
val runner = GradleRunner.create()
|
||||
.withProjectDir(testProjectDir.toFile())
|
||||
.withArguments("--stacktrace", "--no-build-cache", taskName)
|
||||
.withPluginClasspath()
|
||||
.withDebug(true)
|
||||
|
||||
if (gradleVersion != null) {
|
||||
runner.withGradleVersion(gradleVersion)
|
||||
}
|
||||
if (gradleDistributionUrl != null) {
|
||||
runner.withGradleDistribution(URI(gradleDistributionUrl))
|
||||
}
|
||||
|
||||
return try {
|
||||
if (expectFailure) runner.buildAndFail() else runner.build()
|
||||
} catch (e: UnexpectedBuildFailure) {
|
||||
throw AssertionError(e.buildResult.output)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun writeFile(fileName: String, contents: String): Path {
|
||||
return testProjectDir.resolve(fileName)
|
||||
.apply { createParentDirectories() }
|
||||
.writeString(contents.trimIndent())
|
||||
}
|
||||
|
||||
protected fun checkFileContents(file: Path, contents: String) {
|
||||
assertThat(file).exists()
|
||||
assertThat(file.readString().trim())
|
||||
.isEqualTo(contents.trim())
|
||||
}
|
||||
|
||||
protected fun checkTextContains(text: String, vararg contents: String) {
|
||||
for (content in contents) {
|
||||
try {
|
||||
assertThat(text).contains(content.trimMargin())
|
||||
} catch (e: AssertionError) {
|
||||
// to get diff output in IDE
|
||||
assertThat(text).isEqualTo(content.trimMargin())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,505 @@
|
||||
package org.pkl.gradle
|
||||
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.pkl.commons.readString
|
||||
import org.pkl.commons.test.PackageServer
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import java.nio.file.Path
|
||||
|
||||
class EvaluatorsTest : AbstractTest() {
|
||||
@Test
|
||||
fun `render Pcf`() {
|
||||
writeBuildFile("pcf")
|
||||
|
||||
writePklFile()
|
||||
|
||||
runTask("evalTest")
|
||||
|
||||
val outputFile = testProjectDir.resolve("test.pcf")
|
||||
checkFileContents(
|
||||
outputFile, """
|
||||
person {
|
||||
name = "Pigeon"
|
||||
age = 30
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `render YAML`() {
|
||||
writeBuildFile("yaml")
|
||||
|
||||
writePklFile()
|
||||
|
||||
runTask("evalTest")
|
||||
|
||||
val outputFile = testProjectDir.resolve("test.yaml")
|
||||
checkFileContents(
|
||||
outputFile, """
|
||||
person:
|
||||
name: Pigeon
|
||||
age: 30
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `render JSON`() {
|
||||
writeBuildFile("json")
|
||||
|
||||
writePklFile()
|
||||
|
||||
runTask("evalTest")
|
||||
|
||||
val outputFile = testProjectDir.resolve("test.json")
|
||||
checkFileContents(
|
||||
outputFile, """
|
||||
{
|
||||
"person": {
|
||||
"name": "Pigeon",
|
||||
"age": 30
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `render plist`() {
|
||||
writeBuildFile("plist")
|
||||
|
||||
writePklFile()
|
||||
|
||||
runTask("evalTest")
|
||||
|
||||
val outputFile = testProjectDir.resolve("test.plist")
|
||||
checkFileContents(
|
||||
outputFile, """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>person</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Pigeon</string>
|
||||
<key>age</key>
|
||||
<integer>30</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `set external properties`() {
|
||||
writeBuildFile(
|
||||
"pcf", """
|
||||
externalProperties = [prop1: "value1", prop2: "value2"]
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
writePklFile(
|
||||
"""
|
||||
prop1 = read("prop:prop1")
|
||||
prop2 = read("prop:prop2")
|
||||
other = read?("prop:other")
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
runTask("evalTest")
|
||||
|
||||
val outputFile = testProjectDir.resolve("test.pcf")
|
||||
checkFileContents(
|
||||
outputFile, """
|
||||
prop1 = "value1"
|
||||
prop2 = "value2"
|
||||
other = null
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `defaults to empty environment variables`() {
|
||||
writeBuildFile("pcf")
|
||||
|
||||
writePklFile(
|
||||
"""
|
||||
prop1 = read?("env:USER")
|
||||
prop2 = read?("env:PATH")
|
||||
prop3 = read?("env:JAVA_HOME")
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
runTask("evalTest")
|
||||
|
||||
val outputFile = testProjectDir.resolve("test.pcf")
|
||||
checkFileContents(
|
||||
outputFile, """
|
||||
prop1 = null
|
||||
prop2 = null
|
||||
prop3 = null
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `set environment variables`() {
|
||||
writeBuildFile(
|
||||
"pcf", """
|
||||
environmentVariables = [VAR1: "value1", VAR2: "value2"]
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
writePklFile(
|
||||
"""
|
||||
prop1 = read("env:VAR1")
|
||||
prop2 = read("env:VAR2")
|
||||
other = read?("env:OTHER")
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
runTask("evalTest")
|
||||
|
||||
val outputFile = testProjectDir.resolve("test.pcf")
|
||||
checkFileContents(
|
||||
outputFile, """
|
||||
prop1 = "value1"
|
||||
prop2 = "value2"
|
||||
other = null
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no source modules`() {
|
||||
writeFile(
|
||||
"build.gradle", """
|
||||
plugins {
|
||||
id "org.pkl-lang"
|
||||
}
|
||||
|
||||
pkl {
|
||||
evaluators {
|
||||
evalTest {
|
||||
outputFormat = "pcf"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
val result = runTask("evalTest", true)
|
||||
assertThat(result.output).contains("No source modules specified.")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `source module URIs`() {
|
||||
val pklFile = writeFile(
|
||||
"test.pkl", """
|
||||
person {
|
||||
name = "Pigeon"
|
||||
age = 20 + 10
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
writeFile(
|
||||
"build.gradle", """
|
||||
plugins {
|
||||
id "org.pkl-lang"
|
||||
}
|
||||
|
||||
pkl {
|
||||
evaluators {
|
||||
evalTest {
|
||||
sourceModules = [uri("modulepath:/test.pkl")]
|
||||
modulePath.from "${pklFile.parent}"
|
||||
outputFile = layout.projectDirectory.file("test.pcf")
|
||||
settingsModule = "pkl:settings"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
runTask("evalTest")
|
||||
|
||||
val outputFile = testProjectDir.resolve("test.pcf")
|
||||
checkFileContents(
|
||||
outputFile, """
|
||||
person {
|
||||
name = "Pigeon"
|
||||
age = 30
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cannot evaluate module located outside evalRootDir`() {
|
||||
writeFile(
|
||||
"build.gradle", """
|
||||
plugins {
|
||||
id "org.pkl-lang"
|
||||
}
|
||||
|
||||
pkl {
|
||||
evaluators {
|
||||
evalTest {
|
||||
evalRootDir = file("/non/existing")
|
||||
sourceModules = ["test.pkl"]
|
||||
settingsModule = "pkl:settings"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
val result = runTask("evalTest", expectFailure = true)
|
||||
assertThat(result.output).contains("Refusing to load module")
|
||||
assertThat(result.output).contains("because it does not match any entry in the module allowlist (`--allowed-modules`).")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `evaluation timeout`() {
|
||||
// Gradle 4.10 doesn't automatically import Duration
|
||||
writeBuildFile(
|
||||
"pcf", """
|
||||
evalTimeout = java.time.Duration.ofMillis(100)
|
||||
"""
|
||||
)
|
||||
|
||||
writePklFile(
|
||||
"""
|
||||
function fib(n) = if (n < 2) 0 else fib(n - 1) + fib(n - 2)
|
||||
x = fib(100)
|
||||
"""
|
||||
)
|
||||
|
||||
val result = runTask("evalTest", expectFailure = true)
|
||||
assertThat(result.output).contains("timed out")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `module output separator`() {
|
||||
val outputFile = testProjectDir.resolve("test.pcf")
|
||||
writeFile(
|
||||
"build.gradle", """
|
||||
plugins {
|
||||
id "org.pkl-lang"
|
||||
}
|
||||
|
||||
pkl {
|
||||
evaluators {
|
||||
evalTask {
|
||||
moduleOutputSeparator = "// hello"
|
||||
sourceModules = ["test1.pkl", "test2.pkl"]
|
||||
settingsModule = "pkl:settings"
|
||||
outputFile = layout.projectDirectory.file("test.pcf")
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
writeFile(
|
||||
"test1.pkl",
|
||||
"foo = 1"
|
||||
)
|
||||
writeFile(
|
||||
"test2.pkl",
|
||||
"bar = 2"
|
||||
)
|
||||
runTask("evalTask")
|
||||
|
||||
checkFileContents(outputFile, """
|
||||
foo = 1
|
||||
// hello
|
||||
bar = 2
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `compliant file URIs`() {
|
||||
writeBuildFile("pcf")
|
||||
writeFile("test.pkl", """
|
||||
import "pkl:reflect"
|
||||
output {
|
||||
text = reflect.Module(module).uri
|
||||
}
|
||||
""".trimIndent())
|
||||
|
||||
runTask("evalTest")
|
||||
|
||||
val outputFile = testProjectDir.resolve("test.pcf")
|
||||
assertThat(outputFile).exists()
|
||||
assertThat(outputFile.readString().trim()).startsWith("file:///")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `multiple file output`() {
|
||||
writeBuildFile("pcf", """
|
||||
multipleFileOutputDir = layout.projectDirectory.dir("my-output")
|
||||
""".trimIndent())
|
||||
writeFile(
|
||||
"test.pkl",
|
||||
"""
|
||||
output {
|
||||
files {
|
||||
["output-1.txt"] {
|
||||
text = "My output 1"
|
||||
}
|
||||
["output-2.txt"] {
|
||||
text = "My output 2"
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
runTask("evalTest")
|
||||
checkFileContents(testProjectDir.resolve("my-output/output-1.txt"), "My output 1")
|
||||
checkFileContents(testProjectDir.resolve("my-output/output-2.txt"), "My output 2")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun expression() {
|
||||
writeBuildFile("yaml", """
|
||||
expression = "metadata.name"
|
||||
outputFile = layout.projectDirectory.file("output.txt")
|
||||
""".trimIndent())
|
||||
writeFile(
|
||||
"test.pkl",
|
||||
"""
|
||||
metadata {
|
||||
name = "Uni"
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
runTask("evalTest")
|
||||
checkFileContents(testProjectDir.resolve("output.txt"), "Uni")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `explicitly set cache dir`(@TempDir tempDir: Path) {
|
||||
writeBuildFile("pcf", """
|
||||
moduleCacheDir = file("$tempDir")
|
||||
""".trimIndent())
|
||||
writeFile(
|
||||
"test.pkl",
|
||||
"""
|
||||
import "package://localhost:12110/birds@0.5.0#/Bird.pkl"
|
||||
|
||||
res = new Bird { name = "Wally"; favoriteFruit { name = "bananas" } }
|
||||
""".trimIndent()
|
||||
)
|
||||
PackageServer.populateCacheDir(tempDir)
|
||||
runTask("evalTest")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `explicitly set project dir`() {
|
||||
writeBuildFile("pcf", "projectDir = file(\"proj1\")", listOf("proj1/foo.pkl"))
|
||||
|
||||
writeFile("proj1/PklProject", """
|
||||
amends "pkl:Project"
|
||||
|
||||
dependencies {
|
||||
["proj2"] = import("../proj2/PklProject")
|
||||
}
|
||||
|
||||
package {
|
||||
name = "proj1"
|
||||
baseUri = "package://localhost:12110/\(name)"
|
||||
version = "1.0.0"
|
||||
packageZipUrl = "https://localhost:12110/\(name)@\(version).zip"
|
||||
}
|
||||
""".trimIndent())
|
||||
|
||||
writeFile("proj2/PklProject", """
|
||||
amends "pkl:Project"
|
||||
|
||||
package {
|
||||
name = "proj2"
|
||||
baseUri = "package://localhost:12110/\(name)"
|
||||
version = "1.0.0"
|
||||
packageZipUrl = "https://localhost:12110/\(name)@\(version).zip"
|
||||
}
|
||||
""".trimIndent())
|
||||
|
||||
writeFile("proj1/PklProject.deps.json", """
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"resolvedDependencies": {
|
||||
"package://localhost:12110/proj2@1": {
|
||||
"type": "local",
|
||||
"uri": "projectpackage://localhost:12110/proj2@1.0.0",
|
||||
"path": "../proj2"
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent())
|
||||
|
||||
writeFile("proj2/PklProject.deps.json", """
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"resolvedDependencies": {}
|
||||
}
|
||||
""".trimIndent())
|
||||
|
||||
writeFile("proj1/foo.pkl", """
|
||||
module proj1.foo
|
||||
|
||||
bar: String = import("@proj2/baz.pkl").qux
|
||||
""".trimIndent())
|
||||
|
||||
writeFile("proj2/baz.pkl", """
|
||||
qux: String = "Contents of @proj2/qux"
|
||||
""".trimIndent())
|
||||
|
||||
runTask("evalTest")
|
||||
assertThat(testProjectDir.resolve("proj1/foo.pcf")).exists()
|
||||
}
|
||||
|
||||
private fun writeBuildFile(
|
||||
// don't use `org.pkl.core.OutputFormat`
|
||||
// because test compile class path doesn't contain pkl-core
|
||||
outputFormat: String,
|
||||
additionalContents: String = "",
|
||||
sourceModules: List<String> = listOf("test.pkl")
|
||||
) {
|
||||
writeFile(
|
||||
"build.gradle", """
|
||||
plugins {
|
||||
id "org.pkl-lang"
|
||||
}
|
||||
|
||||
pkl {
|
||||
evaluators {
|
||||
evalTest {
|
||||
sourceModules = [${sourceModules.joinToString(separator = ", ") { "\"$it\"" }}]
|
||||
outputFormat = "$outputFormat"
|
||||
settingsModule = "pkl:settings"
|
||||
$additionalContents
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
private fun writePklFile(
|
||||
contents: String = """
|
||||
person {
|
||||
name = "Pigeon"
|
||||
age = 20 + 10
|
||||
}
|
||||
"""
|
||||
) {
|
||||
writeFile("test.pkl", contents)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package org.pkl.gradle
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.io.path.listDirectoryEntries
|
||||
import kotlin.io.path.readText
|
||||
|
||||
class JavaCodeGeneratorsTest : AbstractTest() {
|
||||
@Test
|
||||
fun `generate code`() {
|
||||
writeBuildFile()
|
||||
writePklFile()
|
||||
|
||||
runTask("configClasses")
|
||||
|
||||
val baseDir = testProjectDir.resolve("build/generated/java/org")
|
||||
val moduleFile = baseDir.resolve("Mod.java")
|
||||
|
||||
assertThat(baseDir.listDirectoryEntries().count()).isEqualTo(1)
|
||||
assertThat(moduleFile).exists()
|
||||
|
||||
val text = moduleFile.readText()
|
||||
|
||||
// shading must not affect generated code
|
||||
assertThat(text).doesNotContain("org.pkl.thirdparty")
|
||||
|
||||
checkTextContains(
|
||||
text, """
|
||||
|public final class Mod {
|
||||
| public final @Nonnull Object other;
|
||||
"""
|
||||
)
|
||||
|
||||
checkTextContains(
|
||||
text, """
|
||||
| public static final class Person {
|
||||
| public final @Nonnull String name;
|
||||
|
|
||||
| public final @Nonnull List<@Nonnull Address> addresses;
|
||||
"""
|
||||
)
|
||||
|
||||
checkTextContains(
|
||||
text, """
|
||||
| public static final class Address {
|
||||
| public final @Nonnull String street;
|
||||
|
|
||||
| public final long zip;
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `compile generated code`() {
|
||||
writeBuildFile()
|
||||
writeFile("mod.pkl", """
|
||||
module org.mod
|
||||
|
||||
class Person {
|
||||
name: String
|
||||
addresses: List<Address?>
|
||||
}
|
||||
|
||||
class Address {
|
||||
street: String
|
||||
zip: Int
|
||||
}
|
||||
|
||||
other: Any = 42
|
||||
""".trimIndent())
|
||||
|
||||
runTask("compileJava")
|
||||
|
||||
val classesDir = testProjectDir.resolve("build/classes/java/main")
|
||||
val moduleClassFile = classesDir.resolve("org/Mod.class")
|
||||
val personClassFile = classesDir.resolve("org/Mod\$Person.class")
|
||||
val addressClassFile = classesDir.resolve("org/Mod\$Address.class")
|
||||
assertThat(moduleClassFile).exists()
|
||||
assertThat(personClassFile).exists()
|
||||
assertThat(addressClassFile).exists()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no source modules`() {
|
||||
writeFile(
|
||||
"build.gradle", """
|
||||
plugins {
|
||||
id "org.pkl-lang"
|
||||
}
|
||||
|
||||
pkl {
|
||||
evaluators {
|
||||
evalTest {
|
||||
outputFormat = "pcf"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
val result = runTask("evalTest", true)
|
||||
assertThat(result.output).contains("No source modules specified.")
|
||||
}
|
||||
|
||||
private fun writeBuildFile() {
|
||||
writeFile(
|
||||
"build.gradle", """
|
||||
plugins {
|
||||
id "java"
|
||||
id "org.pkl-lang"
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "javax.inject:javax.inject:1"
|
||||
implementation "com.google.code.findbugs:jsr305:3.0.2"
|
||||
}
|
||||
|
||||
pkl {
|
||||
javaCodeGenerators {
|
||||
configClasses {
|
||||
sourceModules = ["mod.pkl"]
|
||||
outputDir = file("build/generated")
|
||||
paramsAnnotation = "javax.inject.Named"
|
||||
nonNullAnnotation = "javax.annotation.Nonnull"
|
||||
settingsModule = "pkl:settings"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
private fun writeGradlePropertiesFile() {
|
||||
writeFile("gradle.properties", """
|
||||
systemProp.http.proxyHost=proxy.config.pcp.local
|
||||
systemProp.http.proxyPort=3128
|
||||
systemProp.http.nonProxyHosts=localhost|*.apple.com
|
||||
|
||||
systemProp.https.proxyHost=proxy.config.pcp.local
|
||||
systemProp.https.proxyPort=3128
|
||||
systemProp.https.nonProxyHosts=localhost|*.apple.com
|
||||
""")
|
||||
}
|
||||
|
||||
private fun writePklFile() {
|
||||
writeFile(
|
||||
"mod.pkl", """
|
||||
module org.mod
|
||||
|
||||
class Person {
|
||||
name: String
|
||||
addresses: List<Address>
|
||||
}
|
||||
|
||||
class Address {
|
||||
street: String
|
||||
zip: Int
|
||||
}
|
||||
|
||||
other = 42
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package org.pkl.gradle
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.io.path.listDirectoryEntries
|
||||
import kotlin.io.path.readText
|
||||
|
||||
class KotlinCodeGeneratorsTest : AbstractTest() {
|
||||
@Test
|
||||
fun `generate code`() {
|
||||
writeBuildFile()
|
||||
writePklFile()
|
||||
|
||||
runTask("configClasses")
|
||||
|
||||
val baseDir = testProjectDir.resolve("build/generated/kotlin/org")
|
||||
val kotlinFile = baseDir.resolve("Mod.kt")
|
||||
|
||||
assertThat(baseDir.listDirectoryEntries().count()).isEqualTo(1)
|
||||
assertThat(kotlinFile).exists()
|
||||
|
||||
val text = kotlinFile.readText()
|
||||
|
||||
// shading must not affect generated code
|
||||
assertThat(text).doesNotContain("org.pkl.thirdparty")
|
||||
|
||||
checkTextContains(
|
||||
text, """
|
||||
|data class Mod(
|
||||
| val other: Any?
|
||||
|)
|
||||
"""
|
||||
)
|
||||
|
||||
checkTextContains(
|
||||
text, """
|
||||
| data class Person(
|
||||
| val name: String,
|
||||
| val addresses: List<Address>
|
||||
| )
|
||||
"""
|
||||
)
|
||||
|
||||
checkTextContains(
|
||||
text, """
|
||||
| open class Address(
|
||||
| open val street: String,
|
||||
| open val zip: Long
|
||||
| )
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `compile generated code`() {
|
||||
writeBuildFile()
|
||||
writePklFile()
|
||||
runTask("compileKotlin")
|
||||
|
||||
val classesDir = testProjectDir.resolve("build/classes/kotlin/main")
|
||||
val moduleClassFile = classesDir.resolve("org/Mod.class")
|
||||
val personClassFile = classesDir.resolve("org/Mod\$Person.class")
|
||||
val addressClassFile = classesDir.resolve("org/Mod\$Address.class")
|
||||
assertThat(moduleClassFile).exists()
|
||||
assertThat(personClassFile).exists()
|
||||
assertThat(addressClassFile).exists()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no source modules`() {
|
||||
writeFile(
|
||||
"build.gradle", """
|
||||
plugins {
|
||||
id "org.pkl-lang"
|
||||
}
|
||||
|
||||
pkl {
|
||||
evaluators {
|
||||
evalTest {
|
||||
outputFormat = "pcf"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
val result = runTask("evalTest", true)
|
||||
assertThat(result.output).contains("No source modules specified.")
|
||||
}
|
||||
|
||||
private fun writeBuildFile() {
|
||||
val kotlinVersion = "1.6.0"
|
||||
|
||||
writeFile(
|
||||
"build.gradle", """
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") {
|
||||
exclude module: "kotlin-android-extensions"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "org.pkl-lang"
|
||||
}
|
||||
|
||||
apply plugin: "kotlin"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
|
||||
}
|
||||
|
||||
pkl {
|
||||
kotlinCodeGenerators {
|
||||
configClasses {
|
||||
sourceModules = ["mod.pkl"]
|
||||
outputDir = file("build/generated")
|
||||
settingsModule = "pkl:settings"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
private fun writePklFile() {
|
||||
writeFile(
|
||||
"mod.pkl", """
|
||||
module org.mod
|
||||
|
||||
class Person {
|
||||
name: String
|
||||
addresses: List<Address>
|
||||
}
|
||||
|
||||
// "open" to test generating regular class
|
||||
open class Address {
|
||||
street: String
|
||||
zip: Int
|
||||
}
|
||||
|
||||
other = 42
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package org.pkl.gradle
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.io.path.readText
|
||||
|
||||
class PkldocGeneratorsTest : AbstractTest() {
|
||||
@Test
|
||||
fun `generate docs`() {
|
||||
writeFile(
|
||||
"build.gradle", """
|
||||
plugins {
|
||||
id "org.pkl-lang"
|
||||
}
|
||||
|
||||
pkl {
|
||||
pkldocGenerators {
|
||||
pkldoc {
|
||||
sourceModules = ["person.pkl", "doc-package-info.pkl"]
|
||||
outputDir = file("build/pkldoc")
|
||||
settingsModule = "pkl:settings"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
writeFile(
|
||||
"doc-package-info.pkl", """
|
||||
/// A test package.
|
||||
amends "pkl:DocPackageInfo"
|
||||
name = "test"
|
||||
version = "1.0.0"
|
||||
importUri = "https://pkl-lang.org/"
|
||||
authors { "publisher@apple.com" }
|
||||
sourceCode = "sources.apple.com/"
|
||||
issueTracker = "issues.apple.com"
|
||||
""".trimIndent()
|
||||
)
|
||||
writeFile(
|
||||
"person.pkl", """
|
||||
module test.person
|
||||
|
||||
class Person {
|
||||
name: String
|
||||
addresses: List<Address>
|
||||
}
|
||||
|
||||
class Address {
|
||||
street: String
|
||||
zip: Int
|
||||
}
|
||||
|
||||
other = 42
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
runTask("pkldoc")
|
||||
|
||||
val baseDir = testProjectDir.resolve("build/pkldoc")
|
||||
val mainFile = baseDir.resolve("index.html")
|
||||
val packageFile = baseDir.resolve("test/1.0.0/index.html")
|
||||
val moduleFile = baseDir.resolve("test/1.0.0/person/index.html")
|
||||
val personFile = baseDir.resolve("test/1.0.0/person/Person.html")
|
||||
val addressFile = baseDir.resolve("test/1.0.0/person/Address.html")
|
||||
|
||||
assertThat(mainFile).exists()
|
||||
assertThat(packageFile).exists()
|
||||
assertThat(moduleFile).exists()
|
||||
assertThat(personFile).exists()
|
||||
assertThat(addressFile).exists()
|
||||
|
||||
checkTextContains(mainFile.readText(), "<html>", "test")
|
||||
checkTextContains(packageFile.readText(), "<html>", "test.person")
|
||||
checkTextContains(moduleFile.readText(), "<html>", "Person", "Address", "other")
|
||||
checkTextContains(personFile.readText(), "<html>", "name", "addresses")
|
||||
checkTextContains(addressFile.readText(), "<html>", "street", "zip")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no source modules`() {
|
||||
writeFile(
|
||||
"build.gradle", """
|
||||
plugins {
|
||||
id "org.pkl-lang"
|
||||
}
|
||||
|
||||
pkl {
|
||||
pkldocGenerators {
|
||||
pkldoc {
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
val result = runTask("pkldoc", true)
|
||||
assertThat(result.output).contains("No source modules specified.")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package org.pkl.gradle
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class ProjectPackageTest : AbstractTest() {
|
||||
@Test
|
||||
fun basic() {
|
||||
writeBuildFile("skipPublishCheck.set(true)")
|
||||
writeProjectContent()
|
||||
runTask("createMyPackages")
|
||||
assertThat(testProjectDir.resolve("build/generated/pkl/packages/proj1@1.0.0.zip")).exists()
|
||||
assertThat(testProjectDir.resolve("build/generated/pkl/packages/proj1@1.0.0")).exists()
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `custom output dir`() {
|
||||
writeBuildFile( """
|
||||
outputPath.set(file("thepackages"))
|
||||
skipPublishCheck.set(true)
|
||||
""")
|
||||
writeProjectContent()
|
||||
runTask("createMyPackages")
|
||||
assertThat(testProjectDir.resolve("thepackages/proj1@1.0.0.zip")).exists()
|
||||
assertThat(testProjectDir.resolve("thepackages/proj1@1.0.0")).exists()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `junit dir`() {
|
||||
writeBuildFile("""
|
||||
junitReportsDir.set(file("test-reports"))
|
||||
skipPublishCheck.set(true)
|
||||
""".trimIndent())
|
||||
writeProjectContent()
|
||||
runTask("createMyPackages")
|
||||
assertThat(testProjectDir.resolve("test-reports")).isNotEmptyDirectory()
|
||||
}
|
||||
|
||||
private fun writeBuildFile(additionalContents: String = "") {
|
||||
writeFile(
|
||||
"build.gradle", """
|
||||
plugins {
|
||||
id "org.pkl-lang"
|
||||
}
|
||||
|
||||
pkl {
|
||||
project {
|
||||
packagers {
|
||||
createMyPackages {
|
||||
projectDirectories.from(file("proj1"))
|
||||
settingsModule = "pkl:settings"
|
||||
$additionalContents
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
private fun writeProjectContent() {
|
||||
writeFile("proj1/PklProject", """
|
||||
amends "pkl:Project"
|
||||
|
||||
package {
|
||||
name = "proj1"
|
||||
baseUri = "package://localhost:12110/proj1"
|
||||
version = "1.0.0"
|
||||
packageZipUrl = "https://localhost:12110/proj1@\(version).zip"
|
||||
apiTests {
|
||||
"tests.pkl"
|
||||
}
|
||||
}
|
||||
""".trimIndent())
|
||||
writeFile("proj1/PklProject.deps.json", """
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"dependencies": {}
|
||||
}
|
||||
""".trimIndent())
|
||||
writeFile("proj1/foo.pkl", """
|
||||
module proj1.foo
|
||||
|
||||
bar: String
|
||||
""".trimIndent())
|
||||
writeFile("proj1/tests.pkl", """
|
||||
amends "pkl:test"
|
||||
|
||||
facts {
|
||||
["it works"] {
|
||||
1 == 1
|
||||
}
|
||||
}
|
||||
""".trimIndent())
|
||||
writeFile("foo.txt", "The contents of foo.txt")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.pkl.gradle
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class ProjectResolveTest : AbstractTest() {
|
||||
@Test
|
||||
fun basic() {
|
||||
writeBuildFile()
|
||||
writeProjectContent()
|
||||
runTask("resolveMyProj")
|
||||
assertThat(testProjectDir.resolve("proj1/PklProject.deps.json")).hasContent("""
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"resolvedDependencies": {}
|
||||
}
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
private fun writeBuildFile(additionalContents: String = "") {
|
||||
writeFile(
|
||||
"build.gradle", """
|
||||
plugins {
|
||||
id "org.pkl-lang"
|
||||
}
|
||||
|
||||
pkl {
|
||||
project {
|
||||
resolvers {
|
||||
resolveMyProj {
|
||||
projectDirectories.from(file("proj1"))
|
||||
settingsModule = "pkl:settings"
|
||||
$additionalContents
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
private fun writeProjectContent() {
|
||||
writeFile("proj1/PklProject", """
|
||||
amends "pkl:Project"
|
||||
""".trimIndent())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
package org.pkl.gradle
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.readText
|
||||
|
||||
class TestsTest : AbstractTest() {
|
||||
|
||||
@Test
|
||||
fun `facts pass`() {
|
||||
writeBuildFile()
|
||||
|
||||
writePklFile()
|
||||
|
||||
val res = runTask("evalTest")
|
||||
assertThat(res.output).contains("should pass ✅")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `facts fail`() {
|
||||
writeBuildFile()
|
||||
|
||||
writePklFile(additionalFacts = """
|
||||
["should fail"] {
|
||||
1 == 3
|
||||
"foo" == "bar"
|
||||
}
|
||||
""".trimIndent())
|
||||
|
||||
val res = runTask("evalTest", expectFailure = true)
|
||||
assertThat(res.output).contains("should fail ❌")
|
||||
assertThat(res.output).contains("1 == 3 ❌")
|
||||
assertThat(res.output).contains(""""foo" == "bar" ❌""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun error() {
|
||||
writeBuildFile()
|
||||
|
||||
writePklFile(additionalFacts = """
|
||||
["error"] {
|
||||
throw("exception")
|
||||
}
|
||||
""".trimIndent())
|
||||
|
||||
val output = runTask("evalTest", expectFailure = true).output.stripFilesAndLines()
|
||||
|
||||
assertThat(output.trimStart()).startsWith("""
|
||||
> Task :evalTest FAILED
|
||||
module test (file:///file, line x)
|
||||
test ❌
|
||||
Error:
|
||||
–– Pkl Error ––
|
||||
exception
|
||||
|
||||
9 | throw("exception")
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
at test#facts["error"][#1] (file:///file, line x)
|
||||
|
||||
3 | facts {
|
||||
^^^^^^^
|
||||
at test#facts (file:///file, line x)
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `full example`() {
|
||||
writePklFile(contents = bigTest)
|
||||
writeFile("test.pkl-expected.pcf", bigTestExpected)
|
||||
|
||||
writeBuildFile()
|
||||
|
||||
val output = runTask("evalTest", expectFailure = true).output.stripFilesAndLines()
|
||||
|
||||
assertThat(output.trimStart()).contains("""
|
||||
module test (file:///file, line x)
|
||||
sum numbers ✅
|
||||
divide numbers ✅
|
||||
fail ❌
|
||||
4 == 9 ❌ (file:///file, line x)
|
||||
"foo" == "bar" ❌ (file:///file, line x)
|
||||
user 0 ✅
|
||||
user 1 ❌
|
||||
(file:///file, line x)
|
||||
Expected: (file:///file, line x)
|
||||
new {
|
||||
name = "Pigeon"
|
||||
age = 40
|
||||
}
|
||||
Actual: (file:///file, line x)
|
||||
new {
|
||||
name = "Pigeon"
|
||||
age = 41
|
||||
}
|
||||
user 1 ❌
|
||||
(file:///file, line x)
|
||||
Expected: (file:///file, line x)
|
||||
new {
|
||||
name = "Parrot"
|
||||
age = 35
|
||||
}
|
||||
Actual: (file:///file, line x)
|
||||
new {
|
||||
name = "Welma"
|
||||
age = 35
|
||||
}
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `overwrite expected examples`() {
|
||||
writePklFile(additionalExamples = """
|
||||
["user 0"] {
|
||||
new {
|
||||
name = "Cool"
|
||||
age = 11
|
||||
}
|
||||
}
|
||||
["user 1"] {
|
||||
new {
|
||||
name = "Pigeon"
|
||||
age = 41
|
||||
}
|
||||
new {
|
||||
name = "Welma"
|
||||
age = 35
|
||||
}
|
||||
}
|
||||
""".trimIndent())
|
||||
writeFile("test.pkl-expected.pcf", bigTestExpected)
|
||||
|
||||
writeBuildFile("overwrite = true")
|
||||
|
||||
val output = runTask("evalTest").output
|
||||
|
||||
assertThat(output).contains("user 0 ✍️")
|
||||
assertThat(output).contains("user 1 ✍️")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `JUnit reports`() {
|
||||
val pklFile = writePklFile(contents = bigTest)
|
||||
writeFile("test.pkl-expected.pcf", bigTestExpected)
|
||||
|
||||
writeBuildFile("junitReportsDir = file('${pklFile.parent}/build')")
|
||||
|
||||
runTask("evalTest", expectFailure = true)
|
||||
|
||||
val outputFile = testProjectDir.resolve("build/test.xml")
|
||||
val report = outputFile.readText().stripFilesAndLines()
|
||||
|
||||
assertThat(report).isEqualTo("""
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuite name="test" tests="6" failures="4">
|
||||
<testcase classname="test" name="sum numbers"></testcase>
|
||||
<testcase classname="test" name="divide numbers"></testcase>
|
||||
<testcase classname="test" name="fail">
|
||||
<failure message="Fact Failure">4 == 9 ❌ (file:///file, line x)</failure>
|
||||
<failure message="Fact Failure">"foo" == "bar" ❌ (file:///file, line x)</failure>
|
||||
</testcase>
|
||||
<testcase classname="test" name="user 0"></testcase>
|
||||
<testcase classname="test" name="user 1">
|
||||
<failure message="Example Failure">(file:///file, line x)
|
||||
Expected: (file:///file, line x)
|
||||
new {
|
||||
name = "Pigeon"
|
||||
age = 40
|
||||
}
|
||||
Actual: (file:///file, line x)
|
||||
new {
|
||||
name = "Pigeon"
|
||||
age = 41
|
||||
}</failure>
|
||||
</testcase>
|
||||
<testcase classname="test" name="user 1">
|
||||
<failure message="Example Failure">(file:///file, line x)
|
||||
Expected: (file:///file, line x)
|
||||
new {
|
||||
name = "Parrot"
|
||||
age = 35
|
||||
}
|
||||
Actual: (file:///file, line x)
|
||||
new {
|
||||
name = "Welma"
|
||||
age = 35
|
||||
}</failure>
|
||||
</testcase>
|
||||
<system-err><![CDATA[8 = 8
|
||||
]]></system-err>
|
||||
</testsuite>
|
||||
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
private val bigTest = """
|
||||
amends "pkl:test"
|
||||
|
||||
local function sum(a, b) = a + b
|
||||
|
||||
facts {
|
||||
["sum numbers"] {
|
||||
sum(3, 5) == trace(8)
|
||||
sum(3, 0) == 3
|
||||
}
|
||||
["divide numbers"] {
|
||||
(8 / 4) == 2
|
||||
(12 / 2) == 6
|
||||
}
|
||||
["fail"] {
|
||||
4 == 9
|
||||
"foo" == "bar"
|
||||
}
|
||||
}
|
||||
|
||||
examples {
|
||||
["user 0"] {
|
||||
new {
|
||||
name = "Cool"
|
||||
age = 11
|
||||
}
|
||||
}
|
||||
["user 1"] {
|
||||
new {
|
||||
name = "Pigeon"
|
||||
age = 41
|
||||
}
|
||||
new {
|
||||
name = "Welma"
|
||||
age = 35
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
private val bigTestExpected = """
|
||||
examples {
|
||||
["user 0"] {
|
||||
new {
|
||||
name = "Cool"
|
||||
age = 11
|
||||
}
|
||||
}
|
||||
["user 1"] {
|
||||
new {
|
||||
name = "Pigeon"
|
||||
age = 40
|
||||
}
|
||||
new {
|
||||
name = "Parrot"
|
||||
age = 35
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
private fun writeBuildFile(additionalContents: String = "") {
|
||||
writeFile(
|
||||
"build.gradle", """
|
||||
plugins {
|
||||
id "org.pkl-lang"
|
||||
}
|
||||
|
||||
pkl {
|
||||
tests {
|
||||
evalTest {
|
||||
sourceModules = ["test.pkl"]
|
||||
settingsModule = "pkl:settings"
|
||||
$additionalContents
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
private fun writePklFile(
|
||||
additionalFacts: String = "",
|
||||
additionalExamples: String = "",
|
||||
contents: String = """
|
||||
amends "pkl:test"
|
||||
|
||||
facts {
|
||||
["should pass"] {
|
||||
1 == 1
|
||||
10 == 10
|
||||
}
|
||||
$additionalFacts
|
||||
}
|
||||
|
||||
examples {
|
||||
$additionalExamples
|
||||
}
|
||||
"""
|
||||
): Path {
|
||||
return writeFile("test.pkl", contents)
|
||||
}
|
||||
|
||||
private fun String.stripFilesAndLines(): String =
|
||||
replace(Regex("""\(file:///.*, line \d+\)"""), "(file:///file, line x)")
|
||||
}
|
||||
Reference in New Issue
Block a user