Refactored GroowtAntlrPlugin to be much simpler.

This commit is contained in:
JesseBrault0709 2024-05-04 07:44:50 +02:00
parent b66ecd89b7
commit f573f1cec4
14 changed files with 183 additions and 1018 deletions

View File

@ -1,5 +0,0 @@
package groowt.gradle.antlr;
import org.gradle.api.plugins.antlr.AntlrTask;
public class GroowtAntlrAllTask extends AntlrTask {}

View File

@ -0,0 +1,95 @@
package groowt.gradle.antlr;
import org.gradle.api.DefaultTask;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.model.ObjectFactory;
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.OutputDirectory;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.options.Option;
import org.gradle.process.ExecOperations;
import org.gradle.process.ExecResult;
import javax.inject.Inject;
import java.io.File;
public class GroowtAntlrExecTask extends DefaultTask {
private final ExecOperations execOperations;
private final Provider<Configuration> configurationProvider;
private final RegularFileProperty antlrSourceFile;
private final DirectoryProperty outputDirectory;
private final Property<String> packageName;
private final Property<Boolean> visitor;
@Inject
public GroowtAntlrExecTask(
ObjectFactory objectFactory,
ExecOperations execOperations,
Provider<Configuration> configurationProvider
) {
this.execOperations = execOperations;
this.configurationProvider = configurationProvider;
this.antlrSourceFile = objectFactory.fileProperty();
this.outputDirectory = objectFactory.directoryProperty();
this.packageName = objectFactory.property(String.class);
this.visitor = objectFactory.property(Boolean.class);
this.visitor.convention(false);
}
@InputFile
public RegularFileProperty getAntlrSourceFile() {
return this.antlrSourceFile;
}
@OutputDirectory
public DirectoryProperty getOutputDirectory() {
return this.outputDirectory;
}
@Option(option = "packageName", description = "The packageName argument for Antlr.")
@Input
public Property<String> getPackageName() {
return this.packageName;
}
@Option(option = "visitor", description = "Whether Antlr should generate visitor classes or not.")
@Input
public Property<Boolean> getVisitor() {
return this.visitor;
}
public File resolveOutputFile(String name) {
return new File(this.outputDirectory.get().getAsFile(), name);
}
@TaskAction
public void doGenerate() {
final Configuration antlrConfiguration = configurationProvider.get();
final ExecResult result = this.execOperations.javaexec(javaExecSpec -> {
javaExecSpec.classpath(antlrConfiguration);
javaExecSpec.getMainClass().set("org.antlr.v4.Tool");
final String visitorArg = this.visitor.map(isVisitor -> isVisitor ? "-visitor" : "").get();
if (!visitorArg.isEmpty()) {
javaExecSpec.args(visitorArg);
}
final String packageNameArg = this.packageName.getOrElse("");
if (!packageNameArg.isEmpty()) {
javaExecSpec.args("-package", packageNameArg);
}
javaExecSpec.args("-o", this.outputDirectory.get().getAsFile().toString());
javaExecSpec.args(this.antlrSourceFile.getAsFile().get().toString());
});
result.assertNormalExitValue();
}
}

View File

@ -1,34 +0,0 @@
package groowt.gradle.antlr;
import org.gradle.api.Action;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.Property;
import javax.inject.Inject;
public abstract class GroowtAntlrExtension {
private final SourceSpecContainer sourceSpecs;
@Inject
public GroowtAntlrExtension(ObjectFactory objectFactory) {
this.sourceSpecs = objectFactory.newInstance(SourceSpecContainer.class, (Action<SourceSpec>) sourceSpec -> {
sourceSpec.getPackageName().convention(this.getPackageName());
sourceSpec.getVisitor().convention(this.getVisitor());
sourceSpec.getIsCompileDependency().convention(true);
sourceSpec.getDebug().convention(false);
});
}
public abstract Property<String> getPackageName();
public abstract Property<Boolean> getVisitor();
public SourceSpecContainer getSourceSpecs() {
return this.sourceSpecs;
}
public void sourceSpecs(Action<SourceSpecContainer> configure) {
configure.execute(this.getSourceSpecs());
}
}

View File

