diff --git a/plugins/gradle/java/src/model/data/AnnotationProcessingData.java b/plugins/gradle/java/src/model/data/AnnotationProcessingData.java index 71f37496ff0a..1658346c0c0a 100644 --- a/plugins/gradle/java/src/model/data/AnnotationProcessingData.java +++ b/plugins/gradle/java/src/model/data/AnnotationProcessingData.java @@ -14,6 +14,8 @@ import static com.intellij.util.containers.ContainerUtil.immutableList; public class AnnotationProcessingData { public static final Key KEY = Key.create(AnnotationProcessingData.class, ExternalSystemConstants.UNORDERED); + public static final Key OUTPUT_KEY = + Key.create(AnnotationProcessorOutput.class, ExternalSystemConstants.UNORDERED); private static final WeakInterner ourInterner = new WeakInterner<>(); @@ -47,4 +49,24 @@ public class AnnotationProcessingData { public Collection getPath() { return path; } + + public static class AnnotationProcessorOutput { + private final String outputPath; + private final boolean testSources; + + @PropertyMapping({"outputPath", "testSources"}) + public AnnotationProcessorOutput(@NotNull String path, boolean isTestSources) { + outputPath = path; + testSources = isTestSources; + } + + @NotNull + public String getOutputPath() { + return outputPath; + } + + public boolean isTestSources() { + return testSources; + } + } } diff --git a/plugins/gradle/java/src/service/project/JavaGradleProjectResolver.java b/plugins/gradle/java/src/service/project/JavaGradleProjectResolver.java index 37d20e7b11ac..aaafc6df5744 100644 --- a/plugins/gradle/java/src/service/project/JavaGradleProjectResolver.java +++ b/plugins/gradle/java/src/service/project/JavaGradleProjectResolver.java @@ -89,18 +89,40 @@ public class JavaGradleProjectResolver extends AbstractProjectResolverExtension } if (!resolverCtx.isResolveModulePerSourceSet()) { final AnnotationProcessingData apData = getMergedAnnotationProcessingData(apModel); - ideModule.createChild(AnnotationProcessingData.KEY, apData); + DataNode dataNode = ideModule.createChild(AnnotationProcessingData.KEY, apData); + populateAnnotationProcessingOutput(dataNode, apModel); } else { Collection> all = ExternalSystemApiUtil.findAll(ideModule, GradleSourceSetData.KEY); for (DataNode node : all) { final AnnotationProcessingData apData = getAnnotationProcessingData(apModel, node.getData().getModuleName()); if (apData != null) { - node.createChild(AnnotationProcessingData.KEY, apData); + DataNode dataNode = node.createChild(AnnotationProcessingData.KEY, apData); + populateAnnotationProcessorOutput(dataNode, apModel, node.getData().getModuleName()); } } } } + private static void populateAnnotationProcessorOutput(@NotNull DataNode parent, + @NotNull AnnotationProcessingModel apModel, + @NotNull String sourceSetName) { + AnnotationProcessingConfig config = apModel.bySourceSetName(sourceSetName); + if (config != null && config.getProcessorOutput() != null) { + parent.createChild(AnnotationProcessingData.OUTPUT_KEY, + new AnnotationProcessingData.AnnotationProcessorOutput(config.getProcessorOutput(), config.isTestSources())); + } + } + + private static void populateAnnotationProcessingOutput(@NotNull DataNode parent, + @NotNull AnnotationProcessingModel apModel) { + for (AnnotationProcessingConfig config : apModel.allConfigs().values()) { + if (config.getProcessorOutput() != null) { + parent.createChild(AnnotationProcessingData.OUTPUT_KEY, + new AnnotationProcessingData.AnnotationProcessorOutput(config.getProcessorOutput(), config.isTestSources())); + } + } + } + @NotNull private static AnnotationProcessingData getMergedAnnotationProcessingData(@NotNull AnnotationProcessingModel apModel) { diff --git a/plugins/gradle/java/src/service/project/data/AnnotationProcessingDataService.kt b/plugins/gradle/java/src/service/project/data/AnnotationProcessingDataService.kt index 8e1fb70abff6..3c37ada0a4ed 100644 --- a/plugins/gradle/java/src/service/project/data/AnnotationProcessingDataService.kt +++ b/plugins/gradle/java/src/service/project/data/AnnotationProcessingDataService.kt @@ -3,6 +3,7 @@ package org.jetbrains.plugins.gradle.service.project.data import com.intellij.compiler.CompilerConfiguration import com.intellij.compiler.CompilerConfigurationImpl +import com.intellij.ide.projectView.actions.MarkRootActionBase import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.externalSystem.model.DataNode import com.intellij.openapi.externalSystem.model.Key @@ -10,18 +11,36 @@ import com.intellij.openapi.externalSystem.model.project.ModuleData import com.intellij.openapi.externalSystem.model.project.ProjectData import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider import com.intellij.openapi.externalSystem.service.project.manage.AbstractProjectDataService +import com.intellij.openapi.externalSystem.service.project.manage.SourceFolderManager +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil import com.intellij.openapi.externalSystem.util.ExternalSystemConstants import com.intellij.openapi.externalSystem.util.Order import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ContentEntry +import com.intellij.openapi.roots.ModifiableRootModel +import com.intellij.openapi.roots.SourceFolder import com.intellij.openapi.util.Computable +import com.intellij.openapi.util.text.StringUtil +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.openapi.vfs.VfsUtilCore +import com.intellij.openapi.vfs.VirtualFileManager +import org.jetbrains.jps.model.java.JavaSourceRootType +import org.jetbrains.jps.model.java.JpsJavaExtensionService import org.jetbrains.jps.model.java.compiler.ProcessorConfigProfile import org.jetbrains.jps.model.java.impl.compiler.ProcessorConfigProfileImpl import org.jetbrains.plugins.gradle.model.data.AnnotationProcessingData +import org.jetbrains.plugins.gradle.settings.GradleSettings import java.io.File +import java.util.* +import kotlin.collections.ArrayList @Order(ExternalSystemConstants.UNORDERED) class AnnotationProcessingDataService : AbstractProjectDataService() { + + private lateinit var sourceFolderManager: SourceFolderManager + private lateinit var modelsProvider: IdeModifiableModelsProvider + override fun getTargetDataKey(): Key { return AnnotationProcessingData.KEY } @@ -29,9 +48,11 @@ class AnnotationProcessingDataService : AbstractProjectDataService>, projectData: ProjectData?, project: Project, - modelsProvider: IdeModifiableModelsProvider) { + modifiableModelsProvider: IdeModifiableModelsProvider) { val importedData = mutableSetOf() val config = CompilerConfiguration.getInstance(project) as CompilerConfigurationImpl + sourceFolderManager = SourceFolderManager.getInstance(project) + modelsProvider = modifiableModelsProvider for (node in toImport) { val moduleData = node.parent?.data as? ModuleData if (moduleData == null) { @@ -46,9 +67,126 @@ class AnnotationProcessingDataService : AbstractProjectDataService) { + val gradleOutputs = ExternalSystemApiUtil.findAll(node, AnnotationProcessingData.OUTPUT_KEY) + + val pathsToRemove = + (gradleOutputs + .map { it.data.outputPath } + + listOf( + getAnnotationProcessorGenerationPath(ideModule, false), + getAnnotationProcessorGenerationPath(ideModule, true) + )) + .filterNotNull() + + pathsToRemove.forEach { path -> + val url = VfsUtilCore.pathToUrl(path) + val modifiableRootModel = modelsProvider.getModifiableRootModel(ideModule) + + val (entry, folder) = findContentEntryOrFolder(modifiableRootModel, url) + + if (entry != null) { + if (folder != null) { + entry.removeSourceFolder(folder) + } + + if (entry.sourceFolders.isEmpty()) { + modifiableRootModel.removeContentEntry(entry) + } + } + } + } + + private fun addGeneratedSourceFolders(ideModule: Module, + node: DataNode, + delegatedBuild: Boolean) { + + if (delegatedBuild) { + val outputs = ExternalSystemApiUtil.findAll(node, AnnotationProcessingData.OUTPUT_KEY) + outputs.forEach { + val outputPath = it.data.outputPath + val isTestSource = it.data.isTestSources + addGeneratedSourceFolder(ideModule, outputPath, isTestSource) + } + } + else { + val outputPath = getAnnotationProcessorGenerationPath(ideModule, false) + if (outputPath != null) { + addGeneratedSourceFolder(ideModule, outputPath, false) + } + + val testOutputPath = getAnnotationProcessorGenerationPath(ideModule, true) + if (testOutputPath != null) { + addGeneratedSourceFolder(ideModule, testOutputPath, true) + } + } + } + + + private fun addGeneratedSourceFolder(ideModule: Module, + path: String, + isTest: Boolean) { + val type = if (isTest) JavaSourceRootType.TEST_SOURCE else JavaSourceRootType.SOURCE + val url = VfsUtilCore.pathToUrl(path) + val vf = LocalFileSystem.getInstance().refreshAndFindFileByPath(path) + if (vf == null || !vf.exists()) { + sourceFolderManager.addSourceFolder(ideModule, url, type) + sourceFolderManager.setSourceFolderGenerated(url, true) + } else { + val modifiableRootModel = modelsProvider.getModifiableRootModel(ideModule) + val contentEntry = MarkRootActionBase.findContentEntry(modifiableRootModel, vf) + ?: modifiableRootModel.addContentEntry(url) + val properties = JpsJavaExtensionService.getInstance().createSourceRootProperties("", true) + contentEntry.addSourceFolder(url, type, properties) + } + } + + private fun findContentEntryOrFolder(modifiableRootModel: ModifiableRootModel, + url: String): Pair { + var entryVar: ContentEntry? = null + var folderVar: SourceFolder? = null + modifiableRootModel.contentEntries.forEach search@{ ce -> + ce.sourceFolders.forEach { sf -> + if (sf.url == url) { + entryVar = ce + folderVar = sf + return@search + } + } + if (ce.url == url) { + entryVar = ce + } + } + return entryVar to folderVar + } + + private fun getAnnotationProcessorGenerationPath(ideModule: Module, forTests: Boolean): String? { + val config = CompilerConfiguration.getInstance(ideModule.project).getAnnotationProcessingConfiguration(ideModule) + val sourceDirName = config.getGeneratedSourcesDirectoryName(forTests) + val roots = modelsProvider.getModifiableRootModel(ideModule).contentRootUrls + if (roots.isEmpty()) { + return null + } + if (roots.size > 1) { + Arrays.sort(roots) + } + return if (StringUtil.isEmpty(sourceDirName)) VirtualFileManager.extractPath(roots[0]) + else VirtualFileManager.extractPath(roots[0]) + "/" + sourceDirName + } + + override fun computeOrphanData(toImport: MutableCollection>, projectData: ProjectData, project: Project, @@ -94,6 +232,7 @@ class AnnotationProcessingDataService : AbstractProjectDataService getAnnotationProcessorArguments(); + /** + * If annotation processing is performed over test sources + * @return see above + */ + boolean isTestSources(); + + /** + * Location of files generated by annotation processing. + * Will be used as sources root, if needed + * @return see above + */ + @Nullable + String getProcessorOutput(); } diff --git a/plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/builder/AnnotationProcessingModelBuilder.java b/plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/builder/AnnotationProcessingModelBuilder.java index 3638c6f02b97..dacc7c6a4de1 100644 --- a/plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/builder/AnnotationProcessingModelBuilder.java +++ b/plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/builder/AnnotationProcessingModelBuilder.java @@ -25,7 +25,8 @@ import java.util.*; public class AnnotationProcessingModelBuilder extends AbstractModelBuilderService { private static final boolean isAtLeastGradle3_4 = GradleVersion.current().compareTo(GradleVersion.version("3.4")) >= 0; - private static final boolean isAtLeastGradle4_5 = isAtLeastGradle3_4 && GradleVersion.current().compareTo(GradleVersion.version("4.5")) >= 0; + private static final boolean isAtLeastGradle4_3 = isAtLeastGradle3_4 && GradleVersion.current().compareTo(GradleVersion.version("4.3")) >= 0; + private static final boolean isAtLeastGradle4_5 = isAtLeastGradle4_3 && GradleVersion.current().compareTo(GradleVersion.version("4.5")) >= 0; @Override public Object buildAll(@NotNull String modelName, @NotNull Project project, @NotNull ModelBuilderContext context) { @@ -64,7 +65,11 @@ public class AnnotationProcessingModelBuilder extends AbstractModelBuilderServic for (File file : files) { paths.add(file.getAbsolutePath()); } - sourceSetConfigs.put(sourceSet.getName(), new AnnotationProcessingConfigImpl(paths, annotationProcessorArgs)); + + File generatedSourcesDirectory = isAtLeastGradle4_3 ? options.getAnnotationProcessorGeneratedSourcesDirectory() : null; + String output = generatedSourcesDirectory != null ? generatedSourcesDirectory.getAbsolutePath() : null; + boolean isTest = SourceSet.TEST_SOURCE_SET_NAME.equals(sourceSet.getName()); + sourceSetConfigs.put(sourceSet.getName(), new AnnotationProcessingConfigImpl(paths, annotationProcessorArgs, output, isTest)); } } } diff --git a/plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/builder/ExternalProjectBuilderImpl.groovy b/plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/builder/ExternalProjectBuilderImpl.groovy index d6eb60b17616..9d059e032d65 100644 --- a/plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/builder/ExternalProjectBuilderImpl.groovy +++ b/plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/builder/ExternalProjectBuilderImpl.groovy @@ -306,15 +306,9 @@ class ExternalProjectBuilderImpl extends AbstractModelBuilderService { DefaultExternalSourceDirectorySet generatedDirectorySet = null def hasExplicitlyDefinedGeneratedSources = generatedSourceDirs && !generatedSourceDirs.isEmpty() - FileCollection generatedSourcesOutput = sourceSet.output.hasProperty("generatedSourcesDirs") ? - sourceSet.output.generatedSourcesDirs : null - def hasAnnotationProcessorClasspath = sourceSet.hasProperty("annotationProcessorPath") && !isEmpty(sourceSet.annotationProcessorPath) - if (hasExplicitlyDefinedGeneratedSources || hasAnnotationProcessorClasspath) { + if (hasExplicitlyDefinedGeneratedSources) { def files = new HashSet() - if (hasAnnotationProcessorClasspath && generatedSourcesOutput != null) { - files.addAll(generatedSourcesOutput.files) - } for (File file : generatedSourceDirs) { if (javaDirectorySet.srcDirs.contains(file)) { files.add(file) diff --git a/plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/internal/AnnotationProcessingConfigImpl.java b/plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/internal/AnnotationProcessingConfigImpl.java index 2c5db76e6e95..82060525846b 100644 --- a/plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/internal/AnnotationProcessingConfigImpl.java +++ b/plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/internal/AnnotationProcessingConfigImpl.java @@ -3,6 +3,7 @@ package org.jetbrains.plugins.gradle.tooling.internal; import org.gradle.internal.impldep.com.google.common.base.Objects; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.gradle.model.AnnotationProcessingConfig; import java.io.Serializable; @@ -13,8 +14,12 @@ import java.util.Set; public class AnnotationProcessingConfigImpl implements AnnotationProcessingConfig, Serializable { private final Set myPaths; private final List myArgs; + private final String myProcessorOutput; + private final boolean isTestSources; - public AnnotationProcessingConfigImpl(Set files, List args) { + public AnnotationProcessingConfigImpl(Set files, List args, String output, boolean sources) { + myProcessorOutput = output; + isTestSources = sources; myPaths = files; myArgs = args; } @@ -30,18 +35,30 @@ public class AnnotationProcessingConfigImpl implements AnnotationProcessingConfi public Collection getAnnotationProcessorArguments() { return myArgs; } + @Override + public boolean isTestSources() { + return isTestSources; + } + + @Nullable + @Override + public String getProcessorOutput() { + return myProcessorOutput; + } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; AnnotationProcessingConfigImpl config = (AnnotationProcessingConfigImpl)o; - return Objects.equal(myPaths, config.myPaths) && + return myProcessorOutput == config.myProcessorOutput && + isTestSources == config.isTestSources && + Objects.equal(myPaths, config.myPaths) && Objects.equal(myArgs, config.myArgs); } @Override public int hashCode() { - return Objects.hashCode(myPaths, myArgs); + return Objects.hashCode(myPaths, myArgs, myProcessorOutput, isTestSources); } } diff --git a/plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/serialization/AnnotationProcessingModelSerializationService.java b/plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/serialization/AnnotationProcessingModelSerializationService.java index 4386c78a9a79..186a6bf9c2d0 100644 --- a/plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/serialization/AnnotationProcessingModelSerializationService.java +++ b/plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/serialization/AnnotationProcessingModelSerializationService.java @@ -16,7 +16,6 @@ import org.jetbrains.plugins.gradle.tooling.util.IntObjectMap; import org.jetbrains.plugins.gradle.tooling.util.ObjectCollector; import java.io.ByteArrayOutputStream; -import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; @@ -97,6 +96,8 @@ public class AnnotationProcessingModelSerializationService implements Serializat if (isAdded) { writeStrings(writer, "args", config.getAnnotationProcessorArguments()); writeStrings(writer, "paths", config.getAnnotationProcessorPath()); + writeString(writer, "output", config.getProcessorOutput()); + writeBoolean(writer, "isTestSources", config.isTestSources()); } writer.stepOut(); } @@ -144,7 +145,9 @@ public class AnnotationProcessingModelSerializationService implements Serializat public AnnotationProcessingConfigImpl create() { List args = readStringList(reader); Set files = readStringSet(reader); - return new AnnotationProcessingConfigImpl(files, args); + String output = readString(reader, "output"); + boolean isTest = readBoolean(reader,"isTestSources"); + return new AnnotationProcessingConfigImpl(files, args, output, isTest); } }); reader.stepOut();