Skip to content
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

Support Gradle annotation processing #2319

Merged
merged 4 commits into from
Nov 25, 2022
Merged
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
1 change: 1 addition & 0 deletions org.eclipse.jdt.ls.core/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Require-Bundle: org.eclipse.core.runtime;bundle-version="3.12.0",
org.eclipse.m2e.maven.runtime;resolution:=optional,
org.eclipse.buildship.core;bundle-version="1.0.18";resolution:=optional,
org.eclipse.jdt.launching,
org.eclipse.jdt.apt.core,
org.eclipse.jdt.core.manipulation;bundle-version="1.8.0",
com.google.gson;bundle-version="2.7.0",
org.apache.commons.lang3;bundle-version="3.1.0",
Expand Down
3 changes: 2 additions & 1 deletion org.eclipse.jdt.ls.core/build.properties
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ bin.includes = META-INF/,\
gradle/checksums/versions.json,\
gradle/protobuf/init.gradle,\
gradle/init/init.gradle,\
gradle/android/init.gradle
gradle/android/init.gradle,\
gradle/apt/init.gradle
src.includes = src/
101 changes: 101 additions & 0 deletions org.eclipse.jdt.ls.core/gradle/apt/init.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*******************************************************************************
* Copyright (c) 2022 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
import org.gradle.api.Project
jdneo marked this conversation as resolved.
Show resolved Hide resolved
import org.gradle.api.Plugin
import org.gradle.api.file.FileCollection
import org.gradle.api.plugins.PluginContainer
import org.gradle.api.tasks.compile.CompileOptions
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.tooling.provider.model.ToolingModelBuilder
import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
import org.gradle.util.GradleVersion

import javax.inject.Inject

class GradleAnnotationProcessorPatchPlugin implements Plugin<Project> {

private final ToolingModelBuilderRegistry registry

@Inject
GradleAnnotationProcessorPatchPlugin(ToolingModelBuilderRegistry registry) {
this.registry = registry
}

@Override
void apply(Project project) {
this.registry.register(new AnnotationProcessorModelBuilder())
}

private static class AnnotationProcessorModelBuilder implements ToolingModelBuilder {

@Override
boolean canBuild(String modelName) {
return "java.util.Map" == modelName
}

@Override
Object buildAll(String modelName, Project rootProject) {
final GradleVersion current = GradleVersion.current().getBaseVersion()
if (current < GradleVersion.version("5.2")) {
return Collections.emptyMap()
}

Set<Project> allProject = rootProject.getAllprojects()
Map<File, Map<String, Object>> annotationProcessorInfo = new HashMap<>()
for (Project project : allProject) {
PluginContainer plugins = project.getPlugins()
if (!hasPlugin(plugins, "java")) {
continue
}

Set<File> processors = new HashSet<>()
List<String> compilerArgs = new LinkedList<>()
// Compiler args for test are ignored.
// Due to JDT does not differentiate the args between main and test.
collectApConfiguration(project, "compileJava", processors, compilerArgs)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There may be different processors present on compileJava vs compileTestJava, how does this PR address that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason that test annotation processors are ignored is that the APT support on JDT side cannot differentiate the processors from different source sets. My approach here is to ignore test annotation processors.

Another approach we can take is to mix all the processors from main and test together, but personally I think this is not ideal neither.

This is the case that we cannot handle gracefully now until the JDT can support that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I filed an issue to track the testAnnotationProcessor support: #2345, which we can address separately from this pull request.


Map<String, Object> apInfo = new HashMap<>()
if (processors.size() > 0) {
apInfo.put("processors", processors)
apInfo.put("compilerArgs", compilerArgs)
}
annotationProcessorInfo.put(project.getProjectDir(), apInfo)
}
return annotationProcessorInfo
}

private static boolean hasPlugin(PluginContainer plugins, String pluginId) {
return plugins.findPlugin(pluginId) != null
}

private void collectApConfiguration(project, compileTaskName, processors, compilerArgs) {
JavaCompile javaCompile = project.getTasks().findByName(compileTaskName)
if (javaCompile != null) {
CompileOptions options = javaCompile.getOptions()
if (!options.compilerArgs.contains("-proc:none")) {
FileCollection apPath = options.getAnnotationProcessorPath()
if (apPath != null) {
processors.addAll(apPath.getFiles())
}
compilerArgs.addAll(options.getCompilerArgs())
}
}
}
}
}