@ -1,215 +1,75 @@
package groowt.gradle.antlr; package groowt.gradle.antlr;
import org.gradle.api.Action;
import org.gradle.api.Plugin; import org.gradle.api.Plugin;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.file.FileTree;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.plugins.antlr.AntlrPlugin;
import org.gradle.api.plugins.antlr.AntlrSourceDirectorySet;
import org.gradle.api.plugins.antlr.AntlrTask;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.compile.JavaCompile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.inject.Inject;
import java.io.File; import java.io.File;
import java.util.*;
import java.util.stream.Collectors;
import static groowt.gradle.antlr.GroowtAntlrUtil.*;
public final class GroowtAntlrPlugin implements Plugin<Project> { public final class GroowtAntlrPlugin implements Plugin<Project> {
public static final String taskGroup = "groowtAntlr"; public static final String GROOWT_ANTLR = "groowtAntlr";
private static final String packageArg = "-package";
private static final String traceArg = "-trace";
private static final String visitorArg = "-visitor";
private final ObjectFactory objectFactory;
@Inject
public GroowtAntlrPlugin(ObjectFactory objectFactory) {
this.objectFactory = objectFactory;
}
private static NullableProviderList<String> getArguments(SourceSpec sourceSpec) {
final NullableProviderList<String> arguments = new NullableProviderList<>();
arguments.addCollectionProvider(sourceSpec.getPackageName().map(packageName -> List.of(packageArg, packageName)));
arguments.addProvider(sourceSpec.getVisitor().map(isVisitor -> isVisitor ? visitorArg : null));
arguments.addProvider(sourceSpec.getDebug().map(isDebug -> isDebug ? traceArg : null));
return arguments;
}
// For afterEvaluate!
private static List<GroowtAntlrTask> createAndRegister(Project project, GroowtAntlrExtension extension) {
final TaskContainer taskContainer = project.getTasks();
return extension.getSourceSpecs().stream().map(sourceSpec -> {
return taskContainer.create(
getGenerateTaskName(sourceSpec),
GroowtAntlrTask.class,
sourceSpec,
(Action<GroowtAntlrTask>) task -> {
task.setGroup(taskGroup);
task.setArguments(getArguments(sourceSpec));
task.setSource(sourceSpec.getResolvedSource().getSourceFile());
task.setOutputDirectory(
getOutputDirectory(
project,
sourceSpec.getResolvedSource().getSourceSet(),
sourceSpec.getPackageName()
)
);
}
);
}).toList();
}
// For afterEvaluate!
private static void addCompileDependencies(Project project, List<GroowtAntlrTask> tasks) {
tasks.forEach(task -> {
// if it is a compile dependency, add it as input to java source set
final var isCompileDependency = task.getSourceSpec().getIsCompileDependency().get();
if (isCompileDependency) {
project.getTasks().withType(JavaCompile.class).configureEach(javaCompile -> {
javaCompile.dependsOn(task);
});
}
});
}
private record GenerateAllSpec(
@Nullable FileTree source,
@NotNull List<String> args,
@NotNull Provider<String> packageName
) {}
private GenerateAllSpec getBlankGenerateAllSpec() {
return new GenerateAllSpec(null, List.of(), this.objectFactory.property(String.class));
}
private static GenerateAllSpec getGenerateAllSpecFromTask(GroowtAntlrTask task) {
return new GenerateAllSpec(task.getSource(), task.getArguments(), task.getSourceSpec().getPackageName());
}
private static @Nullable FileTree combineFileTrees(@Nullable FileTree f0, @Nullable FileTree f1) {
if (f0 != null && f1 != null) {
return f0.plus(f1);
} else if (f0 != null) {
return f0;
} else {
return f1; // null
}
}
private static List<String> combineArguments(List<String> a0, List<String> a1) {
final List<String> result = new ArrayList<>(a0);
final Iterator<String> a1Iter = a1.iterator();
while (a1Iter.hasNext()) {
final String arg = a1Iter.next();
if (arg.equals(packageArg) && result.contains(packageArg)) {
if (!a1Iter.hasNext()) {
throw new IllegalStateException("shouldn't get here");
}
final String a0PackageName = result.get(result.indexOf(arg) + 1);
final String a1PackageName = a1Iter.next();
if (!a0PackageName.equals(a1PackageName)) {
throw new IllegalArgumentException("Cannot have separate package arguments for two files from the same source set.");
}
} else if (!result.contains(arg)) {
result.add(arg);
}
}
return result;
}
private static Provider<String> combinePackageNames(Provider<String> p0, Provider<String> p1) {
return p0.zip(p1, (pn0, pn1) -> {
if (!pn0.equals(pn1)) {
throw new IllegalArgumentException("Cannot have separate package names for two files from the same source set.");
}
return pn0;
});
}
private static GenerateAllSpec combineGenerateAllSpecs(GenerateAllSpec s0, GenerateAllSpec s1) {
return new GenerateAllSpec(
combineFileTrees(s0.source(), s1.source()),
combineArguments(s0.args(), s1.args()),
combinePackageNames(s0.packageName(), s1.packageName())
);
}
// For afterEvaluate!
private static void addGenerateAllTasks(Project project, List<GroowtAntlrTask> tasks) {
final Map<SourceSet, List<GroowtAntlrTask>> sourceSetToTasks = tasks.stream().collect(Collectors.groupingBy(
task -> task.getSourceSpec().getResolvedSource().getSourceSet()
));
final Map<SourceSet, GenerateAllSpec> sourceSetToSpec = new HashMap<>();
sourceSetToTasks.forEach((sourceSet, sourceSetTasks) -> {
List<GenerateAllSpec> specs = sourceSetTasks.stream().map(task ->
new GenerateAllSpec(task.getSource(), task.getArguments(), task.getSourceSpec().getPackageName())
).toList();
specs.stream().reduce(GroowtAntlrPlugin::combineGenerateAllSpecs).ifPresent(allSpec -> {
sourceSetToSpec.put(sourceSet, allSpec);
});
});
sourceSetToSpec.forEach((sourceSet, spec) -> {
project.getTasks().register(
getGenerateAllTaskName(sourceSet),
GroowtAntlrAllTask.class,
task -> {
task.setGroup(taskGroup);
if (spec.source() != null) {
task.setSource(spec.source());
}
task.setArguments(spec.args());
task.setOutputDirectory(
getOutputDirectory(project, sourceSet, spec.packageName()).get().getAsFile()
);
}
);
});
}
@Override @Override
public void apply(Project project) { public void apply(Project project) {
project.getPluginManager().apply(AntlrPlugin.class); // register configuration
final var configurationProvider = project.getConfigurations().register(GROOWT_ANTLR, configuration -> {
// undo the antlr plugin creating its own tasks configuration.setCanBeConsumed(false);
project.getTasks().withType(AntlrTask.class, antlrTask -> { configuration.setCanBeResolved(true);
if (!(antlrTask instanceof GroowtAntlrTask || antlrTask instanceof GroowtAntlrAllTask)) {
antlrTask.setEnabled(false);
}
}); });
// create extension // register extension
final GroowtAntlrExtension extension = project.getExtensions().create("groowtAntlr", GroowtAntlrExtension.class); final var extension = project.getExtensions().create(GROOWT_ANTLR, GroowtAntlrSimpleExtension.class);
extension.getPackageName().convention("");
extension.getVisitor().convention(false);
// find all antlr files first and add them to extension // register tasks for each source file
project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets().forEach(sourceSet -> { project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets().forEach(sourceSet -> {
final Set<File> antlrFiles = sourceSet.getExtensions().getByType(AntlrSourceDirectorySet.class).getFiles(); // register sourceDirectorySet
for (final File antlrFile : antlrFiles) { final var antlrSourceDirectorySet = project.getObjects().sourceDirectorySet(GROOWT_ANTLR, GROOWT_ANTLR);
if (isAntlrSourceFile(antlrFile)) { antlrSourceDirectorySet.srcDir(
extension.getSourceSpecs().register(sourceSet, antlrFile); String.join(File.separator, "src", sourceSet.getName(), "antlr")
} );
} sourceSet.getAllSource().source(antlrSourceDirectorySet);
});
// after evaluate, generate tasks for each registered sourceSpec final var baseOutputDir = project.getLayout().getBuildDirectory().dir(String.join(
project.afterEvaluate(postEvaluateProject -> { File.separator,
final List<GroowtAntlrTask> tasks = createAndRegister(postEvaluateProject, extension); "generated-src",
addCompileDependencies(project, tasks); "antlr",
addGenerateAllTasks(project, tasks); sourceSet.getName()
));
sourceSet.getJava().srcDir(baseOutputDir);
final var sourceSetTasks = antlrSourceDirectorySet.getFiles().stream()
.filter(GroowtAntlrUtil::isAntlrFile)
.map(file -> {
final var taskProvider = project.getTasks().register(
GroowtAntlrUtil.getGenerateTaskName(sourceSet, file),
GroowtAntlrExecTask.class,
configurationProvider
);
taskProvider.configure(task -> {
task.setGroup(GROOWT_ANTLR);
task.getAntlrSourceFile().set(file);
task.getOutputDirectory().convention(
baseOutputDir.flatMap(base -> base.dir(
task.getPackageName().map(packageName ->
packageName.replace(".", File.separator)
)
))
);
task.getVisitor().convention(extension.getVisitor());
task.getPackageName().convention(extension.getPackageName());
});
return taskProvider;
})
.toList();
if (!sourceSetTasks.isEmpty()) {
project.getTasks().register(sourceSet.getTaskName("generate", "AllAntlr"), task -> {
task.dependsOn(sourceSetTasks);
task.setGroup(GROOWT_ANTLR);
});
}
}); });
} }

