From ecdeb9091591475eadd5f928d69ee244e6cf46e2 Mon Sep 17 00:00:00 2001 From: Colin Alworth Date: Fri, 11 Feb 2022 10:49:59 -0600 Subject: [PATCH] Build output must be consistent between reactor projects and dependencies (#105) The "bytecode" and "apt" tasks have been officially merged now - while these separate output types were an attempt to keep generated sources separate from bytecode, in order to have reactor projects produce equivalent contents to the contents one might expect to find in distributed jars. This patch also modifies the maven goals from permitting generated-sources/annotations source directories from being added as source directories. Some basic logic is present to try to permit this directory instead of allowing the BytecodeTask run annotation processors (see SkipAptTask), but this is incomplete and doesn't work in reactor tasks yet. Output directories are also created at the beginning of goals now to hopefully make it clear where output will be written to users while waiting for compilation to complete. Error from javac are improved, to include the filename and line number with each error, if present. Added an integration test project that includes an annotation processor in the reactor so we can confirm it can build, and a project with resources to ensure they can be found from other projects in the same reactor. Fixes #101 Fixes #14 --- Readme.md | 4 + .../j2cl/build/task/OutputTypes.java | 36 +++-- .../app/pom.xml | 98 ++++++++++++++ .../main/java/com/example/MyAnnotation.java | 12 ++ .../app/src/main/java/com/example/MyApp.java | 7 + .../main/java/com/example/MyResources.java | 17 +++ .../app/src/main/webapp/WEB-INF/web.xml | 1 + .../test/java/com/example/ResourceTest.java | 20 +++ .../annotation-processor-in-reactor/pom.xml | 44 ++++++ .../processor/pom.xml | 37 +++++ .../main/java/com/example/MyProcessor.java | 126 ++++++++++++++++++ .../javax.annotation.processing.Processor | 1 + .../resources/pom.xml | 23 ++++ .../example/res-in-java-nested-package.txt | 1 + .../main/java/res-in-java-default-package.txt | 1 + .../resources/com/example/res-in-package.txt | 1 + .../src/main/resources/res-in-root-dir.txt | 1 + .../j2cl/mojo/AbstractBuildMojo.java | 18 +++ .../j2cl/mojo/AnnotationProcessorMode.java | 34 +++++ .../com/vertispan/j2cl/mojo/BuildMojo.java | 38 ++++-- .../com/vertispan/j2cl/mojo/TestMojo.java | 50 +++++-- .../com/vertispan/j2cl/mojo/WatchMojo.java | 25 +++- .../j2cl/build/provided/AptTask.java | 47 ------- .../j2cl/build/provided/BytecodeTask.java | 85 +++++++----- .../build/provided/ClosureBundleTask.java | 3 +- .../j2cl/build/provided/ClosureTask.java | 16 ++- .../j2cl/build/provided/J2clTask.java | 7 +- .../j2cl/build/provided/SkipAptTask.java | 27 ++-- .../j2cl/build/provided/StripSourcesTask.java | 8 +- .../build/provided/TestCollectionTask.java | 14 +- .../java/com/vertispan/j2cl/tools/Javac.java | 9 +- 31 files changed, 654 insertions(+), 157 deletions(-) create mode 100644 j2cl-maven-plugin/src/it/annotation-processor-in-reactor/app/pom.xml create mode 100644 j2cl-maven-plugin/src/it/annotation-processor-in-reactor/app/src/main/java/com/example/MyAnnotation.java create mode 100644 j2cl-maven-plugin/src/it/annotation-processor-in-reactor/app/src/main/java/com/example/MyApp.java create mode 100644 j2cl-maven-plugin/src/it/annotation-processor-in-reactor/app/src/main/java/com/example/MyResources.java create mode 100644 j2cl-maven-plugin/src/it/annotation-processor-in-reactor/app/src/main/webapp/WEB-INF/web.xml create mode 100644 j2cl-maven-plugin/src/it/annotation-processor-in-reactor/app/src/test/java/com/example/ResourceTest.java create mode 100644 j2cl-maven-plugin/src/it/annotation-processor-in-reactor/pom.xml create mode 100644 j2cl-maven-plugin/src/it/annotation-processor-in-reactor/processor/pom.xml create mode 100644 j2cl-maven-plugin/src/it/annotation-processor-in-reactor/processor/src/main/java/com/example/MyProcessor.java create mode 100644 j2cl-maven-plugin/src/it/annotation-processor-in-reactor/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor create mode 100644 j2cl-maven-plugin/src/it/annotation-processor-in-reactor/resources/pom.xml create mode 100644 j2cl-maven-plugin/src/it/annotation-processor-in-reactor/resources/src/main/java/com/example/res-in-java-nested-package.txt create mode 100644 j2cl-maven-plugin/src/it/annotation-processor-in-reactor/resources/src/main/java/res-in-java-default-package.txt create mode 100644 j2cl-maven-plugin/src/it/annotation-processor-in-reactor/resources/src/main/resources/com/example/res-in-package.txt create mode 100644 j2cl-maven-plugin/src/it/annotation-processor-in-reactor/resources/src/main/resources/res-in-root-dir.txt create mode 100644 j2cl-maven-plugin/src/main/java/com/vertispan/j2cl/mojo/AnnotationProcessorMode.java delete mode 100644 j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/AptTask.java diff --git a/Readme.md b/Readme.md index b2db3f42..e3ac2233 100644 --- a/Readme.md +++ b/Readme.md @@ -158,6 +158,10 @@ that is incompatible for GWT will annotate appropriately with `@GwtIncompatible` all annotation processors after stripping bytecode, which would require running on all artifacts from remote repositories as well. +Reactor projects all have their generated content and bytecode built into a single directory, alongside original +sources, resources, and bytecode. This results in a directory that should reflect accurately unpacking a jar +which happens to include its own sources. + ### Strip `@GwtIncompatible` The J2CL-provided `JavaPreprocessor` class is used to process all sources before they are compiled, stripping out diff --git a/build-caching/src/main/java/com/vertispan/j2cl/build/task/OutputTypes.java b/build-caching/src/main/java/com/vertispan/j2cl/build/task/OutputTypes.java index f72ea618..8b00976a 100644 --- a/build-caching/src/main/java/com/vertispan/j2cl/build/task/OutputTypes.java +++ b/build-caching/src/main/java/com/vertispan/j2cl/build/task/OutputTypes.java @@ -6,21 +6,41 @@ */ public interface OutputTypes { /** - * A special output type to indicate to use project's own sources. + * A special output type to indicate to use project's own sources, or + * the contents of an external dependency's jar/zip file. */ String INPUT_SOURCES = "input_sources"; /** - * Annotation processor output. + * Represents the contents of a project if it were built into a jar + * file as an external dependency. Ostensibly should contain the + * un-stripped bytecode for a project and all of its resources (so + * that downstream projects can look for those resources on the + * classpath), but also presently ends up holding generated resources + * (so that the {@link #GENERATED_SOURCES} task can copy them out), + * and at that point it might as well contain the original Java + * sources too, unstripped. Including those sources however means + * that this becomes the source of truth for stripping sources, + * rather than the union of {@link #INPUT_SOURCES} and {@link #GENERATED_SOURCES}. + * This conflict arises since there could be .js files in the original + * sources, and we must copy them here since downstream projects + * could require them on the classpath - and after APT runs, we can't + * tell which sources were copied in and which came from sources, + * so we can't let downstream closure point to this (or generated + * sources) and input sources, it will find duplicate files. */ - String GENERATED_SOURCES = "generated_sources"; + String BYTECODE = "bytecode"; + /** - * Bytecode from the original and generated sources. Not suitable - * for j2cl, only meant to be used for downstream generated sources - * tasks that need a compile classpath and will generate sources - * that also need to be stripped. + * Formerly annotation processor output. + * + * DISABLED FOR NOW - reintroducing this would require an intermediate + * output that would feed .java files to this, and everything else to + * {@link #BYTECODE} including source .js files. Taking this step may + * be necessary for better incremental builds. */ - String BYTECODE = "bytecode"; + @Deprecated + String GENERATED_SOURCES = "generated_sources"; /** * Sources where the Java code has had GwtIncompatible members stripped. diff --git a/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/app/pom.xml b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/app/pom.xml new file mode 100644 index 00000000..30d16f77 --- /dev/null +++ b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/app/pom.xml @@ -0,0 +1,98 @@ + + 4.0.0 + + annotation-processor-in-reactor + app + 1.0 + war + + + + + annotation-processor-in-reactor + processor + ${project.version} + provided + + + annotation-processor-in-reactor + resources + ${project.version} + + + + + com.vertispan.j2cl + junit-annotations + @j2cl.version@ + test + + + com.vertispan.j2cl + junit-emul + @j2cl.version@ + test + + + com.vertispan.j2cl + junit-emul + @j2cl.version@ + sources + test + + + com.vertispan.j2cl + gwttestcase-emul + @j2cl.version@ + test + + + + + + @project.groupId@ + @project.artifactId@ + @project.version@ + + + build-js + prepare-package + + build + + + + test-js + test + + test + + + ADVANCED + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + + + + + + google-snapshots + https://oss.sonatype.org/content/repositories/google-snapshots/ + + + diff --git a/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/app/src/main/java/com/example/MyAnnotation.java b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/app/src/main/java/com/example/MyAnnotation.java new file mode 100644 index 00000000..958b4feb --- /dev/null +++ b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/app/src/main/java/com/example/MyAnnotation.java @@ -0,0 +1,12 @@ +package com.example; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.SOURCE) +@interface MyAnnotation { + String value() default ""; +} diff --git a/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/app/src/main/java/com/example/MyApp.java b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/app/src/main/java/com/example/MyApp.java new file mode 100644 index 00000000..e9be7b8e --- /dev/null +++ b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/app/src/main/java/com/example/MyApp.java @@ -0,0 +1,7 @@ +package com.example; + +public class MyApp { + public static void start() { + MyResources.INSTANCE.resourceInRoot(); + } +} diff --git a/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/app/src/main/java/com/example/MyResources.java b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/app/src/main/java/com/example/MyResources.java new file mode 100644 index 00000000..60980323 --- /dev/null +++ b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/app/src/main/java/com/example/MyResources.java @@ -0,0 +1,17 @@ +package com.example; + +@MyAnnotation +public interface MyResources { + public static final MyResources INSTANCE = new MyResources_Impl(); + + @MyAnnotation("res-in-root-dir.txt") + String resourceInRoot(); + @MyAnnotation("com/example/res-in-package.txt") + String resourceInPackage(); + + @MyAnnotation("res-in-java-default-package.txt") + String resourceInJavaSourceRoot(); + + @MyAnnotation("com/example/res-in-java-nested-package.txt") + String resourceInJavaPackage(); +} diff --git a/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/app/src/main/webapp/WEB-INF/web.xml b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/app/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000..979f38e0 --- /dev/null +++ b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/app/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1 @@ + diff --git a/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/app/src/test/java/com/example/ResourceTest.java b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/app/src/test/java/com/example/ResourceTest.java new file mode 100644 index 00000000..90f6fec3 --- /dev/null +++ b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/app/src/test/java/com/example/ResourceTest.java @@ -0,0 +1,20 @@ +package com.example; + +import com.google.j2cl.junit.apt.J2clTestInput; + +import org.junit.Assert; +import org.junit.Test; + +@J2clTestInput(ResourceTest.class) +public class ResourceTest { + @Test + public void resourceContents() { + // This test verifies that the resource contents were correctly read at build time + MyResources res = MyResources.INSTANCE; + + Assert.assertEquals("res-in-root-dir.txt", res.resourceInRoot()); + Assert.assertEquals("res-in-package.txt", res.resourceInPackage()); + Assert.assertEquals("res-in-java-default-package.txt", res.resourceInJavaSourceRoot()); + Assert.assertEquals("res-in-java-nested-package.txt", res.resourceInJavaPackage()); + } +} diff --git a/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/pom.xml b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/pom.xml new file mode 100644 index 00000000..5e24808c --- /dev/null +++ b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/pom.xml @@ -0,0 +1,44 @@ + + 4.0.0 + + annotation-processor-in-reactor + annotation-processor-in-reactor + 1.0 + pom + + + This project tests not only if an annotation processor can be in the reactor (as a dependency of + the actual j2cl app), but also if the processor can correctly read files out of java and resources + directories of projects and dependencies. + + + + processor + resources + app + + + + + google-snapshots + https://oss.sonatype.org/content/repositories/google-snapshots/ + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.1 + + 1.8 + 1.8 + + + + + + diff --git a/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/processor/pom.xml b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/processor/pom.xml new file mode 100644 index 00000000..d64e2a76 --- /dev/null +++ b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/processor/pom.xml @@ -0,0 +1,37 @@ + + 4.0.0 + + + annotation-processor-in-reactor + annotation-processor-in-reactor + 1.0 + + processor + jar + + + + com.squareup + javapoet + 1.13.0 + + + com.google.auto + auto-common + 1.0.1 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + none + + + + + diff --git a/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/processor/src/main/java/com/example/MyProcessor.java b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/processor/src/main/java/com/example/MyProcessor.java new file mode 100644 index 00000000..a83b1f6e --- /dev/null +++ b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/processor/src/main/java/com/example/MyProcessor.java @@ -0,0 +1,126 @@ +package com.example; + +import com.google.auto.common.AnnotationMirrors; +import com.google.auto.common.AnnotationValues; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeSpec; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Filer; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.tools.FileObject; +import javax.tools.JavaFileManager; +import javax.tools.StandardLocation; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; + +@SupportedSourceVersion(SourceVersion.RELEASE_8) +public class MyProcessor extends AbstractProcessor { + @Override + public Set getSupportedAnnotationTypes() { + return Collections.singleton("com.example.MyAnnotation"); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + TypeElement annotationElt = processingEnv.getElementUtils().getTypeElement("com.example.MyAnnotation"); + Set elements = roundEnv.getElementsAnnotatedWith(annotationElt); + + Filer filer = processingEnv.getFiler(); + elements.stream().filter(e -> e.getKind().isInterface()).forEach(type -> { + //emit a new class for that type, with no-arg string methods + try { + ClassName origClassName = ClassName.get((TypeElement) type); + TypeSpec.Builder builder = TypeSpec.classBuilder(origClassName.simpleName() + "_Impl") + .addModifiers(Modifier.PUBLIC) + .addSuperinterface(origClassName); + for (Element enclosedElement : type.getEnclosedElements()) { + if (enclosedElement instanceof ExecutableElement) { + ExecutableElement method = (ExecutableElement) enclosedElement; + + Optional myAnnotation = enclosedElement.getAnnotationMirrors() + .stream() + .filter(annMirror -> annMirror.getAnnotationType().asElement().equals(annotationElt)) + .findAny(); + if (!myAnnotation.isPresent()) { + continue; + } + AnnotationValue value = AnnotationMirrors.getAnnotationValue(myAnnotation.get(), "value"); + String path = AnnotationValues.getString(value); + + CharSequence contents = getStringContentsOfPath(filer, path); + MethodSpec methodSpec = MethodSpec.methodBuilder(method.getSimpleName().toString()) + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .returns(String.class) + .addCode("return $S;", contents) + .build(); + + builder.addMethod(methodSpec); + } + } + JavaFile newFile = JavaFile.builder(origClassName.packageName(), builder.build()).build(); + newFile.writeTo(filer); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + return true; + } + + private CharSequence getStringContentsOfPath(Filer filer, String path) throws IOException { + for (JavaFileManager.Location location : Arrays.asList( + StandardLocation.SOURCE_PATH, + StandardLocation.SOURCE_OUTPUT, + StandardLocation.CLASS_PATH, + StandardLocation.CLASS_OUTPUT, + StandardLocation.ANNOTATION_PROCESSOR_PATH + )) { + try { + FileObject resource = filer.getResource(location, "", path); + if (resource != null && new File(resource.getName()).exists()) { + return resource.getCharContent(false); + } + } catch (IOException e) { + //ignore, look in the next entry + } + } + try (InputStream inputStream = getClass().getResourceAsStream("/" + path)) { + if (inputStream != null) { + final char[] buffer = new char[1024]; + final StringBuilder out = new StringBuilder(); + try (Reader in = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { + while (true) { + int rsz = in.read(buffer, 0, buffer.length); + if (rsz < 0) + break; + out.append(buffer, 0, rsz); + } + } + return out.toString(); + } + throw new IllegalStateException("Failed to find resource " + path); + } catch (IOException e) { + throw new IllegalStateException("Failed to read resource " + path, e); + } + } +} diff --git a/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 00000000..c96379d5 --- /dev/null +++ b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +com.example.MyProcessor diff --git a/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/resources/pom.xml b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/resources/pom.xml new file mode 100644 index 00000000..eecb6ef9 --- /dev/null +++ b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/resources/pom.xml @@ -0,0 +1,23 @@ + + 4.0.0 + + + annotation-processor-in-reactor + annotation-processor-in-reactor + 1.0 + + resources + jar + + + + + src/main/resources + + + src/main/java + + + + diff --git a/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/resources/src/main/java/com/example/res-in-java-nested-package.txt b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/resources/src/main/java/com/example/res-in-java-nested-package.txt new file mode 100644 index 00000000..ac412301 --- /dev/null +++ b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/resources/src/main/java/com/example/res-in-java-nested-package.txt @@ -0,0 +1 @@ +res-in-java-nested-package.txt \ No newline at end of file diff --git a/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/resources/src/main/java/res-in-java-default-package.txt b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/resources/src/main/java/res-in-java-default-package.txt new file mode 100644 index 00000000..ed9d0c55 --- /dev/null +++ b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/resources/src/main/java/res-in-java-default-package.txt @@ -0,0 +1 @@ +res-in-java-default-package.txt \ No newline at end of file diff --git a/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/resources/src/main/resources/com/example/res-in-package.txt b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/resources/src/main/resources/com/example/res-in-package.txt new file mode 100644 index 00000000..a2fdbc77 --- /dev/null +++ b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/resources/src/main/resources/com/example/res-in-package.txt @@ -0,0 +1 @@ +res-in-package.txt \ No newline at end of file diff --git a/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/resources/src/main/resources/res-in-root-dir.txt b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/resources/src/main/resources/res-in-root-dir.txt new file mode 100644 index 00000000..45b5d693 --- /dev/null +++ b/j2cl-maven-plugin/src/it/annotation-processor-in-reactor/resources/src/main/resources/res-in-root-dir.txt @@ -0,0 +1 @@ +res-in-root-dir.txt \ No newline at end of file diff --git a/j2cl-maven-plugin/src/main/java/com/vertispan/j2cl/mojo/AbstractBuildMojo.java b/j2cl-maven-plugin/src/main/java/com/vertispan/j2cl/mojo/AbstractBuildMojo.java index e9074d25..a7b6cde2 100644 --- a/j2cl-maven-plugin/src/main/java/com/vertispan/j2cl/mojo/AbstractBuildMojo.java +++ b/j2cl-maven-plugin/src/main/java/com/vertispan/j2cl/mojo/AbstractBuildMojo.java @@ -2,6 +2,8 @@ import com.vertispan.j2cl.build.Dependency; import com.vertispan.j2cl.build.Project; +import com.vertispan.j2cl.build.TaskRegistry; +import com.vertispan.j2cl.build.provided.SkipAptTask; import com.vertispan.j2cl.build.task.OutputTypes; import org.apache.maven.RepositoryUtils; import org.apache.maven.artifact.Artifact; @@ -73,6 +75,9 @@ public abstract class AbstractBuildMojo extends AbstractCacheMojo { @Parameter protected List dependencyReplacements; + @Parameter(defaultValue = "AVOID_MAVEN") + private AnnotationProcessorMode annotationProcessorMode; + private List defaultDependencyReplacements = Arrays.asList( new DependencyReplacement("com.google.jsinterop:base", "com.vertispan.jsinterop:base:" + Versions.VERTISPAN_JSINTEROP_BASE_VERSION), new DependencyReplacement("org.realityforge.com.google.jsinterop:base", "com.vertispan.jsinterop:base:" + Versions.VERTISPAN_JSINTEROP_BASE_VERSION), @@ -97,6 +102,9 @@ public abstract class AbstractBuildMojo extends AbstractCacheMojo { @Parameter(readonly = true, defaultValue = "${mojoExecution}") protected MojoExecution mojoExecution; + @Parameter + protected Map taskMappings = new HashMap<>(); + private static String key(Artifact artifact) { // this is roughly DefaultArtifact.toString, minus scope, since we don't care what the scope is for the purposes of building projects String key = artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getBaseVersion(); @@ -275,6 +283,8 @@ private Project buildProject(MavenProject mavenProject, Artifact artifact, boole ) .distinct() .filter(path -> new File(path).exists()) + .filter(path -> !(annotationProcessorMode.pluginShouldExcludeGeneratedAnnotationsDir() + && path.endsWith("generated-sources" + File.separator + "annotations"))) .collect(Collectors.toList()) ); } else { @@ -305,4 +315,12 @@ private Dependency.Scope translateScope(String scope) { throw new IllegalStateException("Unsupported scope: " + scope); } } + + protected TaskRegistry createTaskRegistry() { + if (!annotationProcessorMode.pluginShouldRunApt()) { + taskMappings.put(OutputTypes.BYTECODE, SkipAptTask.SKIP_TASK_NAME); + } + // use any task wiring if specified + return new TaskRegistry(taskMappings); + } } diff --git a/j2cl-maven-plugin/src/main/java/com/vertispan/j2cl/mojo/AnnotationProcessorMode.java b/j2cl-maven-plugin/src/main/java/com/vertispan/j2cl/mojo/AnnotationProcessorMode.java new file mode 100644 index 00000000..22795577 --- /dev/null +++ b/j2cl-maven-plugin/src/main/java/com/vertispan/j2cl/mojo/AnnotationProcessorMode.java @@ -0,0 +1,34 @@ +package com.vertispan.j2cl.mojo; + +/** + * Currently unused, this enum is meant to give the pom.xml author the power to decide when + * the j2cl plugin should generate code via annotation processors, and when the maven build + * will take care of that. + * + * For now, only IGNORE_MAVEN is properly supported, it seems that maven doesn't include the + * generated-sources/annotations directory to reactor projects not currently being evaluated, + * so we might need to ignore the source directory entirely, and just consume the "artifact" + * of that reactor project (which might be the target/classes directory, etc). + * + * See https://github.com/Vertispan/j2clmavenplugin/issues/112 + */ +public enum AnnotationProcessorMode { + PREFER_MAVEN(false, false), + IGNORE_MAVEN(true, false), + AVOID_MAVEN(true, true); + + private final boolean pluginShouldRunApt; + private final boolean pluginShouldExcludeGeneratedAnnotationsDir; + + AnnotationProcessorMode(boolean pluginShouldRunApt, boolean pluginShouldExcludeGeneratedAnnotationsDir) { + this.pluginShouldRunApt = pluginShouldRunApt; + this.pluginShouldExcludeGeneratedAnnotationsDir = pluginShouldExcludeGeneratedAnnotationsDir; + } + + public boolean pluginShouldRunApt() { + return pluginShouldRunApt; + } + public boolean pluginShouldExcludeGeneratedAnnotationsDir() { + return pluginShouldExcludeGeneratedAnnotationsDir; + } +} diff --git a/j2cl-maven-plugin/src/main/java/com/vertispan/j2cl/mojo/BuildMojo.java b/j2cl-maven-plugin/src/main/java/com/vertispan/j2cl/mojo/BuildMojo.java index 25d3b796..1cf2e7cf 100644 --- a/j2cl-maven-plugin/src/main/java/com/vertispan/j2cl/mojo/BuildMojo.java +++ b/j2cl-maven-plugin/src/main/java/com/vertispan/j2cl/mojo/BuildMojo.java @@ -1,12 +1,19 @@ package com.vertispan.j2cl.mojo; -import com.vertispan.j2cl.build.*; +import com.vertispan.j2cl.build.BlockingBuildListener; +import com.vertispan.j2cl.build.BuildService; +import com.vertispan.j2cl.build.DefaultDiskCache; +import com.vertispan.j2cl.build.DiskCache; +import com.vertispan.j2cl.build.Project; +import com.vertispan.j2cl.build.TaskRegistry; +import com.vertispan.j2cl.build.TaskScheduler; import org.apache.maven.artifact.Artifact; import org.apache.maven.model.Plugin; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugin.PluginParameterExpressionEvaluator; import org.apache.maven.plugin.descriptor.PluginDescriptor; +import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; @@ -18,7 +25,16 @@ import java.io.File; import java.io.IOException; -import java.util.*; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -87,8 +103,7 @@ *
  • https://github.com/google/jsinterop-base/blob/18973cb/java/jsinterop/base/jsinterop.js#L25-L28
  • * */ -@Mojo(name = "build", requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME) -//@Execute(phase = LifecyclePhase.PROCESS_CLASSES) +@Mojo(name = "build", requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, defaultPhase = LifecyclePhase.PREPARE_PACKAGE) public class BuildMojo extends AbstractBuildMojo { /** @@ -160,9 +175,6 @@ public class BuildMojo extends AbstractBuildMojo { @Parameter protected Map defines = new TreeMap<>(); - - @Parameter - protected Map taskMappings = new HashMap<>(); /** * Closure flag: "Rewrite ES6 library calls to use polyfills provided by the compiler's runtime." @@ -197,6 +209,13 @@ public class BuildMojo extends AbstractBuildMojo { @Override public void execute() throws MojoExecutionException, MojoFailureException { + // pre-create the directory so it is easier to find up front, even if it starts off empty + try { + Files.createDirectories(Paths.get(webappDirectory)); + } catch (IOException e) { + throw new MojoExecutionException("Failed to create the webappDirectory " + webappDirectory, e); + } + PluginDescriptor pluginDescriptor = (PluginDescriptor) getPluginContext().get("pluginDescriptor"); String pluginVersion = pluginDescriptor.getVersion(); @@ -237,9 +256,6 @@ public void execute() throws MojoExecutionException, MojoFailureException { // given the build output, determine what tasks we're going to run String outputTask = getOutputTask(compilationLevel); - // use any task wiring if specified - Map outputToNameMappings = taskMappings; - // construct other required elements to get the work done ScheduledExecutorService executor = Executors.newScheduledThreadPool(getWorkerTheadCount()); final DiskCache diskCache; @@ -250,7 +266,7 @@ public void execute() throws MojoExecutionException, MojoFailureException { } MavenLog mavenLog = new MavenLog(getLog()); TaskScheduler taskScheduler = new TaskScheduler(executor, diskCache, mavenLog); - TaskRegistry taskRegistry = new TaskRegistry(outputToNameMappings); + TaskRegistry taskRegistry = createTaskRegistry(); // Given these, build the graph of work we need to complete BuildService buildService = new BuildService(taskRegistry, taskScheduler, diskCache); diff --git a/j2cl-maven-plugin/src/main/java/com/vertispan/j2cl/mojo/TestMojo.java b/j2cl-maven-plugin/src/main/java/com/vertispan/j2cl/mojo/TestMojo.java index f6a67069..71e3ee89 100644 --- a/j2cl-maven-plugin/src/main/java/com/vertispan/j2cl/mojo/TestMojo.java +++ b/j2cl-maven-plugin/src/main/java/com/vertispan/j2cl/mojo/TestMojo.java @@ -4,7 +4,16 @@ import com.google.common.io.CharStreams; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; -import com.vertispan.j2cl.build.*; +import com.vertispan.j2cl.build.BlockingBuildListener; +import com.vertispan.j2cl.build.BuildService; +import com.vertispan.j2cl.build.DefaultDiskCache; +import com.vertispan.j2cl.build.Dependency; +import com.vertispan.j2cl.build.DiskCache; +import com.vertispan.j2cl.build.Project; +import com.vertispan.j2cl.build.PropertyTrackingConfig; +import com.vertispan.j2cl.build.TaskRegistry; +import com.vertispan.j2cl.build.TaskScheduler; +import com.vertispan.j2cl.build.provided.TestCollectionTask; import org.apache.maven.artifact.Artifact; import org.apache.maven.model.FileSet; import org.apache.maven.model.Plugin; @@ -12,6 +21,7 @@ import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugin.PluginParameterExpressionEvaluator; import org.apache.maven.plugin.descriptor.PluginDescriptor; +import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; @@ -35,14 +45,29 @@ import org.openqa.selenium.logging.LoggingPreferences; import org.openqa.selenium.support.ui.FluentWait; -import java.io.*; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.time.Duration; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; import java.util.concurrent.CompletionException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; @@ -51,7 +76,7 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -@Mojo(name = "test", requiresDependencyResolution = ResolutionScope.TEST) +@Mojo(name = "test", requiresDependencyResolution = ResolutionScope.TEST, defaultPhase = LifecyclePhase.TEST) public class TestMojo extends AbstractBuildMojo { /** * The dependency scope to use for the classpath. @@ -122,9 +147,6 @@ public class TestMojo extends AbstractBuildMojo { @Parameter protected Map defines = new TreeMap<>(); - @Parameter - protected Map taskMappings = new HashMap<>(); - /** * Whether or not to leave Java assert checks in the compiled code. In j2cl:test, defaults to true. Has no * effect when the compilation level isn't set to ADVANCED_OPTIMIZATIONS, assertions will always remain @@ -180,6 +202,13 @@ public void execute() throws MojoExecutionException, MojoFailureException { return; } + // pre-create the directory so it is easier to find up front, even if it starts off empty + try { + Files.createDirectories(Paths.get(webappDirectory)); + } catch (IOException e) { + throw new MojoExecutionException("Failed to create the webappDirectory " + webappDirectory, e); + } + Map failedTests = new HashMap<>(); PluginDescriptor pluginDescriptor = (PluginDescriptor) getPluginContext().get("pluginDescriptor"); @@ -238,9 +267,6 @@ public void execute() throws MojoExecutionException, MojoFailureException { // given the build output, determine what tasks we're going to run String outputTask = getOutputTask(compilationLevel); - // use any task wiring if specified - Map outputToNameMappings = taskMappings; - // construct other required elements to get the work done ScheduledExecutorService executor = Executors.newScheduledThreadPool(getWorkerTheadCount()); final DiskCache diskCache; @@ -251,11 +277,11 @@ public void execute() throws MojoExecutionException, MojoFailureException { } MavenLog mavenLog = new MavenLog(getLog()); TaskScheduler taskScheduler = new TaskScheduler(executor, diskCache, mavenLog); - TaskRegistry taskRegistry = new TaskRegistry(outputToNameMappings); + TaskRegistry taskRegistry = createTaskRegistry(); // Given these, build the graph of work we need to complete to get the list of tests BuildService buildService = new BuildService(taskRegistry, taskScheduler, diskCache); - buildService.assignProject(test, "test_summary", config); + buildService.assignProject(test, TestCollectionTask.TEST_COLLECTION_OUTPUT_TYPE, config); // Get the hash of all current files, since we aren't running a watch service buildService.initialHashes(); diff --git a/j2cl-maven-plugin/src/main/java/com/vertispan/j2cl/mojo/WatchMojo.java b/j2cl-maven-plugin/src/main/java/com/vertispan/j2cl/mojo/WatchMojo.java index eb2b3289..faeebc1c 100644 --- a/j2cl-maven-plugin/src/main/java/com/vertispan/j2cl/mojo/WatchMojo.java +++ b/j2cl-maven-plugin/src/main/java/com/vertispan/j2cl/mojo/WatchMojo.java @@ -1,6 +1,12 @@ package com.vertispan.j2cl.mojo; -import com.vertispan.j2cl.build.*; +import com.vertispan.j2cl.build.BuildService; +import com.vertispan.j2cl.build.DefaultDiskCache; +import com.vertispan.j2cl.build.DiskCache; +import com.vertispan.j2cl.build.Project; +import com.vertispan.j2cl.build.TaskRegistry; +import com.vertispan.j2cl.build.TaskScheduler; +import com.vertispan.j2cl.build.WatchService; import org.apache.maven.artifact.Artifact; import org.apache.maven.model.Plugin; import org.apache.maven.model.PluginExecution; @@ -19,8 +25,11 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Paths; -import java.util.*; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.function.Function; @@ -111,9 +120,6 @@ public class WatchMojo extends AbstractBuildMojo { @Parameter(defaultValue = "SORT_ONLY") protected String dependencyMode; - @Parameter - protected Map taskMappings = new HashMap<>(); - @Override public void execute() throws MojoExecutionException, MojoFailureException { PluginDescriptor pluginDescriptor = (PluginDescriptor) getPluginContext().get("pluginDescriptor"); @@ -130,6 +136,13 @@ public void execute() throws MojoExecutionException, MojoFailureException { } } + // pre-create the directory so it is easier to find up front, even if it starts off empty + try { + Files.createDirectories(Paths.get(webappDirectory)); + } catch (IOException e) { + throw new MojoExecutionException("Failed to create the webappDirectory " + webappDirectory, e); + } + //TODO need to be very careful about allowing these to be configurable, possibly should tie them to the "plugin version" aspect of the hash // or stitch them into the module's dependencies, that probably makes more sense... List extraClasspath = Arrays.asList( @@ -168,7 +181,7 @@ public void execute() throws MojoExecutionException, MojoFailureException { TaskScheduler taskScheduler = new TaskScheduler(executor, diskCache, mavenLog); // TODO support individual task registries per execution - TaskRegistry taskRegistry = new TaskRegistry(taskMappings); + TaskRegistry taskRegistry = createTaskRegistry(); BuildService buildService = new BuildService(taskRegistry, taskScheduler, diskCache); // TODO end diff --git a/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/AptTask.java b/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/AptTask.java deleted file mode 100644 index 1db17b7f..00000000 --- a/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/AptTask.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.vertispan.j2cl.build.provided; - -import com.google.auto.service.AutoService; -import com.vertispan.j2cl.build.task.*; - -import java.nio.file.Files; -import java.nio.file.PathMatcher; - -@AutoService(TaskFactory.class) -public class AptTask extends TaskFactory { - public static final PathMatcher BYTECODE = withSuffix(".class"); - public static final PathMatcher NOT_BYTECODE = p -> !BYTECODE.matches(p); - - @Override - public String getOutputType() { - return OutputTypes.GENERATED_SOURCES; - } - - @Override - public String getTaskName() { - return "default"; - } - - @Override - public String getVersion() { - return "0"; - } - - @Override - public Task resolve(Project project, Config config) { - if (!project.hasSourcesMapped()) { - // we explicitly don't copy the generated sources, they already exist in the proj sources - return ignored -> {}; - } - - // we assume that bytecode was generated by javac, and will read the generated sources out of there - Input myBytecode = input(project, OutputTypes.BYTECODE).filter(NOT_BYTECODE); - - return context -> { - // the BytecodeTask already did the work for us, just copy sources to output - for (CachedPath entry : myBytecode.getFilesAndHashes()) { - Files.createDirectories(context.outputPath().resolve(entry.getSourcePath()).getParent()); - Files.copy(entry.getAbsolutePath(), context.outputPath().resolve(entry.getSourcePath())); - } - }; - } -} diff --git a/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/BytecodeTask.java b/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/BytecodeTask.java index 8199b296..4bc9ea10 100644 --- a/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/BytecodeTask.java +++ b/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/BytecodeTask.java @@ -5,6 +5,7 @@ import com.vertispan.j2cl.build.task.*; import com.vertispan.j2cl.tools.Javac; +import javax.annotation.Nullable; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; @@ -15,20 +16,18 @@ import java.util.stream.Stream; /** - * This implementation and {@link AptTask} are wired together, if you replace - * one you may need to replace the other at the same time (the SkipAptTask is an - * exception to this). - * - * The assumption is that since these are generated at the same time by a single - * invocation of javac, we want to generate the bytecode first for downstream - * projects so they can also generate their own sources. With this though, - * the AptTask should be a no-op, so it shouldn't really matter. + * This runs javac (and so, all annotation processors) on the input source, and + * produces a directory of all sources, resources, and bytecode of a reactor + * project. This results in a directory with the same contents you might get from + * unpacking the jar from a non-reactor dependency, so we can treat all + * dependencies the same, regardless of their origin. */ @AutoService(TaskFactory.class) public class BytecodeTask extends TaskFactory { public static final PathMatcher JAVA_SOURCES = withSuffix(".java"); public static final PathMatcher JAVA_BYTECODE = withSuffix(".class"); + public static final PathMatcher NOT_BYTECODE = p -> !JAVA_BYTECODE.matches(p); @Override public String getOutputType() { @@ -48,8 +47,8 @@ public String getVersion() { @Override public Task resolve(Project project, Config config) { if (!project.hasSourcesMapped()) { - // instead copy the bytecode out of the jar so it can be used by downtream bytecode/apt tasks - Input existingUnpackedBytecode = input(project, OutputTypes.INPUT_SOURCES);//.filter(JAVA_BYTECODE); + // instead, copy the bytecode+resources out of the jar so it can be used by downstream bytecode/apt tasks + Input existingUnpackedBytecode = input(project, OutputTypes.INPUT_SOURCES); return context -> { for (CachedPath entry : existingUnpackedBytecode.getFilesAndHashes()) { Files.createDirectories(context.outputPath().resolve(entry.getSourcePath()).getParent()); @@ -63,6 +62,9 @@ public Task resolve(Project project, Config config) { Input inputDirs = input(project, OutputTypes.INPUT_SOURCES); // track just java files (so we can just compile them) Input inputSources = input(project, OutputTypes.INPUT_SOURCES).filter(JAVA_SOURCES); + // track resources so they are available to downstream processors on the classpath, as they would + // be if we had built a jar + Input resources = input(project, OutputTypes.INPUT_SOURCES).filter(NOT_BYTECODE); List bytecodeClasspath = scope(project.getDependencies(), com.vertispan.j2cl.build.task.Dependency.Scope.COMPILE) .stream() @@ -72,35 +74,48 @@ public Task resolve(Project project, Config config) { File bootstrapClasspath = config.getBootstrapClasspath(); List extraClasspath = config.getExtraClasspath(); return context -> { - if (inputSources.getFilesAndHashes().isEmpty()) { - return;// no work to do + if (!inputSources.getFilesAndHashes().isEmpty()) { + // At least one .java file in sources, compile it (otherwise skip this and just copy resource) + + List classpathDirs = Stream.concat( + bytecodeClasspath.stream().map(Input::getParentPaths).flatMap(Collection::stream).map(Path::toFile), + extraClasspath.stream() + ).collect(Collectors.toList()); + + List sourcePaths = inputDirs.getParentPaths().stream().map(Path::toFile).collect(Collectors.toList()); + File generatedClassesDir = getGeneratedClassesDir(context); + File classOutputDir = context.outputPath().toFile(); + Javac javac = new Javac(context, generatedClassesDir, sourcePaths, classpathDirs, classOutputDir, bootstrapClasspath); + + // TODO convention for mapping to original file paths, provide FileInfo out of Inputs instead of Paths, + // automatically relativized? + List sources = inputSources.getFilesAndHashes() + .stream() + .map(p -> SourceUtils.FileInfo.create(p.getAbsolutePath().toString(), p.getSourcePath().toString())) + .collect(Collectors.toList()); + + try { + if (!javac.compile(sources)) { + throw new RuntimeException("Failed to complete bytecode task, check log"); + } + } catch (Exception exception) { + exception.printStackTrace(); + throw exception; + } } - List classpathDirs = Stream.concat( - bytecodeClasspath.stream().map(Input::getParentPaths).flatMap(Collection::stream).map(Path::toFile), - extraClasspath.stream() - ).collect(Collectors.toList()); - - // TODO don't dump APT to the same dir? - List sourcePaths = inputDirs.getParentPaths().stream().map(Path::toFile).collect(Collectors.toList()); - Javac javac = new Javac(context, context.outputPath().toFile(), sourcePaths, classpathDirs, context.outputPath().toFile(), bootstrapClasspath); - - // TODO convention for mapping to original file paths, provide FileInfo out of Inputs instead of Paths, - // automatically relativized? - List sources = inputSources.getFilesAndHashes() - .stream() - .map(p -> SourceUtils.FileInfo.create(p.getAbsolutePath().toString(), p.getSourcePath().toString())) - .collect(Collectors.toList()); - - try { - if (!javac.compile(sources)) { - throw new RuntimeException("Failed to complete bytecode task, check log"); - } - } catch (Exception exception) { - exception.printStackTrace(); - throw exception; + // Copy all resources, even .java files, so that this output is the source of truth as if this + // were freshly unpacked from a jar + for (CachedPath entry : resources.getFilesAndHashes()) { + Files.createDirectories(context.outputPath().resolve(entry.getSourcePath()).getParent()); + Files.copy(entry.getAbsolutePath(), context.outputPath().resolve(entry.getSourcePath())); } }; } + + @Nullable + protected File getGeneratedClassesDir(TaskContext context) { + return context.outputPath().toFile(); + } } diff --git a/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/ClosureBundleTask.java b/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/ClosureBundleTask.java index 73838629..3ad10a1e 100644 --- a/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/ClosureBundleTask.java +++ b/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/ClosureBundleTask.java @@ -47,8 +47,7 @@ public Task resolve(Project project, Config config) { // from the actual input source instead of copying it along each step List js = Stream.of( input(project, OutputTypes.TRANSPILED_JS), - input(project, OutputTypes.GENERATED_SOURCES), - input(project, OutputTypes.INPUT_SOURCES) + input(project, OutputTypes.BYTECODE) ) .map(i -> i.filter(ClosureTask.PLAIN_JS_SOURCES)) .collect(Collectors.toList()); diff --git a/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/ClosureTask.java b/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/ClosureTask.java index 0d597d0d..4a41c3d8 100644 --- a/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/ClosureTask.java +++ b/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/ClosureTask.java @@ -53,16 +53,18 @@ public Task resolve(Project project, Config config) { List jsSources = Stream.concat( Stream.of( input(project, OutputTypes.TRANSPILED_JS).filter(JS_SOURCES), - input(project, OutputTypes.GENERATED_SOURCES).filter(PLAIN_JS_SOURCES), - input(project, OutputTypes.INPUT_SOURCES).filter(PLAIN_JS_SOURCES) + // BYTECODE will contains original and generated js sources + input(project, OutputTypes.BYTECODE).filter(PLAIN_JS_SOURCES) ), scope(project.getDependencies(), Dependency.Scope.RUNTIME) .stream() - .flatMap(p -> Stream.of( - input(p, OutputTypes.TRANSPILED_JS).filter(JS_SOURCES), - input(p, OutputTypes.GENERATED_SOURCES).filter(PLAIN_JS_SOURCES), - input(p, OutputTypes.INPUT_SOURCES).filter(PLAIN_JS_SOURCES) - )) + .flatMap(p -> { + return Stream.of( + input(p, OutputTypes.TRANSPILED_JS).filter(JS_SOURCES), + // generated sources will include original input sources + input(p, OutputTypes.BYTECODE).filter(PLAIN_JS_SOURCES) + ); + }) ).collect(Collectors.toList()); diff --git a/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/J2clTask.java b/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/J2clTask.java index 596bae4c..a5e94858 100644 --- a/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/J2clTask.java +++ b/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/J2clTask.java @@ -8,6 +8,7 @@ import java.io.File; import java.nio.file.Path; import java.nio.file.PathMatcher; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -38,11 +39,7 @@ public String getVersion() { public Task resolve(Project project, Config config) { // J2CL is only interested in .java and .native.js files in our own sources Input ownJavaSources = input(project, OutputTypes.STRIPPED_SOURCES).filter(JAVA_SOURCES, NATIVE_JS_SOURCES); - List ownNativeJsSources = Stream.of( - input(project, OutputTypes.INPUT_SOURCES).filter(NATIVE_JS_SOURCES), - input(project, OutputTypes.GENERATED_SOURCES).filter(NATIVE_JS_SOURCES) - ) - .collect(Collectors.toList()); + List ownNativeJsSources = Collections.singletonList(input(project, OutputTypes.BYTECODE).filter(NATIVE_JS_SOURCES)); // From our classpath, j2cl is only interested in our compile classpath's bytecode List classpathHeaders = scope(project.getDependencies(), com.vertispan.j2cl.build.task.Dependency.Scope.COMPILE) diff --git a/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/SkipAptTask.java b/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/SkipAptTask.java index 5b6cd88c..4bd9f12c 100644 --- a/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/SkipAptTask.java +++ b/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/SkipAptTask.java @@ -4,33 +4,42 @@ import com.vertispan.j2cl.build.task.Config; import com.vertispan.j2cl.build.task.OutputTypes; import com.vertispan.j2cl.build.task.Project; +import com.vertispan.j2cl.build.task.TaskContext; import com.vertispan.j2cl.build.task.TaskFactory; +import javax.annotation.Nullable; +import java.io.File; + /** * Disables annotation processors from within this build, and relies instead on some other tooling - * already generating sources (hopefully incrementally). By producing no sources and depending on - * nothing it will run quickly and be cached after the first time even if inputs change - and without - * it, the "extra" non-stripped bytecode task is unnecessary. + * already generating sources (hopefully incrementally). It is assumed that the source directories + * passed in the Project already contain that generated source - the WatchService will then notice + * them change. */ @AutoService(TaskFactory.class) -public class SkipAptTask extends TaskFactory { +public class SkipAptTask extends BytecodeTask { + + public static final String SKIP_TASK_NAME = "skip"; + @Override public String getOutputType() { - return OutputTypes.GENERATED_SOURCES; + return OutputTypes.BYTECODE; } @Override public String getTaskName() { - return "skip"; + return SKIP_TASK_NAME; } @Override public String getVersion() { - return "0"; + return super.getVersion() + "0"; } + @Nullable @Override - public Task resolve(Project project, Config config) { - return ignore -> {}; + protected File getGeneratedClassesDir(TaskContext context) { + // By returning null, we signal to javac not to attempt to run annotation processors. + return null; } } diff --git a/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/StripSourcesTask.java b/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/StripSourcesTask.java index 4ea4bae3..0ae10076 100644 --- a/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/StripSourcesTask.java +++ b/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/StripSourcesTask.java @@ -31,8 +31,7 @@ public String getVersion() { @Override public Task resolve(Project project, Config config) { - Input inputSources = input(project, OutputTypes.INPUT_SOURCES).filter(JAVA_SOURCES, NATIVE_JS_SOURCES); - Input generatedSources = input(project, OutputTypes.GENERATED_SOURCES).filter(JAVA_SOURCES, NATIVE_JS_SOURCES); + Input inputSources = input(project, OutputTypes.BYTECODE).filter(JAVA_SOURCES); return context -> { if (inputSources.getFilesAndHashes().isEmpty()) { @@ -40,10 +39,7 @@ public Task resolve(Project project, Config config) { } GwtIncompatiblePreprocessor preprocessor = new GwtIncompatiblePreprocessor(context.outputPath().toFile(), context); preprocessor.preprocess( - Stream.concat( - inputSources.getFilesAndHashes().stream(), - generatedSources.getFilesAndHashes().stream() - ) + inputSources.getFilesAndHashes().stream() .map(p -> SourceUtils.FileInfo.create(p.getAbsolutePath().toString(), p.getSourcePath().toString())) .collect(Collectors.toList()) ); diff --git a/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/TestCollectionTask.java b/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/TestCollectionTask.java index 79d5c30d..9a3ada98 100644 --- a/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/TestCollectionTask.java +++ b/j2cl-tasks/src/main/java/com/vertispan/j2cl/build/provided/TestCollectionTask.java @@ -11,13 +11,16 @@ */ @AutoService(TaskFactory.class) public class TestCollectionTask extends TaskFactory { + // While this is an internal task, it is still possible to provide an alternative implementation + public static final String TEST_COLLECTION_OUTPUT_TYPE = "test_summary"; + private static final String TEST_SUMMARY_FILENAME = "test_summary.json"; private static final PathMatcher TEST_SUMMARY_JSON = FileSystems.getDefault().getPathMatcher("glob:" + TEST_SUMMARY_FILENAME); private static final PathMatcher TEST_SUITE = withSuffix(".testsuite"); @Override public String getOutputType() { - return "test_summary"; + return TEST_COLLECTION_OUTPUT_TYPE; } @Override @@ -34,18 +37,13 @@ public String getVersion() { public Task resolve(Project project, Config config) { // gather possible inputs so we can get the test summary file // we assume here that the user will correctly depend on the junit apt, might revise this later - Input src = input(project, OutputTypes.INPUT_SOURCES).filter(TEST_SUMMARY_JSON, TEST_SUITE); - Input apt = input(project, OutputTypes.GENERATED_SOURCES).filter(TEST_SUMMARY_JSON, TEST_SUITE); + Input apt = input(project, OutputTypes.BYTECODE).filter(TEST_SUMMARY_JSON, TEST_SUITE); return new FinalOutputTask() { @Override public void execute(TaskContext context) throws Exception { - // TODO If both container a test summary, we should fail, rather than overwrite + // TODO If both contain a test summary, we should fail, rather than overwrite // Or even better, merge? - for (CachedPath entry : src.getFilesAndHashes()) { - Files.createDirectories(context.outputPath().resolve(entry.getSourcePath()).getParent()); - Files.copy(entry.getAbsolutePath(), context.outputPath().resolve(entry.getSourcePath())); - } for (CachedPath entry : apt.getFilesAndHashes()) { Files.createDirectories(context.outputPath().resolve(entry.getSourcePath()).getParent()); Files.copy(entry.getAbsolutePath(), context.outputPath().resolve(entry.getSourcePath())); diff --git a/j2cl-tasks/src/main/java/com/vertispan/j2cl/tools/Javac.java b/j2cl-tasks/src/main/java/com/vertispan/j2cl/tools/Javac.java index 921d14c3..45b06502 100644 --- a/j2cl-tasks/src/main/java/com/vertispan/j2cl/tools/Javac.java +++ b/j2cl-tasks/src/main/java/com/vertispan/j2cl/tools/Javac.java @@ -31,7 +31,7 @@ public class Javac { List javacOptions; JavaCompiler compiler; StandardJavaFileManager fileManager; - private DiagnosticCollector listener; + private DiagnosticCollector listener; public Javac(BuildLog log, File generatedClassesPath, List sourcePaths, List classpath, File classesDirFile, File bootstrap) throws IOException { this.log = log; @@ -70,6 +70,13 @@ public boolean compile(List modifiedJavaFiles) { } finally { listener.getDiagnostics().forEach(d -> { String messageToLog = d.getMessage(Locale.getDefault()); + JavaFileObject source = d.getSource(); + + if (source != null) { + String longFileName = source.toUri().getPath(); + String prefix = longFileName + ((d.getLineNumber() > 0) ? ":" + d.getLineNumber() : "") + " "; + messageToLog = prefix + messageToLog; + } switch (d.getKind()) { case ERROR: log.error(messageToLog);