Skip to content

Commit 346747c

Browse files
committed
Implement initial ECJ support
Implement a ForwardingJctFileManager to use for aggregation/delegation Add EcjJctFlagBuilderImpl for ECJ integration Implement EcjJctCompilerImpl and requried components Implement .newEcjCompiler() static method in JctCompilers class Implement EcjCompilerTest annotation and provider. Set 'since' attributes to 'TBC' for ECJ features Enable EcjCompilerTest on BasicLegacyCompilationIntegrationTest Enable EcjCompilerTest on BasicModuleCompilationIntegrationTest Add placeholders for EcjCompilerTest on BasicMultiModuleCompilationIntegrationTest This is currently marked as disabled due to buggy behaviour for MODULE_SOURCE_PATH locations in ECJ. Enable ECJ for CompilingSpecificClassesIntegrationTest Enable MultiTieredCompilationIntegrationTest for ECJ This is currently failing due to an integration issue with the classpath. Bump ECJ from 3.30.0 to 3.33.0 Tidy up modified integration tests Fix classpath lookups for ECJ when package names are slash-delimited Fix PathWrappingContainerImpl logs Output compiler verbose log messages to stderr rather than stdout Enable ECJ in Avaje Inject tests Add script to programmatically invoke ECJ Enable -XprintProcessorInfo and -XprintRounds when .verbose(true) on ECJ compilers Set minimum ECJ release version to JDK 8 rather than JDK 11.
1 parent 62b1405 commit 346747c

31 files changed

+2728
-308
lines changed

acceptance-tests/acceptance-tests-avaje-inject/src/test/groovy/io/github/ascopes/jct/acceptancetests/avajeinject/AvajeInjectTest.groovy

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,42 +15,62 @@
1515
*/
1616
package io.github.ascopes.jct.acceptancetests.avajeinject
1717

18+
import static io.github.ascopes.jct.assertions.JctAssertions.assertThatCompilation
19+
1820
import io.github.ascopes.jct.compilers.JctCompiler
1921
import io.github.ascopes.jct.filemanagers.LoggingMode
22+
import io.github.ascopes.jct.junit.EcjCompilerTest
2023
import io.github.ascopes.jct.junit.JavacCompilerTest
24+
import io.github.ascopes.jct.workspaces.PathStrategy
25+
import io.github.ascopes.jct.workspaces.Workspace
2126
import io.github.ascopes.jct.workspaces.Workspaces
2227
import org.junit.jupiter.api.DisplayName
2328