View File

@ -0,0 +1,8 @@
package groowt.gradle.antlr;
import org.gradle.api.provider.Property;
public interface GroowtAntlrSimpleExtension {
Property<String> getPackageName();
Property<Boolean> getVisitor();
}

View File

@ -1,70 +0,0 @@
package groowt.gradle.antlr;
import org.gradle.api.Action;
import org.gradle.api.file.Directory;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.plugins.antlr.AntlrTask;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.Nested;
import org.gradle.api.tasks.OutputDirectory;
import org.jetbrains.annotations.NotNull;
import javax.inject.Inject;
import java.io.File;
import java.util.List;
public class GroowtAntlrTask extends AntlrTask {
private final ObjectFactory objectFactory;
private final SourceSpec sourceSpec;
private Provider<Directory> outputDirectory;
private NullableProviderList<String> arguments;
@Inject
public GroowtAntlrTask(ObjectFactory objectFactory, SourceSpec sourceSpec, Action<? super GroowtAntlrTask> configure) {
this.objectFactory = objectFactory;
this.sourceSpec = sourceSpec;
configure.execute(this);
}
@Nested
public SourceSpec getSourceSpec() {
return this.sourceSpec;
}
@Override
public @NotNull File getOutputDirectory() {
return this.outputDirectory.get().getAsFile();
}
@Override
public void setOutputDirectory(@NotNull File outputDirectory) {
final DirectoryProperty directoryProperty = this.objectFactory.directoryProperty();
directoryProperty.set(outputDirectory);
this.outputDirectory = directoryProperty;
}
public void setOutputDirectory(Provider<Directory> outputDirectoryProvider) {
this.outputDirectory = outputDirectoryProvider;
}
@Override
@Internal
public @NotNull List<String> getArguments() {
return this.arguments.getElements();
}
@Override
public void setArguments(@NotNull List<String> arguments) {
this.arguments = new NullableProviderList<>();
this.arguments.addAllElements(arguments);
}
public void setArguments(@NotNull NullableProviderList<String> arguments) {
this.arguments = arguments;
}
}

