From 2be4b860544477aa0dafcf24db923fb18b612c6a Mon Sep 17 00:00:00 2001 From: Ashley Scopes <73482956+ascopes@users.noreply.github.com> Date: Sat, 28 Dec 2024 15:53:51 +0000 Subject: [PATCH 1/4] Implement support for ECJ Rejig RAM directory naming, add ability to dump tree view of workspaces Port across ECJ support This is still provisional and an active work in progress, please track https://github.com/eclipse-jdt/eclipse.jdt.core/issues/958 for this work. Enable tests for eclipse Run against CI build of ECJ temporarily Address ShellCheck warnings Fix ECJ injection script Add ability to adjust log level in POM Apply 2025 license headers to new ECJ classes Use eclipse snapshot repositories --- .github/workflows/build.yml | 3 +- java-compiler-testing/pom.xml | 5 + .../avajehttp/AvajeHttpTest.java | 4 +- .../avajeinject/AvajeInjectTest.java | 4 +- .../avajejsonb/AvajeJsonbTest.java | 4 +- .../acceptancetests/dagger/DaggerTest.java | 4 +- .../dogfood/JctDogfoodTest.java | 2 + .../autofactory/AutoFactoryTest.java | 4 +- .../autoservice/AutoServiceTest.java | 4 +- .../autovalue/AutoValueTest.java | 4 +- .../immutables/ImmutablesTest.java | 3 + .../mapstruct/MapStructTest.java | 7 +- .../micronaut/MicronautIntegrationTest.java | 2 + .../testing/ServiceProcessorJpmsTest.java | 6 +- .../testing/ServiceProcessorTest.java | 4 +- .../SpringBootAutoconfigureProcessorTest.java | 9 +- .../SpringBootConfigurationProcessorTest.java | 3 + .../jct/assertions/TypeAwareListAssert.java | 1 + .../ascopes/jct/compilers/JctCompilers.java | 11 + .../compilers/impl/EcjJctCompilerImpl.java | 73 +++ .../compilers/impl/EcjJctFlagBuilderImpl.java | 184 ++++++ .../ascopes/jct/junit/EcjCompilerTest.java | 140 +++++ .../jct/junit/EcjCompilersProvider.java | 64 +++ .../src/main/java/module-info.java | 6 +- .../jct/compilers/JctCompilersTest.java | 19 + .../impl/EcjJctCompilerImplTest.java | 145 +++++ .../impl/EcjJctFlagBuilderImplTest.java | 541 ++++++++++++++++++ ...BasicLegacyCompilationIntegrationTest.java | 3 + ...BasicModuleCompilationIntegrationTest.java | 3 + ...MultiModuleCompilationIntegrationTest.java | 3 + ...mpilingSpecificClassesIntegrationTest.java | 2 + ...MultiTieredCompilationIntegrationTest.java | 3 + .../jct/junit/EcjCompilersProviderTest.java | 166 ++++++ pom.xml | 19 +- ...add-development-ecj-to-maven-repository.sh | 77 +++ scripts/ecj.sh | 54 ++ 36 files changed, 1566 insertions(+), 20 deletions(-) create mode 100644 java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/impl/EcjJctCompilerImpl.java create mode 100644 java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/impl/EcjJctFlagBuilderImpl.java create mode 100644 java-compiler-testing/src/main/java/io/github/ascopes/jct/junit/EcjCompilerTest.java create mode 100644 java-compiler-testing/src/main/java/io/github/ascopes/jct/junit/EcjCompilersProvider.java create mode 100644 java-compiler-testing/src/test/java/io/github/ascopes/jct/compilers/impl/EcjJctCompilerImplTest.java create mode 100644 java-compiler-testing/src/test/java/io/github/ascopes/jct/compilers/impl/EcjJctFlagBuilderImplTest.java create mode 100644 java-compiler-testing/src/test/java/io/github/ascopes/jct/junit/EcjCompilersProviderTest.java create mode 100755 scripts/add-development-ecj-to-maven-repository.sh create mode 100755 scripts/ecj.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 28202f61d..14421185c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -113,8 +113,7 @@ jobs: env: cache-name: maven-cache with: - path: - ~/.m2 + path: ~/.m2 key: build-${{ env.cache-name }} - name: Compile and run tests diff --git a/java-compiler-testing/pom.xml b/java-compiler-testing/pom.xml index 601200cea..a84e0ec50 100644 --- a/java-compiler-testing/pom.xml +++ b/java-compiler-testing/pom.xml @@ -53,6 +53,11 @@ assertj-core + + org.eclipse.jdt + ecj + + org.jspecify jspecify diff --git a/java-compiler-testing/src/it/avaje-http/src/test/java/io/github/ascopes/jct/acceptancetests/avajehttp/AvajeHttpTest.java b/java-compiler-testing/src/it/avaje-http/src/test/java/io/github/ascopes/jct/acceptancetests/avajehttp/AvajeHttpTest.java index 60e37fb13..6b49db01d 100644 --- a/java-compiler-testing/src/it/avaje-http/src/test/java/io/github/ascopes/jct/acceptancetests/avajehttp/AvajeHttpTest.java +++ b/java-compiler-testing/src/it/avaje-http/src/test/java/io/github/ascopes/jct/acceptancetests/avajehttp/AvajeHttpTest.java @@ -18,6 +18,7 @@ import static io.github.ascopes.jct.assertions.JctAssertions.assertThatCompilation; import io.github.ascopes.jct.compilers.JctCompiler; +import io.github.ascopes.jct.junit.EcjCompilerTest; import io.github.ascopes.jct.junit.JavacCompilerTest; import io.github.ascopes.jct.workspaces.Workspaces; import org.junit.jupiter.api.DisplayName; @@ -26,6 +27,7 @@ class AvajeHttpTest { @DisplayName("HTTP client code gets generated as expected") + @EcjCompilerTest(minVersion = 11) @JavacCompilerTest(minVersion = 11) void httpClientCodeGetsGeneratedAsExpected(JctCompiler compiler) { // Given @@ -40,7 +42,7 @@ void httpClientCodeGetsGeneratedAsExpected(JctCompiler compiler) { // Then assertThatCompilation(compilation) - .isSuccessfulWithoutWarnings() + .isSuccessful() .classOutputPackages() .allFilesExist( "org/example/httpclient/GeneratedHttpComponent.class", diff --git a/java-compiler-testing/src/it/avaje-inject/src/test/java/io/github/ascopes/jct/acceptancetests/avajeinject/AvajeInjectTest.java b/java-compiler-testing/src/it/avaje-inject/src/test/java/io/github/ascopes/jct/acceptancetests/avajeinject/AvajeInjectTest.java index 40052e234..035d331af 100644 --- a/java-compiler-testing/src/it/avaje-inject/src/test/java/io/github/ascopes/jct/acceptancetests/avajeinject/AvajeInjectTest.java +++ b/java-compiler-testing/src/it/avaje-inject/src/test/java/io/github/ascopes/jct/acceptancetests/avajeinject/AvajeInjectTest.java @@ -18,6 +18,7 @@ import static io.github.ascopes.jct.assertions.JctAssertions.assertThatCompilation; import io.github.ascopes.jct.compilers.JctCompiler; +import io.github.ascopes.jct.junit.EcjCompilerTest; import io.github.ascopes.jct.junit.JavacCompilerTest; import io.github.ascopes.jct.workspaces.PathStrategy; import io.github.ascopes.jct.workspaces.Workspaces; @@ -27,6 +28,7 @@ class AvajeInjectTest { @DisplayName("Dependency injection code gets generated as expected") + @EcjCompilerTest(minVersion = 11) @JavacCompilerTest(minVersion = 11) void dependencyInjectionCodeGetsGeneratedAsExpected(JctCompiler compiler) { // Given @@ -41,7 +43,7 @@ void dependencyInjectionCodeGetsGeneratedAsExpected(JctCompiler compiler) { // Then assertThatCompilation(compilation) - .isSuccessfulWithoutWarnings() + .isSuccessful() .classOutputPackages() .allFilesExist( "org/example/CoffeeMaker.class", diff --git a/java-compiler-testing/src/it/avaje-jsonb/src/test/java/io/github/ascopes/jct/acceptancetests/avajejsonb/AvajeJsonbTest.java b/java-compiler-testing/src/it/avaje-jsonb/src/test/java/io/github/ascopes/jct/acceptancetests/avajejsonb/AvajeJsonbTest.java index a22fabd68..47e3adc61 100644 --- a/java-compiler-testing/src/it/avaje-jsonb/src/test/java/io/github/ascopes/jct/acceptancetests/avajejsonb/AvajeJsonbTest.java +++ b/java-compiler-testing/src/it/avaje-jsonb/src/test/java/io/github/ascopes/jct/acceptancetests/avajejsonb/AvajeJsonbTest.java @@ -21,6 +21,7 @@ import io.avaje.jsonb.Jsonb; import io.avaje.jsonb.generator.Processor; import io.github.ascopes.jct.compilers.JctCompiler; +import io.github.ascopes.jct.junit.EcjCompilerTest; import io.github.ascopes.jct.junit.JavacCompilerTest; import io.github.ascopes.jct.workspaces.Workspaces; import java.time.Instant; @@ -30,6 +31,7 @@ @DisplayName("Avaje Jsonb acceptance tests") class AvajeJsonbTest { @DisplayName("JSON handling logic is generated as expected") + @EcjCompilerTest(minVersion = 11) @JavacCompilerTest(minVersion = 11) void jsonHandlingLogicIsGeneratedAsExpected(JctCompiler compiler) throws Throwable { // Given @@ -46,7 +48,7 @@ void jsonHandlingLogicIsGeneratedAsExpected(JctCompiler compiler) throws Throwab // Then assertThatCompilation(compilation) - .isSuccessfulWithoutWarnings(); + .isSuccessful(); var userClass = compilation .getClassOutputs() diff --git a/java-compiler-testing/src/it/dagger/src/test/java/io/github/ascopes/jct/acceptancetests/dagger/DaggerTest.java b/java-compiler-testing/src/it/dagger/src/test/java/io/github/ascopes/jct/acceptancetests/dagger/DaggerTest.java index f8c39231f..851aa58da 100644 --- a/java-compiler-testing/src/it/dagger/src/test/java/io/github/ascopes/jct/acceptancetests/dagger/DaggerTest.java +++ b/java-compiler-testing/src/it/dagger/src/test/java/io/github/ascopes/jct/acceptancetests/dagger/DaggerTest.java @@ -18,6 +18,7 @@ import static io.github.ascopes.jct.assertions.JctAssertions.assertThatCompilation; import io.github.ascopes.jct.compilers.JctCompiler; +import io.github.ascopes.jct.junit.EcjCompilerTest; import io.github.ascopes.jct.junit.JavacCompilerTest; import io.github.ascopes.jct.workspaces.Workspaces; import org.junit.jupiter.api.DisplayName; @@ -25,6 +26,7 @@ @DisplayName("Dagger acceptance tests") class DaggerTest { @DisplayName("Dagger DI runs as expected in the annotation processing phase") + @EcjCompilerTest @JavacCompilerTest void daggerDiRunsAsExpectedInTheAnnotationProcessingPhase(JctCompiler compiler) { try (var workspace = Workspaces.newWorkspace()) { @@ -38,7 +40,7 @@ void daggerDiRunsAsExpectedInTheAnnotationProcessingPhase(JctCompiler compiler) var compilation = compiler.compile(workspace); // Then - assertThatCompilation(compilation).isSuccessfulWithoutWarnings(); + assertThatCompilation(compilation).isSuccessful(); assertThatCompilation(compilation) .sourceOutputPackages() diff --git a/java-compiler-testing/src/it/dogfood/src/test/java/io/github/ascopes/jct/acceptancetests/dogfood/JctDogfoodTest.java b/java-compiler-testing/src/it/dogfood/src/test/java/io/github/ascopes/jct/acceptancetests/dogfood/JctDogfoodTest.java index 674648715..8577f1ab2 100644 --- a/java-compiler-testing/src/it/dogfood/src/test/java/io/github/ascopes/jct/acceptancetests/dogfood/JctDogfoodTest.java +++ b/java-compiler-testing/src/it/dogfood/src/test/java/io/github/ascopes/jct/acceptancetests/dogfood/JctDogfoodTest.java @@ -18,6 +18,7 @@ import static io.github.ascopes.jct.assertions.JctAssertions.assertThat; import io.github.ascopes.jct.compilers.JctCompiler; +import io.github.ascopes.jct.junit.EcjCompilerTest; import io.github.ascopes.jct.junit.JavacCompilerTest; import io.github.ascopes.jct.workspaces.Workspaces; import java.io.IOException; @@ -52,6 +53,7 @@ class JctDogfoodTest { .resolve("classes"); @DisplayName("JCT can compile itself as a legacy module source") + @EcjCompilerTest(minVersion = 17, configurers = JctCompilationConfigurer.class) @JavacCompilerTest(minVersion = 17, configurers = JctCompilationConfigurer.class) void jctCanCompileItselfAsLegacyModule(JctCompiler compiler) throws IOException { // Given diff --git a/java-compiler-testing/src/it/google-auto-factory/src/test/java/io/github/ascopes/jct/acceptancetests/autofactory/AutoFactoryTest.java b/java-compiler-testing/src/it/google-auto-factory/src/test/java/io/github/ascopes/jct/acceptancetests/autofactory/AutoFactoryTest.java index 658c2d8b6..97dcfaa0a 100644 --- a/java-compiler-testing/src/it/google-auto-factory/src/test/java/io/github/ascopes/jct/acceptancetests/autofactory/AutoFactoryTest.java +++ b/java-compiler-testing/src/it/google-auto-factory/src/test/java/io/github/ascopes/jct/acceptancetests/autofactory/AutoFactoryTest.java @@ -19,6 +19,7 @@ import static org.assertj.core.api.SoftAssertions.assertSoftly; import io.github.ascopes.jct.compilers.JctCompiler; +import io.github.ascopes.jct.junit.EcjCompilerTest; import io.github.ascopes.jct.junit.JavacCompilerTest; import io.github.ascopes.jct.workspaces.Workspaces; import java.time.Instant; @@ -28,6 +29,7 @@ class AutoFactoryTest { @DisplayName("The AutoFactory class is created as expected") + @EcjCompilerTest @JavacCompilerTest void autoFactoryClassIsCreatedAsExpected(JctCompiler compiler) throws Throwable { try (var workspace = Workspaces.newWorkspace()) { @@ -42,7 +44,7 @@ void autoFactoryClassIsCreatedAsExpected(JctCompiler compiler) throws Throwable // Then assertThatCompilation(compilation) - .isSuccessfulWithoutWarnings() + .isSuccessful() .classOutputPackages() .fileExists("org", "example", "UserFactory.class") .isNotEmptyFile(); diff --git a/java-compiler-testing/src/it/google-auto-service/src/test/java/io/github/ascopes/jct/acceptancetests/autoservice/AutoServiceTest.java b/java-compiler-testing/src/it/google-auto-service/src/test/java/io/github/ascopes/jct/acceptancetests/autoservice/AutoServiceTest.java index 81d18b7c2..939dc90b9 100644 --- a/java-compiler-testing/src/it/google-auto-service/src/test/java/io/github/ascopes/jct/acceptancetests/autoservice/AutoServiceTest.java +++ b/java-compiler-testing/src/it/google-auto-service/src/test/java/io/github/ascopes/jct/acceptancetests/autoservice/AutoServiceTest.java @@ -18,6 +18,7 @@ import static io.github.ascopes.jct.assertions.JctAssertions.assertThatCompilation; import io.github.ascopes.jct.compilers.JctCompiler; +import io.github.ascopes.jct.junit.EcjCompilerTest; import io.github.ascopes.jct.junit.JavacCompilerTest; import io.github.ascopes.jct.workspaces.Workspaces; import org.junit.jupiter.api.DisplayName; @@ -26,6 +27,7 @@ class AutoServiceTest { @DisplayName("The AutoService descriptor is created as expected") + @EcjCompilerTest @JavacCompilerTest void autoServiceDescriptorIsCreatedAsExpected(JctCompiler compiler) { try (var workspace = Workspaces.newWorkspace()) { @@ -40,7 +42,7 @@ void autoServiceDescriptorIsCreatedAsExpected(JctCompiler compiler) { // Then assertThatCompilation(compilation) - .isSuccessfulWithoutWarnings() + .isSuccessful() .classOutputPackages() .fileExists("META-INF", "services", "org.example.SomeInterface") .hasContent("org.example.SomeImpl"); diff --git a/java-compiler-testing/src/it/google-auto-value/src/test/java/io/github/ascopes/jct/acceptancetests/autovalue/AutoValueTest.java b/java-compiler-testing/src/it/google-auto-value/src/test/java/io/github/ascopes/jct/acceptancetests/autovalue/AutoValueTest.java index 092ca0ab8..bf5256704 100644 --- a/java-compiler-testing/src/it/google-auto-value/src/test/java/io/github/ascopes/jct/acceptancetests/autovalue/AutoValueTest.java +++ b/java-compiler-testing/src/it/google-auto-value/src/test/java/io/github/ascopes/jct/acceptancetests/autovalue/AutoValueTest.java @@ -19,6 +19,7 @@ import static org.assertj.core.api.SoftAssertions.assertSoftly; import io.github.ascopes.jct.compilers.JctCompiler; +import io.github.ascopes.jct.junit.EcjCompilerTest; import io.github.ascopes.jct.junit.JavacCompilerTest; import io.github.ascopes.jct.workspaces.Workspaces; import java.time.Instant; @@ -29,6 +30,7 @@ class AutoValueTest { @DisplayName("The AutoValue implementation class is created as expected") + @EcjCompilerTest @JavacCompilerTest void autoValueImplementationClassIsCreatedAsExpected(JctCompiler compiler) throws Throwable { try (var workspace = Workspaces.newWorkspace()) { @@ -43,7 +45,7 @@ void autoValueImplementationClassIsCreatedAsExpected(JctCompiler compiler) throw // Then assertThatCompilation(compilation) - .isSuccessfulWithoutWarnings() + .isSuccessful() .classOutputPackages() .allFilesExist( "org/example/AutoValue_User.class", diff --git a/java-compiler-testing/src/it/immutables/src/test/java/io/github/ascopes/jct/acceptancetests/immutables/ImmutablesTest.java b/java-compiler-testing/src/it/immutables/src/test/java/io/github/ascopes/jct/acceptancetests/immutables/ImmutablesTest.java index 8ffc95ca5..2a409cfcc 100644 --- a/java-compiler-testing/src/it/immutables/src/test/java/io/github/ascopes/jct/acceptancetests/immutables/ImmutablesTest.java +++ b/java-compiler-testing/src/it/immutables/src/test/java/io/github/ascopes/jct/acceptancetests/immutables/ImmutablesTest.java @@ -19,6 +19,7 @@ import static org.assertj.core.api.SoftAssertions.assertSoftly; import io.github.ascopes.jct.compilers.JctCompiler; +import io.github.ascopes.jct.junit.EcjCompilerTest; import io.github.ascopes.jct.junit.JavacCompilerTest; import io.github.ascopes.jct.workspaces.Workspaces; import java.util.stream.Stream; @@ -36,6 +37,7 @@ class ImmutablesTest { @DisplayName("Immutables @Value produces the expected class") + @EcjCompilerTest @JavacCompilerTest void immutablesValueProducesTheExpectedClass(JctCompiler compiler) throws Throwable { try (var workspace = Workspaces.newWorkspace()) { @@ -68,6 +70,7 @@ void immutablesValueProducesTheExpectedClass(JctCompiler compiler) throws Throwa } @DisplayName("Immutables @Value produces the expected class for modules") + @EcjCompilerTest(minVersion = 9) @JavacCompilerTest(minVersion = 9) void immutablesValueProducesTheExpectedClassForModules(JctCompiler compiler) throws Throwable { try (var workspace = Workspaces.newWorkspace()) { diff --git a/java-compiler-testing/src/it/mapstruct/src/test/java/io/github/ascopes/jct/acceptancetests/mapstruct/MapStructTest.java b/java-compiler-testing/src/it/mapstruct/src/test/java/io/github/ascopes/jct/acceptancetests/mapstruct/MapStructTest.java index 04cade220..7313f5953 100644 --- a/java-compiler-testing/src/it/mapstruct/src/test/java/io/github/ascopes/jct/acceptancetests/mapstruct/MapStructTest.java +++ b/java-compiler-testing/src/it/mapstruct/src/test/java/io/github/ascopes/jct/acceptancetests/mapstruct/MapStructTest.java @@ -19,6 +19,7 @@ import static org.assertj.core.api.SoftAssertions.assertSoftly; import io.github.ascopes.jct.compilers.JctCompiler; +import io.github.ascopes.jct.junit.EcjCompilerTest; import io.github.ascopes.jct.junit.JavacCompilerTest; import io.github.ascopes.jct.workspaces.Workspaces; import java.util.stream.Stream; @@ -28,6 +29,7 @@ class MapStructTest { @DisplayName("MapStruct generates expected mapping code") + @EcjCompilerTest @JavacCompilerTest void mapStructGeneratesExpectedMappingCode(JctCompiler compiler) throws Throwable { try (final var workspace = Workspaces.newWorkspace()) { @@ -40,7 +42,7 @@ void mapStructGeneratesExpectedMappingCode(JctCompiler compiler) throws Throwabl final var compilation = compiler.compile(workspace); // Then - assertThatCompilation(compilation).isSuccessfulWithoutWarnings(); + assertThatCompilation(compilation).isSuccessful(); final var classLoader = compilation.getFileManager() .getClassLoader(StandardLocation.CLASS_OUTPUT); @@ -68,6 +70,7 @@ void mapStructGeneratesExpectedMappingCode(JctCompiler compiler) throws Throwabl } @DisplayName("MapStruct generates expected mapping code for modules") + @EcjCompilerTest(minVersion = 9) @JavacCompilerTest(minVersion = 9) void mapStructGeneratesExpectedMappingCodeForModules(JctCompiler compiler) throws Throwable { try (final var workspace = Workspaces.newWorkspace()) { @@ -80,7 +83,7 @@ void mapStructGeneratesExpectedMappingCodeForModules(JctCompiler compiler) throw final var compilation = compiler.compile(workspace); // Then - assertThatCompilation(compilation).isSuccessfulWithoutWarnings(); + assertThatCompilation(compilation).isSuccessful(); final var classLoader = compilation.getFileManager() .getClassLoader(StandardLocation.CLASS_OUTPUT); diff --git a/java-compiler-testing/src/it/micronaut/src/test/java/io/github/ascopes/jct/acceptancetests/micronaut/MicronautIntegrationTest.java b/java-compiler-testing/src/it/micronaut/src/test/java/io/github/ascopes/jct/acceptancetests/micronaut/MicronautIntegrationTest.java index 9f55f68fe..94eeece6f 100644 --- a/java-compiler-testing/src/it/micronaut/src/test/java/io/github/ascopes/jct/acceptancetests/micronaut/MicronautIntegrationTest.java +++ b/java-compiler-testing/src/it/micronaut/src/test/java/io/github/ascopes/jct/acceptancetests/micronaut/MicronautIntegrationTest.java @@ -18,6 +18,7 @@ import static io.github.ascopes.jct.assertions.JctAssertions.assertThatCompilation; import io.github.ascopes.jct.compilers.JctCompiler; +import io.github.ascopes.jct.junit.EcjCompilerTest; import io.github.ascopes.jct.junit.JavacCompilerTest; import io.github.ascopes.jct.workspaces.Workspaces; import org.junit.jupiter.api.DisplayName; @@ -26,6 +27,7 @@ class MicronautIntegrationTest { @DisplayName("Micronaut generates the expected code") + @EcjCompilerTest(configurers = MicronautConfigurer.class) @JavacCompilerTest(configurers = MicronautConfigurer.class) void micronautGeneratesTheExpectedCode(JctCompiler compiler) { try (var workspace = Workspaces.newWorkspace()) { diff --git a/java-compiler-testing/src/it/serviceloader-jpms/src/test/java/io/github/ascopes/jct/acceptancetests/serviceloaderjpms/testing/ServiceProcessorJpmsTest.java b/java-compiler-testing/src/it/serviceloader-jpms/src/test/java/io/github/ascopes/jct/acceptancetests/serviceloaderjpms/testing/ServiceProcessorJpmsTest.java index 92687dae3..961d91e6f 100644 --- a/java-compiler-testing/src/it/serviceloader-jpms/src/test/java/io/github/ascopes/jct/acceptancetests/serviceloaderjpms/testing/ServiceProcessorJpmsTest.java +++ b/java-compiler-testing/src/it/serviceloader-jpms/src/test/java/io/github/ascopes/jct/acceptancetests/serviceloaderjpms/testing/ServiceProcessorJpmsTest.java @@ -19,6 +19,7 @@ import io.github.ascopes.jct.acceptancetests.serviceloaderjpms.ServiceProcessor; import io.github.ascopes.jct.compilers.JctCompiler; +import io.github.ascopes.jct.junit.EcjCompilerTest; import io.github.ascopes.jct.junit.JavacCompilerTest; import io.github.ascopes.jct.workspaces.Workspaces; import org.junit.jupiter.api.DisplayName; @@ -27,6 +28,7 @@ class ServiceProcessorJpmsTest { @DisplayName("Expected files get created when the processor is run") + @EcjCompilerTest(minVersion = 9) @JavacCompilerTest(minVersion = 9) void expectedFilesGetCreated(JctCompiler compiler) { try (var workspace = Workspaces.newWorkspace()) { @@ -41,7 +43,7 @@ void expectedFilesGetCreated(JctCompiler compiler) { .compile(workspace); assertThatCompilation(compilation) - .isSuccessfulWithoutWarnings() + .isSuccessful() .classOutputPackages() .fileExists("META-INF", "services", "org.example.InsultProvider") .hasContent("org.example.MeanInsultProviderImpl"); @@ -62,7 +64,7 @@ void expectedFilesGetCreatedInMultiModuleRoots(JctCompiler compiler) { .compile(workspace); assertThatCompilation(compilation) - .isSuccessfulWithoutWarnings() + .isSuccessful() .classOutputModules().moduleExists("org.example") .fileExists("META-INF", "services", "org.example.InsultProvider") .hasContent("org.example.MeanInsultProviderImpl"); diff --git a/java-compiler-testing/src/it/serviceloader/src/test/java/io/github/ascopes/jct/acceptancetests/serviceloader/testing/ServiceProcessorTest.java b/java-compiler-testing/src/it/serviceloader/src/test/java/io/github/ascopes/jct/acceptancetests/serviceloader/testing/ServiceProcessorTest.java index e982477bc..7c6f4245e 100644 --- a/java-compiler-testing/src/it/serviceloader/src/test/java/io/github/ascopes/jct/acceptancetests/serviceloader/testing/ServiceProcessorTest.java +++ b/java-compiler-testing/src/it/serviceloader/src/test/java/io/github/ascopes/jct/acceptancetests/serviceloader/testing/ServiceProcessorTest.java @@ -19,6 +19,7 @@ import io.github.ascopes.jct.acceptancetests.serviceloader.ServiceProcessor; import io.github.ascopes.jct.compilers.JctCompiler; +import io.github.ascopes.jct.junit.EcjCompilerTest; import io.github.ascopes.jct.junit.JavacCompilerTest; import io.github.ascopes.jct.workspaces.Workspaces; import org.junit.jupiter.api.DisplayName; @@ -27,6 +28,7 @@ class ServiceProcessorTest { @DisplayName("Expected files get created when the processor is run") + @EcjCompilerTest @JavacCompilerTest void expectedFilesGetCreated(JctCompiler compiler) { try (var workspace = Workspaces.newWorkspace()) { @@ -41,7 +43,7 @@ void expectedFilesGetCreated(JctCompiler compiler) { .compile(workspace); assertThatCompilation(compilation) - .isSuccessfulWithoutWarnings() + .isSuccessful() .classOutputPackages() .fileExists("META-INF", "services", "org.example.InsultProvider") .hasContent("org.example.MeanInsultProviderImpl"); diff --git a/java-compiler-testing/src/it/spring/src/test/java/io/github/ascopes/jct/acceptancetests/spring/SpringBootAutoconfigureProcessorTest.java b/java-compiler-testing/src/it/spring/src/test/java/io/github/ascopes/jct/acceptancetests/spring/SpringBootAutoconfigureProcessorTest.java index 8635c1791..4bbfc806e 100644 --- a/java-compiler-testing/src/it/spring/src/test/java/io/github/ascopes/jct/acceptancetests/spring/SpringBootAutoconfigureProcessorTest.java +++ b/java-compiler-testing/src/it/spring/src/test/java/io/github/ascopes/jct/acceptancetests/spring/SpringBootAutoconfigureProcessorTest.java @@ -18,6 +18,7 @@ import static io.github.ascopes.jct.assertions.JctAssertions.assertThatCompilation; import io.github.ascopes.jct.compilers.JctCompiler; +import io.github.ascopes.jct.junit.EcjCompilerTest; import io.github.ascopes.jct.junit.JavacCompilerTest; import io.github.ascopes.jct.workspaces.PathStrategy; import io.github.ascopes.jct.workspaces.Workspaces; @@ -28,6 +29,7 @@ class SpringBootAutoconfigureProcessorTest { @DisplayName("Spring will index the application context as expected") + @EcjCompilerTest(minVersion = 17) @JavacCompilerTest(minVersion = 17) void springWillIndexTheApplicationContextAsExpected(JctCompiler compiler) { try (var workspace = Workspaces.newWorkspace(PathStrategy.TEMP_DIRECTORIES)) { @@ -44,7 +46,6 @@ void springWillIndexTheApplicationContextAsExpected(JctCompiler compiler) { // Then assertThatCompilation(compilation) - .isSuccessfulWithoutWarnings() .classOutputPackages() .fileExists("META-INF", "spring-autoconfigure-metadata.properties") .isNotEmptyFile(); @@ -52,6 +53,7 @@ void springWillIndexTheApplicationContextAsExpected(JctCompiler compiler) { } @DisplayName("Spring will index the application context as expected when using modules") + @EcjCompilerTest(minVersion = 17) @JavacCompilerTest(minVersion = 17) void springWillIndexTheApplicationContextAsExpectedWhenUsingModules(JctCompiler compiler) { try (var workspace = Workspaces.newWorkspace(PathStrategy.TEMP_DIRECTORIES)) { @@ -67,7 +69,7 @@ void springWillIndexTheApplicationContextAsExpectedWhenUsingModules(JctCompiler // Then assertThatCompilation(compilation) - .isSuccessfulWithoutWarnings() + .isSuccessful() .classOutputPackages() .fileExists("META-INF", "spring-autoconfigure-metadata.properties") .isNotEmptyFile(); @@ -75,6 +77,7 @@ void springWillIndexTheApplicationContextAsExpectedWhenUsingModules(JctCompiler } @DisplayName("Spring will index the application context as expected when using multi-modules") + @EcjCompilerTest(minVersion = 17) @JavacCompilerTest(minVersion = 17) void springWillIndexTheApplicationContextAsExpectedWhenUsingMultiModules(JctCompiler compiler) { try (var workspace = Workspaces.newWorkspace(PathStrategy.TEMP_DIRECTORIES)) { @@ -90,7 +93,7 @@ void springWillIndexTheApplicationContextAsExpectedWhenUsingMultiModules(JctComp // Then assertThatCompilation(compilation) - .isSuccessfulWithoutWarnings() + .isSuccessful() .classOutputModules().moduleExists("org.example") .fileExists("META-INF", "spring-autoconfigure-metadata.properties") .isNotEmptyFile(); diff --git a/java-compiler-testing/src/it/spring/src/test/java/io/github/ascopes/jct/acceptancetests/spring/SpringBootConfigurationProcessorTest.java b/java-compiler-testing/src/it/spring/src/test/java/io/github/ascopes/jct/acceptancetests/spring/SpringBootConfigurationProcessorTest.java index 0ed1682c5..ff78cda9c 100644 --- a/java-compiler-testing/src/it/spring/src/test/java/io/github/ascopes/jct/acceptancetests/spring/SpringBootConfigurationProcessorTest.java +++ b/java-compiler-testing/src/it/spring/src/test/java/io/github/ascopes/jct/acceptancetests/spring/SpringBootConfigurationProcessorTest.java @@ -28,6 +28,7 @@ class SpringBootConfigurationProcessorTest { @DisplayName("Spring will index the application context as expected") + @EcjCompilerTest(minVersion = 17) @JavacCompilerTest(minVersion = 17) void springWillIndexTheApplicationContextAsExpected(JctCompiler compiler) { try (var workspace = Workspaces.newWorkspace(PathStrategy.TEMP_DIRECTORIES)) { @@ -52,6 +53,7 @@ void springWillIndexTheApplicationContextAsExpected(JctCompiler compiler) { } @DisplayName("Spring will index the application context as expected with modules") + @EcjCompilerTest(minVersion = 17) @JavacCompilerTest(minVersion = 17) void springWillIndexTheApplicationContextAsExpectedWithModules(JctCompiler compiler) { try (var workspace = Workspaces.newWorkspace(PathStrategy.TEMP_DIRECTORIES)) { @@ -75,6 +77,7 @@ void springWillIndexTheApplicationContextAsExpectedWithModules(JctCompiler compi } @DisplayName("Spring will index the application context as expected with multi-modules") + @EcjCompilerTest(minVersion = 17) @JavacCompilerTest(minVersion = 17) void springWillIndexTheApplicationContextAsExpectedWithMultiModules(JctCompiler compiler) { try (var workspace = Workspaces.newWorkspace(PathStrategy.TEMP_DIRECTORIES)) { diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/assertions/TypeAwareListAssert.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/assertions/TypeAwareListAssert.java index c4c018f1f..940cc2a97 100644 --- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/assertions/TypeAwareListAssert.java +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/assertions/TypeAwareListAssert.java @@ -57,6 +57,7 @@ protected A toAssert(@Nullable E value, String description) { Iterable iterable ) { var list = StreamSupport.stream(iterable.spliterator(), false).toList(); + return new TypeAwareListAssert<>(list, assertFactory); } } diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/JctCompilers.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/JctCompilers.java index fc58418e4..4c1cf57da 100644 --- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/JctCompilers.java +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/JctCompilers.java @@ -15,6 +15,7 @@ */ package io.github.ascopes.jct.compilers; +import io.github.ascopes.jct.compilers.impl.EcjJctCompilerImpl; import io.github.ascopes.jct.compilers.impl.JavacJctCompilerImpl; /** @@ -39,4 +40,14 @@ private JctCompilers() { public static JctCompiler newPlatformCompiler() { return new JavacJctCompilerImpl(); } + + /** + * Create a new instance of the ECJ compiler (Eclipse Compiler for Java). + * + * @return the compiler instance. + * @since 5.0.0 + */ + public static JctCompiler newEcjCompiler() { + return new EcjJctCompilerImpl(); + } } diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/impl/EcjJctCompilerImpl.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/impl/EcjJctCompilerImpl.java new file mode 100644 index 000000000..521164491 --- /dev/null +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/impl/EcjJctCompilerImpl.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2022 - 2025, the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.ascopes.jct.compilers.impl; + +import io.github.ascopes.jct.compilers.AbstractJctCompiler; +import io.github.ascopes.jct.compilers.JctFlagBuilderFactory; +import io.github.ascopes.jct.compilers.Jsr199CompilerFactory; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler; + + +/** + * Implementation of a JCT compiler that integrates with the Eclipse Java Compiler. + * + * @author Ashley Scopes + * @since 5.0.0 + */ +public final class EcjJctCompilerImpl extends AbstractJctCompiler { + + public EcjJctCompilerImpl() { + super("ECJ"); + } + + @Override + public JctFlagBuilderFactory getFlagBuilderFactory() { + return EcjJctFlagBuilderImpl::new; + } + + @Override + public Jsr199CompilerFactory getCompilerFactory() { + return EclipseCompiler::new; + } + + @Override + public String getDefaultRelease() { + return Integer.toString(getLatestSupportedVersionInt()); + } + + /** + * Get the minimum version of ECJ that is supported. + * + * @return the minimum supported version. + */ + public static int getEarliestSupportedVersionInt() { + return decodeMajorVersion(ClassFileConstants.JDK1_8); + } + + /** + * Get the maximum version of ECJ that is supported. + * + * @return the maximum supported version. + */ + public static int getLatestSupportedVersionInt() { + return decodeMajorVersion(ClassFileConstants.getLatestJDKLevel()); + } + + private static int decodeMajorVersion(long classFileConstant) { + return (int) ((classFileConstant >> 16L) - ClassFileConstants.MAJOR_VERSION_0); + } +} diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/impl/EcjJctFlagBuilderImpl.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/impl/EcjJctFlagBuilderImpl.java new file mode 100644 index 000000000..249d98d23 --- /dev/null +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/impl/EcjJctFlagBuilderImpl.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2022 - 2025, the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.ascopes.jct.compilers.impl; + +import io.github.ascopes.jct.compilers.CompilationMode; +import io.github.ascopes.jct.compilers.DebuggingInfo; +import io.github.ascopes.jct.compilers.JctFlagBuilder; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import org.jspecify.annotations.Nullable; + +/** + * Helper to build flags for the ECJ compiler implementation. + * + * @author Ashley Scopes + * @since 5.0.0 + */ +public final class EcjJctFlagBuilderImpl implements JctFlagBuilder { + + private static final String VERBOSE = "-verbose"; + private static final String PRINT_ANNOTATION_PROCESSOR_INFO = "-XprintProcessorInfo"; + private static final String PRINT_ANNOTATION_PROCESSOR_ROUNDS = "-XprintRounds"; + private static final String ENABLE_PREVIEW = "--enable-preview"; + private static final String NOWARN = "-nowarn"; + private static final String FAIL_ON_WARNING = "--failOnWarning"; + private static final String DEPRECATION = "-deprecation"; + private static final String RELEASE = "--release"; + private static final String SOURCE = "-source"; + private static final String TARGET = "-target"; + private static final String ANNOTATION_OPT = "-A"; + private static final String PROC_NONE = "-proc:none"; + private static final String PROC_ONLY = "-proc:only"; + private static final String DEBUG_LINES = "-g:lines"; + private static final String DEBUG_VARS = "-g:vars"; + private static final String DEBUG_SOURCE = "-g:source"; + private static final String DEBUG_NONE = "-g:none"; + private static final String PARAMETERS = "-parameters"; + + private final List craftedFlags; + + /** + * Initialize this flag builder. + */ + public EcjJctFlagBuilderImpl() { + craftedFlags = new ArrayList<>(); + } + + @Override + public EcjJctFlagBuilderImpl verbose(boolean enabled) { + return addFlagIfTrue(enabled, VERBOSE) + .addFlagIfTrue(enabled, PRINT_ANNOTATION_PROCESSOR_INFO) + .addFlagIfTrue(enabled, PRINT_ANNOTATION_PROCESSOR_ROUNDS); + } + + @Override + public EcjJctFlagBuilderImpl previewFeatures(boolean enabled) { + return addFlagIfTrue(enabled, ENABLE_PREVIEW); + } + + @Override + public EcjJctFlagBuilderImpl showWarnings(boolean enabled) { + return addFlagIfTrue(!enabled, NOWARN); + } + + @Override + public EcjJctFlagBuilderImpl failOnWarnings(boolean enabled) { + return addFlagIfTrue(enabled, FAIL_ON_WARNING); + } + + @Override + public JctFlagBuilder compilationMode(CompilationMode compilationMode) { + switch (compilationMode) { + case COMPILATION_ONLY: + craftedFlags.add(PROC_NONE); + break; + + case ANNOTATION_PROCESSING_ONLY: + craftedFlags.add(PROC_ONLY); + break; + + default: + // Do nothing. The default behaviour is to allow this. + break; + } + + return this; + } + + @Override + public EcjJctFlagBuilderImpl showDeprecationWarnings(boolean enabled) { + return addFlagIfTrue(enabled, DEPRECATION); + } + + @Override + public EcjJctFlagBuilderImpl release(@Nullable String version) { + return addVersionIfPresent(RELEASE, version); + } + + @Override + public EcjJctFlagBuilderImpl source(@Nullable String version) { + return addVersionIfPresent(SOURCE, version); + } + + @Override + public EcjJctFlagBuilderImpl target(@Nullable String version) { + return addVersionIfPresent(TARGET, version); + } + + @Override + public EcjJctFlagBuilderImpl debuggingInfo(Set set) { + if (set.isEmpty()) { + craftedFlags.add(DEBUG_NONE); + return this; + } + + if (set.contains(DebuggingInfo.LINES)) { + craftedFlags.add(DEBUG_LINES); + } + + if (set.contains(DebuggingInfo.SOURCE)) { + craftedFlags.add(DEBUG_SOURCE); + } + + if (set.contains(DebuggingInfo.VARS)) { + craftedFlags.add(DEBUG_VARS); + } + + return this; + } + + @Override + public EcjJctFlagBuilderImpl parameterInfoEnabled(boolean enabled) { + return addFlagIfTrue(enabled, PARAMETERS); + } + + @Override + public EcjJctFlagBuilderImpl annotationProcessorOptions(List options) { + options.forEach(option -> craftedFlags.add(ANNOTATION_OPT + option)); + return this; + } + + @Override + public EcjJctFlagBuilderImpl compilerOptions(List options) { + craftedFlags.addAll(options); + return this; + } + + @Override + public List build() { + // Immutable copy. + return List.copyOf(craftedFlags); + } + + private EcjJctFlagBuilderImpl addFlagIfTrue(boolean condition, String flag) { + if (condition) { + craftedFlags.add(flag); + } + + return this; + } + + private EcjJctFlagBuilderImpl addVersionIfPresent(String flagPrefix, @Nullable String version) { + if (version != null) { + craftedFlags.add(flagPrefix); + craftedFlags.add(version); + } + + return this; + } +} diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/junit/EcjCompilerTest.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/junit/EcjCompilerTest.java new file mode 100644 index 000000000..62532cc0e --- /dev/null +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/junit/EcjCompilerTest.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2022 - 2025, the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.ascopes.jct.junit; + +import io.github.ascopes.jct.compilers.JctCompilerConfigurer; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Tags; +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.condition.DisabledInNativeImage; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ArgumentsSource; + +/** + * Annotation that can be applied to a JUnit parameterized test to invoke that test case across + * multiple ECJ compilers, each configured to a specific version in a range of Java language + * versions. + * + *

