| // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. | |
| package org.jetbrains.plugins.gradle.config; | |
| import com.intellij.ProjectTopics; | |
| import com.intellij.compiler.server.BuildManager; | |
| import com.intellij.facet.Facet; | |
| import com.intellij.facet.FacetManager; | |
| import com.intellij.openapi.compiler.CompileContext; | |
| import com.intellij.openapi.compiler.CompilerMessageCategory; | |
| import com.intellij.openapi.diagnostic.Logger; | |
| import com.intellij.openapi.externalSystem.model.project.ExternalSystemSourceType; | |
| import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil; | |
| import com.intellij.openapi.module.Module; | |
| import com.intellij.openapi.project.ModuleListener; | |
| import com.intellij.openapi.project.Project; | |
| import com.intellij.openapi.project.ProjectType; | |
| import com.intellij.openapi.project.ProjectTypeService; | |
| import com.intellij.openapi.roots.CompilerModuleExtension; | |
| import com.intellij.openapi.roots.ModuleRootManager; | |
| import com.intellij.openapi.util.JDOMUtil; | |
| import com.intellij.openapi.util.io.FileUtil; | |
| import com.intellij.openapi.vfs.VfsUtilCore; | |
| import com.intellij.openapi.vfs.VirtualFile; | |
| import com.intellij.util.ArrayUtil; | |
| import com.intellij.util.Function; | |
| import com.intellij.util.JdomKt; | |
| import com.intellij.util.containers.ContainerUtil; | |
| import com.intellij.util.containers.FactoryMap; | |
| import com.intellij.util.xmlb.XmlSerializer; | |
| import gnu.trove.THashMap; | |
| import org.jdom.Element; | |
| import org.jetbrains.annotations.NotNull; | |
| import org.jetbrains.annotations.Nullable; | |
| import org.jetbrains.jps.gradle.model.impl.*; | |
| import org.jetbrains.plugins.gradle.model.ExternalFilter; | |
| import org.jetbrains.plugins.gradle.model.ExternalProject; | |
| import org.jetbrains.plugins.gradle.model.ExternalSourceDirectorySet; | |
| import org.jetbrains.plugins.gradle.model.ExternalSourceSet; | |
| import org.jetbrains.plugins.gradle.service.project.data.ExternalProjectDataCache; | |
| import org.jetbrains.plugins.gradle.util.GradleConstants; | |
| import java.io.File; | |
| import java.io.IOException; | |
| import java.util.Iterator; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.Set; | |
| /** | |
| * @author Vladislav.Soroka | |
| */ | |
| public class GradleResourceCompilerConfigurationGenerator { | |
| private static final Logger LOG = Logger.getInstance(GradleResourceCompilerConfigurationGenerator.class); | |
| @NotNull | |
| private final Project myProject; | |
| @NotNull | |
| private final Map<String, Integer> myModulesConfigurationHash; | |
| private final ExternalProjectDataCache externalProjectDataCache; | |
| public GradleResourceCompilerConfigurationGenerator(@NotNull final Project project) { | |
| myProject = project; | |
| myModulesConfigurationHash = ContainerUtil.newConcurrentMap(); | |
| externalProjectDataCache = ExternalProjectDataCache.getInstance(project); | |
| assert externalProjectDataCache != null; | |
| project.getMessageBus().connect().subscribe(ProjectTopics.MODULES, new ModuleListener() { | |
| @Override | |
| public void moduleRemoved(@NotNull Project project, @NotNull Module module) { | |
| myModulesConfigurationHash.remove(module.getName()); | |
| } | |
| @Override | |
| public void modulesRenamed(@NotNull Project project, | |
| @NotNull List<Module> modules, | |
| @NotNull Function<Module, String> oldNameProvider) { | |
| for (Module module : modules) { | |
| moduleRemoved(project, module); | |
| } | |
| } | |
| }); | |
| } | |
| public void generateBuildConfiguration(@NotNull final CompileContext context) { | |
| if (shouldBeBuiltByExternalSystem(myProject)) return; | |
| if (!hasGradleModules(context)) return; | |
| final BuildManager buildManager = BuildManager.getInstance(); | |
| final File projectSystemDir = buildManager.getProjectSystemDirectory(myProject); | |
| if (projectSystemDir == null) return; | |
| final File gradleConfigFile = new File(projectSystemDir, GradleProjectConfiguration.CONFIGURATION_FILE_RELATIVE_PATH); | |
| final Map<String, GradleModuleResourceConfiguration> affectedGradleModuleConfigurations = | |
| generateAffectedGradleModulesConfiguration(context); | |
| if (affectedGradleModuleConfigurations.isEmpty()) return; | |
| boolean configurationUpdateRequired = context.isRebuild() || !gradleConfigFile.exists(); | |
| final Map<String, Integer> affectedConfigurationHash = new THashMap<>(); | |
| for (Map.Entry<String, GradleModuleResourceConfiguration> entry : affectedGradleModuleConfigurations.entrySet()) { | |
| Integer moduleLastConfigurationHash = myModulesConfigurationHash.get(entry.getKey()); | |
| int moduleCurrentConfigurationHash = entry.getValue().computeConfigurationHash(); | |
| if (moduleLastConfigurationHash == null || moduleLastConfigurationHash.intValue() != moduleCurrentConfigurationHash) { | |
| configurationUpdateRequired = true; | |
| } | |
| affectedConfigurationHash.put(entry.getKey(), moduleCurrentConfigurationHash); | |
| } | |
| final GradleProjectConfiguration projectConfig = loadLastConfiguration(gradleConfigFile); | |
| projectConfig.moduleConfigurations.putAll(affectedGradleModuleConfigurations); | |
| final Element element = new Element("gradle-project-configuration"); | |
| XmlSerializer.serializeInto(projectConfig, element); | |
| final boolean finalConfigurationUpdateRequired = configurationUpdateRequired; | |
| buildManager.runCommand(() -> { | |
| if (finalConfigurationUpdateRequired) { | |
| buildManager.clearState(myProject); | |
| } | |
| FileUtil.createIfDoesntExist(gradleConfigFile); | |
| try { | |
| JdomKt.write(element, gradleConfigFile.toPath()); | |
| myModulesConfigurationHash.putAll(affectedConfigurationHash); | |
| } | |
| catch (IOException e) { | |
| throw new RuntimeException(e); | |
| } | |
| }); | |
| } | |
| @NotNull | |
| private GradleProjectConfiguration loadLastConfiguration(@NotNull File gradleConfigFile) { | |
| final GradleProjectConfiguration projectConfig = new GradleProjectConfiguration(); | |
| if (gradleConfigFile.exists()) { | |
| try { | |
| XmlSerializer.deserializeInto(projectConfig, JDOMUtil.load(gradleConfigFile)); | |
| // filter orphan modules | |
| final Set<String> actualModules = myModulesConfigurationHash.keySet(); | |
| for (Iterator<Map.Entry<String, GradleModuleResourceConfiguration>> iterator = | |
| projectConfig.moduleConfigurations.entrySet().iterator(); iterator.hasNext(); ) { | |
| Map.Entry<String, GradleModuleResourceConfiguration> configurationEntry = iterator.next(); | |
| if (!actualModules.contains(configurationEntry.getKey())) { | |
| iterator.remove(); | |
| } | |
| } | |
| } | |
| catch (Exception e) { | |
| LOG.info(e); | |
| } | |
| } | |
| return projectConfig; | |
| } | |
| @NotNull | |
| private Map<String, GradleModuleResourceConfiguration> generateAffectedGradleModulesConfiguration(@NotNull CompileContext context) { | |
| final Map<String, GradleModuleResourceConfiguration> affectedGradleModuleConfigurations = new THashMap<>(); | |
| final Map<String, ExternalProject> lazyExternalProjectMap = FactoryMap.create( | |
| gradleProjectPath1 -> externalProjectDataCache.getRootExternalProject(gradleProjectPath1)); | |
| for (Module module : context.getCompileScope().getAffectedModules()) { | |
| if (!ExternalSystemApiUtil.isExternalSystemAwareModule(GradleConstants.SYSTEM_ID, module)) continue; | |
| final String gradleProjectPath = ExternalSystemApiUtil.getExternalRootProjectPath(module); | |
| assert gradleProjectPath != null; | |
| if (shouldBeBuiltByExternalSystem(module)) continue; | |
| final ExternalProject externalRootProject = lazyExternalProjectMap.get(gradleProjectPath); | |
| if (externalRootProject == null) { | |
| context.addMessage(CompilerMessageCategory.WARNING, | |
| String.format("Unable to make the module: %s, related gradle configuration was not found. " + | |
| "Please, re-import the Gradle project and try again.", | |
| module.getName()), VfsUtilCore.pathToUrl(gradleProjectPath), -1, -1); | |
| continue; | |
| } | |
| Map<String, ExternalSourceSet> externalSourceSets = externalProjectDataCache.findExternalProject(externalRootProject, module); | |
| if (externalSourceSets.isEmpty()) { | |
| LOG.debug("Unable to find source sets config for module: " + module.getName()); | |
| continue; | |
| } | |
| VirtualFile[] sourceRoots = ModuleRootManager.getInstance(module).getSourceRoots(true); | |
| if (sourceRoots.length == 0) continue; | |
| GradleModuleResourceConfiguration resourceConfig = new GradleModuleResourceConfiguration(); | |
| resourceConfig.id = new ModuleVersion( | |
| ExternalSystemApiUtil.getExternalProjectGroup(module), | |
| ExternalSystemApiUtil.getExternalProjectId(module), | |
| ExternalSystemApiUtil.getExternalProjectVersion(module)); | |
| for (ExternalSourceSet sourceSet : externalSourceSets.values()) { | |
| addResources(resourceConfig.resources, sourceSet.getSources().get(ExternalSystemSourceType.RESOURCE), | |
| sourceSet.getSources().get(ExternalSystemSourceType.SOURCE)); | |
| addResources(resourceConfig.testResources, sourceSet.getSources().get(ExternalSystemSourceType.TEST_RESOURCE), | |
| sourceSet.getSources().get(ExternalSystemSourceType.TEST)); | |
| } | |
| final CompilerModuleExtension compilerModuleExtension = CompilerModuleExtension.getInstance(module); | |
| if (compilerModuleExtension != null && compilerModuleExtension.isCompilerOutputPathInherited()) { | |
| String outputPath = VfsUtilCore.urlToPath(compilerModuleExtension.getCompilerOutputUrl()); | |
| for (ResourceRootConfiguration resource : resourceConfig.resources) { | |
| resource.targetPath = outputPath; | |
| } | |
| String testOutputPath = VfsUtilCore.urlToPath(compilerModuleExtension.getCompilerOutputUrlForTests()); | |
| for (ResourceRootConfiguration resource : resourceConfig.testResources) { | |
| resource.targetPath = testOutputPath; | |
| } | |
| } | |
| affectedGradleModuleConfigurations.put(module.getName(), resourceConfig); | |
| } | |
| return affectedGradleModuleConfigurations; | |
| } | |
| private static boolean shouldBeBuiltByExternalSystem(@NotNull Project project) { | |
| // skip resource compilation by IDE for Android projects | |
| // TODO [vlad] this check should be replaced when an option to make any gradle project with gradle be introduced. | |
| ProjectType projectType = ProjectTypeService.getProjectType(project); | |
| if (projectType != null && "Android".equals(projectType.getId())) return true; | |
| return false; | |
| } | |
| private static boolean shouldBeBuiltByExternalSystem(@NotNull Module module) { | |
| for (Facet facet : FacetManager.getInstance(module).getAllFacets()) { | |
| if (ArrayUtil.contains(facet.getName(), "Android", "Android-Gradle", "Java-Gradle")) return true; | |
| } | |
| return false; | |
| } | |
| private static boolean hasGradleModules(@NotNull CompileContext context) { | |
| for (Module module : context.getCompileScope().getAffectedModules()) { | |
| if (ExternalSystemApiUtil.isExternalSystemAwareModule(GradleConstants.SYSTEM_ID, module)) return true; | |
| } | |
| return false; | |
| } | |
| private static void addResources(@NotNull List<ResourceRootConfiguration> container, | |
| @Nullable final ExternalSourceDirectorySet directorySet, | |
| @Nullable final ExternalSourceDirectorySet sourcesDirectorySet) { | |
| if (directorySet == null) return; | |
| for (File file : directorySet.getSrcDirs()) { | |
| final String dir = file.getPath(); | |
| final ResourceRootConfiguration rootConfiguration = new ResourceRootConfiguration(); | |
| rootConfiguration.directory = FileUtil.toSystemIndependentName(dir); | |
| final String target = directorySet.getOutputDir().getPath(); | |
| rootConfiguration.targetPath = FileUtil.toSystemIndependentName(target); | |
| rootConfiguration.includes.clear(); | |
| for (String include : directorySet.getPatterns().getIncludes()) { | |
| rootConfiguration.includes.add(include.trim()); | |
| } | |
| rootConfiguration.excludes.clear(); | |
| for (String exclude : directorySet.getPatterns().getExcludes()) { | |
| rootConfiguration.excludes.add(exclude.trim()); | |
| } | |
| if (sourcesDirectorySet != null && sourcesDirectorySet.getSrcDirs().contains(file)) { | |
| rootConfiguration.excludes.add("**/*.java"); | |
| rootConfiguration.excludes.add("**/*.scala"); | |
| rootConfiguration.excludes.add("**/*.groovy"); | |
| rootConfiguration.excludes.add("**/*.kt"); | |
| } | |
| rootConfiguration.isFiltered = !directorySet.getFilters().isEmpty(); | |
| rootConfiguration.filters.clear(); | |
| for (ExternalFilter filter : directorySet.getFilters()) { | |
| final ResourceRootFilter resourceRootFilter = new ResourceRootFilter(); | |
| resourceRootFilter.filterType = filter.getFilterType(); | |
| resourceRootFilter.properties = filter.getPropertiesAsJsonMap(); | |
| rootConfiguration.filters.add(resourceRootFilter); | |
| } | |
| container.add(rootConfiguration); | |
| } | |
| } | |
| } |