View File

@ -1,147 +1,35 @@
package groowt.gradle.antlr; package groowt.gradle.antlr;
import org.gradle.api.Project;
import org.gradle.api.file.Directory;
import org.gradle.api.file.SourceDirectorySet;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSet;
import java.io.File; import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public final class GroowtAntlrUtil { public final class GroowtAntlrUtil {
public static final List<String> antlrFileExtensions = List.of("g4", "g"); public static final List<String> antlrExtensions = List.of("g4", "g");
private static final Pattern extensionPattern = Pattern.compile("(?<name>.*)\\.(?<ext>.*)$"); private static final Pattern nameAndExtension = Pattern.compile("(?<name>.*)\\.(?<ext>.*)$");
public static File resolve(File from, File to) { public static boolean isAntlrFile(File file) {
return from.toPath().resolve(to.toPath()).toFile(); for (final var antlrExtension : antlrExtensions) {
if (file.getName().endsWith(antlrExtension)) {
return true;
}
}
return false;
} }
public static File relativize(File from, File to) { public static String getGenerateTaskName(SourceSet sourceSet, File sourceFile) {
return from.toPath().relativize(to.toPath()).toFile(); final var matcher = nameAndExtension.matcher(sourceFile.getName());
} if (matcher.matches()) {
return sourceSet.getTaskName("generate", matcher.group("name"));
public static boolean isAntlrSourceFile(File file) {
final var m = extensionPattern.matcher(file.getName());
if (m.matches()) {
return antlrFileExtensions.contains(m.group("ext"));
} else { } else {
throw new IllegalArgumentException("Cannot determine extension of file: " + file); throw new IllegalArgumentException("Cannot determine source name for " + sourceFile);
} }
} }
public static List<String> sourceFileToIdentifierParts(File sourceDir, File sourceFile) {
final var relative = getRelativePathToSourceFile(sourceDir, sourceFile);
final List<String> result = new ArrayList<>();
for (int i = 0; i < relative.getNameCount(); i++) {
final var name = relative.getName(i);
final var m = extensionPattern.matcher(name.toString());
if (m.matches()) {
result.add(m.group("name"));
} else {
result.add(name.toString());
}
}
return result;
}
private static Path getRelativePathToSourceFile(File sourceDir, File sourceFile) {
if (!sourceDir.isAbsolute()) {
throw new IllegalArgumentException("sourceDir must be absolute, given: " + sourceDir);
}
if (sourceFile.isAbsolute()) {
final var sourceDirPath = sourceDir.toPath();
final var sourceFilePath = sourceFile.toPath();
return sourceDirPath.relativize(sourceFilePath);
} else {
return sourceFile.toPath();
}
}
public static String getSourceIdentifier(ResolvedSource resolvedSource) {
final List<String> parts = new ArrayList<>();
if (!resolvedSource.getSourceSet().getName().equals("main")) {
parts.add(resolvedSource.getSourceSet().getName());
}
parts.addAll(sourceFileToIdentifierParts(resolvedSource.getSourceDir(), resolvedSource.getSourceFile()));
final List<String> capitalizedParts = parts.stream()
.map(part -> {
final var first = part.substring(0, 1);
final var rest = part.substring(1);
return first.toUpperCase() + rest;
})
.toList();
return String.join("", capitalizedParts);
}
public static Provider<Directory> getOutputDirectory(
Project project,
SourceSet sourceSet,
Provider<String> packageNameProvider
) {
return project.getLayout().getBuildDirectory().flatMap(buildDir -> {
return buildDir.dir(packageNameProvider.map(packageName -> {
return String.join(File.separator, List.of(
"generated-src",
"antlr",
sourceSet.getName(),
packageName.replace(".", File.separator)
));
}));
});
}
public static String getGenerateTaskName(SourceSpec sourceSpec) {
return sourceSpec.getResolvedSource().getSourceSet().getTaskName("generate", sourceSpec.getIdentifier());
}
public static String getGenerateAllTaskName(SourceSet sourceSet) {
return sourceSet.getTaskName("generate", "AllAntlr");
}
public static ResolvedSource resolveSource(
Project project,
SourceSet sourceSet,
SourceDirectorySet sourceDirectorySet,
File sourceFile
) {
if (!isAntlrSourceFile(sourceFile)) {
throw new IllegalArgumentException(
"The given source file " + sourceFile + " is not a recognized antlr file (bad extension)."
);
}
final List<File> potentialSrcDirs = new ArrayList<>();
if (sourceFile.isAbsolute()) {
for (final File srcDir : sourceDirectorySet.getSrcDirs()) {
if (sourceFile.getPath().startsWith(srcDir.getPath())) {
potentialSrcDirs.add(srcDir);
}
}
} else {
for (final File srcDir : sourceDirectorySet.getSrcDirs()) {
if (resolve(srcDir, sourceFile).exists()) {
potentialSrcDirs.add(srcDir);
}
}
}
if (potentialSrcDirs.size() > 1) {
throw new IllegalArgumentException("Multiple source directories in " + sourceDirectorySet.getName() + " contain a source file " + sourceFile);
} else if (potentialSrcDirs.isEmpty()) {
throw new IllegalArgumentException("No directories in " + sourceDirectorySet.getName() + " contain a source file " + sourceFile);
}
final File srcDir = potentialSrcDirs.getFirst();
return new ResolvedSource(project, sourceSet, srcDir, resolve(srcDir, sourceFile));
}
private GroowtAntlrUtil() {} private GroowtAntlrUtil() {}
} }

