/* * Copyright 2000-2013 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.google.gson.GsonBuilder; import com.intellij.execution.ExecutionException; import com.intellij.execution.configurations.SimpleJavaParameters; import com.intellij.externalSystem.JavaProjectData; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.externalSystem.model.*; import com.intellij.openapi.externalSystem.model.project.*; import com.intellij.openapi.externalSystem.model.task.TaskData; import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil; import com.intellij.openapi.externalSystem.util.ExternalSystemDebugEnvironment; import com.intellij.openapi.externalSystem.util.Order; import com.intellij.openapi.module.EmptyModuleType; import com.intellij.openapi.module.JavaModuleType; import com.intellij.openapi.module.ModuleType; import com.intellij.openapi.module.StdModuleTypes; import com.intellij.openapi.roots.DependencyScope; import com.intellij.openapi.util.KeyValue; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.pom.java.LanguageLevel; import com.intellij.util.*; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.ContainerUtilRt; import com.intellij.util.net.HttpConfigurable; import com.intellij.util.text.CharArrayUtil; import groovy.lang.GroovyObject; import org.gradle.tooling.ProjectConnection; import org.gradle.tooling.model.DomainObjectSet; import org.gradle.tooling.model.GradleModuleVersion; import org.gradle.tooling.model.GradleTask; import org.gradle.tooling.model.gradle.BasicGradleProject; import org.gradle.tooling.model.gradle.GradleBuild; import org.gradle.tooling.model.idea.*; import org.gradle.util.GradleVersion; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.gradle.model.*; import org.jetbrains.plugins.gradle.model.data.BuildScriptClasspathData; import org.jetbrains.plugins.gradle.service.project.data.ExternalProjectDataService; import org.jetbrains.plugins.gradle.tooling.builder.ModelBuildScriptClasspathBuilderImpl; import org.jetbrains.plugins.gradle.tooling.internal.init.Init; import org.jetbrains.plugins.gradle.util.GradleBundle; import org.jetbrains.plugins.gradle.util.GradleConstants; import org.jetbrains.plugins.gradle.util.GradleUtil; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.net.URL; import java.util.*; /** * {@link BaseGradleProjectResolverExtension} provides base implementation of Gradle project resolver. * * @author Vladislav.Soroka * @since 10/14/13 */ @Order(Integer.MAX_VALUE) public class BaseGradleProjectResolverExtension implements GradleProjectResolverExtension { private static final Logger LOG = Logger.getInstance("#" + BaseGradleProjectResolverExtension.class.getName()); @NotNull @NonNls private static final String UNRESOLVED_DEPENDENCY_PREFIX = "unresolved dependency - "; private static final String MAIN_SOURCE_SET = "main"; private static final String TEST_SOURCE_SET = "test"; @NotNull private ProjectResolverContext resolverCtx; @NotNull private final BaseProjectImportErrorHandler myErrorHandler = new BaseProjectImportErrorHandler(); @Override public void setProjectResolverContext(@NotNull ProjectResolverContext projectResolverContext) { resolverCtx = projectResolverContext; } @Override public void setNext(@NotNull GradleProjectResolverExtension next) { // should be the last extension in the chain } @Nullable @Override public GradleProjectResolverExtension getNext() { return null; } @NotNull @Override public ProjectData createProject() { final String projectDirPath = resolverCtx.getProjectPath(); final IdeaProject ideaProject = resolverCtx.getModels().getIdeaProject(); return new ProjectData(GradleConstants.SYSTEM_ID, ideaProject.getName(), projectDirPath, projectDirPath); } @NotNull @Override public JavaProjectData createJavaProjectData() { final String projectDirPath = resolverCtx.getProjectPath(); final IdeaProject ideaProject = resolverCtx.getModels().getIdeaProject(); // Gradle API doesn't expose gradleProject compile output path yet. JavaProjectData javaProjectData = new JavaProjectData(GradleConstants.SYSTEM_ID, projectDirPath + "/build/classes"); javaProjectData.setJdkVersion(ideaProject.getJdkName()); javaProjectData.setLanguageLevel(ideaProject.getLanguageLevel().getLevel()); return javaProjectData; } @Override public void populateProjectExtraModels(@NotNull IdeaProject gradleProject, @NotNull DataNode ideProject) { final ExternalProject externalProject = resolverCtx.getExtraProject(ExternalProject.class); if (externalProject != null) { ideProject.createChild(ExternalProjectDataService.KEY, externalProject); } } @NotNull @Override public ModuleData createModule(@NotNull IdeaModule gradleModule, @NotNull ProjectData projectData) { final String moduleName = gradleModule.getName(); if (moduleName == null) { throw new IllegalStateException("Module with undefined name detected: " + gradleModule); } final String moduleConfigPath = getModuleConfigPath(gradleModule, projectData.getLinkedExternalProjectPath()); if (ExternalSystemDebugEnvironment.DEBUG_ORPHAN_MODULES_PROCESSING) { LOG.info(String.format( "Creating module data ('%s') with the external config path: '%s'", gradleModule.getGradleProject().getPath(), moduleConfigPath )); } String gradlePath = gradleModule.getGradleProject().getPath(); String moduleId = StringUtil.isEmpty(gradlePath) || ":".equals(gradlePath) ? moduleName : gradlePath; ModuleData moduleData = new ModuleData(moduleId, GradleConstants.SYSTEM_ID, StdModuleTypes.JAVA.getId(), moduleName, moduleConfigPath, moduleConfigPath); final ModuleExtendedModel moduleExtendedModel = resolverCtx.getExtraProject(gradleModule, ModuleExtendedModel.class); if (moduleExtendedModel != null) { moduleData.setGroup(moduleExtendedModel.getGroup()); moduleData.setVersion(moduleExtendedModel.getVersion()); moduleData.setArtifacts(moduleExtendedModel.getArtifacts()); } return moduleData; } @Override public void populateModuleExtraModels(@NotNull IdeaModule gradleModule, @NotNull DataNode ideModule) { final BuildScriptClasspathModel buildScriptClasspathModel = resolverCtx.getExtraProject(gradleModule, BuildScriptClasspathModel.class); final List classpathEntries; if (buildScriptClasspathModel != null) { classpathEntries = ContainerUtil .map(buildScriptClasspathModel.getClasspath(), new Function() { @Override public BuildScriptClasspathData.ClasspathEntry fun(ClasspathEntryModel model) { return new BuildScriptClasspathData.ClasspathEntry(model.getClasses(), model.getSources(), model.getJavadoc()); } }); } else { classpathEntries = ContainerUtil.emptyList(); } BuildScriptClasspathData buildScriptClasspathData = new BuildScriptClasspathData(GradleConstants.SYSTEM_ID, classpathEntries); ideModule.createChild(BuildScriptClasspathData.KEY, buildScriptClasspathData); } @Override public void populateModuleContentRoots(@NotNull IdeaModule gradleModule, @NotNull DataNode ideModule) { DomainObjectSet contentRoots; ModuleExtendedModel moduleExtendedModel = resolverCtx.getExtraProject(gradleModule, ModuleExtendedModel.class); if (moduleExtendedModel != null) { contentRoots = moduleExtendedModel.getContentRoots(); } else { contentRoots = gradleModule.getContentRoots(); } if (contentRoots == null) { return; } for (IdeaContentRoot gradleContentRoot : contentRoots) { if (gradleContentRoot == null) continue; File rootDirectory = gradleContentRoot.getRootDirectory(); if (rootDirectory == null) continue; ContentRootData ideContentRoot = new ContentRootData(GradleConstants.SYSTEM_ID, rootDirectory.getAbsolutePath()); ideModule.getData().setModuleFileDirectoryPath(ideContentRoot.getRootPath()); populateContentRoot(ideContentRoot, ExternalSystemSourceType.SOURCE, gradleContentRoot.getSourceDirectories()); populateContentRoot(ideContentRoot, ExternalSystemSourceType.TEST, gradleContentRoot.getTestDirectories()); if (gradleContentRoot instanceof ExtIdeaContentRoot) { ExtIdeaContentRoot extIdeaContentRoot = (ExtIdeaContentRoot)gradleContentRoot; populateContentRoot(ideContentRoot, ExternalSystemSourceType.RESOURCE, extIdeaContentRoot.getResourceDirectories()); populateContentRoot(ideContentRoot, ExternalSystemSourceType.TEST_RESOURCE, extIdeaContentRoot.getTestResourceDirectories()); } Set excluded = gradleContentRoot.getExcludeDirectories(); if (excluded != null) { for (File file : excluded) { ideContentRoot.storePath(ExternalSystemSourceType.EXCLUDED, file.getAbsolutePath()); } } ideModule.createChild(ProjectKeys.CONTENT_ROOT, ideContentRoot); } } @Override public void populateModuleCompileOutputSettings(@NotNull IdeaModule gradleModule, @NotNull DataNode ideModule) { IdeaCompilerOutput moduleCompilerOutput = gradleModule.getCompilerOutput(); File sourceCompileOutputPath = null; File testCompileOutputPath = null; File resourceCompileOutputPath; File testResourceCompileOutputPath; boolean inheritOutputDirs = false; ModuleData moduleData = ideModule.getData(); if (moduleCompilerOutput != null) { sourceCompileOutputPath = moduleCompilerOutput.getOutputDir(); testCompileOutputPath = moduleCompilerOutput.getTestOutputDir(); inheritOutputDirs = moduleCompilerOutput.getInheritOutputDirs(); } ExternalProject externalProject = resolverCtx.getExtraProject(gradleModule, ExternalProject.class); if (externalProject != null) { externalProject = new DefaultExternalProject(externalProject); } if (!inheritOutputDirs && (sourceCompileOutputPath == null || testCompileOutputPath == null)) { sourceCompileOutputPath = getCompileOutputPath(externalProject, MAIN_SOURCE_SET, ExternalSystemSourceType.SOURCE); resourceCompileOutputPath = getCompileOutputPath(externalProject, MAIN_SOURCE_SET, ExternalSystemSourceType.RESOURCE); testCompileOutputPath = getCompileOutputPath(externalProject, TEST_SOURCE_SET, ExternalSystemSourceType.TEST); testResourceCompileOutputPath = getCompileOutputPath(externalProject, TEST_SOURCE_SET, ExternalSystemSourceType.TEST_RESOURCE); } else { resourceCompileOutputPath = sourceCompileOutputPath; testResourceCompileOutputPath = testCompileOutputPath; if (externalProject != null) { final ExternalSourceSet mainSourceSet = externalProject.getSourceSets().get(MAIN_SOURCE_SET); if (mainSourceSet != null) { final ExternalSourceDirectorySet resourceDirectories = mainSourceSet.getSources().get(ExternalSystemSourceType.RESOURCE); if (resourceDirectories instanceof DefaultExternalSourceDirectorySet) { ((DefaultExternalSourceDirectorySet)resourceDirectories).setOutputDir(sourceCompileOutputPath); } } final ExternalSourceSet testSourceSet = externalProject.getSourceSets().get(TEST_SOURCE_SET); if (testSourceSet != null) { final ExternalSourceDirectorySet testResourceDirectories = testSourceSet.getSources().get(ExternalSystemSourceType.TEST_RESOURCE); if (testResourceDirectories instanceof DefaultExternalSourceDirectorySet) { ((DefaultExternalSourceDirectorySet)testResourceDirectories).setOutputDir(testCompileOutputPath); } } final DataNode projectDataNode = ExternalSystemApiUtil.findParent(ideModule, ProjectKeys.PROJECT); assert projectDataNode != null; projectDataNode.createOrReplaceChild(ExternalProjectDataService.KEY, externalProject); } } if (sourceCompileOutputPath != null) { moduleData.setCompileOutputPath(ExternalSystemSourceType.SOURCE, sourceCompileOutputPath.getAbsolutePath()); } if (resourceCompileOutputPath != null) { moduleData.setCompileOutputPath(ExternalSystemSourceType.RESOURCE, resourceCompileOutputPath.getAbsolutePath()); } if (testCompileOutputPath != null) { moduleData.setCompileOutputPath(ExternalSystemSourceType.TEST, testCompileOutputPath.getAbsolutePath()); } if (testResourceCompileOutputPath != null) { moduleData.setCompileOutputPath(ExternalSystemSourceType.TEST_RESOURCE, testResourceCompileOutputPath.getAbsolutePath()); } moduleData.setInheritProjectCompileOutputPath(inheritOutputDirs || sourceCompileOutputPath == null); } @Nullable private static File getCompileOutputPath(@Nullable ExternalProject externalProject, @NotNull String sourceSetName, @NotNull ExternalSystemSourceType sourceType) { if (externalProject == null) return null; final ExternalSourceSet sourceSet = externalProject.getSourceSets().get(sourceSetName); if(sourceSet == null) return null; final ExternalSourceDirectorySet directorySet = sourceSet.getSources().get(sourceType); return directorySet != null ? directorySet.getOutputDir() : null; } @Override public void populateModuleDependencies(@NotNull IdeaModule gradleModule, @NotNull DataNode ideModule, @NotNull DataNode ideProject) { final List dependencies = gradleModule.getDependencies().getAll(); if (dependencies == null) return; for (IdeaDependency dependency : dependencies) { if (dependency == null) { continue; } DependencyScope scope = parseScope(dependency.getScope()); if (dependency instanceof IdeaModuleDependency) { ModuleDependencyData d = buildDependency(ideModule, (IdeaModuleDependency)dependency, ideProject); d.setExported(dependency.getExported()); if (scope != null) { d.setScope(scope); } ideModule.createChild(ProjectKeys.MODULE_DEPENDENCY, d); } else if (dependency instanceof IdeaSingleEntryLibraryDependency) { LibraryDependencyData d = buildDependency(gradleModule, ideModule, (IdeaSingleEntryLibraryDependency)dependency, ideProject); d.setExported(dependency.getExported()); if (scope != null) { d.setScope(scope); } ideModule.createChild(ProjectKeys.LIBRARY_DEPENDENCY, d); } } } @NotNull @Override public Collection populateModuleTasks(@NotNull IdeaModule gradleModule, @NotNull DataNode ideModule, @NotNull DataNode ideProject) throws IllegalArgumentException, IllegalStateException { final Collection tasks = ContainerUtil.newArrayList(); final String moduleConfigPath = ideModule.getData().getLinkedExternalProjectPath(); for (GradleTask task : gradleModule.getGradleProject().getTasks()) { String taskName = task.getName(); if (taskName == null || taskName.trim().isEmpty() || isIdeaTask(taskName)) { continue; } TaskData taskData = new TaskData(GradleConstants.SYSTEM_ID, taskName, moduleConfigPath, task.getDescription()); ideModule.createChild(ProjectKeys.TASK, taskData); tasks.add(taskData); } return tasks; } @NotNull @Override public Collection filterRootProjectTasks(@NotNull List allTasks) { return allTasks; } @NotNull @Override public Set getExtraProjectModelClasses() { return ContainerUtil.set( GradleBuild.class, ExternalProject.class, ModuleExtendedModel.class, BuildScriptClasspathModel.class); } @NotNull @Override public Set getToolingExtensionsClasses() { return ContainerUtil.set( ExternalProject.class, // gradle-tooling-extension-api jar ProjectImportAction.class, // gradle-tooling-extension-impl jar ModelBuildScriptClasspathBuilderImpl.class, GsonBuilder.class ); } @NotNull @Override public List> getExtraJvmArgs() { if (ExternalSystemApiUtil.isInProcessMode(GradleConstants.SYSTEM_ID)) { final List> extraJvmArgs = ContainerUtil.newArrayList(); final HttpConfigurable httpConfigurable = HttpConfigurable.getInstance(); if (!StringUtil.isEmpty(httpConfigurable.PROXY_EXCEPTIONS)) { List hosts = StringUtil.split(httpConfigurable.PROXY_EXCEPTIONS, ","); if (!hosts.isEmpty()) { extraJvmArgs.add(KeyValue.create("http.nonProxyHosts", StringUtil.join(hosts, StringUtil.TRIMMER, "|"))); } } extraJvmArgs.addAll(HttpConfigurable.getJvmPropertiesList(false, null)); return extraJvmArgs; } return Collections.emptyList(); } @NotNull @Override public List getExtraCommandLineArgs() { return Collections.emptyList(); } @NotNull @Override public ExternalSystemException getUserFriendlyError(@NotNull Throwable error, @NotNull String projectPath, @Nullable String buildFilePath) { return myErrorHandler.getUserFriendlyError(error, projectPath, buildFilePath); } @Override public void preImportCheck() { } @Override public void enhanceTaskProcessing(@NotNull List taskNames, @Nullable String debuggerSetup, @NotNull Consumer initScriptConsumer) { if (!StringUtil.isEmpty(debuggerSetup)) { final String[] lines = { "gradle.taskGraph.beforeTask { Task task ->", " if (task instanceof JavaForkOptions) {", " task.jvmArgs '" + debuggerSetup.trim() + '\'', " }" + "}", }; final String script = StringUtil.join(lines, SystemProperties.getLineSeparator()); initScriptConsumer.consume(script); } } @Override public void enhanceRemoteProcessing(@NotNull SimpleJavaParameters parameters) throws ExecutionException { PathsList classPath = parameters.getClassPath(); // Gradle i18n bundle. ExternalSystemApiUtil.addBundle(classPath, GradleBundle.PATH_TO_BUNDLE, GradleBundle.class); // Gradle tool jars. String toolingApiPath = PathManager.getJarPathForClass(ProjectConnection.class); if (toolingApiPath == null) { LOG.warn(GradleBundle.message("gradle.generic.text.error.jar.not.found")); throw new ExecutionException("Can't find gradle libraries"); } File gradleJarsDir = new File(toolingApiPath).getParentFile(); String[] gradleJars = gradleJarsDir.list(new FilenameFilter() { @Override public boolean accept(@NotNull File dir, @NotNull String name) { return name.endsWith(".jar"); } }); if (gradleJars == null) { LOG.warn(GradleBundle.message("gradle.generic.text.error.jar.not.found")); throw new ExecutionException("Can't find gradle libraries at " + gradleJarsDir.getAbsolutePath()); } for (String jar : gradleJars) { classPath.add(new File(gradleJarsDir, jar).getAbsolutePath()); } List additionalEntries = ContainerUtilRt.newArrayList(); ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(GroovyObject.class)); ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(GsonBuilder.class)); ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(ExternalProject.class)); ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(JavaProjectData.class)); ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(LanguageLevel.class)); ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(StdModuleTypes.class)); ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(JavaModuleType.class)); ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(ModuleType.class)); ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(EmptyModuleType.class)); ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(ProjectImportAction.class)); ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(Init.class)); for (String entry : additionalEntries) { classPath.add(entry); } } @Override public void enhanceLocalProcessing(@NotNull List urls) { } @NotNull private String getModuleConfigPath(@NotNull IdeaModule gradleModule, @NotNull String rootProjectPath) { GradleBuild build = resolverCtx.getExtraProject(gradleModule, GradleBuild.class); if (build != null) { String gradlePath = gradleModule.getGradleProject().getPath(); File moduleDirPath = getModuleDirPath(build, gradlePath); if (moduleDirPath == null) { throw new IllegalStateException(String.format("Unable to find root directory for module '%s'", gradleModule.getName())); } try { return ExternalSystemApiUtil.toCanonicalPath(moduleDirPath.getCanonicalPath()); } catch (IOException e) { LOG.warn("construction of the canonical path for the module fails", e); } } return GradleUtil.getConfigPath(gradleModule.getGradleProject(), rootProjectPath); } /** * Returns the physical path of the module's root directory (the path in the file system.) *

* It is important to note that Gradle has its own "logical" path that may or may not be equal to the physical path of a Gradle project. * For example, the sub-project at ${projectRootDir}/apps/app will have the Gradle path :apps:app. Gradle also allows mapping physical * paths to a different logical path. For example, in settings.gradle: *

   *   include ':app'
   *   project(':app').projectDir = new File(rootDir, 'apps/app')
   * 
* In this example, sub-project at ${projectRootDir}/apps/app will have the Gradle path :app. *

* * @param build contains information about the root Gradle project and its sub-projects. Such information includes the physical path of * the root Gradle project and its sub-projects. * @param path the Gradle "logical" path. This path uses colon as separator, and may or may not be equal to the physical path of a * Gradle project. * @return the physical path of the module's root directory. */ @Nullable static File getModuleDirPath(@NotNull GradleBuild build, @NotNull String path) { for (BasicGradleProject project : build.getProjects()) { if (project.getPath().equals(path)) { return project.getProjectDirectory(); } } return null; } /** * Stores information about given directories at the given content root * * @param contentRoot target paths info holder * @param type type of data located at the given directories * @param dirs directories which paths should be stored at the given content root * @throws IllegalArgumentException if specified by {@link ContentRootData#storePath(ExternalSystemSourceType, String)} */ private static void populateContentRoot(@NotNull ContentRootData contentRoot, @NotNull ExternalSystemSourceType type, @Nullable Iterable dirs) throws IllegalArgumentException { if (dirs == null) { return; } for (IdeaSourceDirectory dir : dirs) { contentRoot.storePath(type, dir.getDirectory().getAbsolutePath()); } } @Nullable private static DependencyScope parseScope(@Nullable IdeaDependencyScope scope) { if (scope == null) { return null; } String scopeAsString = scope.getScope(); if (scopeAsString == null) { return null; } for (DependencyScope dependencyScope : DependencyScope.values()) { if (scopeAsString.equalsIgnoreCase(dependencyScope.toString())) { return dependencyScope; } } return null; } @NotNull private static ModuleDependencyData buildDependency(@NotNull DataNode ownerModule, @NotNull IdeaModuleDependency dependency, @NotNull DataNode ideProject) throws IllegalStateException { IdeaModule module = dependency.getDependencyModule(); if (module == null) { throw new IllegalStateException( String.format("Can't parse gradle module dependency '%s'. Reason: referenced module is null", dependency) ); } String moduleName = module.getName(); if (moduleName == null) { throw new IllegalStateException(String.format( "Can't parse gradle module dependency '%s'. Reason: referenced module name is undefined (module: '%s') ", dependency, module )); } Set registeredModuleNames = ContainerUtilRt.newHashSet(); Collection> modulesDataNode = ExternalSystemApiUtil.getChildren(ideProject, ProjectKeys.MODULE); for (DataNode moduleDataNode : modulesDataNode) { String name = moduleDataNode.getData().getExternalName(); registeredModuleNames.add(name); if (name.equals(moduleName)) { return new ModuleDependencyData(ownerModule.getData(), moduleDataNode.getData()); } } throw new IllegalStateException(String.format( "Can't parse gradle module dependency '%s'. Reason: no module with such name (%s) is found. Registered modules: %s", dependency, moduleName, registeredModuleNames )); } @NotNull private LibraryDependencyData buildDependency(@NotNull IdeaModule gradleModule, @NotNull DataNode ownerModule, @NotNull IdeaSingleEntryLibraryDependency dependency, @NotNull DataNode ideProject) throws IllegalStateException { File binaryPath = dependency.getFile(); if (binaryPath == null) { throw new IllegalStateException(String.format( "Can't parse external library dependency '%s'. Reason: it doesn't specify path to the binaries", dependency )); } String libraryName; final GradleModuleVersion moduleVersion = dependency.getGradleModuleVersion(); final LibraryLevel level; // Gradle API doesn't explicitly provide information about unresolved libraries (http://issues.gradle.org/browse/GRADLE-1995). // That's why we use this dirty hack here. boolean unresolved = binaryPath.getPath().startsWith(UNRESOLVED_DEPENDENCY_PREFIX); if (moduleVersion == null) { // use module library level if the dependency does not originate from a remote repository. level = LibraryLevel.MODULE; if (binaryPath.isFile()) { libraryName = FileUtil.getNameWithoutExtension(binaryPath); } else { libraryName = FileUtil.sanitizeFileName(binaryPath.getPath()); } if (unresolved) { // Gradle uses names like 'unresolved dependency - commons-collections commons-collections 3.2' for unresolved dependencies. libraryName = binaryPath.getName().substring(UNRESOLVED_DEPENDENCY_PREFIX.length()); int i = libraryName.indexOf(' '); if (i >= 0) { i = CharArrayUtil.shiftForward(libraryName, i + 1, " "); } if (i >= 0 && i < libraryName.length()) { int dependencyNameIndex = i; i = libraryName.indexOf(' ', dependencyNameIndex); if (i > 0) { libraryName = String.format("%s-%s", libraryName.substring(dependencyNameIndex, i), libraryName.substring(i + 1)); } } } } else { level = LibraryLevel.PROJECT; libraryName = String.format("%s:%s:%s", moduleVersion.getGroup(), moduleVersion.getName(), moduleVersion.getVersion()); } final LibraryData library = new LibraryData(GradleConstants.SYSTEM_ID, libraryName, unresolved); if (!unresolved) { library.addPath(LibraryPathType.BINARY, binaryPath.getAbsolutePath()); } File sourcePath = dependency.getSource(); if (!unresolved && sourcePath != null) { library.addPath(LibraryPathType.SOURCE, sourcePath.getAbsolutePath()); } if (!unresolved && sourcePath == null) { attachGradleSdkSources(gradleModule, libraryName, binaryPath, library); } File javadocPath = dependency.getJavadoc(); if (!unresolved && javadocPath != null) { library.addPath(LibraryPathType.DOC, javadocPath.getAbsolutePath()); } if(level == LibraryLevel.PROJECT) { DataNode libraryData = ExternalSystemApiUtil.find(ideProject, ProjectKeys.LIBRARY, new BooleanFunction>() { @Override public boolean fun(DataNode node) { return library.equals(node.getData()); } }); if (libraryData == null) { ideProject.createChild(ProjectKeys.LIBRARY, library); } } return new LibraryDependencyData(ownerModule.getData(), library, level); } private void attachGradleSdkSources(@NotNull IdeaModule gradleModule, @NotNull final String libName, @Nullable final File libFile, LibraryData library) { if (libFile == null || !libName.startsWith("gradle-")) return; final BuildScriptClasspathModel buildScriptClasspathModel = resolverCtx.getExtraProject(gradleModule, BuildScriptClasspathModel.class); if (buildScriptClasspathModel == null) return; final File gradleHomeDir = buildScriptClasspathModel.getGradleHomeDir(); if (gradleHomeDir == null) return; if (!FileUtil.isAncestor(gradleHomeDir, libFile, true)) return; File libOrPluginsFile = libFile.getParentFile(); if (libOrPluginsFile != null && ("plugins".equals(libOrPluginsFile.getName()))) { libOrPluginsFile = libOrPluginsFile.getParentFile(); } if (libOrPluginsFile != null && "lib".equals(libOrPluginsFile.getName()) && libOrPluginsFile.getParentFile() != null) { File srcDir = new File(libOrPluginsFile.getParentFile(), "src"); GradleVersion current = GradleVersion.version(buildScriptClasspathModel.getGradleVersion()); if (current.compareTo(GradleVersion.version("1.9")) >= 0) { int endIndex = libName.indexOf(current.getVersion()); if (endIndex != -1) { String srcDirChild = libName.substring("gradle-".length(), endIndex - 1); srcDir = new File(srcDir, srcDirChild); } } if (srcDir.isDirectory()) { library.addPath(LibraryPathType.SOURCE, srcDir.getAbsolutePath()); } } } private static boolean isIdeaTask(final String taskName) { return taskName.toLowerCase(Locale.ENGLISH).contains("idea"); } }