IDEA-150835: Provide an API which tells which production module corresponding to...
[idea/community.git] / plugins / gradle / src / org / jetbrains / plugins / gradle / service / project / BaseGradleProjectResolverExtension.java
1 /*
2  * Copyright 2000-2013 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.jetbrains.plugins.gradle.service.project;
17
18 import com.google.common.collect.Multimap;
19 import com.google.gson.GsonBuilder;
20 import com.intellij.execution.ExecutionException;
21 import com.intellij.execution.configurations.SimpleJavaParameters;
22 import com.intellij.externalSystem.JavaProjectData;
23 import com.intellij.openapi.application.PathManager;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.externalSystem.model.DataNode;
26 import com.intellij.openapi.externalSystem.model.ExternalSystemException;
27 import com.intellij.openapi.externalSystem.model.ProjectKeys;
28 import com.intellij.openapi.externalSystem.model.project.*;
29 import com.intellij.openapi.externalSystem.model.task.TaskData;
30 import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil;
31 import com.intellij.openapi.externalSystem.util.Order;
32 import com.intellij.openapi.module.EmptyModuleType;
33 import com.intellij.openapi.module.JavaModuleType;
34 import com.intellij.openapi.module.ModuleType;
35 import com.intellij.openapi.module.StdModuleTypes;
36 import com.intellij.openapi.roots.DependencyScope;
37 import com.intellij.openapi.util.KeyValue;
38 import com.intellij.openapi.util.Pair;
39 import com.intellij.openapi.util.io.FileFilters;
40 import com.intellij.openapi.util.io.FileUtil;
41 import com.intellij.openapi.util.io.FileUtilRt;
42 import com.intellij.openapi.util.text.StringUtil;
43 import com.intellij.pom.java.LanguageLevel;
44 import com.intellij.util.*;
45 import com.intellij.util.containers.ContainerUtil;
46 import com.intellij.util.containers.ContainerUtilRt;
47 import com.intellij.util.net.HttpConfigurable;
48 import com.intellij.util.text.CharArrayUtil;
49 import groovy.lang.GroovyObject;
50 import org.codehaus.groovy.runtime.typehandling.ShortTypeHandling;
51 import org.gradle.tooling.ProjectConnection;
52 import org.gradle.tooling.model.DomainObjectSet;
53 import org.gradle.tooling.model.GradleModuleVersion;
54 import org.gradle.tooling.model.GradleTask;
55 import org.gradle.tooling.model.UnsupportedMethodException;
56 import org.gradle.tooling.model.gradle.GradleBuild;
57 import org.gradle.tooling.model.idea.*;
58 import org.jetbrains.annotations.NonNls;
59 import org.jetbrains.annotations.NotNull;
60 import org.jetbrains.annotations.Nullable;
61 import org.jetbrains.plugins.gradle.model.*;
62 import org.jetbrains.plugins.gradle.model.data.BuildScriptClasspathData;
63 import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData;
64 import org.jetbrains.plugins.gradle.service.project.data.ExternalProjectDataService;
65 import org.jetbrains.plugins.gradle.tooling.builder.ModelBuildScriptClasspathBuilderImpl;
66 import org.jetbrains.plugins.gradle.tooling.internal.init.Init;
67 import org.jetbrains.plugins.gradle.util.GradleBundle;
68 import org.jetbrains.plugins.gradle.util.GradleConstants;
69 import org.slf4j.impl.Log4jLoggerFactory;
70
71 import java.io.File;
72 import java.lang.reflect.Field;
73 import java.lang.reflect.Method;
74 import java.lang.reflect.Proxy;
75 import java.net.URL;
76 import java.util.*;
77 import java.util.regex.Matcher;
78 import java.util.regex.Pattern;
79
80 import static org.jetbrains.plugins.gradle.service.project.GradleProjectResolverUtil.*;
81
82 /**
83  * {@link BaseGradleProjectResolverExtension} provides base implementation of Gradle project resolver.
84  *
85  * @author Vladislav.Soroka
86  * @since 10/14/13
87  */
88 @Order(Integer.MAX_VALUE)
89 public class BaseGradleProjectResolverExtension implements GradleProjectResolverExtension {
90   private static final Logger LOG = Logger.getInstance(BaseGradleProjectResolverExtension.class);
91
92   @NotNull @NonNls private static final String UNRESOLVED_DEPENDENCY_PREFIX = "unresolved dependency - ";
93
94   @NotNull private ProjectResolverContext resolverCtx;
95   @NotNull private final BaseProjectImportErrorHandler myErrorHandler = new BaseProjectImportErrorHandler();
96
97   @Override
98   public void setProjectResolverContext(@NotNull ProjectResolverContext projectResolverContext) {
99     resolverCtx = projectResolverContext;
100   }
101
102   @Override
103   public void setNext(@NotNull GradleProjectResolverExtension next) {
104     // should be the last extension in the chain
105   }
106
107   @Nullable
108   @Override
109   public GradleProjectResolverExtension getNext() {
110     return null;
111   }
112
113   @NotNull
114   @Override
115   public ProjectData createProject() {
116     final String projectDirPath = resolverCtx.getProjectPath();
117     final IdeaProject ideaProject = resolverCtx.getModels().getIdeaProject();
118     return new ProjectData(GradleConstants.SYSTEM_ID, ideaProject.getName(), projectDirPath, projectDirPath);
119   }
120
121   @NotNull
122   @Override
123   public JavaProjectData createJavaProjectData() {
124     final String projectDirPath = resolverCtx.getProjectPath();
125     final IdeaProject ideaProject = resolverCtx.getModels().getIdeaProject();
126
127     // Gradle API doesn't expose gradleProject compile output path yet.
128     JavaProjectData javaProjectData = new JavaProjectData(GradleConstants.SYSTEM_ID, projectDirPath + "/build/classes");
129     javaProjectData.setJdkVersion(ideaProject.getJdkName());
130     LanguageLevel resolvedLanguageLevel = null;
131     // org.gradle.tooling.model.idea.IdeaLanguageLevel.getLevel() returns something like JDK_1_6
132     final String languageLevel = ideaProject.getLanguageLevel().getLevel();
133     for (LanguageLevel level : LanguageLevel.values()) {
134       if (level.name().equals(languageLevel)) {
135         resolvedLanguageLevel = level;
136         break;
137       }
138     }
139     if (resolvedLanguageLevel != null) {
140       javaProjectData.setLanguageLevel(resolvedLanguageLevel);
141     }
142     else {
143       javaProjectData.setLanguageLevel(languageLevel);
144     }
145     return javaProjectData;
146   }
147
148   @Override
149   public void populateProjectExtraModels(@NotNull IdeaProject gradleProject, @NotNull DataNode<ProjectData> ideProject) {
150     final ExternalProject externalProject = resolverCtx.getExtraProject(ExternalProject.class);
151     if (externalProject != null) {
152       ideProject.createChild(ExternalProjectDataService.KEY, externalProject);
153       ideProject.getData().setDescription(externalProject.getDescription());
154     }
155   }
156
157   @NotNull
158   @Override
159   public DataNode<ModuleData> createModule(@NotNull IdeaModule gradleModule, @NotNull DataNode<ProjectData> projectDataNode) {
160     DataNode<ModuleData> mainModuleNode = createMainModule(resolverCtx, gradleModule, projectDataNode);
161     final ModuleData mainModuleData = mainModuleNode.getData();
162     final String mainModuleConfigPath = mainModuleData.getLinkedExternalProjectPath();
163     final String mainModuleFileDirectoryPath = mainModuleData.getModuleFileDirectoryPath();
164
165     ExternalProject externalProject = resolverCtx.getExtraProject(gradleModule, ExternalProject.class);
166     if (externalProject != null) {
167       String gradlePath = gradleModule.getGradleProject().getPath();
168       final boolean isRootModule = StringUtil.isEmpty(gradlePath) || ":".equals(gradlePath);
169       final String[] moduleGroup;
170       if (isRootModule) {
171         moduleGroup = new String[]{mainModuleData.getInternalName()};
172       }
173       else {
174         moduleGroup = ArrayUtil.remove(gradlePath.split(":"), 0);
175       }
176       mainModuleData.setIdeModuleGroup(isRootModule ? null : moduleGroup);
177
178       for (ExternalSourceSet sourceSet : externalProject.getSourceSets().values()) {
179         final String moduleId = getModuleId(externalProject, sourceSet);
180         final String moduleExternalName = gradleModule.getName() + ":" + sourceSet.getName();
181         final String moduleInternalName = getInternalModuleName(gradleModule, sourceSet.getName());
182
183         GradleSourceSetData sourceSetData = new GradleSourceSetData(
184           moduleId, moduleExternalName, moduleInternalName, mainModuleFileDirectoryPath, mainModuleConfigPath);
185
186         sourceSetData.setGroup(externalProject.getGroup());
187         sourceSetData.setVersion(externalProject.getVersion());
188         sourceSetData.setIdeModuleGroup(moduleGroup);
189
190         sourceSetData.setSourceCompatibility(sourceSet.getSourceCompatibility());
191         sourceSetData.setTargetCompatibility(sourceSet.getTargetCompatibility());
192
193         final Set<File> artifacts = ContainerUtil.newTroveSet(FileUtil.FILE_HASHING_STRATEGY);
194         if ("main".equals(sourceSet.getName())) {
195           final Set<File> defaultArtifacts = externalProject.getArtifactsByConfiguration().get("default");
196           if (defaultArtifacts != null) {
197             artifacts.addAll(defaultArtifacts);
198           }
199           if (externalProject.getArtifactsByConfiguration().get("archives") != null) {
200             final Set<File> archivesArtifacts = ContainerUtil.newHashSet(externalProject.getArtifactsByConfiguration().get("archives"));
201             final Set<File> testsArtifacts = externalProject.getArtifactsByConfiguration().get("tests");
202             if (testsArtifacts != null) {
203               archivesArtifacts.removeAll(testsArtifacts);
204             }
205             artifacts.addAll(archivesArtifacts);
206           }
207         }
208         else {
209           sourceSetData.setProductionModuleId(getInternalModuleName(gradleModule, "main"));
210           if ("test".equals(sourceSet.getName())) {
211             final Set<File> testsArtifacts = externalProject.getArtifactsByConfiguration().get("tests");
212             if (testsArtifacts != null) {
213               artifacts.addAll(testsArtifacts);
214             }
215           }
216         }
217         sourceSetData.setArtifacts(ContainerUtil.newArrayList(artifacts));
218
219         DataNode<GradleSourceSetData> sourceSetDataNode = mainModuleNode.createChild(GradleSourceSetData.KEY, sourceSetData);
220         final Map<String, Pair<DataNode<GradleSourceSetData>, ExternalSourceSet>> sourceSetMap =
221           projectDataNode.getUserData(GradleProjectResolver.RESOLVED_SOURCE_SETS);
222         assert sourceSetMap != null;
223         sourceSetMap.put(moduleId, Pair.create(sourceSetDataNode, sourceSet));
224       }
225     }
226
227     final ProjectData projectData = projectDataNode.getData();
228     if (StringUtil.equals(mainModuleData.getLinkedExternalProjectPath(), projectData.getLinkedExternalProjectPath())) {
229       projectData.setGroup(mainModuleData.getGroup());
230       projectData.setVersion(mainModuleData.getVersion());
231     }
232
233     return mainModuleNode;
234   }
235
236   @NotNull
237   public String getInternalModuleName(@NotNull IdeaModule gradleModule, @NotNull String sourceSetName) {
238     return gradleModule.getName() + "_" + sourceSetName;
239   }
240
241   @Override
242   public void populateModuleExtraModels(@NotNull IdeaModule gradleModule, @NotNull DataNode<ModuleData> ideModule) {
243     final BuildScriptClasspathModel buildScriptClasspathModel = resolverCtx.getExtraProject(gradleModule, BuildScriptClasspathModel.class);
244     final List<BuildScriptClasspathData.ClasspathEntry> classpathEntries;
245     if (buildScriptClasspathModel != null) {
246       classpathEntries = ContainerUtil
247         .map(buildScriptClasspathModel.getClasspath(), new Function<ClasspathEntryModel, BuildScriptClasspathData.ClasspathEntry>() {
248           @Override
249           public BuildScriptClasspathData.ClasspathEntry fun(ClasspathEntryModel model) {
250             return new BuildScriptClasspathData.ClasspathEntry(model.getClasses(), model.getSources(), model.getJavadoc());
251           }
252         });
253     }
254     else {
255       classpathEntries = ContainerUtil.emptyList();
256     }
257     BuildScriptClasspathData buildScriptClasspathData = new BuildScriptClasspathData(GradleConstants.SYSTEM_ID, classpathEntries);
258     ideModule.createChild(BuildScriptClasspathData.KEY, buildScriptClasspathData);
259   }
260
261   @Override
262   public void populateModuleContentRoots(@NotNull IdeaModule gradleModule,
263                                          @NotNull DataNode<ModuleData> ideModule) {
264     ExternalProject externalProject = resolverCtx.getExtraProject(gradleModule, ExternalProject.class);
265     if (externalProject != null) {
266       processSourceSets(externalProject, ideModule, new SourceSetsProcessor() {
267         @Override
268         public void process(@NotNull DataNode<GradleSourceSetData> dataNode, @NotNull ExternalSourceSet sourceSet) {
269           for (Map.Entry<IExternalSystemSourceType, ExternalSourceDirectorySet> directorySetEntry : sourceSet.getSources().entrySet()) {
270             ExternalSystemSourceType sourceType = ExternalSystemSourceType.from(directorySetEntry.getKey());
271             ExternalSourceDirectorySet sourceDirectorySet = directorySetEntry.getValue();
272
273             for (File file : sourceDirectorySet.getSrcDirs()) {
274               ContentRootData ideContentRoot = new ContentRootData(GradleConstants.SYSTEM_ID, file.getAbsolutePath());
275               ideContentRoot.storePath(sourceType, file.getAbsolutePath());
276               dataNode.createChild(ProjectKeys.CONTENT_ROOT, ideContentRoot);
277             }
278           }
279         }
280       });
281     }
282
283     DomainObjectSet<? extends IdeaContentRoot> contentRoots = gradleModule.getContentRoots();
284     if (contentRoots == null) {
285       return;
286     }
287     for (IdeaContentRoot gradleContentRoot : contentRoots) {
288       if (gradleContentRoot == null) continue;
289
290       File rootDirectory = gradleContentRoot.getRootDirectory();
291       if (rootDirectory == null) continue;
292
293       ContentRootData ideContentRoot = new ContentRootData(GradleConstants.SYSTEM_ID, rootDirectory.getAbsolutePath());
294       if (externalProject == null) {
295         populateContentRoot(ideContentRoot, ExternalSystemSourceType.SOURCE, gradleContentRoot.getSourceDirectories());
296         populateContentRoot(ideContentRoot, ExternalSystemSourceType.TEST, gradleContentRoot.getTestDirectories());
297
298         if (gradleContentRoot instanceof ExtIdeaContentRoot) {
299           ExtIdeaContentRoot extIdeaContentRoot = (ExtIdeaContentRoot)gradleContentRoot;
300           populateContentRoot(ideContentRoot, ExternalSystemSourceType.RESOURCE, extIdeaContentRoot.getResourceDirectories());
301           populateContentRoot(ideContentRoot, ExternalSystemSourceType.TEST_RESOURCE, extIdeaContentRoot.getTestResourceDirectories());
302         }
303       }
304
305       Set<File> excluded = gradleContentRoot.getExcludeDirectories();
306       if (excluded != null) {
307         for (File file : excluded) {
308           ideContentRoot.storePath(ExternalSystemSourceType.EXCLUDED, file.getAbsolutePath());
309         }
310       }
311       ideModule.createChild(ProjectKeys.CONTENT_ROOT, ideContentRoot);
312     }
313   }
314
315   private static void processSourceSets(@NotNull ExternalProject externalProject,
316                                         @NotNull DataNode<ModuleData> ideModule,
317                                         @NotNull SourceSetsProcessor processor) {
318     Map<String, DataNode<GradleSourceSetData>> sourceSetsMap = ContainerUtil.newHashMap();
319     for (DataNode<GradleSourceSetData> dataNode : ExternalSystemApiUtil.findAll(ideModule, GradleSourceSetData.KEY)) {
320       sourceSetsMap.put(dataNode.getData().getId(), dataNode);
321     }
322
323     for (ExternalSourceSet sourceSet : externalProject.getSourceSets().values()) {
324       if (sourceSet == null || sourceSet.getSources().isEmpty()) continue;
325
326       final String moduleId = getModuleId(externalProject, sourceSet);
327       final DataNode<GradleSourceSetData> sourceSetDataNode = sourceSetsMap.get(moduleId);
328       if (sourceSetDataNode == null) continue;
329
330       processor.process(sourceSetDataNode, sourceSet);
331     }
332   }
333
334
335   @Override
336   public void populateModuleCompileOutputSettings(@NotNull IdeaModule gradleModule,
337                                                   @NotNull DataNode<ModuleData> ideModule) {
338     ExternalProject externalProject = resolverCtx.getExtraProject(gradleModule, ExternalProject.class);
339     if (externalProject != null) {
340       processSourceSets(externalProject, ideModule, new SourceSetsProcessor() {
341         @Override
342         public void process(@NotNull DataNode<GradleSourceSetData> dataNode, @NotNull ExternalSourceSet sourceSet) {
343           for (Map.Entry<IExternalSystemSourceType, ExternalSourceDirectorySet> directorySetEntry : sourceSet.getSources().entrySet()) {
344             ExternalSystemSourceType sourceType = ExternalSystemSourceType.from(directorySetEntry.getKey());
345             ExternalSourceDirectorySet sourceDirectorySet = directorySetEntry.getValue();
346             final GradleSourceSetData sourceSetData = dataNode.getData();
347             sourceSetData.setCompileOutputPath(sourceType, sourceDirectorySet.getOutputDir().getAbsolutePath());
348             sourceSetData.setInheritProjectCompileOutputPath(sourceDirectorySet.isCompilerOutputPathInherited());
349           }
350         }
351       });
352
353       return;
354     }
355
356     IdeaCompilerOutput moduleCompilerOutput = gradleModule.getCompilerOutput();
357
358     File buildDir = null;
359     try {
360       buildDir = gradleModule.getGradleProject().getBuildDirectory();
361     }
362     catch (UnsupportedMethodException ignore) {
363       // see org.gradle.tooling.model.GradleProject.getBuildDirectory method supported only since Gradle 2.0
364       // will use com.intellij.openapi.externalSystem.model.ExternalProject.getBuildDir() instead
365     }
366
367     Map<ExternalSystemSourceType, File> compileOutputPaths = ContainerUtil.newHashMap();
368
369     boolean inheritOutputDirs = false;
370
371     ModuleData moduleData = ideModule.getData();
372     if (moduleCompilerOutput != null) {
373       compileOutputPaths.put(ExternalSystemSourceType.SOURCE, moduleCompilerOutput.getOutputDir());
374       compileOutputPaths.put(ExternalSystemSourceType.RESOURCE, moduleCompilerOutput.getOutputDir());
375       compileOutputPaths.put(ExternalSystemSourceType.TEST, moduleCompilerOutput.getTestOutputDir());
376       compileOutputPaths.put(ExternalSystemSourceType.TEST_RESOURCE, moduleCompilerOutput.getTestOutputDir());
377
378       inheritOutputDirs = moduleCompilerOutput.getInheritOutputDirs();
379     }
380
381     for (Map.Entry<ExternalSystemSourceType, File> sourceTypeFileEntry : compileOutputPaths.entrySet()) {
382       final File outputPath = ObjectUtils.chooseNotNull(sourceTypeFileEntry.getValue(), buildDir);
383       if (outputPath != null) {
384         moduleData.setCompileOutputPath(sourceTypeFileEntry.getKey(), outputPath.getAbsolutePath());
385       }
386     }
387
388     moduleData.setInheritProjectCompileOutputPath(inheritOutputDirs);
389   }
390
391   @Override
392   public void populateModuleDependencies(@NotNull IdeaModule gradleModule,
393                                          @NotNull DataNode<ModuleData> ideModule,
394                                          @NotNull final DataNode<ProjectData> ideProject) {
395
396     ExternalProject externalProject = resolverCtx.getExtraProject(gradleModule, ExternalProject.class);
397     if (externalProject != null) {
398       final Map<String, Pair<DataNode<GradleSourceSetData>, ExternalSourceSet>> sourceSetMap =
399         ideProject.getUserData(GradleProjectResolver.RESOLVED_SOURCE_SETS);
400       assert sourceSetMap != null;
401
402       processSourceSets(externalProject, ideModule, new SourceSetsProcessor() {
403         @Override
404         public void process(@NotNull DataNode<GradleSourceSetData> dataNode, @NotNull ExternalSourceSet sourceSet) {
405           buildDependencies(sourceSetMap, dataNode, sourceSet.getDependencies(), ideProject);
406         }
407       });
408
409       return;
410     }
411
412     final List<? extends IdeaDependency> dependencies = gradleModule.getDependencies().getAll();
413
414     if (dependencies == null) return;
415
416     for (IdeaDependency dependency : dependencies) {
417       if (dependency == null) {
418         continue;
419       }
420       DependencyScope scope = parseScope(dependency.getScope());
421
422       if (dependency instanceof IdeaModuleDependency) {
423         ModuleDependencyData d = buildDependency(ideModule, (IdeaModuleDependency)dependency, ideProject);
424         d.setExported(dependency.getExported());
425         if (scope != null) {
426           d.setScope(scope);
427         }
428         ideModule.createChild(ProjectKeys.MODULE_DEPENDENCY, d);
429       }
430       else if (dependency instanceof IdeaSingleEntryLibraryDependency) {
431         LibraryDependencyData d = buildDependency(gradleModule, ideModule, (IdeaSingleEntryLibraryDependency)dependency, ideProject);
432         d.setExported(dependency.getExported());
433         if (scope != null) {
434           d.setScope(scope);
435         }
436         ideModule.createChild(ProjectKeys.LIBRARY_DEPENDENCY, d);
437       }
438     }
439   }
440
441   @NotNull
442   @Override
443   public Collection<TaskData> populateModuleTasks(@NotNull IdeaModule gradleModule,
444                                                   @NotNull DataNode<ModuleData> ideModule,
445                                                   @NotNull DataNode<ProjectData> ideProject)
446     throws IllegalArgumentException, IllegalStateException {
447
448     final Collection<TaskData> tasks = ContainerUtil.newArrayList();
449     final String moduleConfigPath = ideModule.getData().getLinkedExternalProjectPath();
450
451     ExternalProject externalProject = resolverCtx.getExtraProject(gradleModule, ExternalProject.class);
452     final String rootProjectPath = ideProject.getData().getLinkedExternalProjectPath();
453     final boolean isFlatProject = !FileUtil.isAncestor(rootProjectPath, moduleConfigPath, false);
454     if (externalProject != null) {
455       for (ExternalTask task : externalProject.getTasks().values()) {
456         String taskName = isFlatProject ? task.getQName() : task.getName();
457         String taskGroup = task.getGroup();
458         if (taskName.trim().isEmpty() || isIdeaTask(taskName, taskGroup)) {
459           continue;
460         }
461         final String taskPath = isFlatProject ? rootProjectPath : moduleConfigPath;
462         TaskData taskData = new TaskData(GradleConstants.SYSTEM_ID, taskName, taskPath, task.getDescription());
463         taskData.setGroup(taskGroup);
464         taskData.setType(task.getType());
465         ideModule.createChild(ProjectKeys.TASK, taskData);
466         taskData.setInherited(StringUtil.equals(task.getName(), task.getQName()));
467         tasks.add(taskData);
468       }
469
470       return tasks;
471     }
472
473     for (GradleTask task : gradleModule.getGradleProject().getTasks()) {
474       String taskName = task.getName();
475       String taskGroup = getTaskGroup(task);
476       if (taskName == null || taskName.trim().isEmpty() || isIdeaTask(taskName, taskGroup)) {
477         continue;
478       }
479       TaskData taskData = new TaskData(GradleConstants.SYSTEM_ID, taskName, moduleConfigPath, task.getDescription());
480       taskData.setGroup(taskGroup);
481       ideModule.createChild(ProjectKeys.TASK, taskData);
482       tasks.add(taskData);
483     }
484
485     return tasks;
486   }
487
488   @Nullable
489   private static String getTaskGroup(GradleTask task) {
490     String taskGroup;
491     try {
492       taskGroup = task.getGroup();
493     }
494     catch (UnsupportedMethodException e) {
495       taskGroup = null;
496     }
497     return taskGroup;
498   }
499
500   @NotNull
501   @Override
502   public Set<Class> getExtraProjectModelClasses() {
503     Set<Class> result = ContainerUtil.<Class>set(GradleBuild.class, ModuleExtendedModel.class);
504     result.add(BuildScriptClasspathModel.class);
505     result.add(ExternalProject.class);
506     return result;
507   }
508
509   @NotNull
510   @Override
511   public Set<Class> getToolingExtensionsClasses() {
512     return ContainerUtil.<Class>set(
513       // external-system-rt.jar
514       ExternalSystemSourceType.class,
515       // gradle-tooling-extension-api jar
516       ProjectImportAction.class,
517       // gradle-tooling-extension-impl jar
518       ModelBuildScriptClasspathBuilderImpl.class,
519       Multimap.class,
520       GsonBuilder.class,
521       ShortTypeHandling.class
522     );
523   }
524
525   @NotNull
526   @Override
527   public List<KeyValue<String, String>> getExtraJvmArgs() {
528     if (ExternalSystemApiUtil.isInProcessMode(GradleConstants.SYSTEM_ID)) {
529       final List<KeyValue<String, String>> extraJvmArgs = ContainerUtil.newArrayList();
530       final HttpConfigurable httpConfigurable = HttpConfigurable.getInstance();
531       if (!StringUtil.isEmpty(httpConfigurable.PROXY_EXCEPTIONS)) {
532         List<String> hosts = StringUtil.split(httpConfigurable.PROXY_EXCEPTIONS, ",");
533         if (!hosts.isEmpty()) {
534           final String nonProxyHosts = StringUtil.join(hosts, StringUtil.TRIMMER, "|");
535           extraJvmArgs.add(KeyValue.create("http.nonProxyHosts", nonProxyHosts));
536           extraJvmArgs.add(KeyValue.create("https.nonProxyHosts", nonProxyHosts));
537         }
538       }
539       if (httpConfigurable.USE_HTTP_PROXY && StringUtil.isNotEmpty(httpConfigurable.PROXY_LOGIN)) {
540         extraJvmArgs.add(KeyValue.create("http.proxyUser", httpConfigurable.PROXY_LOGIN));
541         extraJvmArgs.add(KeyValue.create("https.proxyUser", httpConfigurable.PROXY_LOGIN));
542         final String plainProxyPassword = httpConfigurable.getPlainProxyPassword();
543         extraJvmArgs.add(KeyValue.create("http.proxyPassword", plainProxyPassword));
544         extraJvmArgs.add(KeyValue.create("https.proxyPassword", plainProxyPassword));
545       }
546       extraJvmArgs.addAll(HttpConfigurable.getJvmPropertiesList(false, null));
547
548       return extraJvmArgs;
549     }
550     return Collections.emptyList();
551   }
552
553   @NotNull
554   @Override
555   public List<String> getExtraCommandLineArgs() {
556     return Collections.emptyList();
557   }
558
559   @NotNull
560   @Override
561   public ExternalSystemException getUserFriendlyError(@NotNull Throwable error,
562                                                       @NotNull String projectPath,
563                                                       @Nullable String buildFilePath) {
564     return myErrorHandler.getUserFriendlyError(error, projectPath, buildFilePath);
565   }
566
567   @Override
568   public void preImportCheck() {
569   }
570
571   @Override
572   public void enhanceTaskProcessing(@NotNull List<String> taskNames,
573                                     @Nullable String debuggerSetup,
574                                     @NotNull Consumer<String> initScriptConsumer) {
575     if (!StringUtil.isEmpty(debuggerSetup)) {
576       final String[] lines = {
577         "gradle.taskGraph.beforeTask { Task task ->",
578         "    if (task instanceof JavaForkOptions) {",
579         "        def jvmArgs = task.jvmArgs.findAll{!it?.startsWith('-agentlib') && !it?.startsWith('-Xrunjdwp')}",
580         "        jvmArgs << '" + debuggerSetup.trim() + '\'',
581         "        task.jvmArgs jvmArgs",
582         "    }" +
583         "}",
584       };
585       final String script = StringUtil.join(lines, SystemProperties.getLineSeparator());
586       initScriptConsumer.consume(script);
587     }
588   }
589
590   @Override
591   public void enhanceRemoteProcessing(@NotNull SimpleJavaParameters parameters) throws ExecutionException {
592     PathsList classPath = parameters.getClassPath();
593
594     // Gradle i18n bundle.
595     ExternalSystemApiUtil.addBundle(classPath, GradleBundle.PATH_TO_BUNDLE, GradleBundle.class);
596
597     // Gradle tool jars.
598     String toolingApiPath = PathManager.getJarPathForClass(ProjectConnection.class);
599     if (toolingApiPath == null) {
600       LOG.warn(GradleBundle.message("gradle.generic.text.error.jar.not.found"));
601       throw new ExecutionException("Can't find gradle libraries");
602     }
603     File gradleJarsDir = new File(toolingApiPath).getParentFile();
604     File[] gradleJars = gradleJarsDir.listFiles(FileFilters.filesWithExtension("jar"));
605     if (gradleJars == null) {
606       LOG.warn(GradleBundle.message("gradle.generic.text.error.jar.not.found"));
607       throw new ExecutionException("Can't find gradle libraries at " + gradleJarsDir.getAbsolutePath());
608     }
609     for (File jar : gradleJars) {
610       classPath.add(jar.getAbsolutePath());
611     }
612
613     List<String> additionalEntries = ContainerUtilRt.newArrayList();
614     ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(GroovyObject.class));
615     ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(GsonBuilder.class));
616     ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(ExternalProject.class));
617     ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(JavaProjectData.class));
618     ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(LanguageLevel.class));
619     ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(StdModuleTypes.class));
620     ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(JavaModuleType.class));
621     ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(ModuleType.class));
622     ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(EmptyModuleType.class));
623     ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(ProjectImportAction.class));
624     ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(Init.class));
625     ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(org.slf4j.Logger.class));
626     ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(Log4jLoggerFactory.class));
627     for (String entry : additionalEntries) {
628       classPath.add(entry);
629     }
630   }
631
632   @Override
633   public void enhanceLocalProcessing(@NotNull List<URL> urls) {
634   }
635
636   /**
637    * Stores information about given directories at the given content root
638    *
639    * @param contentRoot target paths info holder
640    * @param type        type of data located at the given directories
641    * @param dirs        directories which paths should be stored at the given content root
642    * @throws IllegalArgumentException if specified by {@link ContentRootData#storePath(ExternalSystemSourceType, String)}
643    */
644   private static void populateContentRoot(@NotNull final ContentRootData contentRoot,
645                                           @NotNull final ExternalSystemSourceType type,
646                                           @Nullable final Iterable<? extends IdeaSourceDirectory> dirs)
647     throws IllegalArgumentException {
648     if (dirs == null) {
649       return;
650     }
651     for (IdeaSourceDirectory dir : dirs) {
652       ExternalSystemSourceType dirSourceType = type;
653       try {
654         if (dir.isGenerated() && !dirSourceType.isGenerated()) {
655           final ExternalSystemSourceType generatedType = ExternalSystemSourceType.from(
656             dirSourceType.isTest(), dir.isGenerated(), dirSourceType.isResource(), dirSourceType.isExcluded()
657           );
658           dirSourceType = generatedType != null ? generatedType : dirSourceType;
659         }
660       }
661       catch (UnsupportedMethodException e) {
662         // org.gradle.tooling.model.idea.IdeaSourceDirectory.isGenerated method supported only since Gradle 2.2
663         LOG.warn(e.getMessage());
664         printToolingProxyDiagnosticInfo(dir);
665       }
666       catch (Throwable e) {
667         LOG.debug(e);
668         printToolingProxyDiagnosticInfo(dir);
669       }
670       contentRoot.storePath(dirSourceType, dir.getDirectory().getAbsolutePath());
671     }
672   }
673
674   private static void printToolingProxyDiagnosticInfo(@Nullable Object obj) {
675     if (!LOG.isDebugEnabled() || obj == null) return;
676
677     LOG.debug(String.format("obj: %s", obj));
678     final Class<?> aClass = obj.getClass();
679     LOG.debug(String.format("obj class: %s", aClass));
680     LOG.debug(String.format("classloader: %s", aClass.getClassLoader()));
681     for (Method m : aClass.getDeclaredMethods()) {
682       LOG.debug(String.format("obj m: %s", m));
683     }
684
685     if (obj instanceof Proxy) {
686       try {
687         final Field hField = ReflectionUtil.findField(obj.getClass(), null, "h");
688         hField.setAccessible(true);
689         final Object h = hField.get(obj);
690         final Field delegateField = ReflectionUtil.findField(h.getClass(), null, "delegate");
691         delegateField.setAccessible(true);
692         final Object delegate = delegateField.get(h);
693         LOG.debug(String.format("delegate: %s", delegate));
694         LOG.debug(String.format("delegate class: %s", delegate.getClass()));
695         LOG.debug(String.format("delegate classloader: %s", delegate.getClass().getClassLoader()));
696         for (Method m : delegate.getClass().getDeclaredMethods()) {
697           LOG.debug(String.format("delegate m: %s", m));
698         }
699       }
700       catch (NoSuchFieldException e) {
701         LOG.debug(e);
702       }
703       catch (IllegalAccessException e) {
704         LOG.debug(e);
705       }
706     }
707   }
708
709   @Nullable
710   private static DependencyScope parseScope(@Nullable IdeaDependencyScope scope) {
711     if (scope == null) {
712       return null;
713     }
714     String scopeAsString = scope.getScope();
715     if (scopeAsString == null) {
716       return null;
717     }
718     for (DependencyScope dependencyScope : DependencyScope.values()) {
719       if (scopeAsString.equalsIgnoreCase(dependencyScope.toString())) {
720         return dependencyScope;
721       }
722     }
723     return null;
724   }
725
726   @NotNull
727   private static ModuleDependencyData buildDependency(@NotNull DataNode<ModuleData> ownerModule,
728                                                       @NotNull IdeaModuleDependency dependency,
729                                                       @NotNull DataNode<ProjectData> ideProject)
730     throws IllegalStateException {
731     IdeaModule module = dependency.getDependencyModule();
732     if (module == null) {
733       throw new IllegalStateException(
734         String.format("Can't parse gradle module dependency '%s'. Reason: referenced module is null", dependency)
735       );
736     }
737
738     String moduleName = module.getName();
739     if (moduleName == null) {
740       throw new IllegalStateException(String.format(
741         "Can't parse gradle module dependency '%s'. Reason: referenced module name is undefined (module: '%s') ", dependency, module
742       ));
743     }
744
745     Set<String> registeredModuleNames = ContainerUtilRt.newHashSet();
746     Collection<DataNode<ModuleData>> modulesDataNode = ExternalSystemApiUtil.getChildren(ideProject, ProjectKeys.MODULE);
747     for (DataNode<ModuleData> moduleDataNode : modulesDataNode) {
748       String name = moduleDataNode.getData().getExternalName();
749       registeredModuleNames.add(name);
750       if (name.equals(moduleName)) {
751         return new ModuleDependencyData(ownerModule.getData(), moduleDataNode.getData());
752       }
753     }
754     throw new IllegalStateException(String.format(
755       "Can't parse gradle module dependency '%s'. Reason: no module with such name (%s) is found. Registered modules: %s",
756       dependency, moduleName, registeredModuleNames
757     ));
758   }
759
760   @NotNull
761   private LibraryDependencyData buildDependency(@NotNull IdeaModule gradleModule,
762                                                 @NotNull DataNode<ModuleData> ownerModule,
763                                                 @NotNull IdeaSingleEntryLibraryDependency dependency,
764                                                 @NotNull DataNode<ProjectData> ideProject)
765     throws IllegalStateException {
766     File binaryPath = dependency.getFile();
767     if (binaryPath == null) {
768       throw new IllegalStateException(String.format(
769         "Can't parse external library dependency '%s'. Reason: it doesn't specify path to the binaries", dependency
770       ));
771     }
772
773     String libraryName;
774     final GradleModuleVersion moduleVersion = dependency.getGradleModuleVersion();
775     final LibraryLevel level;
776
777     // Gradle API doesn't explicitly provide information about unresolved libraries (http://issues.gradle.org/browse/GRADLE-1995).
778     // That's why we use this dirty hack here.
779     boolean unresolved = binaryPath.getPath().startsWith(UNRESOLVED_DEPENDENCY_PREFIX);
780
781     if (moduleVersion == null) {
782       // use module library level if the dependency does not originate from a remote repository.
783       level = LibraryLevel.MODULE;
784
785       if (binaryPath.isFile()) {
786         libraryName = FileUtil.getNameWithoutExtension(binaryPath);
787       }
788       else {
789         libraryName = "";
790       }
791
792       if (unresolved) {
793         // Gradle uses names like 'unresolved dependency - commons-collections commons-collections 3.2' for unresolved dependencies.
794         libraryName = binaryPath.getPath().substring(UNRESOLVED_DEPENDENCY_PREFIX.length());
795         int i = libraryName.indexOf(' ');
796         if (i >= 0) {
797           i = CharArrayUtil.shiftForward(libraryName, i + 1, " ");
798         }
799
800         if (i >= 0 && i < libraryName.length()) {
801           int dependencyNameIndex = i;
802           i = libraryName.indexOf(' ', dependencyNameIndex);
803           if (i > 0) {
804             libraryName = String.format("%s-%s", libraryName.substring(dependencyNameIndex, i), libraryName.substring(i + 1));
805           }
806         }
807       }
808     }
809     else {
810       level = LibraryLevel.PROJECT;
811       libraryName = String.format("%s:%s:%s", moduleVersion.getGroup(), moduleVersion.getName(), moduleVersion.getVersion());
812       if (binaryPath.isFile()) {
813         String libraryFileName = FileUtil.getNameWithoutExtension(binaryPath);
814         final String mavenLibraryFileName = String.format("%s-%s", moduleVersion.getName(), moduleVersion.getVersion());
815         if (!mavenLibraryFileName.equals(libraryFileName)) {
816           Pattern pattern = Pattern.compile(moduleVersion.getName() + "-" + moduleVersion.getVersion() + "-(.*)");
817           Matcher matcher = pattern.matcher(libraryFileName);
818           if (matcher.matches()) {
819             final String classifier = matcher.group(1);
820             libraryName += (":" + classifier);
821           }
822           else {
823             final String artifactId = StringUtil.trimEnd(StringUtil.trimEnd(libraryFileName, moduleVersion.getVersion()), "-");
824             libraryName = String.format("%s:%s:%s",
825                                         moduleVersion.getGroup(),
826                                         artifactId,
827                                         moduleVersion.getVersion());
828           }
829         }
830       }
831     }
832
833     // add packaging type to distinguish different artifact dependencies with same groupId:artifactId:version
834     if (StringUtil.isNotEmpty(libraryName) && !FileUtilRt.extensionEquals(binaryPath.getPath(), "jar")) {
835       libraryName += (":" + FileUtilRt.getExtension(binaryPath.getPath()));
836     }
837
838     final LibraryData library = new LibraryData(GradleConstants.SYSTEM_ID, libraryName, unresolved);
839     if (!unresolved) {
840       library.addPath(LibraryPathType.BINARY, binaryPath.getAbsolutePath());
841     }
842
843     File sourcePath = dependency.getSource();
844     if (!unresolved && sourcePath != null) {
845       library.addPath(LibraryPathType.SOURCE, sourcePath.getAbsolutePath());
846     }
847
848     if (!unresolved && sourcePath == null) {
849       attachGradleSdkSources(gradleModule, binaryPath, library, resolverCtx);
850     }
851
852     File javadocPath = dependency.getJavadoc();
853     if (!unresolved && javadocPath != null) {
854       library.addPath(LibraryPathType.DOC, javadocPath.getAbsolutePath());
855     }
856
857     if (level == LibraryLevel.PROJECT) {
858       linkProjectLibrary(ideProject, library);
859     }
860
861     return new LibraryDependencyData(ownerModule.getData(), library, level);
862   }
863
864   private interface SourceSetsProcessor {
865     void process(@NotNull DataNode<GradleSourceSetData> dataNode, @NotNull ExternalSourceSet sourceSet);
866   }
867 }