allprojects {
afterEvaluate {
it.getPlugins().apply(GradleAnnotationProcessorPatchPlugin)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;

import org.eclipse.buildship.core.BuildConfiguration;
Expand All @@ -39,6 +41,8 @@
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.jdt.apt.core.util.AptConfig;
import org.eclipse.jdt.apt.core.util.IFactoryPath;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
Expand Down Expand Up @@ -66,6 +70,15 @@ public class GradleBuildSupport implements IBuildSupport {

private static IPreferencesChangeListener listener = new GradlePreferenceChangeListener();

/**
* The relative path where store the sources generated by annotation processors
*/
private static final String GENERATED_SOURCES_PATH = "bin/generated-sources/annotations";
/**
* The relative path where store the test sources generated by annotation processors
*/
private static final String GENERATED_TEST_SOURCES_PATH = "bin/generated-test-sources/annotations";

@Override
public boolean applies(IProject project) {
return ProjectUtils.isGradleProject(project);
Expand Down Expand Up @@ -96,6 +109,100 @@ public void update(IProject project, boolean force, IProgressMonitor monitor) th
|| (settingsKtsFile.exists() && JavaLanguageServerPlugin.getDigestStore().updateDigest(settingsKtsFile.toPath()));
if (isRoot || shouldUpdate) {
gradleBuild.synchronize(monitor);
syncAnnotationProcessingConfiguration(gradleBuild, monitor);
}
}
}

public static void syncAnnotationProcessingConfiguration(IProject project, IProgressMonitor monitor) {
Optional<GradleBuild> build = GradleCore.getWorkspace().getBuild(project);
if (build.isPresent()) {
syncAnnotationProcessingConfiguration(build.get(), monitor);
}
}

/**
* Synchronize the annotation processing configurations to JDT APT.
* @param gradleBuild The GradleBuild instance.
* @param monitor progress monitor.
*/
@SuppressWarnings("unchecked")
public static void syncAnnotationProcessingConfiguration(GradleBuild gradleBuild, IProgressMonitor monitor) {
PreferenceManager preferencesManager = JavaLanguageServerPlugin.getPreferencesManager();
if (preferencesManager == null) {
return;
}
if (!preferencesManager.getPreferences().isGradleAnnotationProcessingEnabled()) {
return;
}

File initScript = GradleUtils.getGradleInitScript("/gradle/apt/init.gradle");
if (initScript == null) {
return;
}

Map<File, Map<String, Object>> model = null;
try {
model = gradleBuild.withConnection(connection -> {
return connection.model(Map.class).withArguments("--init-script", initScript.getAbsolutePath()).get();
}, monitor);
} catch (Exception e) {
JavaLanguageServerPlugin.logException(e.getMessage(), e);
}

if (model == null) {
return;
}

// Even reloading a sub project will get the annotation processors for all
// the projects, due to the Gradle custom model api's limitation.
for (IProject project : ProjectUtils.getGradleProjects()) {
IJavaProject javaProject = JavaCore.create(project);
if (javaProject == null) {
continue;
}

Map<String, Object> apConfigurations = model.get(project.getLocation().toFile());
if (apConfigurations == null) {
continue;
}

if (apConfigurations.isEmpty()) {
disableApt(javaProject);
continue;
}

Set<File> processors = getProcessors(apConfigurations);
if (processors.isEmpty()) {
continue;
}

AptConfig.setGenSrcDir(javaProject, GENERATED_SOURCES_PATH);
AptConfig.setGenTestSrcDir(javaProject, GENERATED_TEST_SOURCES_PATH);

if (!AptConfig.isEnabled(javaProject)) {
// setEnabled will ensure the output folder existing on disk, that why
// we set enabled status after the output folder is set to APT, which can
// avoid generating default output folder.
AptConfig.setEnabled(javaProject, true);
}

IFactoryPath factoryPath = AptConfig.getDefaultFactoryPath(javaProject);
for(File processor : processors){
factoryPath.addExternalJar(processor);
}

try {
AptConfig.setFactoryPath(javaProject, factoryPath);
} catch (CoreException e) {
JavaLanguageServerPlugin.log(e);
}

List<String> compilerArgs = getCompilerArgs(apConfigurations);
Map<String, String> newOptions = GradleUtils.parseProcessorOptions(compilerArgs);
Map<String, String> currentOptions = AptConfig.getRawProcessorOptions(javaProject);
if(!currentOptions.equals(newOptions)) {
AptConfig.setProcessorOptions(newOptions, javaProject);
}
}
}
Expand Down Expand Up @@ -292,4 +399,27 @@ private List<IProject> findUnrelatedGradleProjects(List<IProject> suspiciousProj
return result;
}

@SuppressWarnings("unchecked")
private static List<String> getCompilerArgs(Map<String, Object> apConfigurations) {
Object object = apConfigurations.get("compilerArgs");
if (!(object instanceof List)) {
return Collections.emptyList();
}
return (List<String>) object;
}

@SuppressWarnings("unchecked")
private static Set<File> getProcessors(Map<String, Object> apConfigurations) {
Object object = apConfigurations.get("processors");
if (!(object instanceof Set)) {
return Collections.emptySet();
}
return (Set<File>) object;
}

private static void disableApt(IJavaProject javaProject) {
if (AptConfig.isEnabled(javaProject)) {
AptConfig.setEnabled(javaProject, false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
import org.eclipse.buildship.core.internal.configuration.ProjectConfiguration;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.apt.core.util.AptConfig;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
import org.eclipse.jdt.ls.core.internal.preferences.IPreferencesChangeListener;
Expand Down Expand Up @@ -61,6 +65,20 @@ public void preferencesChange(Preferences oldPreferences, Preferences newPrefere
projectsManager.updateProject(project, true);
}
}

boolean annotationProcessingChanged = !Objects.equals(oldPreferences.isGradleAnnotationProcessingEnabled(), newPreferences.isGradleAnnotationProcessingEnabled());
if (annotationProcessingChanged) {
if (newPreferences.isGradleAnnotationProcessingEnabled()) {
GradleUtils.synchronizeAnnotationProcessingConfiguration(new NullProgressMonitor());
} else {
for (IProject project : ProjectUtils.getGradleProjects()) {
IJavaProject javaProject = JavaCore.create(project);
if (javaProject != null) {
AptConfig.setEnabled(javaProject, false);
}
}
}
}
}
}

Expand Down
Loading