This will also add the {@code "java-compiler-testing-test"} tag and {@code "ecj-test"} + * tags to your test method, meaning you can instruct your IDE or build system to optionally only + * run tests annotated with this method for development purposes. As an example, Maven Surefire + * could be instructed to only run these tests by passing {@code -Dgroup="ecj-test"} to Maven. + * + * @author Ashley Scopes + * @since TBC + */ +@ArgumentsSource(EcjCompilersProvider.class) +@DisabledInNativeImage +@Documented +@ParameterizedTest(name = "for compiler \"{0}\"") +@Retention(RetentionPolicy.RUNTIME) +@Tags({ + @Tag("java-compiler-testing-test"), + @Tag("ecj-test") +}) +@Target({ + ElementType.ANNOTATION_TYPE, + ElementType.METHOD, +}) +@TestTemplate +public @interface EcjCompilerTest { + + /** + * Minimum version to use (inclusive). + * + *

By default, it will use the lowest possible version supported by the compiler. This + * varies between versions of the JDK that are in use. + * + *

If the version is lower than the minimum supported version, then the minimum supported + * version of the compiler will be used instead. This enables writing tests that will work on a + * range of JDKs during builds without needing to duplicate the test to satisfy different JDK + * supported version ranges. + * + * @return the minimum version. + */ + int minVersion() default Integer.MIN_VALUE; + + /** + * Maximum version to use (inclusive). + * + *

By default, it will use the highest possible version supported by the compiler. This + * varies between versions of the JDK that are in use. + * + *

If the version is higher than the maximum supported version, then the maximum supported + * version of the compiler will be used instead. This enables writing tests that will work on a + * range of JDKs during builds without needing to duplicate the test to satisfy different JDK + * supported version ranges. + * + * @return the maximum version. + */ + int maxVersion() default Integer.MAX_VALUE; + + /** + * Get an array of compiler configurer classes to apply in-order before starting the test. + * + *

Each configurer must have a public no-args constructor, and their package must be + * open to this module if JPMS modules are in-use, for example: + * + *


+   * module mytests {
+   *   requires io.github.ascopes.jct;
+   *   requires org.junit.jupiter.api;
+   *
+   *   opens org.example.mytests to io.github.ascopes.jct;
+   * }
+   * 
+ * + *

An example of usage: + * + *


+   *   public class WerrorConfigurer implements JctCompilerConfigurer<RuntimeException> {
+   *     {@literal @Override}
+   *     public void configure(JctCompiler compiler) {
+   *       compiler.failOnWarnings(true);
+   *     }
+   *   }
+   *
+   *   // ...
+   *
+   *   class SomeTest {
+   *     {@literal @EcjCompilerTest(configurers = WerrorConfigurer.class)}
+   *     void someTest(JctCompiler compiler) {
+   *       // ...
+   *     }
+   *   }
+   * 
+ * + * @return an array of classes to run to configure the compiler. These run in the given order. + */ + Class>[] configurers() default {}; + + /** + * The version strategy to use. + * + *

This determines whether the version number being iterated across specifies the + * release, source, target, or source and target versions. + * + *

The default is to specify the release. + * + * @return the version strategy to use. + */ + VersionStrategy versionStrategy() default VersionStrategy.RELEASE; +} diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/junit/EcjCompilersProvider.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/junit/EcjCompilersProvider.java new file mode 100644 index 000000000..4df27e4dd --- /dev/null +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/junit/EcjCompilersProvider.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2022 - 2025, the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.ascopes.jct.junit; + +import io.github.ascopes.jct.compilers.JctCompiler; +import io.github.ascopes.jct.compilers.impl.EcjJctCompilerImpl; +import org.junit.jupiter.params.support.AnnotationConsumer; + +/** + * Argument provider for the {@link EcjCompilerTest} annotation. + * + * @author Ashley Scopes + * @since 5.0.0 + */ +public final class EcjCompilersProvider extends AbstractCompilersProvider + implements AnnotationConsumer { + + /** + * Initialise the provider. + * + *

This is only visible for testing purposes, users should have no need to + * initialise this class directly. + */ + EcjCompilersProvider() { + // Visible for testing only. + } + + @Override + protected JctCompiler initializeNewCompiler() { + return new EcjJctCompilerImpl(); + } + + @Override + protected int minSupportedVersion() { + return EcjJctCompilerImpl.getEarliestSupportedVersionInt(); + } + + @Override + protected int maxSupportedVersion() { + return EcjJctCompilerImpl.getLatestSupportedVersionInt(); + } + + @Override + public void accept(EcjCompilerTest annotation) { + var min = annotation.minVersion(); + var max = annotation.maxVersion(); + var configurers = annotation.configurers(); + var versioning = annotation.versionStrategy(); + configure(min, max, configurers, versioning); + } +} diff --git a/java-compiler-testing/src/main/java/module-info.java b/java-compiler-testing/src/main/java/module-info.java index 31e21fb67..f3b02304d 100644 --- a/java-compiler-testing/src/main/java/module-info.java +++ b/java-compiler-testing/src/main/java/module-info.java @@ -78,7 +78,8 @@ * * class JsonSchemaAnnotationProcessorTest { * - * {@literal @JavacCompilerTest(minVersion=11, maxVersion=19)} + * {@literal @EcjCompilerTest(minVersion=17)} + * {@literal @JavacCompilerTest(minVersion=17)} * void theJsonSchemaIsCreatedFromTheInputCode(JctCompiler compiler) { * * try (var workspace = Workspaces.newWorkspace()) { @@ -126,8 +127,9 @@ requires transitive com.github.marschall.memoryfilesystem; requires transitive java.compiler; requires transitive java.management; - requires me.xdrop.fuzzywuzzy; // automatic module + requires me.xdrop.fuzzywuzzy; // Automatic module requires transitive org.assertj.core; + requires transitive org.eclipse.jdt.core.compiler.batch; requires transitive org.jspecify; requires static transitive org.junit.jupiter.api; requires static transitive org.junit.jupiter.params; diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/compilers/JctCompilersTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/compilers/JctCompilersTest.java index d11a4e1d0..c8bf0febf 100644 --- a/java-compiler-testing/src/test/java/io/github/ascopes/jct/compilers/JctCompilersTest.java +++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/compilers/JctCompilersTest.java @@ -17,6 +17,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import io.github.ascopes.jct.compilers.impl.EcjJctCompilerImpl; import io.github.ascopes.jct.compilers.impl.JavacJctCompilerImpl; import io.github.ascopes.jct.fixtures.UtilityClassTestTemplate; import org.junit.jupiter.api.DisplayName; @@ -53,4 +54,22 @@ void newPlatformCompilerReturnsTheExpectedInstance() { .satisfies(constructed -> assertThat(compiler).isSameAs(constructed)); } } + + @DisplayName(".newEcjCompiler() creates an EcjJctCompilerImpl instance") + @Test + void newEcjCompilerReturnsTheExpectedInstance() { + try (var ecjJctCompilerImplMock = Mockito.mockConstruction(EcjJctCompilerImpl.class)) { + // When + var compiler = JctCompilers.newEcjCompiler(); + + // Then + assertThat(compiler) + .isInstanceOf(EcjJctCompilerImpl.class); + + assertThat(ecjJctCompilerImplMock.constructed()) + .singleElement() + // Nested assertion to swap expected/actual args. + .satisfies(constructed -> assertThat(compiler).isSameAs(constructed)); + } + } } diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/compilers/impl/EcjJctCompilerImplTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/compilers/impl/EcjJctCompilerImplTest.java new file mode 100644 index 000000000..002833292 --- /dev/null +++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/compilers/impl/EcjJctCompilerImplTest.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2022 - 2025, the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.ascopes.jct.compilers.impl; + +import static io.github.ascopes.jct.fixtures.Fixtures.someInt; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mockConstruction; +import static org.mockito.Mockito.mockStatic; + +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * {@link EcjJctCompilerImpl} tests. + * + * @author Ashley Scopes + */ +@DisplayName("EcjJctCompilerImpl tests") +class EcjJctCompilerImplTest { + + EcjJctCompilerImpl compiler; + + @BeforeEach + void setUp() { + compiler = new EcjJctCompilerImpl(); + } + + @DisplayName("Compilers have the expected JSR-199 compiler factory") + @Test + void compilersHaveTheExpectedCompilerFactory() { + // When + var actualCompiler = compiler.getCompilerFactory().createCompiler(); + + // Then + assertThat(actualCompiler).isInstanceOf(EclipseCompiler.class); + } + + @DisplayName("Compilers have the expected flag builder factory") + @Test + void compilersHaveTheExpectedFlagBuilderFactory() { + // Given + try (var flagBuilderMock = mockConstruction(EcjJctFlagBuilderImpl.class)) { + // When + var flagBuilder = compiler.getFlagBuilderFactory().createFlagBuilder(); + + // Then + assertThat(flagBuilderMock.constructed()).hasSize(1); + assertThat(flagBuilder).isSameAs(flagBuilderMock.constructed().get(0)); + } + } + + @DisplayName("Compilers have the expected default release string") + @Test + void compilersHaveTheExpectedDefaultRelease() { + // Given + try (var compilerClassMock = mockStatic(EcjJctCompilerImpl.class)) { + var latestSupportedInt = someInt(17, 21); + compilerClassMock + .when(EcjJctCompilerImpl::getLatestSupportedVersionInt) + .thenReturn(latestSupportedInt); + + // When + var defaultRelease = compiler.getDefaultRelease(); + + // Then + compilerClassMock + .verify(EcjJctCompilerImpl::getLatestSupportedVersionInt); + + assertThat(defaultRelease) + .isEqualTo("%d", latestSupportedInt); + } + } + + @DisplayName("Compilers have the expected default name") + @Test + void compilersHaveTheExpectedDefaultName() { + // Then + assertThat(compiler.getName()).isEqualTo("ECJ"); + } + + @DisplayName("Compilers have no default compiler flags set") + @Test + void compilersHaveNoDefaultCompilerFlagsSet() { + // Then + assertThat(compiler.getCompilerOptions()).isEmpty(); + } + + @DisplayName("Compilers have no default annotation processor flags set") + @Test + void compilersHaveNoDefaultAnnotationProcessorFlagsSet() { + // Then + assertThat(compiler.getAnnotationProcessorOptions()).isEmpty(); + } + + @DisplayName("Compilers have no default annotation processors set") + @Test + void compilersHaveNoDefaultAnnotationProcessorsSet() { + // Then + assertThat(compiler.getAnnotationProcessors()).isEmpty(); + } + + @DisplayName("Compilers have the expected latest release") + @Test + void latestSupportedVersionReturnsTheExpectedValue() { + // Given + var expected = (int) ((ClassFileConstants.getLatestJDKLevel() >> 16L) + - ClassFileConstants.MAJOR_VERSION_0); + + // When + var actual = EcjJctCompilerImpl.getLatestSupportedVersionInt(); + + // Then + assertThat(expected).isEqualTo(actual); + } + + @DisplayName("Compilers have the expected earliest release") + @Test + void earliestSupportedVersionReturnsTheExpectedValue() { + // Given + var expected = (int) ((ClassFileConstants.JDK1_8 >> 16L) + - ClassFileConstants.MAJOR_VERSION_0); + + // When + var actual = EcjJctCompilerImpl.getEarliestSupportedVersionInt(); + + // Then + assertThat(expected).isEqualTo(actual); + } +} diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/compilers/impl/EcjJctFlagBuilderImplTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/compilers/impl/EcjJctFlagBuilderImplTest.java new file mode 100644 index 000000000..ea4995d74 --- /dev/null +++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/compilers/impl/EcjJctFlagBuilderImplTest.java @@ -0,0 +1,541 @@ +/* + * Copyright (C) 2022 - 2025, the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.ascopes.jct.compilers.impl; + +import static io.github.ascopes.jct.fixtures.Fixtures.someBoolean; +import static io.github.ascopes.jct.fixtures.Fixtures.someRelease; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.github.ascopes.jct.compilers.CompilationMode; +import io.github.ascopes.jct.compilers.DebuggingInfo; +import io.github.ascopes.jct.fixtures.Fixtures; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; + +/** + * {@link EcjJctFlagBuilderImpl} tests. + * + * @author Ashley Scopes + */ +@DisplayName("EcjJctFlagBuilderImpl tests") +@TestMethodOrder(OrderAnnotation.class) +class EcjJctFlagBuilderImplTest { + + EcjJctFlagBuilderImpl flagBuilder; + + @BeforeEach + void setUp() { + flagBuilder = new EcjJctFlagBuilderImpl(); + } + + @DisplayName(".verbose(boolean) tests") + @Nested + class VerboseFlagTest { + + @DisplayName("Setting .verbose(true) adds the '-verbose' flag") + @Test + void addsFlagIfTrue() { + // When + flagBuilder.verbose(true); + + // Then + assertThat(flagBuilder.build()).contains("-verbose"); + } + + @DisplayName("Setting .verbose(false) does not add the '-verbose' flag") + @Test + void doesNotAddFlagIfFalse() { + // When + flagBuilder.verbose(false); + + // Then + assertThat(flagBuilder.build()).doesNotContain("-verbose"); + } + + @DisplayName(".verbose(...) returns the flag builder") + @Test + void returnsFlagBuilder() { + // Then + assertThat(flagBuilder.verbose(someBoolean())) + .isSameAs(flagBuilder); + } + } + + @DisplayName(".previewFeatures(boolean) tests") + @Nested + class PreviewFeaturesFlagTest { + + @DisplayName("Setting .previewFeatures(true) adds the '--enable-preview' flag") + @Test + void addsFlagIfTrue() { + // When + flagBuilder.previewFeatures(true); + + // Then + assertThat(flagBuilder.build()).contains("--enable-preview"); + } + + @DisplayName("Setting .previewFeatures(false) does not add the '--enable-preview' flag") + @Test + void doesNotAddFlagIfFalse() { + // When + flagBuilder.previewFeatures(false); + + // Then + assertThat(flagBuilder.build()).doesNotContain("--enable-preview"); + } + + @DisplayName(".previewFeatures(...) returns the flag builder") + @Test + void returnsFlagBuilder() { + // Then + assertThat(flagBuilder.previewFeatures(someBoolean())) + .isSameAs(flagBuilder); + } + } + + @DisplayName(".showWarnings(boolean) tests") + @Nested + class ShowWarningsFlagTest { + + @DisplayName("Setting .showWarnings(true) does not add the '-nowarn' flag") + @Test + void doesNotAddFlagIfTrue() { + // When + flagBuilder.showWarnings(true); + + // Then + assertThat(flagBuilder.build()).doesNotContain("-nowarn"); + } + + @DisplayName("Setting .showWarnings(false) adds the '-nowarn' flag") + @Test + void addsFlagIfFalse() { + // When + flagBuilder.showWarnings(false); + + // Then + assertThat(flagBuilder.build()).contains("-nowarn"); + } + + @DisplayName(".showWarnings(...) returns the flag builder") + @Test + void returnsFlagBuilder() { + // Then + assertThat(flagBuilder.showWarnings(someBoolean())) + .isSameAs(flagBuilder); + } + } + + @DisplayName(".failOnWarnings(boolean) tests") + @Nested + class FailOnWarningsFlagTest { + + @DisplayName("Setting .failOnWarnings(true) adds the '--failOnWarning' flag") + @Test + void addsFlagIfTrue() { + // When + flagBuilder.failOnWarnings(true); + + // Then + assertThat(flagBuilder.build()).contains("--failOnWarning"); + } + + @DisplayName("Setting .failOnWarnings(false) does not add the '-Werror' flag") + @Test + void doesNotAddFlagIfFalse() { + // When + flagBuilder.failOnWarnings(false); + + // Then + assertThat(flagBuilder.build()).doesNotContain("-Werror"); + } + + @DisplayName(".failOnWarnings(...) returns the flag builder") + @Test + void returnsFlagBuilder() { + // Then + assertThat(flagBuilder.failOnWarnings(someBoolean())) + .isSameAs(flagBuilder); + } + } + + @DisplayName(".compilationMode(CompilationMode) tests") + @Nested + class CompilationModeFlagTest { + + @DisplayName(".compilationMode(COMPILATION_ONLY) adds -proc:none") + @Test + void compilationOnlyAddsProcNone() { + // When + flagBuilder.compilationMode(CompilationMode.COMPILATION_ONLY); + + // Then + assertThat(flagBuilder.build()).containsExactly("-proc:none"); + } + + @DisplayName(".compilationMode(ANNOTATION_PROCESSING_ONLY) adds -proc:only") + @Test + void annotationProcessingOnlyAddsProcOnly() { + // When + flagBuilder.compilationMode(CompilationMode.ANNOTATION_PROCESSING_ONLY); + + // Then + assertThat(flagBuilder.build()).containsExactly("-proc:only"); + } + + @DisplayName(".compilationMode(COMPILATION_AND_ANNOTATION_PROCESSING) adds nothing") + @Test + void compilationAndAnnotationProcessingAddsNothing() { + // When + flagBuilder.compilationMode(CompilationMode.COMPILATION_AND_ANNOTATION_PROCESSING); + + // Then + assertThat(flagBuilder.build()).isEmpty(); + } + + @DisplayName(".compilationMode(...) returns the flag builder") + @EnumSource(CompilationMode.class) + @ParameterizedTest(name = "for compilationMode = {0}") + void returnsFlagBuilder(CompilationMode mode) { + // Then + assertThat(flagBuilder.compilationMode(mode)) + .isSameAs(flagBuilder); + } + } + + @DisplayName(".showDeprecationWarnings(boolean) tests") + @Nested + class ShowDeprecationWarningsFlagTest { + + @DisplayName("Setting .showDeprecationWarnings(true) adds the '-deprecation' flag") + @Test + void addsFlagIfTrue() { + // When + flagBuilder.showDeprecationWarnings(true); + + // Then + assertThat(flagBuilder.build()).contains("-deprecation"); + } + + @DisplayName("Setting .showDeprecationWarnings(false) does not add the '-deprecation' flag") + @Test + void doesNotAddFlagIfFalse() { + // When + flagBuilder.showDeprecationWarnings(false); + + // Then + assertThat(flagBuilder.build()).doesNotContain("-deprecation"); + } + + @DisplayName(".showDeprecationWarnings(...) returns the flag builder") + @Test + void returnsFlagBuilder() { + // Then + assertThat(flagBuilder.showDeprecationWarnings(someBoolean())) + .isSameAs(flagBuilder); + } + } + + @DisplayName(".release(String) tests") + @Nested + class ReleaseFlagTest { + + @DisplayName("Setting .release(String) adds the '--release ' flag") + @ValueSource(strings = {"8", "11", "17"}) + @ParameterizedTest(name = "Setting .release(String) adds the \"--release {0}\" flag") + void addsFlagIfPresent(String version) { + // When + flagBuilder.release(version); + + // Then + assertThat(flagBuilder.build()).containsSequence("--release", version); + } + + @DisplayName("Setting .release(null) does not add the '--release' flag") + @Test + void doesNotAddFlagIfNotPresent() { + // When + flagBuilder.release(null); + + // Then + assertThat(flagBuilder.build()).doesNotContain("--release"); + } + + @DisplayName(".release(...) returns the flag builder") + @Test + void returnsFlagBuilder() { + // Then + assertThat(flagBuilder.release(someRelease())) + .isSameAs(flagBuilder); + } + } + + @DisplayName(".source(String) tests") + @Nested + class SourceFlagTest { + + @DisplayName("Setting .source(String) adds the '-source ' flag") + @ValueSource(strings = {"8", "11", "17"}) + @ParameterizedTest(name = "Setting .source(String) adds the \"-source {0}\" flag") + void addsFlagIfPresent(String version) { + // When + flagBuilder.source(version); + + // Then + assertThat(flagBuilder.build()).containsSequence("-source", version); + } + + @DisplayName("Setting .source(null) does not add the '-source' flag") + @Test + void doesNotAddFlagIfNotPresent() { + // When + flagBuilder.source(null); + + // Then + assertThat(flagBuilder.build()).doesNotContain("-source"); + } + + + @DisplayName(".source(...) returns the flag builder") + @Test + void returnsFlagBuilder() { + // Then + assertThat(flagBuilder.source(someRelease())) + .isSameAs(flagBuilder); + } + } + + @DisplayName(".target(String) tests") + @Nested + class TargetFlagTest { + + @DisplayName("Setting .target(String) adds the '-target ' flag") + @ValueSource(strings = {"8", "11", "17"}) + @ParameterizedTest(name = "Setting .target(String) adds the \"-target {0}\" flag") + void addsFlagIfPresent(String version) { + // When + flagBuilder.target(version); + + // Then + assertThat(flagBuilder.build()).containsSequence("-target", version); + } + + @DisplayName("Setting .target(null) does not add the '-target' flag") + @Test + void doesNotAddFlagIfNotPresent() { + // When + flagBuilder.target(null); + + // Then + assertThat(flagBuilder.build()).doesNotContain("-target"); + } + + @DisplayName(".target(...) returns the flag builder") + @Test + void returnsFlagBuilder() { + // Then + assertThat(flagBuilder.target(someRelease())) + .isSameAs(flagBuilder); + } + } + + @DisplayName(".debuggingInfo(Set) tests") + @Nested + class DebuggingInfoTest { + + @DisplayName("Setting .debuggingInfo with an empty set adds the '-g:none' flag") + @Test + void emptySetAddsGnoneFlag() { + // When + flagBuilder.debuggingInfo(DebuggingInfo.none()); + + // Then + assertThat(flagBuilder.build()).containsOnlyOnce("-g:none"); + } + + @DisplayName("Setting .debuggingInfo with some values set adds the '-g:xxx' flags") + @CsvSource({ + " LINES, -g:lines", + "SOURCE, -g:source", + " VARS, -g:vars", + }) + @ParameterizedTest(name = "expect {0} to set flag {1}") + void correctFlagsAreSet(DebuggingInfo flag, String flagString) { + // When + flagBuilder.debuggingInfo(DebuggingInfo.just(flag)); + + // Then + assertThat(flagBuilder.build()).containsExactly(flagString); + } + + @DisplayName("Setting .debuggingInfo with all values set adds the '-g:xxx' flags") + @Test + void allAddsValues() { + // When + flagBuilder.debuggingInfo(DebuggingInfo.all()); + + // Then + assertThat(flagBuilder.build()) + .doesNotContain("-g", "-g:none") + .containsOnlyOnce("-g:lines", "-g:source", "-g:vars"); + } + } + + @DisplayName(".parameterInfoEnabled(boolean) tests") + @Nested + class ParameterInfoEnabledTest { + + @DisplayName("Setting .parameterInfoEnabled(true) adds the '-parameters' flag") + @Test + void trueAddsFlag() { + // When + flagBuilder.parameterInfoEnabled(true); + + // Then + assertThat(flagBuilder.build()).containsOnlyOnce("-parameters"); + } + + @DisplayName("Setting .parameterInfoEnabled(false) does not add the '-parameters' flag") + @Test + void falseDoesNotAddFlag() { + // When + flagBuilder.parameterInfoEnabled(false); + + // Then + assertThat(flagBuilder.build()).doesNotContain("-parameters"); + } + } + + @DisplayName(".addAnnotationProcessorOptions(List) tests") + @Nested + class AnnotationProcessorOptionsTest { + + @DisplayName("Setting .annotationProcessorOptions(List) adds the options") + @Test + void addsAnnotationProcessorOptions() { + // Given + var options = Stream + .generate(Fixtures::someText) + .limit(5) + .collect(Collectors.toList()); + + // When + flagBuilder.annotationProcessorOptions(options); + + // Then + assertThat(flagBuilder.build()) + .containsSequence(options.stream() + .map("-A"::concat) + .collect(Collectors.toList())); + } + + @DisplayName(".annotationProcessorOptions(...) returns the flag builder") + @Test + void returnsFlagBuilder() { + // Given + var options = Stream + .generate(Fixtures::someText) + .limit(5) + .collect(Collectors.toList()); + + // Then + assertThat(flagBuilder.annotationProcessorOptions(options)) + .isSameAs(flagBuilder); + } + } + + @DisplayName(".compilerOptions(List) tests") + @Nested + class CompilerOptionsTest { + + @DisplayName("Setting .compilerOptions(List) adds the options") + @Test + void addsCompilerOptions() { + // Given + var options = Stream + .generate(Fixtures::someText) + .limit(5) + .collect(Collectors.toList()); + + // When + flagBuilder.compilerOptions(options); + + // Then + assertThat(flagBuilder.build()) + .containsSequence(options); + } + + @DisplayName(".compilerOptions(...) returns the flag builder") + @Test + void returnsFlagBuilder() { + // Given + var options = Stream + .generate(Fixtures::someText) + .limit(5) + .collect(Collectors.toList()); + + // Then + assertThat(flagBuilder.compilerOptions(options)) + .isSameAs(flagBuilder); + } + } + + @Order(Integer.MAX_VALUE - 1) + @DisplayName("The flag builder adds multiple flags correctly") + @Test + void addsMultipleFlagsCorrectly() { + // When + var flags = flagBuilder + .compilerOptions(List.of("--foo", "--bar")) + .release("15") + .annotationProcessorOptions(List.of("--baz", "--bork")) + .build(); + + // Then + assertThat(flags) + .containsExactly("--foo", "--bar", "--release", "15", "-A--baz", "-A--bork"); + } + + @Order(Integer.MAX_VALUE) + @DisplayName("The flag builder produces an immutable list as the result") + @Test + void resultIsImmutable() { + // When + var flags = flagBuilder + .compilerOptions(List.of("--foo", "--bar")) + .release("15") + .annotationProcessorOptions(List.of("--baz", "--bork")) + .build(); + + // Then + assertThatThrownBy(() -> flags.add("something")) + .isInstanceOf(UnsupportedOperationException.class); + } +} diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/BasicLegacyCompilationIntegrationTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/BasicLegacyCompilationIntegrationTest.java index a48f7e3be..394b84470 100644 --- a/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/BasicLegacyCompilationIntegrationTest.java +++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/BasicLegacyCompilationIntegrationTest.java @@ -19,6 +19,7 @@ import io.github.ascopes.jct.compilers.JctCompiler; import io.github.ascopes.jct.integration.AbstractIntegrationTest; +import io.github.ascopes.jct.junit.EcjCompilerTest; import io.github.ascopes.jct.junit.JavacCompilerTest; import io.github.ascopes.jct.workspaces.PathStrategy; import io.github.ascopes.jct.workspaces.Workspaces; @@ -34,6 +35,7 @@ class BasicLegacyCompilationIntegrationTest extends AbstractIntegrationTest { @DisplayName("I can compile a 'Hello, World!' program using a RAM directory") + @EcjCompilerTest @JavacCompilerTest void helloWorldJavacRamDirectory(JctCompiler compiler) { try (var workspace = Workspaces.newWorkspace(PathStrategy.RAM_DIRECTORIES)) { @@ -54,6 +56,7 @@ void helloWorldJavacRamDirectory(JctCompiler compiler) { } @DisplayName("I can compile a 'Hello, World!' program using a temp directory") + @EcjCompilerTest @JavacCompilerTest void helloWorldJavacTempDirectory(JctCompiler compiler) { try (var workspace = Workspaces.newWorkspace(PathStrategy.TEMP_DIRECTORIES)) { diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/BasicModuleCompilationIntegrationTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/BasicModuleCompilationIntegrationTest.java index 5c4a02038..b10b4423c 100644 --- a/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/BasicModuleCompilationIntegrationTest.java +++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/BasicModuleCompilationIntegrationTest.java @@ -19,6 +19,7 @@ import io.github.ascopes.jct.compilers.JctCompiler; import io.github.ascopes.jct.integration.AbstractIntegrationTest; +import io.github.ascopes.jct.junit.EcjCompilerTest; import io.github.ascopes.jct.junit.JavacCompilerTest; import io.github.ascopes.jct.workspaces.PathStrategy; import io.github.ascopes.jct.workspaces.Workspaces; @@ -33,6 +34,7 @@ class BasicModuleCompilationIntegrationTest extends AbstractIntegrationTest { @DisplayName("I can compile a 'Hello, World!' module program using a RAM disk") + @EcjCompilerTest(minVersion = 9) @JavacCompilerTest(minVersion = 9) void helloWorldRamDisk(JctCompiler compiler) { try (var workspace = Workspaces.newWorkspace(PathStrategy.RAM_DIRECTORIES)) { @@ -61,6 +63,7 @@ void helloWorldRamDisk(JctCompiler compiler) { } @DisplayName("I can compile a 'Hello, World!' module program using a temporary directory") + @EcjCompilerTest(minVersion = 9) @JavacCompilerTest(minVersion = 9) void helloWorldUsingTempDirectory(JctCompiler compiler) { try (var workspace = Workspaces.newWorkspace(PathStrategy.TEMP_DIRECTORIES)) { diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/BasicMultiModuleCompilationIntegrationTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/BasicMultiModuleCompilationIntegrationTest.java index dd83dce01..6a9112cba 100644 --- a/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/BasicMultiModuleCompilationIntegrationTest.java +++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/BasicMultiModuleCompilationIntegrationTest.java @@ -20,6 +20,7 @@ import io.github.ascopes.jct.compilers.JctCompiler; import io.github.ascopes.jct.integration.AbstractIntegrationTest; +import io.github.ascopes.jct.junit.EcjCompilerTest; import io.github.ascopes.jct.junit.JavacCompilerTest; import io.github.ascopes.jct.workspaces.PathStrategy; import io.github.ascopes.jct.workspaces.Workspaces; @@ -34,6 +35,7 @@ class BasicMultiModuleCompilationIntegrationTest extends AbstractIntegrationTest { @DisplayName("I can compile a single module using multi-module layout using a RAM disk") + @EcjCompilerTest(minVersion = 9) @JavacCompilerTest(minVersion = 9) void singleModuleInMultiModuleLayoutRamDisk(JctCompiler compiler) { try (var workspace = Workspaces.newWorkspace(PathStrategy.RAM_DIRECTORIES)) { @@ -62,6 +64,7 @@ void singleModuleInMultiModuleLayoutRamDisk(JctCompiler compiler) { } @DisplayName("I can compile a single module using multi-module layout using a temp directory") + @EcjCompilerTest(minVersion = 9) @JavacCompilerTest(minVersion = 9) void singleModuleInMultiModuleLayoutTempDirectory(JctCompiler compiler) { try (var workspace = Workspaces.newWorkspace(PathStrategy.TEMP_DIRECTORIES)) { diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/CompilingSpecificClassesIntegrationTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/CompilingSpecificClassesIntegrationTest.java index 92cf25485..f8796ed63 100644 --- a/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/CompilingSpecificClassesIntegrationTest.java +++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/CompilingSpecificClassesIntegrationTest.java @@ -19,6 +19,7 @@ import io.github.ascopes.jct.compilers.JctCompiler; import io.github.ascopes.jct.integration.AbstractIntegrationTest; +import io.github.ascopes.jct.junit.EcjCompilerTest; import io.github.ascopes.jct.junit.JavacCompilerTest; import io.github.ascopes.jct.workspaces.Workspaces; import org.junit.jupiter.api.DisplayName; @@ -32,6 +33,7 @@ class CompilingSpecificClassesIntegrationTest extends AbstractIntegrationTest { @DisplayName("Only the classes that I specify get compiled") + @EcjCompilerTest @JavacCompilerTest void onlyTheClassesSpecifiedGetCompiled(JctCompiler compiler) { try (var workspace = Workspaces.newWorkspace()) { diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/MultiTieredCompilationIntegrationTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/MultiTieredCompilationIntegrationTest.java index c83b25a33..4ee4d3634 100644 --- a/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/MultiTieredCompilationIntegrationTest.java +++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/MultiTieredCompilationIntegrationTest.java @@ -19,6 +19,7 @@ import io.github.ascopes.jct.compilers.JctCompiler; import io.github.ascopes.jct.integration.AbstractIntegrationTest; +import io.github.ascopes.jct.junit.EcjCompilerTest; import io.github.ascopes.jct.junit.JavacCompilerTest; import io.github.ascopes.jct.workspaces.Workspaces; import org.junit.jupiter.api.DisplayName; @@ -35,6 +36,7 @@ class MultiTieredCompilationIntegrationTest extends AbstractIntegrationTest { @DisplayName( "I can compile sources to classes and provide them in the classpath to a second compilation" ) + @EcjCompilerTest @JavacCompilerTest void compileSourcesToClassesAndProvideThemInClassPathToSecondCompilation( JctCompiler compiler @@ -81,6 +83,7 @@ void compileSourcesToClassesAndProvideThemInClassPathToSecondCompilation( "I can compile sources to classes and provide them in the classpath to a second " + "compilation within a JAR" ) + @EcjCompilerTest @JavacCompilerTest void compileSourcesToClassesAndProvideThemInClassPathToSecondCompilationWithinJar( JctCompiler compiler diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/junit/EcjCompilersProviderTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/junit/EcjCompilersProviderTest.java new file mode 100644 index 000000000..ca3b5a2ec --- /dev/null +++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/junit/EcjCompilersProviderTest.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2022 - 2025, the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.ascopes.jct.junit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.junit.jupiter.params.support.AnnotationConsumerInitializer.initialize; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +import io.github.ascopes.jct.compilers.JctCompilerConfigurer; +import io.github.ascopes.jct.compilers.impl.EcjJctCompilerImpl; +import java.lang.reflect.AnnotatedElement; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * {@link EcjCompilersProvider} tests. + */ +@DisplayName("EcjCompilersProvider tests") +class EcjCompilersProviderTest { + + @DisplayName("Provider uses the user-provided compiler version bounds when valid") + @Test + void providerUsesTheUserProvidedVersionRangesWhenValid() { + // Given + try (var ecjMock = mockStatic(EcjJctCompilerImpl.class)) { + ecjMock.when(EcjJctCompilerImpl::getEarliestSupportedVersionInt).thenReturn(8); + ecjMock.when(EcjJctCompilerImpl::getLatestSupportedVersionInt).thenReturn(17); + var annotation = someAnnotation(10, 15); + var test = someAnnotatedElement(annotation); + var context = mock(ExtensionContext.class); + + // When + var consumer = initialize(test, new EcjCompilersProvider()); + var compilers = consumer.provideArguments(context) + .map(args -> (EcjJctCompilerImpl) args.get()[0]) + .toList(); + + // Then + assertThat(compilers) + .as("compilers that were initialised (%s)", compilers) + .hasSize(6); + + assertSoftly(softly -> { + for (var i = 0; i < compilers.size(); ++i) { + var compiler = compilers.get(i); + softly.assertThat(compiler.getName()) + .as("compilers[%d].getName()", i) + .isEqualTo("ECJ (release = Java %d)", 10 + i); + softly.assertThat(compiler.getRelease()) + .as("compilers[%d].getRelease()", i) + .isEqualTo("%d", 10 + i); + } + }); + } + } + + @DisplayName("Provider uses the minimum compiler version that is allowed if exceeded") + @Test + void providerUsesTheMinCompilerVersionAllowedIfExceeded() { + // Given + try (var ecjMock = mockStatic(EcjJctCompilerImpl.class)) { + ecjMock.when(EcjJctCompilerImpl::getEarliestSupportedVersionInt).thenReturn(8); + ecjMock.when(EcjJctCompilerImpl::getLatestSupportedVersionInt).thenReturn(17); + var annotation = someAnnotation(1, 15); + var test = someAnnotatedElement(annotation); + var context = mock(ExtensionContext.class); + + // When + var consumer = initialize(test, new EcjCompilersProvider()); + var compilers = consumer.provideArguments(context) + .map(args -> (EcjJctCompilerImpl) args.get()[0]) + .toList(); + + // Then + assertThat(compilers) + .as("compilers that were initialised (%s)", compilers) + .hasSize(8); + + assertSoftly(softly -> { + for (var i = 0; i < compilers.size(); ++i) { + var compiler = compilers.get(i); + softly.assertThat(compiler.getName()) + .as("compilers[%d].getName()", i) + .isEqualTo("ECJ (release = Java %d)", 8 + i); + softly.assertThat(compiler.getRelease()) + .as("compilers[%d].getRelease()", i) + .isEqualTo("%d", 8 + i); + } + }); + } + } + + @DisplayName("Provider uses the maximum compiler version that is allowed if exceeded") + @Test + void providerUsesTheMaxCompilerVersionAllowedIfExceeded() { + // Given + try (var ecjMock = mockStatic(EcjJctCompilerImpl.class)) { + ecjMock.when(EcjJctCompilerImpl::getEarliestSupportedVersionInt).thenReturn(8); + ecjMock.when(EcjJctCompilerImpl::getLatestSupportedVersionInt).thenReturn(17); + var annotation = someAnnotation(10, 17); + var test = someAnnotatedElement(annotation); + var context = mock(ExtensionContext.class); + + // When + var consumer = initialize(test, new EcjCompilersProvider()); + var compilers = consumer.provideArguments(context) + .map(args -> (EcjJctCompilerImpl) args.get()[0]) + .toList(); + + // Then + assertThat(compilers) + .as("compilers that were initialised (%s)", compilers) + .hasSize(8); + + assertSoftly(softly -> { + for (var i = 0; i < compilers.size(); ++i) { + var compiler = compilers.get(i); + softly.assertThat(compiler.getName()) + .as("compilers[%d].getName()", i) + .isEqualTo("ECJ (release = Java %d)", 10 + i); + softly.assertThat(compiler.getRelease()) + .as("compilers[%d].getRelease()", i) + .isEqualTo("%d", 10 + i); + } + }); + } + } + + @SafeVarargs + final EcjCompilerTest someAnnotation( + int min, + int max, + Class>... configurers + ) { + var annotation = mock(EcjCompilerTest.class); + when(annotation.minVersion()).thenReturn(min); + when(annotation.maxVersion()).thenReturn(max); + when(annotation.configurers()).thenReturn(configurers); + when(annotation.versionStrategy()).thenReturn(VersionStrategy.RELEASE); + when(annotation.annotationType()).thenAnswer(ctx -> EcjCompilerTest.class); + return annotation; + } + + AnnotatedElement someAnnotatedElement(EcjCompilerTest annotation) { + var element = mock(AnnotatedElement.class); + when(element.getDeclaredAnnotation(EcjCompilerTest.class)).thenReturn(annotation); + return element; + } +} diff --git a/pom.xml b/pom.xml index 0d4c8b109..0334c784e 100644 --- a/pom.xml +++ b/pom.xml @@ -80,6 +80,14 @@ https://github.com/ascopes + + + eclipse-snapshots + Eclipse Snapshots + https://repo.eclipse.org/content/repositories/eclipse-snapshots + + + https://github.com/ascopes/java-compiler-testing scm:git:https://github.com/ascopes/java-compiler-testing @@ -91,6 +99,7 @@ 4.0.0-M1 4.3.0 + 3.41.0-SNAPSHOT 1.4.0 1.0.0 6.0.0 @@ -122,6 +131,7 @@ true + INFO + org.eclipse.jdt + ecj + ${ecj.version} + + org.jspecify jspecify diff --git a/scripts/add-development-ecj-to-maven-repository.sh b/scripts/add-development-ecj-to-maven-repository.sh new file mode 100755 index 000000000..55d9eb45c --- /dev/null +++ b/scripts/add-development-ecj-to-maven-repository.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# +# Copyright (C) 2022 - 2024, the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +### +### Shortcut to injecting a development version of ECJ's JAR into the local Maven registry. +### + +set -o errexit +set -o nounset +[[ -n ${DEBUG+defined} ]] && set -o xtrace + +if [[ $# -ne 2 ]]; then + echo "USAGE: ${BASH_SOURCE[0]} " + echo "Inject the given URL to an ECJ JAR as the given version in the local Maven repository." + echo "" + echo "Arguments:" + echo " The URL to the ECJ JAR to use." + echo " The version number to use for that JAR." + echo "" + exit 1 +fi + +url=$1 +version=$2 + +maven_repository_dir=${M2_HOME:-${HOME}/.m2}/repository +target_dir=${maven_repository_dir}/org/eclipse/jdt/ecj/${version} + +if [[ -d ${target_dir} ]]; then + echo "Clearing existing directory out..." + rm -Rvf "${target_dir}" +fi + +echo "Making ECJ directory" +mkdir -pv "${target_dir}" + +echo "Working out the latest ECJ POM to use..." +latest_published_version=$(curl --fail --silent https://repo1.maven.org/maven2/org/eclipse/jdt/ecj/maven-metadata.xml \ + | grep -oE ".+?" \ + | sed -E 's@@@g' \ + | tail -n 1) + +echo "Making ECJ POM derived from the POM for v${latest_published_version}" +curl --fail --silent https://repo1.maven.org/maven2/org/eclipse/jdt/ecj/"${latest_published_version}"/ecj-"${latest_published_version}".pom \ + | sed 's@'"${latest_published_version}"'@'"${version}"'@g' \ + > "${target_dir}/ecj-${version}.pom" + +echo "Downloading ECJ JAR" +curl --fail --silent "${url}" > "${target_dir}/ecj-${version}.jar" + +echo "Computing SHA1 digest of ECJ JAR" +sha1sum < "${target_dir}/ecj-${version}.jar" | cut -d ' ' -f 1 > "${target_dir}/ecj-${version}.jar.sha1" + +echo "Making dummy _remote.repositories file" + +cat > "${target_dir}/_remote.repositories" <<-EOF +#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice. +#$(date) +ecj-${version}.jar>central= +ecj-${version}.pom>central= +EOF + +echo "Done." diff --git a/scripts/ecj.sh b/scripts/ecj.sh new file mode 100755 index 000000000..cee74ae0f --- /dev/null +++ b/scripts/ecj.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# +# Copyright (C) 2022 - 2024, the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +### +### Shortcut to running the ECJ compiler. +### + +set -o errexit +set -o nounset +[[ -n ${DEBUG+defined} ]] && set -o xtrace + +project_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ecj_dir="${project_dir}/target/ecj" +if [[ ! -d "${ecj_dir}" ]] || [[ ! "$(ls -A "${ecj_dir}")" ]]; then + mkdir -p "${ecj_dir}" + + echo "[[Determining ECJ version to use, please wait...]]" >&2 + ecj_version="$("${project_dir}/mvnw" -f "${project_dir}/pom.xml" help:evaluate \ + --offline \ + --quiet \ + -Dexpression="ecj.version" \ + -DforceStdout)" + + echo "[[Downloading ECJ ${ecj_version} artifact, please wait...]]" >&2 + "${project_dir}/mvnw" dependency:get \ + --quiet \ + -Dartifact="org.eclipse.jdt:ecj:${ecj_version}" + + echo "[[Copying ECJ ${ecj_version} artifact into ${ecj_dir}, please wait...]]" >&2 + "${project_dir}/mvnw" dependency:copy \ + --offline \ + --quiet \ + -Dartifact="org.eclipse.jdt:ecj:${ecj_version}" \ + -DoutputDirectory="${ecj_dir}" \ + -Dtransitive=true + + echo "[[Completed download of ECJ ${ecj_version}.]]" >&2 +fi + +java -jar "$(find "${ecj_dir}" -type f -name "*.jar" -print | head -n 1)" "${@}" From 95cbba6970d84d98f7f0c4be5b9e1dada7f046a7 Mon Sep 17 00:00:00 2001 From: Ashley <73482956+ascopes@users.noreply.github.com> Date: Thu, 8 May 2025 08:17:53 +0100 Subject: [PATCH 2/4] Update pom.xml to use ECJ 3.41.0 stable Signed-off-by: Ashley <73482956+ascopes@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0334c784e..0a034fc15 100644 --- a/pom.xml +++ b/pom.xml @@ -99,7 +99,7 @@ 4.0.0-M1 4.3.0 - 3.41.0-SNAPSHOT + 3.41.0 1.4.0 1.0.0 6.0.0 From ddc82b4e80e211fd9b53b8965ff4db1a44174964 Mon Sep 17 00:00:00 2001 From: Ashley <73482956+ascopes@users.noreply.github.com> Date: Thu, 8 May 2025 08:18:20 +0100 Subject: [PATCH 3/4] Update pom.xml to remove eclipse snapshots repository Signed-off-by: Ashley <73482956+ascopes@users.noreply.github.com> --- pom.xml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pom.xml b/pom.xml index 0a034fc15..81174698e 100644 --- a/pom.xml +++ b/pom.xml @@ -80,14 +80,6 @@ https://github.com/ascopes - - - eclipse-snapshots - Eclipse Snapshots - https://repo.eclipse.org/content/repositories/eclipse-snapshots - - - https://github.com/ascopes/java-compiler-testing scm:git:https://github.com/ascopes/java-compiler-testing From 68732383b11138af80533c31f3d974189a4bb6fa Mon Sep 17 00:00:00 2001 From: Ash <73482956+ascopes@users.noreply.github.com> Date: Mon, 3 Nov 2025 07:40:08 +0000 Subject: [PATCH 4/4] Bump ECJ to 3.43.0 Signed-off-by: Ash <73482956+ascopes@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 81174698e..c5458b52d 100644 --- a/pom.xml +++ b/pom.xml @@ -91,7 +91,7 @@ 4.0.0-M1 4.3.0 - 3.41.0 + 3.43.0 1.4.0 1.0.0 6.0.0