Skip to content

Support the Eclipse Java Compiler #422

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,42 +15,62 @@
*/
package io.github.ascopes.jct.acceptancetests.avajeinject

import static io.github.ascopes.jct.assertions.JctAssertions.assertThatCompilation

import io.github.ascopes.jct.compilers.JctCompiler
import io.github.ascopes.jct.filemanagers.LoggingMode
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.Workspace
import io.github.ascopes.jct.workspaces.Workspaces
import org.junit.jupiter.api.DisplayName

import static io.github.ascopes.jct.assertions.JctAssertions.assertThatCompilation

@DisplayName("Avaje Inject acceptance tests")
class AvajeInjectTest {
@DisplayName("Dependency injection code gets generated as expected")
@DisplayName("Dependency injection code gets generated as expected for Javac")
@JavacCompilerTest(minVersion = 11)
void dependencyInjectionCodeGetsGeneratedAsExpected(JctCompiler compiler) {
void dependencyInjectionCodeGetsGeneratedAsExpectedForJavac(JctCompiler compiler) {
// Given
try (def workspace = Workspaces.newWorkspace()) {
workspace
.createSourcePathPackage()
.copyContentsFrom("src", "test", "resources", "code")

// When
def compilation = compiler
.diagnosticLoggingMode(LoggingMode.STACKTRACES)
.compile(workspace)
runTest(compiler, workspace)
}
}

// Then
assertThatCompilation(compilation)
.isSuccessfulWithoutWarnings()
.classOutputPackages()
.allFilesExist(
'org/example/CoffeeMaker.class',
'org/example/Grinder.class',
'org/example/Pump.class',
'org/example/CoffeeMaker$DI.class',
'org/example/Grinder$DI.class',
'org/example/Pump$DI.class',
)
@DisplayName("Dependency injection code gets generated as expected for ECJ")
@EcjCompilerTest(minVersion = 11)
void dependencyInjectionCodeGetsGeneratedAsExpectedForEcj(JctCompiler compiler) {
// Given
try (def workspace = Workspaces.newWorkspace(PathStrategy.TEMP_DIRECTORIES)) {
runTest(compiler, workspace)
}
}

private static void runTest(JctCompiler compiler, Workspace workspace) {
workspace
.createSourcePathPackage()
.copyContentsFrom("src", "test", "resources", "code")

// When
def compilation = compiler
// TODO(ascopes): disable this
.fileManagerLoggingMode(LoggingMode.ENABLED)
.diagnosticLoggingMode(LoggingMode.STACKTRACES)
.verbose(true)
// end temporary block
.compile(workspace)

// Then
assertThatCompilation(compilation)
.isSuccessfulWithoutWarnings()
.classOutputPackages()
.allFilesExist(
'org/example/CoffeeMaker.class',
'org/example/Grinder.class',
'org/example/Pump.class',
'org/example/CoffeeMaker$DI.class',
'org/example/Grinder$DI.class',
'org/example/Pump$DI.class',
)
}
}
5 changes: 5 additions & 0 deletions acceptance-tests/acceptance-tests-dogfood/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.eclipse.jdt</groupId>
<artifactId>ecj</artifactId>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
Expand Down
5 changes: 5 additions & 0 deletions java-compiler-testing/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@
<artifactId>assertj-core</artifactId>
</dependency>

<dependency>
<groupId>org.eclipse.jdt</groupId>
<artifactId>ecj</artifactId>
</dependency>

<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
import io.github.ascopes.jct.utils.UtilityClass;
import org.apiguardian.api.API;
Expand Down Expand Up @@ -44,4 +45,18 @@ private JctCompilers() {
public static JctCompiler newPlatformCompiler() {
return new JavacJctCompilerImpl();
}

/**
* Create a new instance of an ECJ compiler.
*
* <p>Note that this is highly experimental, and may break in strange and unexpected ways, or may
* even be removed without notice. Use at your own risk.
*
* @return the compiler instance.
* @since TBC
*/
@API(status = Status.EXPERIMENTAL, since = "TBC")
public static JctCompiler newEcjCompiler() {
return new EcjJctCompilerImpl();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright (C) 2022 - 2023, 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 io.github.ascopes.jct.filemanagers.JctFileManagerFactory;
import io.github.ascopes.jct.filemanagers.impl.EcjJctFileManagerFactoryImpl;
import org.apiguardian.api.API;
import org.apiguardian.api.API.Status;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler;
import org.jspecify.annotations.Nullable;

/**
* Implementation of an {@code ECJ} compiler.
*
* @author Ashley Scopes
* @since TBC
*/
@API(since = "TBC", status = Status.INTERNAL)
public final class EcjJctCompilerImpl extends AbstractJctCompiler {

private static final String NAME = "ECJ";

/**
* Initialize this compiler.
*/
public EcjJctCompilerImpl() {
super(NAME);
}

@Override
public JctFlagBuilderFactory getFlagBuilderFactory() {
return EcjJctFlagBuilderImpl::new;
}

@Override
public Jsr199CompilerFactory getCompilerFactory() {
return EclipseCompiler::new;
}

@Override
public JctFileManagerFactory getFileManagerFactory() {
return new EcjJctFileManagerFactoryImpl(this);
}

@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 ECJ version that is loaded.
*
* @return the ECJ version that is loaded on the class path, or {@code null} if the information is
* not available.
*/
@Nullable
public static String getEcjVersion() {
return EclipseCompiler.class.getPackage().getImplementationVersion();
}

/**
* 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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* Copyright (C) 2022 - 2023, 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.JctFlagBuilder;
import java.util.ArrayList;
import java.util.List;
import org.apiguardian.api.API;
import org.apiguardian.api.API.Status;
import org.jspecify.annotations.Nullable;

/**
* Helper to build flags for the ECJ compiler implementation.
*
* @author Ashley Scopes
* @since TBC
*/
@API(since = "TBC", status = Status.INTERNAL)
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 final List<String> 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 annotationProcessorOptions(List<String> options) {
options.forEach(option -> craftedFlags.add(ANNOTATION_OPT + option));
return this;
}

@Override
public EcjJctFlagBuilderImpl compilerOptions(List<String> options) {
craftedFlags.addAll(options);
return this;
}

@Override
public List<String> 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;
}
}
Loading
Loading