/* * Copyright 2000-2016 JetBrains s.r.o. * * 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 org.jetbrains.plugins.gradle.service.project; import com.intellij.execution.configurations.ParametersList; import com.intellij.externalSystem.JavaProjectData; import com.intellij.openapi.application.ex.ApplicationInfoEx; import com.intellij.openapi.application.impl.ApplicationInfoImpl; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.externalSystem.model.DataNode; import com.intellij.openapi.externalSystem.model.ExternalSystemException; import com.intellij.openapi.externalSystem.model.ProjectKeys; import com.intellij.openapi.externalSystem.model.project.*; import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId; import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener; import com.intellij.openapi.externalSystem.service.project.ExternalSystemProjectResolver; import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil; import com.intellij.openapi.externalSystem.util.ExternalSystemDebugEnvironment; import com.intellij.openapi.module.ModuleGrouperKt; import com.intellij.openapi.module.StdModuleTypes; import com.intellij.openapi.roots.DependencyScope; import com.intellij.openapi.util.Factory; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.Function; import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.ContainerUtilRt; import com.intellij.util.containers.MultiMap; import org.gradle.tooling.*; import org.gradle.tooling.model.DomainObjectSet; import org.gradle.tooling.model.GradleProject; import org.gradle.tooling.model.build.BuildEnvironment; import org.gradle.tooling.model.gradle.GradleBuild; import org.gradle.tooling.model.idea.BasicIdeaProject; import org.gradle.tooling.model.idea.IdeaModule; import org.gradle.tooling.model.idea.IdeaProject; import org.gradle.util.GradleVersion; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.gradle.model.*; import org.jetbrains.plugins.gradle.model.data.BuildParticipant; import org.jetbrains.plugins.gradle.model.data.CompositeBuildData; import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData; import org.jetbrains.plugins.gradle.remote.impl.GradleLibraryNamesMixer; import org.jetbrains.plugins.gradle.service.execution.GradleExecutionHelper; import org.jetbrains.plugins.gradle.service.execution.UnsupportedCancellationToken; import org.jetbrains.plugins.gradle.settings.ClassHolder; import org.jetbrains.plugins.gradle.settings.DistributionType; import org.jetbrains.plugins.gradle.settings.GradleBuildParticipant; import org.jetbrains.plugins.gradle.settings.GradleExecutionSettings; import org.jetbrains.plugins.gradle.util.GradleConstants; import java.io.File; import java.io.IOException; import java.util.*; import static org.jetbrains.plugins.gradle.service.project.GradleProjectResolverUtil.attachGradleSdkSources; import static org.jetbrains.plugins.gradle.service.project.GradleProjectResolverUtil.getModuleId; /** * @author Denis Zhdanov, Vladislav Soroka * @since 8/8/11 11:09 AM */ public class GradleProjectResolver implements ExternalSystemProjectResolver<GradleExecutionSettings> { private static final Logger LOG = Logger.getInstance(GradleProjectResolver.class); @NotNull private final GradleExecutionHelper myHelper; private final GradleLibraryNamesMixer myLibraryNamesMixer = new GradleLibraryNamesMixer(); private final MultiMap<ExternalSystemTaskId, CancellationTokenSource> myCancellationMap = MultiMap.create(); public static final Key<Map<String/* module id */, Pair<DataNode<GradleSourceSetData>, ExternalSourceSet>>> RESOLVED_SOURCE_SETS = Key.create("resolvedSourceSets"); public static final Key<Map<String/* output path */, Pair<String /* module id*/, ExternalSystemSourceType>>> MODULES_OUTPUTS = Key.create("moduleOutputsMap"); public static final Key<Map<ExternalSystemSourceType, String /* output path*/>> GRADLE_OUTPUTS = Key.create("gradleOutputs"); public static final Key<Map<String/* artifact path */, String /* module id*/>> CONFIGURATION_ARTIFACTS = Key.create("gradleArtifactsMap"); // This constructor is called by external system API, see AbstractExternalSystemFacadeImpl class constructor. @SuppressWarnings("UnusedDeclaration") public GradleProjectResolver() { this(new GradleExecutionHelper()); } public GradleProjectResolver(@NotNull GradleExecutionHelper helper) { myHelper = helper; } @Nullable @Override public DataNode<ProjectData> resolveProjectInfo(@NotNull final ExternalSystemTaskId id, @NotNull final String projectPath, final boolean isPreviewMode, @Nullable final GradleExecutionSettings settings, @NotNull final ExternalSystemTaskNotificationListener listener) throws ExternalSystemException, IllegalArgumentException, IllegalStateException { if (isPreviewMode) { // Create project preview model w/o request to gradle, there are two main reasons for the it: // * Slow project open - even the simplest project info provided by gradle can be gathered too long (mostly because of new gradle distribution download and downloading buildscript dependencies) // * Ability to open an invalid projects (e.g. with errors in build scripts) String projectName = new File(projectPath).getName(); ProjectData projectData = new ProjectData(GradleConstants.SYSTEM_ID, projectName, projectPath, projectPath); DataNode<ProjectData> projectDataNode = new DataNode<>(ProjectKeys.PROJECT, projectData, null); final String ideProjectPath = settings == null ? null : settings.getIdeProjectPath(); final String mainModuleFileDirectoryPath = ideProjectPath == null ? projectPath : ideProjectPath; projectDataNode .createChild(ProjectKeys.MODULE, new ModuleData(projectName, GradleConstants.SYSTEM_ID, StdModuleTypes.JAVA.getId(), projectName, mainModuleFileDirectoryPath, projectPath)) .createChild(ProjectKeys.CONTENT_ROOT, new ContentRootData(GradleConstants.SYSTEM_ID, projectPath)); return projectDataNode; } if (settings != null) { myHelper.ensureInstalledWrapper(id, projectPath, settings, listener); } final GradleProjectResolverExtension projectResolverChain = createProjectResolverChain(settings); DefaultProjectResolverContext resolverContext = new DefaultProjectResolverContext(id, projectPath, settings, listener, false); final DataNode<ProjectData> resultProjectDataNode = myHelper.execute( projectPath, settings, new ProjectConnectionDataNodeFunction(resolverContext, projectResolverChain, false) ); // auto-discover buildSrc project if needed final String buildSrcProjectPath = projectPath + "/buildSrc"; DefaultProjectResolverContext buildSrcResolverCtx = new DefaultProjectResolverContext(id, buildSrcProjectPath, settings, listener, false); resolverContext.copyUserDataTo(buildSrcResolverCtx); handleBuildSrcProject(resultProjectDataNode, new ProjectConnectionDataNodeFunction(buildSrcResolverCtx, projectResolverChain, true)); return resultProjectDataNode; } @Override public boolean cancelTask(@NotNull ExternalSystemTaskId id, @NotNull ExternalSystemTaskNotificationListener listener) { synchronized (myCancellationMap) { for (CancellationTokenSource cancellationTokenSource : myCancellationMap.get(id)) { cancellationTokenSource.cancel(); } } return true; } @NotNull private DataNode<ProjectData> doResolveProjectInfo(@NotNull final DefaultProjectResolverContext resolverCtx, @NotNull final GradleProjectResolverExtension projectResolverChain, boolean isBuildSrcProject) throws IllegalArgumentException, IllegalStateException { final BuildEnvironment buildEnvironment = GradleExecutionHelper.getBuildEnvironment(resolverCtx); GradleVersion gradleVersion = null; boolean isGradleProjectDirSupported = false; boolean isCompositeBuildsSupported = false; if (buildEnvironment != null) { gradleVersion = GradleVersion.version(buildEnvironment.getGradle().getGradleVersion()); isGradleProjectDirSupported = gradleVersion.compareTo(GradleVersion.version("2.4")) >= 0; isCompositeBuildsSupported = isGradleProjectDirSupported && gradleVersion.compareTo(GradleVersion.version("3.1")) >= 0; } final ProjectImportAction projectImportAction = new ProjectImportAction(resolverCtx.isPreviewMode(), isGradleProjectDirSupported, isCompositeBuildsSupported); GradleExecutionSettings executionSettings = resolverCtx.getSettings(); if (executionSettings == null) { executionSettings = new GradleExecutionSettings(null, null, DistributionType.BUNDLED, false); } executionSettings.withArgument("-Didea.version=" + getIdeaVersion()); if(resolverCtx.isPreviewMode()){ executionSettings.withArgument("-Didea.isPreviewMode=true"); final Set<Class> previewLightWeightToolingModels = ContainerUtil.set(ExternalProjectPreview.class, GradleBuild.class); projectImportAction.addExtraProjectModelClasses(previewLightWeightToolingModels); } if(resolverCtx.isResolveModulePerSourceSet()) { executionSettings.withArgument("-Didea.resolveSourceSetDependencies=true"); } if (!isBuildSrcProject) { for (GradleBuildParticipant buildParticipant : executionSettings.getExecutionWorkspace().getBuildParticipants()) { executionSettings.withArguments(GradleConstants.INCLUDE_BUILD_CMD_OPTION, buildParticipant.getProjectPath()); } } final Set<Class> toolingExtensionClasses = ContainerUtil.newHashSet(); final GradleImportCustomizer importCustomizer = GradleImportCustomizer.get(); for (GradleProjectResolverExtension resolverExtension = projectResolverChain; resolverExtension != null; resolverExtension = resolverExtension.getNext()) { // inject ProjectResolverContext into gradle project resolver extensions resolverExtension.setProjectResolverContext(resolverCtx); // pre-import checks resolverExtension.preImportCheck(); if(!resolverCtx.isPreviewMode()){ // register classes of extra gradle project models required for extensions (e.g. com.android.builder.model.AndroidProject) projectImportAction.addExtraProjectModelClasses(resolverExtension.getExtraProjectModelClasses()); } if (importCustomizer == null || importCustomizer.useExtraJvmArgs()) { // collect extra JVM arguments provided by gradle project resolver extensions final ParametersList parametersList = new ParametersList(); for (Pair<String, String> jvmArg : resolverExtension.getExtraJvmArgs()) { parametersList.addProperty(jvmArg.first, jvmArg.second); } executionSettings.withVmOptions(parametersList.getParameters()); } // collect extra command-line arguments executionSettings.withArguments(resolverExtension.getExtraCommandLineArgs()); // collect tooling extensions classes toolingExtensionClasses.addAll(resolverExtension.getToolingExtensionsClasses()); } BuildActionExecuter<ProjectImportAction.AllModels> buildActionExecutor = resolverCtx.getConnection().action(projectImportAction); File initScript = GradleExecutionHelper.generateInitScript(isBuildSrcProject, toolingExtensionClasses); if (initScript != null) { executionSettings.withArguments(GradleConstants.INIT_SCRIPT_CMD_OPTION, initScript.getAbsolutePath()); } GradleExecutionHelper.prepare(buildActionExecutor, resolverCtx.getExternalSystemTaskId(), executionSettings, resolverCtx.getListener(), resolverCtx.getConnection()); resolverCtx.checkCancelled(); ProjectImportAction.AllModels allModels; final CancellationTokenSource cancellationTokenSource = GradleConnector.newCancellationTokenSource(); final long startTime = System.currentTimeMillis(); try { resolverCtx.setCancellationTokenSource(cancellationTokenSource); buildActionExecutor.withCancellationToken(cancellationTokenSource.token()); synchronized (myCancellationMap) { myCancellationMap.putValue(resolverCtx.getExternalSystemTaskId(), cancellationTokenSource); if (gradleVersion != null && gradleVersion.compareTo(GradleVersion.version("2.1")) < 0) { myCancellationMap.putValue(resolverCtx.getExternalSystemTaskId(), new UnsupportedCancellationToken()); } } allModels = buildActionExecutor.run(); if (allModels == null) { throw new IllegalStateException("Unable to get project model for the project: " + resolverCtx.getProjectPath()); } } catch (UnsupportedVersionException unsupportedVersionException) { resolverCtx.checkCancelled(); // Old gradle distribution version used (before ver. 1.8) // fallback to use ModelBuilder gradle tooling API Class<? extends IdeaProject> aClass = resolverCtx.isPreviewMode() ? BasicIdeaProject.class : IdeaProject.class; ModelBuilder<? extends IdeaProject> modelBuilder = myHelper.getModelBuilder( aClass, resolverCtx.getExternalSystemTaskId(), executionSettings, resolverCtx.getConnection(), resolverCtx.getListener()); final IdeaProject ideaProject = modelBuilder.get(); allModels = new ProjectImportAction.AllModels(ideaProject); } finally { final long timeInMs = (System.currentTimeMillis() - startTime); synchronized (myCancellationMap) { myCancellationMap.remove(resolverCtx.getExternalSystemTaskId(), cancellationTokenSource); } LOG.debug(String.format("Gradle data obtained in %d ms", timeInMs)); } resolverCtx.checkCancelled(); allModels.setBuildEnvironment(buildEnvironment); final long startDataConversionTime = System.currentTimeMillis(); extractExternalProjectModels(allModels, resolverCtx); // import project data ProjectData projectData = projectResolverChain.createProject(); DataNode<ProjectData> projectDataNode = new DataNode<>(ProjectKeys.PROJECT, projectData, null); // import java project data JavaProjectData javaProjectData = projectResolverChain.createJavaProjectData(); projectDataNode.createChild(JavaProjectData.KEY, javaProjectData); IdeaProject ideaProject = resolverCtx.getModels().getIdeaProject(); projectResolverChain.populateProjectExtraModels(ideaProject, projectDataNode); DomainObjectSet<? extends IdeaModule> gradleModules = ideaProject.getModules(); if (gradleModules == null || gradleModules.isEmpty()) { throw new IllegalStateException("No modules found for the target project: " + ideaProject); } Collection<IdeaModule> includedModules = exposeCompositeBuild(allModels, resolverCtx, projectDataNode); final Map<String /* module id */, Pair<DataNode<ModuleData>, IdeaModule>> moduleMap = ContainerUtilRt.newHashMap(); final Map<String /* module id */, Pair<DataNode<GradleSourceSetData>, ExternalSourceSet>> sourceSetsMap = ContainerUtil.newHashMap(); projectDataNode.putUserData(RESOLVED_SOURCE_SETS, sourceSetsMap); final Map<String/* output path */, Pair<String /* module id*/, ExternalSystemSourceType>> moduleOutputsMap = ContainerUtil.newTroveMap(FileUtil.PATH_HASHING_STRATEGY); projectDataNode.putUserData(MODULES_OUTPUTS, moduleOutputsMap); final Map<String/* artifact path */, String /* module id*/> artifactsMap = ContainerUtil.newTroveMap(FileUtil.PATH_HASHING_STRATEGY); projectDataNode.putUserData(CONFIGURATION_ARTIFACTS, artifactsMap); // import modules data for (IdeaModule gradleModule : ContainerUtil.concat(gradleModules, includedModules)) { if (gradleModule == null) { continue; } resolverCtx.checkCancelled(); if (ExternalSystemDebugEnvironment.DEBUG_ORPHAN_MODULES_PROCESSING) { LOG.info(String.format("Importing module data: %s", gradleModule)); } final String moduleName = gradleModule.getName(); if (moduleName == null) { throw new IllegalStateException("Module with undefined name detected: " + gradleModule); } DataNode<ModuleData> moduleDataNode = projectResolverChain.createModule(gradleModule, projectDataNode); String mainModuleId = getModuleId(resolverCtx, gradleModule); if (moduleMap.containsKey(mainModuleId)) { // we should ensure deduplicated module names in the scope of single import throw new IllegalStateException("Duplicate modules names detected: " + gradleModule); } moduleMap.put(mainModuleId, Pair.create(moduleDataNode, gradleModule)); } executionSettings.getExecutionWorkspace().addModuleMap(moduleMap); File gradleHomeDir = null; // populate modules nodes for (final Pair<DataNode<ModuleData>, IdeaModule> pair : moduleMap.values()) { final DataNode<ModuleData> moduleDataNode = pair.first; final IdeaModule ideaModule = pair.second; if (gradleHomeDir == null) { final BuildScriptClasspathModel buildScriptClasspathModel = resolverCtx.getExtraProject(ideaModule, BuildScriptClasspathModel.class); if (buildScriptClasspathModel != null) { gradleHomeDir = buildScriptClasspathModel.getGradleHomeDir(); } } projectResolverChain.populateModuleContentRoots(ideaModule, moduleDataNode); projectResolverChain.populateModuleCompileOutputSettings(ideaModule, moduleDataNode); if (!isBuildSrcProject) { projectResolverChain.populateModuleTasks(ideaModule, moduleDataNode, projectDataNode); } final List<DataNode<? extends ModuleData>> modules = ContainerUtil.newSmartList(); modules.add(moduleDataNode); modules.addAll(ExternalSystemApiUtil.findAll(moduleDataNode, GradleSourceSetData.KEY)); final ExternalSystemSourceType[] sourceTypes = new ExternalSystemSourceType[]{ ExternalSystemSourceType.SOURCE, ExternalSystemSourceType.RESOURCE, ExternalSystemSourceType.TEST, ExternalSystemSourceType.TEST_RESOURCE }; for (DataNode<? extends ModuleData> module : modules) { final ModuleData moduleData = module.getData(); for (ExternalSystemSourceType sourceType : sourceTypes) { final String path = moduleData.getCompileOutputPath(sourceType); if (path != null) { moduleOutputsMap.put(path, Pair.create(moduleData.getId(), sourceType)); } } if (moduleData instanceof GradleSourceSetData) { for (File artifactFile : moduleData.getArtifacts()) { artifactsMap.put(ExternalSystemApiUtil.toCanonicalPath(artifactFile.getAbsolutePath()), moduleData.getId()); } } } } for (final Pair<DataNode<ModuleData>, IdeaModule> pair : moduleMap.values()) { final DataNode<ModuleData> moduleDataNode = pair.first; final IdeaModule ideaModule = pair.second; projectResolverChain.populateModuleDependencies(ideaModule, moduleDataNode, projectDataNode); projectResolverChain.populateModuleExtraModels(ideaModule, moduleDataNode); } mergeSourceSetContentRoots(moduleMap, resolverCtx); if(resolverCtx.isResolveModulePerSourceSet()) { mergeLibraryAndModuleDependencyData(projectDataNode, gradleHomeDir, gradleVersion); } projectDataNode.putUserData(RESOLVED_SOURCE_SETS, null); projectDataNode.putUserData(MODULES_OUTPUTS, null); projectDataNode.putUserData(CONFIGURATION_ARTIFACTS, null); // ensure unique library names Collection<DataNode<LibraryData>> libraries = ExternalSystemApiUtil.getChildren(projectDataNode, ProjectKeys.LIBRARY); myLibraryNamesMixer.mixNames(libraries); final long timeConversionInMs = (System.currentTimeMillis() - startDataConversionTime); LOG.debug(String.format("Project data resolved in %d ms", timeConversionInMs)); return projectDataNode; } @NotNull private static Collection<IdeaModule> exposeCompositeBuild(ProjectImportAction.AllModels allModels, DefaultProjectResolverContext resolverCtx, DataNode<ProjectData> projectDataNode) { if(resolverCtx.getSettings() != null && !resolverCtx.getSettings().getExecutionWorkspace().getBuildParticipants().isEmpty()) { return Collections.emptyList(); } CompositeBuildData compositeBuildData; List<IdeaModule> gradleIncludedModules = new SmartList<>(); List<IdeaProject> includedBuilds = allModels.getIncludedBuilds(); if (!includedBuilds.isEmpty()) { ProjectData projectData = projectDataNode.getData(); compositeBuildData = new CompositeBuildData(projectData.getLinkedExternalProjectPath()); for (IdeaProject project : includedBuilds) { if (!project.getModules().isEmpty()) { String rootProjectName = project.getName(); BuildParticipant buildParticipant = new BuildParticipant(); gradleIncludedModules.addAll(project.getModules()); GradleProject gradleProject = project.getModules().getAt(0).getGradleProject(); String projectPath = null; do { try { projectPath = ExternalSystemApiUtil.toCanonicalPath(gradleProject.getProjectDirectory().getCanonicalPath()); } catch (IOException e) { LOG.warn("construction of the canonical path for the module fails", e); } } while ((gradleProject = gradleProject.getParent()) != null); if (projectPath != null) { buildParticipant.setRootProjectName(rootProjectName); buildParticipant.setRootPath(projectPath); for (IdeaModule module : project.getModules()) { try { String modulePath = ExternalSystemApiUtil.toCanonicalPath(module.getGradleProject().getProjectDirectory().getCanonicalPath()); buildParticipant.getProjects().add(modulePath); } catch (IOException e) { LOG.warn("construction of the canonical path for the module fails", e); } } compositeBuildData.getCompositeParticipants().add(buildParticipant); } } } projectDataNode.createChild(CompositeBuildData.KEY, compositeBuildData); } return gradleIncludedModules; } private static void mergeLibraryAndModuleDependencyData(DataNode<ProjectData> projectDataNode, @Nullable File gradleHomeDir, @Nullable GradleVersion gradleVersion) { final Map<String, Pair<DataNode<GradleSourceSetData>, ExternalSourceSet>> sourceSetMap = projectDataNode.getUserData(RESOLVED_SOURCE_SETS); assert sourceSetMap != null; final Map<String, Pair<String, ExternalSystemSourceType>> moduleOutputsMap = projectDataNode.getUserData(MODULES_OUTPUTS); assert moduleOutputsMap != null; final Map<String, String> artifactsMap = projectDataNode.getUserData(CONFIGURATION_ARTIFACTS); assert artifactsMap != null; final Collection<DataNode<LibraryDependencyData>> libraryDependencies = ExternalSystemApiUtil.findAllRecursively(projectDataNode, ProjectKeys.LIBRARY_DEPENDENCY); for (DataNode<LibraryDependencyData> libraryDependencyDataNode : libraryDependencies) { final DataNode<?> libraryNodeParent = libraryDependencyDataNode.getParent(); if (libraryNodeParent == null) continue; final LibraryDependencyData libraryDependencyData = libraryDependencyDataNode.getData(); final LibraryData libraryData = libraryDependencyData.getTarget(); final Set<String> libraryPaths = libraryData.getPaths(LibraryPathType.BINARY); if (libraryPaths.isEmpty()) continue; if(StringUtil.isNotEmpty(libraryData.getExternalName())) continue; boolean projectDependencyCandidate = libraryPaths.size() == 1 && !libraryDependencyDataNode.getChildren().isEmpty(); final LinkedList<String> unprocessedPaths = ContainerUtil.newLinkedList(libraryPaths); while (!unprocessedPaths.isEmpty()) { final String path = unprocessedPaths.remove(); Set<String> targetModuleOutputPaths = null; final String moduleId; final Pair<String, ExternalSystemSourceType> sourceTypePair = moduleOutputsMap.get(path); if (sourceTypePair == null) { moduleId = artifactsMap.get(path); if (moduleId != null) { targetModuleOutputPaths = ContainerUtil.set(path); } } else { moduleId = sourceTypePair.first; } if (moduleId == null) continue; final Pair<DataNode<GradleSourceSetData>, ExternalSourceSet> pair = sourceSetMap.get(moduleId); if (pair == null) { continue; } final ModuleData moduleData = pair.first.getData(); if (targetModuleOutputPaths == null) { final Set<String> compileSet = ContainerUtil.newHashSet(); Map<ExternalSystemSourceType, String> gradleOutputs = pair.first.getUserData(GRADLE_OUTPUTS); if(gradleOutputs != null) { ContainerUtil.addAllNotNull(compileSet, gradleOutputs.get(ExternalSystemSourceType.SOURCE), gradleOutputs.get(ExternalSystemSourceType.RESOURCE)); } if (!compileSet.isEmpty() && ContainerUtil.intersects(libraryPaths, compileSet)) { targetModuleOutputPaths = compileSet; } else { final Set<String> testSet = ContainerUtil.newHashSet(); if(gradleOutputs != null) { ContainerUtil.addAllNotNull(testSet, gradleOutputs.get(ExternalSystemSourceType.TEST), gradleOutputs.get(ExternalSystemSourceType.TEST_RESOURCE)); } if (!testSet.isEmpty() && ContainerUtil.intersects(libraryPaths, testSet)) { targetModuleOutputPaths = testSet; } } } final ModuleData ownerModule = libraryDependencyData.getOwnerModule(); final ModuleDependencyData moduleDependencyData = new ModuleDependencyData(ownerModule, moduleData); moduleDependencyData.setScope(libraryDependencyData.getScope()); if ("test".equals(pair.second.getName())) { moduleDependencyData.setProductionOnTestDependency(true); } final DataNode<ModuleDependencyData> found = ExternalSystemApiUtil.find( libraryNodeParent, ProjectKeys.MODULE_DEPENDENCY, node -> { if (moduleDependencyData.getInternalName().equals(node.getData().getInternalName())) { moduleDependencyData.setModuleDependencyArtifacts(node.getData().getModuleDependencyArtifacts()); } final boolean result; // ignore provided scope during the search since it can be resolved incorrectly for file dependencies on a source set outputs if(moduleDependencyData.getScope() == DependencyScope.PROVIDED) { moduleDependencyData.setScope(node.getData().getScope()); result = moduleDependencyData.equals(node.getData()); moduleDependencyData.setScope(DependencyScope.PROVIDED); } else { result = moduleDependencyData.equals(node.getData()); } return result; }); if (targetModuleOutputPaths != null) { if (found == null) { DataNode<ModuleDependencyData> moduleDependencyNode = libraryNodeParent.createChild(ProjectKeys.MODULE_DEPENDENCY, moduleDependencyData); if(projectDependencyCandidate) { for (DataNode<?> node : libraryDependencyDataNode.getChildren()) { moduleDependencyNode.addChild(node); } } } libraryPaths.removeAll(targetModuleOutputPaths); unprocessedPaths.removeAll(targetModuleOutputPaths); if (libraryPaths.isEmpty()) { libraryDependencyDataNode.clear(true); break; } continue; } else { // do not add the path as library dependency if another module dependency is already contain the path as one of its output paths if (found != null) { libraryPaths.remove(path); if (libraryPaths.isEmpty()) { libraryDependencyDataNode.clear(true); break; } continue; } } final ExternalSourceDirectorySet directorySet = pair.second.getSources().get(sourceTypePair.second); if (directorySet != null) { for (File file : directorySet.getSrcDirs()) { libraryData.addPath(LibraryPathType.SOURCE, file.getAbsolutePath()); } } } if (libraryDependencyDataNode.getParent() != null) { if (libraryPaths.size() > 1) { List<String> toRemove = ContainerUtil.newSmartList(); for (String path : libraryPaths) { final File binaryPath = new File(path); if (binaryPath.isFile()) { final LibraryData extractedLibrary = new LibraryData(libraryDependencyData.getOwner(), ""); extractedLibrary.addPath(LibraryPathType.BINARY, path); if (gradleHomeDir != null && gradleVersion != null) { attachGradleSdkSources(binaryPath, extractedLibrary, gradleHomeDir, gradleVersion); } LibraryDependencyData extractedDependencyData = new LibraryDependencyData( libraryDependencyData.getOwnerModule(), extractedLibrary, LibraryLevel.MODULE); libraryDependencyDataNode.getParent().createChild(ProjectKeys.LIBRARY_DEPENDENCY, extractedDependencyData); toRemove.add(path); } } libraryPaths.removeAll(toRemove); if (libraryPaths.isEmpty()) { libraryDependencyDataNode.clear(true); } } } } } private static void extractExternalProjectModels(@NotNull ProjectImportAction.AllModels models, @NotNull ProjectResolverContext resolverCtx) { resolverCtx.setModels(models); final Class<? extends ExternalProject> modelClazz = resolverCtx.isPreviewMode() ? ExternalProjectPreview.class : ExternalProject.class; final ExternalProject externalRootProject = models.getExtraProject((IdeaModule)null, modelClazz); if (externalRootProject == null) return; final DefaultExternalProject wrappedExternalRootProject = new DefaultExternalProject(externalRootProject); models.addExtraProject(wrappedExternalRootProject, ExternalProject.class); final Map<String, ExternalProject> externalProjectsMap = createExternalProjectsMap(null, wrappedExternalRootProject); DomainObjectSet<? extends IdeaModule> gradleModules = models.getIdeaProject().getModules(); if (gradleModules != null && !gradleModules.isEmpty()) { for (IdeaModule ideaModule : gradleModules) { final ExternalProject externalProject = externalProjectsMap.get(getModuleId(resolverCtx, ideaModule)); if (externalProject != null) { models.addExtraProject(externalProject, ExternalProject.class, ideaModule.getGradleProject()); } } } for (IdeaProject project : models.getIncludedBuilds()) { DomainObjectSet<? extends IdeaModule> ideaModules = project.getModules(); if (ideaModules.isEmpty()) continue; GradleProject gradleProject = ideaModules.getAt(0).getGradleProject(); while (gradleProject.getParent() != null) { gradleProject = gradleProject.getParent(); } final ExternalProject externalIncludedRootProject = models.getExtraProject(gradleProject, modelClazz); if (externalIncludedRootProject == null) continue; final DefaultExternalProject wrappedExternalIncludedRootProject = new DefaultExternalProject(externalIncludedRootProject); wrappedExternalRootProject.getChildProjects().put(wrappedExternalIncludedRootProject.getName(), wrappedExternalIncludedRootProject); String compositePrefix = project.getName(); final Map<String, ExternalProject> externalIncludedProjectsMap = createExternalProjectsMap(compositePrefix, wrappedExternalIncludedRootProject); for (IdeaModule ideaModule : ideaModules) { final ExternalProject externalProject = externalIncludedProjectsMap.get(getModuleId(resolverCtx, ideaModule)); if (externalProject != null) { models.addExtraProject(externalProject, ExternalProject.class, ideaModule.getGradleProject()); } } } } private static Map<String, ExternalProject> createExternalProjectsMap(@Nullable String compositePrefix, @Nullable final ExternalProject rootExternalProject) { final Map<String, ExternalProject> externalProjectMap = ContainerUtilRt.newHashMap(); if (rootExternalProject == null) return externalProjectMap; Queue<ExternalProject> queue = new LinkedList<>(); queue.add(rootExternalProject); while (!queue.isEmpty()) { ExternalProject externalProject = queue.remove(); queue.addAll(externalProject.getChildProjects().values()); final String moduleName = externalProject.getName(); final String qName = externalProject.getQName(); String moduleId = StringUtil.isEmpty(qName) || ":".equals(qName) ? moduleName : qName; if (compositePrefix != null && externalProject != rootExternalProject) { moduleId = compositePrefix + moduleId; } externalProjectMap.put(moduleId, externalProject); } return externalProjectMap; } private static class Counter { int count; void increment() { count++; } @Override public String toString() { return String.valueOf(count); } } private static void mergeSourceSetContentRoots(@NotNull Map<String, Pair<DataNode<ModuleData>, IdeaModule>> moduleMap, @NotNull ProjectResolverContext resolverCtx) { final Factory<Counter> counterFactory = () -> new Counter(); final Map<String, Counter> weightMap = ContainerUtil.newHashMap(); for (final Pair<DataNode<ModuleData>, IdeaModule> pair : moduleMap.values()) { final DataNode<ModuleData> moduleNode = pair.first; for (DataNode<ContentRootData> contentRootNode : ExternalSystemApiUtil.findAll(moduleNode, ProjectKeys.CONTENT_ROOT)) { File file = new File(contentRootNode.getData().getRootPath()); while (file != null) { ContainerUtil.getOrCreate(weightMap, file.getPath(), counterFactory).increment(); file = file.getParentFile(); } } for (DataNode<GradleSourceSetData> sourceSetNode : ExternalSystemApiUtil.findAll(moduleNode, GradleSourceSetData.KEY)) { final Set<String> set = ContainerUtil.newHashSet(); for (DataNode<ContentRootData> contentRootNode : ExternalSystemApiUtil.findAll(sourceSetNode, ProjectKeys.CONTENT_ROOT)) { File file = new File(contentRootNode.getData().getRootPath()); while (file != null) { set.add(file.getPath()); file = file.getParentFile(); } } for (String path : set) { ContainerUtil.getOrCreate(weightMap, path, counterFactory).increment(); } } } for (final Pair<DataNode<ModuleData>, IdeaModule> pair : moduleMap.values()) { final DataNode<ModuleData> moduleNode = pair.first; final ExternalProject externalProject = resolverCtx.getExtraProject(pair.second, ExternalProject.class); if (externalProject == null) continue; if (resolverCtx.isResolveModulePerSourceSet()) { for (DataNode<GradleSourceSetData> sourceSetNode : ExternalSystemApiUtil.findAll(moduleNode, GradleSourceSetData.KEY)) { mergeModuleContentRoots(weightMap, externalProject, sourceSetNode); } } else { mergeModuleContentRoots(weightMap, externalProject, moduleNode); } } } private static void mergeModuleContentRoots(@NotNull Map<String, Counter> weightMap, @NotNull ExternalProject externalProject, @NotNull DataNode<? extends ModuleData> moduleNode) { final File buildDir = externalProject.getBuildDir(); final MultiMap<String, ContentRootData> sourceSetRoots = MultiMap.create(); Collection<DataNode<ContentRootData>> contentRootNodes = ExternalSystemApiUtil.findAll(moduleNode, ProjectKeys.CONTENT_ROOT); if(contentRootNodes.size() <= 1) return; for (DataNode<ContentRootData> contentRootNode : contentRootNodes) { File root = new File(contentRootNode.getData().getRootPath()); if (FileUtil.isAncestor(buildDir, root, true)) continue; while (weightMap.containsKey(root.getParent()) && weightMap.get(root.getParent()).count <= 1) { root = root.getParentFile(); } ContentRootData mergedContentRoot = null; String rootPath = ExternalSystemApiUtil.toCanonicalPath(root.getAbsolutePath()); Set<String> paths = ContainerUtil.newHashSet(sourceSetRoots.keySet()); for (String path : paths) { if (FileUtil.isAncestor(rootPath, path, true)) { Collection<ContentRootData> values = sourceSetRoots.remove(path); if (values != null) { sourceSetRoots.putValues(rootPath, values); } } else if (FileUtil.isAncestor(path, rootPath, false)) { Collection<ContentRootData> contentRoots = sourceSetRoots.get(path); for (ContentRootData rootData : contentRoots) { if (StringUtil.equals(rootData.getRootPath(), path)) { mergedContentRoot = rootData; break; } } if (mergedContentRoot == null) { mergedContentRoot = contentRoots.iterator().next(); } break; } if(sourceSetRoots.size() == 1) break; } if (mergedContentRoot == null) { mergedContentRoot = new ContentRootData(GradleConstants.SYSTEM_ID, root.getAbsolutePath()); sourceSetRoots.putValue(mergedContentRoot.getRootPath(), mergedContentRoot); } for (ExternalSystemSourceType sourceType : ExternalSystemSourceType.values()) { for (ContentRootData.SourceRoot sourceRoot : contentRootNode.getData().getPaths(sourceType)) { mergedContentRoot.storePath(sourceType, sourceRoot.getPath(), sourceRoot.getPackagePrefix()); } } contentRootNode.clear(true); } for (Map.Entry<String, Collection<ContentRootData>> entry : sourceSetRoots.entrySet()) { final String rootPath = entry.getKey(); final ContentRootData ideContentRoot = new ContentRootData(GradleConstants.SYSTEM_ID, rootPath); for (ContentRootData rootData : entry.getValue()) { for (ExternalSystemSourceType sourceType : ExternalSystemSourceType.values()) { Collection<ContentRootData.SourceRoot> roots = rootData.getPaths(sourceType); for (ContentRootData.SourceRoot sourceRoot : roots) { ideContentRoot.storePath(sourceType, sourceRoot.getPath(), sourceRoot.getPackagePrefix()); } } } moduleNode.createChild(ProjectKeys.CONTENT_ROOT, ideContentRoot); } } private void handleBuildSrcProject(@NotNull final DataNode<ProjectData> resultProjectDataNode, @NotNull final ProjectConnectionDataNodeFunction projectConnectionDataNodeFunction) { final String projectPath = projectConnectionDataNodeFunction.myResolverContext.getProjectPath(); File projectPathFile = new File(projectPath); if (!projectPathFile.isDirectory()) { return; } if (ArrayUtil.isEmpty(projectPathFile.list((dir, name) -> !name.equals(".gradle") && !name.equals("build")))) { return; } if (projectConnectionDataNodeFunction.myResolverContext.isPreviewMode()) { ModuleData buildSrcModuleData = new ModuleData(":buildSrc", GradleConstants.SYSTEM_ID, StdModuleTypes.JAVA.getId(), "buildSrc", projectPath, projectPath); resultProjectDataNode.createChild(ProjectKeys.MODULE, buildSrcModuleData); return; } Set<String> paths = ContainerUtil.newHashSet(); for (DataNode<ModuleData> moduleDataNode : ExternalSystemApiUtil.findAll(resultProjectDataNode, ProjectKeys.MODULE)) { String path = moduleDataNode.getData().getLinkedExternalProjectPath(); if (path.equals(projectPath)) { // check if buildSrc project was already exposed in settings.gradle file return; } paths.add(path); } final DataNode<ProjectData> buildSrcProjectDataDataNode = myHelper.execute( projectPath, projectConnectionDataNodeFunction.myResolverContext.getSettings(), projectConnectionDataNodeFunction); if (buildSrcProjectDataDataNode != null) { for (DataNode<ModuleData> moduleNode : ExternalSystemApiUtil.getChildren(buildSrcProjectDataDataNode, ProjectKeys.MODULE)) { if (paths.contains(moduleNode.getData().getLinkedExternalProjectPath())) continue; resultProjectDataNode.addChild(moduleNode); if (!ModuleGrouperKt.isQualifiedModuleNamesEnabled()) { // adjust ide module group final ModuleData moduleData = moduleNode.getData(); if (moduleData.getIdeModuleGroup() != null) { String[] moduleGroup = ArrayUtil.prepend(resultProjectDataNode.getData().getInternalName(), moduleData.getIdeModuleGroup()); moduleData.setIdeModuleGroup(moduleGroup); for (DataNode<GradleSourceSetData> sourceSetNode : ExternalSystemApiUtil.getChildren(moduleNode, GradleSourceSetData.KEY)) { sourceSetNode.getData().setIdeModuleGroup(moduleGroup); } } } } } } private class ProjectConnectionDataNodeFunction implements Function<ProjectConnection, DataNode<ProjectData>> { @NotNull private final GradleProjectResolverExtension myProjectResolverChain; private final boolean myIsBuildSrcProject; private DefaultProjectResolverContext myResolverContext; private ProjectConnectionDataNodeFunction(@NotNull DefaultProjectResolverContext resolverContext, @NotNull GradleProjectResolverExtension projectResolverChain, boolean isBuildSrcProject) { myResolverContext = resolverContext; myProjectResolverChain = projectResolverChain; myIsBuildSrcProject = isBuildSrcProject; } @Override public DataNode<ProjectData> fun(ProjectConnection connection) { try { myResolverContext.setConnection(connection); return doResolveProjectInfo(myResolverContext, myProjectResolverChain, myIsBuildSrcProject); } catch (RuntimeException e) { LOG.info("Gradle project resolve error", e); throw myProjectResolverChain.getUserFriendlyError(e, myResolverContext.getProjectPath(), null); } } } @NotNull public static GradleProjectResolverExtension createProjectResolverChain(@Nullable final GradleExecutionSettings settings) { GradleProjectResolverExtension projectResolverChain; if (settings != null) { List<ClassHolder<? extends GradleProjectResolverExtension>> extensionClasses = settings.getResolverExtensions(); Deque<GradleProjectResolverExtension> extensions = new ArrayDeque<>(); for (ClassHolder<? extends GradleProjectResolverExtension> holder : extensionClasses) { final GradleProjectResolverExtension extension; try { extension = holder.getTargetClass().newInstance(); } catch (Throwable e) { throw new IllegalArgumentException( String.format("Can't instantiate project resolve extension for class '%s'", holder.getTargetClassName()), e); } final GradleProjectResolverExtension previous = extensions.peekLast(); if (previous != null) { previous.setNext(extension); if (previous.getNext() != extension) { throw new AssertionError("Illegal next resolver got, current resolver class is " + previous.getClass().getName()); } } extensions.add(extension); } projectResolverChain = extensions.peekFirst(); GradleProjectResolverExtension resolverExtension = projectResolverChain; assert resolverExtension != null; while (resolverExtension.getNext() != null) { resolverExtension = resolverExtension.getNext(); } if (!(resolverExtension instanceof BaseGradleProjectResolverExtension)) { throw new AssertionError("Illegal last resolver got of class " + resolverExtension.getClass().getName()); } } else { projectResolverChain = new BaseGradleProjectResolverExtension(); } return projectResolverChain; } private static String getIdeaVersion() { ApplicationInfoEx appInfo = ApplicationInfoImpl.getShadowInstance(); return appInfo.getMajorVersion() + "." + appInfo.getMinorVersion(); } }