View File

@ -1,160 +0,0 @@
package groowt.gradle.antlr;
import org.gradle.api.provider.Provider;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.function.Supplier;
public final class NullableProviderList<T> {
private sealed interface Element<T> extends Iterable<T> permits BareElement, ElementProvider, CollectionProvider {
boolean isPresent();
}
private static final class BareElement<T> implements Element<T> {
private final T element;
public BareElement(T element) {
this.element = element;
}
@Override
public boolean isPresent() {
return this.element != null;
}
@NotNull
@Override
public Iterator<T> iterator() {
return new Iterator<>() {
private boolean hasNext = true;
@Override
public boolean hasNext() {
return this.hasNext;
}
@Override
public T next() {
if (!this.hasNext) throw new IllegalStateException();
this.hasNext = false;
return BareElement.this.element;
}
};
}
}
private static final class ElementProvider<T> implements Element<T> {
private final Provider<T> provider;
public ElementProvider(Provider<T> provider) {
this.provider = provider;
}
@Override
public boolean isPresent() {
return this.provider.isPresent();
}
@NotNull
@Override
public Iterator<T> iterator() {
return new Iterator<T>() {
private boolean hasNext = true;
@Override
public boolean hasNext() {
return this.hasNext;
}
@Override
public T next() {
if (!this.hasNext) throw new IllegalStateException();
this.hasNext = false;
return ElementProvider.this.provider.get();
}
};
}
}
private static final class CollectionProvider<T> implements Element<T> {
private final Provider<Collection<T>> collectionProvider;
public CollectionProvider(Provider<Collection<T>> collectionProvider) {
this.collectionProvider = collectionProvider;
}
@Override
public boolean isPresent() {
return this.collectionProvider.isPresent();
}
@NotNull
@Override
public Iterator<T> iterator() {
return this.collectionProvider.get().iterator();
}
}
private final List<Element<T>> elements = new ArrayList<>();
public void addElement(@Nullable T element) {
this.elements.add(new BareElement<>(element));
}
public void addProvider(Provider<T> elementProvider) {
this.elements.add(new ElementProvider<>(elementProvider));
}
public void addCollectionProvider(Provider<Collection<T>> collectionProvider) {
this.elements.add(new CollectionProvider<>(collectionProvider));
}
public void addAllElements(Collection<T> elements) {
for (final T element : elements) {
this.elements.add(new BareElement<>(element));
}
}
public void addAllProviders(Collection<Provider<T>> providers) {
for (final Provider<T> provider : providers) {
this.elements.add(new ElementProvider<>(provider));
}
}
public void addAllCollectionProviders(Collection<Provider<Collection<T>>> collectionProviders) {
for (final Provider<Collection<T>> collectionProvider : collectionProviders) {
this.elements.add(new CollectionProvider<>(collectionProvider));
}
}
public List<T> getElements() {
return this.getElements(null);
}
public List<T> getElements(@Nullable Supplier<T> onNullElement) {
final List<T> result = new ArrayList<>();
for (final Element<T> element : this.elements) {
if (element.isPresent()) {
for (final T t : element) {
result.add(t);
}
} else if (onNullElement != null) {
result.add(onNullElement.get());
}
}
return result;
}
}

View File