24-
import static io.github.ascopes.jct.assertions.JctAssertions.assertThatCompilation
25-
2629
@DisplayName("Avaje Inject acceptance tests")
2730
class AvajeInjectTest {
28-
@DisplayName("Dependency injection code gets generated as expected")
31+
@DisplayName("Dependency injection code gets generated as expected for Javac")
2932
@JavacCompilerTest(minVersion = 11)
30-
void dependencyInjectionCodeGetsGeneratedAsExpected(JctCompiler compiler) {
33+
void dependencyInjectionCodeGetsGeneratedAsExpectedForJavac(JctCompiler compiler) {
3134
// Given
3235
try (def workspace = Workspaces.newWorkspace()) {
33-
workspace
34-
.createSourcePathPackage()
35-
.copyContentsFrom("src", "test", "resources", "code")
36-
37-
// When
38-
def compilation = compiler
39-
.diagnosticLoggingMode(LoggingMode.STACKTRACES)
40-
.compile(workspace)
36+
runTest(compiler, workspace)
37+
}
38+
}
4139

42-
// Then
43-
assertThatCompilation(compilation)
44-
.isSuccessfulWithoutWarnings()
45-
.classOutputPackages()
46-
.allFilesExist(
47-
'org/example/CoffeeMaker.class',
48-
'org/example/Grinder.class',
49-
'org/example/Pump.class',
50-
'org/example/CoffeeMaker$DI.class',
51-
'org/example/Grinder$DI.class',
52-
'org/example/Pump$DI.class',
53-
)
40+
@DisplayName("Dependency injection code gets generated as expected for ECJ")
41+
@EcjCompilerTest(minVersion = 11)
42+
void dependencyInjectionCodeGetsGeneratedAsExpectedForEcj(JctCompiler compiler) {
43+
// Given
44+
try (def workspace = Workspaces.newWorkspace(PathStrategy.TEMP_DIRECTORIES)) {
45+
runTest(compiler, workspace)
5446
}
5547
}
48+
49+
private static void runTest(JctCompiler compiler, Workspace workspace) {
50+
workspace
51+
.createSourcePathPackage()
52+
.copyContentsFrom("src", "test", "resources", "code")
53+
54+
// When
55+
def compilation = compiler
56+
// TODO(ascopes): disable this
57+
.fileManagerLoggingMode(LoggingMode.ENABLED)
58+
.diagnosticLoggingMode(LoggingMode.STACKTRACES)
59+
.verbose(true)
60+
// end temporary block
61+
.compile(workspace)
62+
63+
// Then
64+
assertThatCompilation(compilation)
65+
.isSuccessfulWithoutWarnings()
66+
.classOutputPackages()
67+
.allFilesExist(
68+
'org/example/CoffeeMaker.class',
69+
'org/example/Grinder.class',
70+
'org/example/Pump.class',
71+
'org/example/CoffeeMaker$DI.class',
72+
'org/example/Grinder$DI.class',
73+
'org/example/Pump$DI.class',
74+
)
75+
}
5676
}

acceptance-tests/acceptance-tests-dogfood/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@
3737
<scope>test</scope>
3838
</dependency>
3939

40+
<dependency>
41+
<groupId>org.eclipse.jdt</groupId>
42+
<artifactId>ecj</artifactId>
43+
</dependency>
44+
4045
<dependency>
4146
<groupId>org.junit.jupiter</groupId>
4247
<artifactId>junit-jupiter</artifactId>

java-compiler-testing/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@
5959
<artifactId>assertj-core</artifactId>
6060
</dependency>
6161

62+
<dependency>
63+
<groupId>org.eclipse.jdt</groupId>
64+
<artifactId>ecj</artifactId>
65+
</dependency>
66+
6267
<dependency>
6368
<groupId>org.jspecify</groupId>
6469
<artifactId>jspecify</artifactId>

java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/JctCompilers.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package io.github.ascopes.jct.compilers;
1717

18+
import io.github.ascopes.jct.compilers.impl.EcjJctCompilerImpl;
1819
import io.github.ascopes.jct.compilers.impl.JavacJctCompilerImpl;
1920
import io.github.ascopes.jct.utils.UtilityClass;
2021
import org.apiguardian.api.API;
@@ -44,4 +45,18 @@ private JctCompilers() {
4445
public static JctCompiler newPlatformCompiler() {
4546
return new JavacJctCompilerImpl();
4647
}
48+
49+
/**
50+
* Create a new instance of an ECJ compiler.
51+
*
52+
* <p>Note that this is highly experimental, and may break in strange and unexpected ways, or may
53+
* even be removed without notice. Use at your own risk.
54+
*
55+
* @return the compiler instance.
56+
* @since TBC
57+
*/
58+
@API(status = Status.EXPERIMENTAL, since = "TBC")
59+
public static JctCompiler newEcjCompiler() {
60+
return new EcjJctCompilerImpl();
61+
}
4762
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright (C) 2022 - 2023, the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.github.ascopes.jct.compilers.impl;
17+
18+
import io.github.ascopes.jct.compilers.AbstractJctCompiler;
19+
import io.github.ascopes.jct.compilers.JctFlagBuilderFactory;
20+
import io.github.ascopes.jct.compilers.Jsr199CompilerFactory;
21+
import io.github.ascopes.jct.filemanagers.JctFileManagerFactory;
22+
import io.github.ascopes.jct.filemanagers.impl.EcjJctFileManagerFactoryImpl;
23+
import org.apiguardian.api.API;
24+
import org.apiguardian.api.API.Status;
25+
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
26+
import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler;
27+
import org.jspecify.annotations.Nullable;
28+
29+
/**
30+
* Implementation of an {@code ECJ} compiler.
31+
*
32+
* @author Ashley Scopes
33+
* @since TBC
34+
*/
35+
@API(since = "TBC", status = Status.INTERNAL)
36+
public final class EcjJctCompilerImpl extends AbstractJctCompiler {
37+
38+
private static final String NAME = "ECJ";
39+
40+
/**
41+
* Initialize this compiler.
42+
*/
43+
public EcjJctCompilerImpl() {
44+
super(NAME);
45+
}
46+
47+
@Override
48+
public JctFlagBuilderFactory getFlagBuilderFactory() {
49+
return EcjJctFlagBuilderImpl::new;
50+
}
51+
52+
@Override
53+
public Jsr199CompilerFactory getCompilerFactory() {
54+
return EclipseCompiler::new;
55+
}
56+
57+
@Override
58+
public JctFileManagerFactory getFileManagerFactory() {
59+
return new EcjJctFileManagerFactoryImpl(this);
60+
}
61+
62+
@Override
63+
public String getDefaultRelease() {
64+
return Integer.toString(getLatestSupportedVersionInt());
65+
}
66+
67+
/**
68+
* Get the minimum version of ECJ that is supported.
69+
*
70+
* @return the minimum supported version.
71+
*/
72+
public static int getEarliestSupportedVersionInt() {
73+
return decodeMajorVersion(ClassFileConstants.JDK1_8);
74+
}
75+
76+
/**
77+
* Get the ECJ version that is loaded.
78+
*
79+
* @return the ECJ version that is loaded on the class path, or {@code null} if the information is
80+
* not available.
81+
*/
82+
@Nullable
83+
public static String getEcjVersion() {
84+
return EclipseCompiler.class.getPackage().getImplementationVersion();
85+
}
86+
87+
/**
88+
* Get the maximum version of ECJ that is supported.
89+
*
90+
* @return the maximum supported version.
91+
*/
92+
public static int getLatestSupportedVersionInt() {
93+
return decodeMajorVersion(ClassFileConstants.getLatestJDKLevel());
94+
}
95+
96+
private static int decodeMajorVersion(long classFileConstant) {
97+
return (int) ((classFileConstant >> 16L) - ClassFileConstants.MAJOR_VERSION_0);
98+
}
99+
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/*
2+
* Copyright (C) 2022 - 2023, the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.github.ascopes.jct.compilers.impl;
17+
18+
import io.github.ascopes.jct.compilers.CompilationMode;
19+
import io.github.ascopes.jct.compilers.JctFlagBuilder;
20+
import java.util.ArrayList;
21+
import java.util.List;
22+
import org.apiguardian.api.API;
23+
import org.apiguardian.api.API.Status;
24+
import org.jspecify.annotations.Nullable;
25+
26+
/**
27+
* Helper to build flags for the ECJ compiler implementation.
28+
*
29+
* @author Ashley Scopes
30+
* @since TBC
31+
*/
32+
@API(since = "TBC", status = Status.INTERNAL)
33+
public final class EcjJctFlagBuilderImpl implements JctFlagBuilder {
34+
35+
private static final String VERBOSE = "-verbose";
36+
private static final String PRINT_ANNOTATION_PROCESSOR_INFO = "-XprintProcessorInfo";
37+
private static final String PRINT_ANNOTATION_PROCESSOR_ROUNDS = "-XprintRounds";
38+
private static final String ENABLE_PREVIEW = "--enable-preview";
39+
private static final String NOWARN = "-nowarn";
40+
private static final String FAIL_ON_WARNING = "--failOnWarning";
41+
private static final String DEPRECATION = "-deprecation";
42+
private static final String RELEASE = "--release";
43+
private static final String SOURCE = "-source";
44+
private static final String TARGET = "-target";
45+
private static final String ANNOTATION_OPT = "-A";
46+
private static final String PROC_NONE = "-proc:none";
47+
private static final String PROC_ONLY = "-proc:only";
48+
49+
private final List<String> craftedFlags;
50+
51+
/**
52+
* Initialize this flag builder.
53+
*/
54+
public EcjJctFlagBuilderImpl() {
55+
craftedFlags = new ArrayList<>();
56+
}
57+
58+
@Override
59+
public EcjJctFlagBuilderImpl verbose(boolean enabled) {
60+
return addFlagIfTrue(enabled, VERBOSE)
61+
.addFlagIfTrue(enabled, PRINT_ANNOTATION_PROCESSOR_INFO)
62+
.addFlagIfTrue(enabled, PRINT_ANNOTATION_PROCESSOR_ROUNDS);
63+
}
64+
65+
@Override
66+
public EcjJctFlagBuilderImpl previewFeatures(boolean enabled) {
67+
return addFlagIfTrue(enabled, ENABLE_PREVIEW);
68+
}
69+
70+
@Override
71+
public EcjJctFlagBuilderImpl showWarnings(boolean enabled) {
72+
return addFlagIfTrue(!enabled, NOWARN);
73+
}
74+
75+
@Override
76+
public EcjJctFlagBuilderImpl failOnWarnings(boolean enabled) {
77+
return addFlagIfTrue(enabled, FAIL_ON_WARNING);
78+
}
79+
80+
@Override
81+
public JctFlagBuilder compilationMode(CompilationMode compilationMode) {
82+
switch (compilationMode) {
83+
case COMPILATION_ONLY:
84+
craftedFlags.add(PROC_NONE);
85+
break;
86+
87+
case ANNOTATION_PROCESSING_ONLY:
88+
craftedFlags.add(PROC_ONLY);
89+
break;
90+
91+
default:
92+
// Do nothing. The default behaviour is to allow this.
93+
break;
94+
}
95+
96+
return this;
97+
}
98+
99+
@Override
100+
public EcjJctFlagBuilderImpl showDeprecationWarnings(boolean enabled) {
101+
return addFlagIfTrue(enabled, DEPRECATION);
102+
}
103+
104+
@Override
105+
public EcjJctFlagBuilderImpl release(@Nullable String version) {
106+
return addVersionIfPresent(RELEASE, version);
107+
}
108+
109+
@Override
110+
public EcjJctFlagBuilderImpl source(@Nullable String version) {
111+
return addVersionIfPresent(SOURCE, version);
112+
}
113+
114+
@Override
115+
public EcjJctFlagBuilderImpl target(@Nullable String version) {
116+
return addVersionIfPresent(TARGET, version);
117+
}
118+
119+
@Override
120+
public EcjJctFlagBuilderImpl annotationProcessorOptions(List<String> options) {
121+
options.forEach(option -> craftedFlags.add(ANNOTATION_OPT + option));
122+
return this;
123+
}
124+
125+
@Override
126+
public EcjJctFlagBuilderImpl compilerOptions(List<String> options) {
127+
craftedFlags.addAll(options);
128+
return this;
129+
}
130+
131+
@Override
132+
public List<String> build() {
133+
// Immutable copy.
134+
return List.copyOf(craftedFlags);
135+
}
136+
137+
private EcjJctFlagBuilderImpl addFlagIfTrue(boolean condition, String flag) {
138+
if (condition) {
139+
craftedFlags.add(flag);
140+
}
141+
142+
return this;
143+
}
144+
145+
private EcjJctFlagBuilderImpl addVersionIfPresent(String flagPrefix, @Nullable String version) {
146+
if (version != null) {
147+
craftedFlags.add(flagPrefix);
148+
craftedFlags.add(version);
149+
}
150+
151+
return this;
152+
}
153+
}

0 commit comments

Comments
 (0)