@ -1,90 +0,0 @@
package groowt.gradle.antlr;
import org.gradle.api.Project;
import org.gradle.api.tasks.InputDirectory;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.SourceSet;
import java.io.File;
import java.util.List;
import static groowt.gradle.antlr.GroowtAntlrUtil.relativize;
public final class ResolvedSource {
private final Project project;
private final SourceSet sourceSet;
private final File sourceDir;
private final File sourceFile;
public ResolvedSource(Project project, SourceSet sourceSet, File sourceDir, File sourceFile) {
this.project = project;
this.sourceSet = sourceSet;
this.sourceDir = sourceDir;
this.sourceFile = sourceFile;
}
@Internal
public Project getProject() {
return this.project;
}
@Internal
public SourceSet getSourceSet() {
return this.sourceSet;
}
@InputDirectory
public File getSourceDir() {
return this.sourceDir;
}
@InputFile
public File getSourceFile() {
return this.sourceFile;
}
private String relativizeSourceDir() {
return relativize(this.project.getProjectDir(), this.sourceDir).toString();
}
private String relativeSourceFile() {
return relativize(this.sourceDir, this.sourceFile).toString();
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (obj instanceof ResolvedSource other) {
return this.project.equals(other.project)
&& this.sourceSet.equals(other.sourceSet)
&& this.sourceDir.equals(other.sourceDir)
&& this.sourceFile.equals(other.sourceFile);
} else {
return false;
}
}
@Override
public int hashCode() {
int result = this.project.hashCode();
result = 31 * result + this.sourceSet.hashCode();
result = 31 * result + this.sourceDir.hashCode();
result = 31 * result + this.sourceFile.hashCode();
return result;
}
@Override
public String toString() {
return "ResolvedSource("
+ String.join(", ", List.of(
"sourceSet: " + this.sourceSet.getName(),
"sourceDir: " + this.relativizeSourceDir(),
"sourceFile: " + this.relativeSourceFile()
))
+ ")";
}
}

View File

@ -1,43 +0,0 @@
package groowt.gradle.antlr;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.Nested;
import javax.inject.Inject;
public abstract class SourceSpec {
private final String identifier;
private final ResolvedSource resolvedSource;
@Inject
public SourceSpec(String identifier, ResolvedSource resolvedSource) {
this.identifier = identifier;
this.resolvedSource = resolvedSource;
}
@Internal
public String getIdentifier() {
return this.identifier;
}
@Nested
public ResolvedSource getResolvedSource() {
return this.resolvedSource;
}
@Input
public abstract Property<Boolean> getIsCompileDependency();
@Input
public abstract Property<Boolean> getDebug();
@Input
public abstract Property<String> getPackageName();
@Input
public abstract Property<Boolean> getVisitor();
}

View File

@ -1,89 +0,0 @@
package groowt.gradle.antlr;
import org.gradle.api.Action;
import org.gradle.api.Project;
import org.gradle.api.internal.CollectionCallbackActionDecorator;
import org.gradle.api.internal.DefaultDomainObjectSet;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.plugins.antlr.AntlrSourceDirectorySet;
import org.gradle.api.tasks.SourceSet;
import javax.inject.Inject;
import java.io.File;
import static groowt.gradle.antlr.GroowtAntlrUtil.getSourceIdentifier;
import static groowt.gradle.antlr.GroowtAntlrUtil.resolveSource;
public class SourceSpecContainer extends DefaultDomainObjectSet<SourceSpec> {
private final Project project;
private final ObjectFactory objectFactory;
private final Action<SourceSpec> applyConventions;
@Inject
public SourceSpecContainer(Project project, ObjectFactory objectFactory, Action<SourceSpec> applyConventions) {
super(SourceSpec.class, CollectionCallbackActionDecorator.NOOP);
this.project = project;
this.objectFactory = objectFactory;
this.applyConventions = applyConventions;
}
/**
* @param sourceFilePaths instances of File or String
*/
public void ignore(SourceSet sourceSet, Object... sourceFilePaths) {
for (final Object sourceFilePath : sourceFilePaths) {
switch (sourceFilePath) {
case File f -> this.ignoreFile(sourceSet, f);
case String s -> this.ignoreFile(sourceSet, new File(s));
default -> throw new IllegalArgumentException("Can only ignore Files or Strings, given: " + sourceFilePath);
}
}
}
private void ignoreFile(SourceSet sourceSet, File target) {
this.removeIf(potentialSourceSpec -> {
final SourceSet potentialSourceSet = potentialSourceSpec.getResolvedSource().getSourceSet();
if (!sourceSet.equals(potentialSourceSet)) {
return false;
}
final var antlrSourceDirectorySet = sourceSet.getExtensions().getByType(AntlrSourceDirectorySet.class);
final ResolvedSource toIgnore = resolveSource(this.project, sourceSet, antlrSourceDirectorySet, target);
final File potentialSourceFile = potentialSourceSpec.getResolvedSource().getSourceFile();
return toIgnore.getSourceFile().equals(potentialSourceFile);
});
}
public void register(SourceSet sourceSet, String sourceFilePath) {
this.register(sourceSet, new File(sourceFilePath), sourceSpec -> {}); // no-op action
}
public void register(SourceSet sourceSet, File sourceFile) {
this.register(sourceSet, sourceFile, sourceSpec -> {});
}
public void register(SourceSet sourceSet, String sourceFilePath, Action<? super SourceSpec> action) {
this.register(sourceSet, new File(sourceFilePath), action);
}
public void register(SourceSet sourceSet, File sourceFile, Action<? super SourceSpec> action) {
final var antlrSourceDirectorySet = sourceSet.getExtensions().getByType(AntlrSourceDirectorySet.class);
final var resolvedSource = resolveSource(this.project, sourceSet, antlrSourceDirectorySet, sourceFile);
final String identifier = getSourceIdentifier(resolvedSource);
final var specOptional = this.stream().filter(sourceSpec -> sourceSpec.getIdentifier().equals(identifier)).findFirst();
if (specOptional.isPresent()) {
// we already have one, so find and run the action against it
final var spec = specOptional.get();
action.execute(spec);
} else {
// create a new one
final var spec = this.objectFactory.newInstance(SourceSpec.class, identifier, resolvedSource);
this.applyConventions.execute(spec);
action.execute(spec);
this.add(spec);
}
}
}

View File

@ -1,53 +0,0 @@
//file:noinspection ConfigurationAvoidance
package groowt.gradle.antlr
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.plugins.antlr.AntlrPlugin
import org.gradle.testfixtures.ProjectBuilder
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.function.Executable
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow
class GroowtAntlrPluginTests {
@Disabled('TODO: figure out why register(SourceSet, String) is not working.')
@Test
void smokeScreen() {
def projectDir = File.createTempDir()
new FileTreeBuilder(projectDir).tap {
dir(['src', 'main', 'antlr'].join(File.separator)) {
file('MyGrammar.g4') {
write("parser grammar MyGrammar;")
}
}
}
def project = ProjectBuilder.builder().with {
withProjectDir(projectDir)
build()
}
project.pluginManager.with {
apply(JavaPlugin)
apply(AntlrPlugin)
apply(GroowtAntlrPlugin)
}
def mainSourceSet = project.extensions.getByType(JavaPluginExtension).sourceSets.findByName('main')
project.extensions.getByType(GroowtAntlrExtension).sourceSpecs.with {
register(mainSourceSet, 'MyGrammar.g4')
}
def findTask = {
project.tasks.named('generateMyGrammar', GroowtAntlrTask)
} as Executable
assertDoesNotThrow(findTask) {
"Could not find task 'generateMyGrammar' (all tasks: ${project.tasks*.name.join(', ')})"
}
}
}

View File

@ -1,132 +0,0 @@
//file:noinspection ConfigurationAvoidance
package groowt.gradle.antlr
import groovy.transform.TupleConstructor
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.plugins.antlr.AntlrPlugin
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.SourceSet
import org.gradle.testfixtures.ProjectBuilder
import org.junit.jupiter.api.DynamicTest
import org.junit.jupiter.api.TestFactory
import java.nio.file.Path
import static groowt.gradle.antlr.GroowtAntlrUtil.*
import static org.junit.jupiter.api.Assertions.assertEquals
import static org.junit.jupiter.api.Assertions.assertIterableEquals
import static org.junit.jupiter.api.DynamicTest.dynamicTest
class GroowtAntlrUtilTests {
@TupleConstructor
private static class SourceFileToIdentifierTestSpec {
File srcDir
File srcFile
List<String> expected
}
@TestFactory
Collection<DynamicTest> sourceFileToIdentifierPartsTests() {
def srcDir = File.createTempDir()
def getSpec = { String path, List<String> expected ->
new SourceFileToIdentifierTestSpec(srcDir, new File(path), expected)
}
def specs = [
getSpec('MyGrammar.g4', ['MyGrammar']),
getSpec('subDir/MyGrammar.g4', ['subDir', 'MyGrammar']),
getSpec('subDir/subSubDir/MyGrammar.g4', ['subDir', 'subSubDir', 'MyGrammar']),
getSpec('My.grammar.g4', ['My.grammar'])
]
return specs.collect { spec ->
dynamicTest(spec.srcFile.toString()) {
def actual = sourceFileToIdentifierParts(spec.srcDir, spec.srcFile)
assertIterableEquals(spec.expected, actual) {
"Unexpected result: ${actual}"
}
}
}
}
@TestFactory
Collection<DynamicTest> getSourceIdentifierTests() {
def projectDir = File.createTempDir()
File srcDir
new FileTreeBuilder(projectDir).with {
srcDir = dir(['src', 'main', 'antlr'].join(File.separator))
}
def project = ProjectBuilder.builder()
.withProjectDir(projectDir)
.build()
project.pluginManager.with {
apply(JavaPlugin)
apply(AntlrPlugin)
}
def mainSourceSet = project.extensions.getByType(JavaPluginExtension)
.sourceSets.getByName('main')
def getResolvedSource = { String path ->
new ResolvedSource(project, mainSourceSet, srcDir, new File(path))
}
Closure<Tuple2<String, ResolvedSource>> getSpec = { String expected, String path ->
new Tuple2(expected, getResolvedSource(path))
}
def specs = [
getSpec('MyGrammar', 'MyGrammar.g4'),
getSpec('SubDirMyGrammar', 'subDir/MyGrammar.g4')
]
return specs.collect { spec ->
dynamicTest(spec.v2.sourceFile.toString()) {
def actual = getSourceIdentifier(spec.v2)
assertEquals(spec.v1, actual)
}
}
}
@TestFactory
Collection<DynamicTest> getOutputDirTests() {
def project = ProjectBuilder.builder().build()
project.pluginManager.tap {
apply(JavaPlugin)
apply(AntlrPlugin)
}
project.layout.buildDirectory.set(new File('build'))
def mainSourceSet = project.extensions.getByType(JavaPluginExtension)
.sourceSets.getByName('main')
Closure<Tuple3<Path, SourceSet, Provider<String>>> getSpec = { String givenPackageName ->
def expectedPackagePath = givenPackageName.replace('.', File.separator)
def expected = Path.of('build', ['generated-src', 'antlr', mainSourceSet.name, expectedPackagePath] as String[])
def packageProperty = project.objects.property(String).tap { set(givenPackageName) }
new Tuple3(expected, mainSourceSet, packageProperty)
}
List<Tuple3<Path, SourceSet, Provider<String>>> specs = [
getSpec('antlr.one.two.three'), // build/generated-src/antlr/main/antlr/one/two/three
getSpec('test.antlr'), // build/generated-src/antlr/main/test/antlr
getSpec('antlr'), // build/generated-src/antlr/main/antlr
getSpec('') // build/generated-src/antlr/main
]
def projectPath = project.layout.projectDirectory.asFile.toPath()
return specs.collect { spec ->
def givenPackageName = spec.v3.get()
dynamicTest(givenPackageName.empty ? '<empty>' : givenPackageName) {
def actualPath = getOutputDirectory(project, spec.v2, spec.v3)
.get().asFile.toPath()
def result = projectPath.relativize(actualPath)
assertEquals(spec.v1, result)
}
}
}
}

View File

@ -1,11 +1,10 @@
import groovy.transform.NullCheck import groovy.transform.NullCheck
import groowt.gradle.antlr.GroowtAntlrTask import groowt.gradle.antlr.GroowtAntlrExecTask
plugins { plugins {
id 'java' id 'java'
id 'java-library' id 'java-library'
id 'groovy' id 'groovy'
id 'antlr'
id 'GroowtAntlrPlugin' id 'GroowtAntlrPlugin'
id 'org.jetbrains.kotlin.jvm' id 'org.jetbrains.kotlin.jvm'
id 'java-test-fixtures' id 'java-test-fixtures'
@ -49,7 +48,7 @@ dependencies {
project(':di'), project(':di'),
project(':extensible') project(':extensible')
) )
antlr libs.antlr groowtAntlr libs.antlr
runtimeOnly libs.log4j.slf4jBinding runtimeOnly libs.log4j.slf4jBinding
def testLibs = [ def testLibs = [
@ -82,13 +81,10 @@ groowtAntlr {
visitor = true visitor = true
} }
// Must be temporarily afterEvaluate unless we switch the GroowtAntlr plugin tasks.named('generateWebViewComponentsLexerBase', GroowtAntlrExecTask) { task ->
// to eagerly create the tasks upon plugin application
afterEvaluate {
tasks.named('generateWebViewComponentsLexerBase', GroowtAntlrTask) { task ->
doLast { doLast {
def pattern = ~/public class WebViewComponentsLexerBase(.*)/ def pattern = ~/public class WebViewComponentsLexerBase(.*)/
def lexerSource = new File(task.outputDirectory, 'WebViewComponentsLexerBase.java') def lexerSource = task.resolveOutputFile 'WebViewComponentsLexerBase.java'
def outLines = lexerSource.readLines().collect { def outLines = lexerSource.readLines().collect {
def matcher = pattern.matcher(it) def matcher = pattern.matcher(it)
if (matcher.matches()) { if (matcher.matches()) {
@ -99,7 +95,6 @@ afterEvaluate {
} }
lexerSource.write(outLines.join('\n')) lexerSource.write(outLines.join('\n'))
} }
}
} }
tasks.register('groovyConsole', JavaExec) { tasks.register('groovyConsole', JavaExec) {
@ -178,11 +173,6 @@ tasks.register('cleanBin', Delete) {
delete file('bin') delete file('bin')
} }
// Hacky
tasks.named('compileKotlin').configure {
dependsOn 'generateLexerFragments', 'generateWebViewComponentsLexerBase', 'generateWebViewComponentsParser'
}
test { test {
jvmArgs '-XX:+EnableDynamicAgentLoading' // for mockito/bytebuddy jvmArgs '-XX:+EnableDynamicAgentLoading' // for mockito/bytebuddy
testLogging.showStandardStreams = true testLogging.showStandardStreams = true