diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/util/ExternalSystemUtil.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/util/ExternalSystemUtil.java
index 809ac6c5a5d7..9a6a3bb4b859 100644
--- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/util/ExternalSystemUtil.java
+++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/util/ExternalSystemUtil.java
@@ -68,6 +68,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
@@ -512,7 +513,17 @@ public class ExternalSystemUtil {
@Nullable final TaskCallback callback,
@NotNull final ProgressExecutionMode progressExecutionMode,
boolean activateToolWindowBeforeRun) {
+ runTask(taskSettings, executorId, project, externalSystemId, callback, progressExecutionMode, activateToolWindowBeforeRun, null);
+ }
+ public static void runTask(@NotNull final ExternalSystemTaskExecutionSettings taskSettings,
+ @NotNull final String executorId,
+ @NotNull final Project project,
+ @NotNull final ProjectSystemId externalSystemId,
+ @Nullable final TaskCallback callback,
+ @NotNull final ProgressExecutionMode progressExecutionMode,
+ boolean activateToolWindowBeforeRun,
+ @Nullable UserDataHolderBase userData) {
ExecutionEnvironment environment = createExecutionEnvironment(project, externalSystemId, taskSettings, executorId);
if (environment == null) return;
@@ -520,6 +531,11 @@ public class ExternalSystemUtil {
assert runnerAndConfigurationSettings != null;
runnerAndConfigurationSettings.setActivateToolWindowBeforeRun(activateToolWindowBeforeRun);
+ if (userData != null) {
+ ExternalSystemRunConfiguration runConfiguration = (ExternalSystemRunConfiguration)runnerAndConfigurationSettings.getConfiguration();
+ userData.copyUserDataTo(runConfiguration);
+ }
+
final TaskUnderProgress task = new TaskUnderProgress() {
@Override
public void execute(@NotNull ProgressIndicator indicator) {
diff --git a/plugins/gradle/resources/i18n/GradleBundle.properties b/plugins/gradle/resources/i18n/GradleBundle.properties
index 5e8f841ad100..2d0764f7db96 100644
--- a/plugins/gradle/resources/i18n/GradleBundle.properties
+++ b/plugins/gradle/resources/i18n/GradleBundle.properties
@@ -51,4 +51,7 @@ gradle.test.runner.ui.tests.actions.open.gradle.report.desc=Open Gradle reports
# Gradle-Maven integration messages
gradle.integrations.maven.notification.not_updated_repository.title=Unindexed remote maven repositories found
gradle.integrations.maven.notification.not_updated_repository.text=Update index for dependency completion or disable this notification
-exportable.GradleSystemRunningSettings.presentable.name=Gradle: System Running
\ No newline at end of file
+exportable.GradleSystemRunningSettings.presentable.name=Gradle: System Running
+
+gradle.action.download.sources=Download Sources
+gradle.action.download.sources.busy.text=Downloading sources...
\ No newline at end of file
diff --git a/plugins/gradle/src/META-INF/plugin.xml b/plugins/gradle/src/META-INF/plugin.xml
index 293b7876c883..e7968431f2e6 100644
--- a/plugins/gradle/src/META-INF/plugin.xml
+++ b/plugins/gradle/src/META-INF/plugin.xml
@@ -174,6 +174,7 @@
+
diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleAttachSourcesProvider.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleAttachSourcesProvider.java
new file mode 100644
index 000000000000..cb6496a37e45
--- /dev/null
+++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleAttachSourcesProvider.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2000-2017 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.util;
+
+import com.intellij.codeInsight.AttachSourcesProvider;
+import com.intellij.execution.executors.DefaultRunExecutor;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.externalSystem.model.execution.ExternalSystemTaskExecutionSettings;
+import com.intellij.openapi.externalSystem.model.project.LibraryData;
+import com.intellij.openapi.externalSystem.model.project.LibraryPathType;
+import com.intellij.openapi.externalSystem.service.execution.ProgressExecutionMode;
+import com.intellij.openapi.externalSystem.service.notification.ExternalSystemNotificationManager;
+import com.intellij.openapi.externalSystem.service.notification.NotificationCategory;
+import com.intellij.openapi.externalSystem.service.notification.NotificationData;
+import com.intellij.openapi.externalSystem.service.notification.NotificationSource;
+import com.intellij.openapi.externalSystem.task.TaskCallback;
+import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil;
+import com.intellij.openapi.externalSystem.util.ExternalSystemUtil;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.LibraryOrderEntry;
+import com.intellij.openapi.roots.OrderRootType;
+import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.util.ActionCallback;
+import com.intellij.openapi.util.UserDataHolderBase;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiFile;
+import com.intellij.util.PathUtil;
+import com.intellij.util.containers.ContainerUtil;
+import org.gradle.initialization.BuildLayoutParameters;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.gradle.service.project.GradleProjectResolverUtil;
+import org.jetbrains.plugins.gradle.service.task.GradleTaskManager;
+import org.jetbrains.plugins.gradle.settings.GradleSettings;
+
+import java.io.File;
+import java.util.*;
+
+import static com.intellij.jarFinder.InternetAttachSourceProvider.attachSourceJar;
+import static org.jetbrains.plugins.gradle.service.project.GradleProjectResolverUtil.attachSourcesAndJavadocFromGradleCacheIfNeeded;
+
+/**
+ * @author Vladislav.Soroka
+ */
+public class GradleAttachSourcesProvider implements AttachSourcesProvider {
+ @NotNull
+ @Override
+ public Collection getActions(List orderEntries, PsiFile psiFile) {
+ Map gradleModules = getGradleModules(orderEntries);
+ if (gradleModules.isEmpty()) return Collections.emptyList();
+
+ return Collections.singleton(new AttachSourcesAction() {
+ @Override
+ public String getName() {
+ return GradleBundle.message("gradle.action.download.sources");
+ }
+
+ @Override
+ public String getBusyText() {
+ return GradleBundle.message("gradle.action.download.sources.busy.text");
+ }
+
+ @Override
+ public ActionCallback perform(List orderEntries) {
+ Map gradleModules = getGradleModules(orderEntries);
+ if (gradleModules.isEmpty()) return ActionCallback.REJECTED;
+ final ActionCallback resultWrapper = new ActionCallback();
+ Project project = psiFile.getProject();
+
+ Map.Entry next = gradleModules.entrySet().iterator().next();
+ LibraryOrderEntry libraryOrderEntry = next.getKey();
+ Module module = next.getValue();
+
+ String libraryName = libraryOrderEntry.getLibraryName();
+ if (libraryName == null) return ActionCallback.REJECTED;
+
+ String artifactCoordinates = StringUtil.trimStart(libraryName, GradleConstants.SYSTEM_ID.getReadableName() + ": ");
+ if (StringUtil.equals(libraryName, artifactCoordinates)) return ActionCallback.REJECTED;
+ final String gradlePath = GradleProjectResolverUtil.getGradlePath(module);
+ if (gradlePath == null) return ActionCallback.REJECTED;
+
+ final String taskName = "Download sources";
+ String initScript = "projectsEvaluated {\n" +
+ " rootProject.allprojects {\n" +
+ " if(project.path == '" + gradlePath + "') {\n" +
+ " project.configurations.maybeCreate('downloadSources')\n" +
+ " project.dependencies.add('downloadSources', '" + artifactCoordinates + ":sources" + "')\n" +
+ " project.tasks.create(name: '" + taskName + "', overwrite: true) {\n" +
+ " doLast{\n" +
+ " project.configurations.downloadSources.resolve()\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
+ "}\n";
+
+ UserDataHolderBase userData = new UserDataHolderBase();
+ userData.putUserData(GradleTaskManager.INIT_SCRIPT_KEY, initScript);
+
+ String gradleVmOptions = GradleSettings.getInstance(project).getGradleVmOptions();
+ ExternalSystemTaskExecutionSettings settings = new ExternalSystemTaskExecutionSettings();
+ settings.setExecutionName("Download sources");
+ settings.setExternalProjectPath(ExternalSystemApiUtil.getExternalRootProjectPath(module));
+ settings.setTaskNames(ContainerUtil.list(taskName));
+ settings.setVmOptions(gradleVmOptions);
+ settings.setExternalSystemIdString(GradleConstants.SYSTEM_ID.getId());
+ ExternalSystemUtil.runTask(
+ settings, DefaultRunExecutor.EXECUTOR_ID, project, GradleConstants.SYSTEM_ID,
+ new TaskCallback() {
+ @Override
+ public void onSuccess() {
+ File sourceJar = getSourceFile(artifactCoordinates, libraryOrderEntry.getFiles(OrderRootType.CLASSES)[0], project);
+ ApplicationManager.getApplication().invokeLater(() -> {
+ final Set libraries = new HashSet<>();
+ for (LibraryOrderEntry orderEntry : orderEntries) {
+ ContainerUtil.addIfNotNull(libraries, orderEntry.getLibrary());
+ }
+ attachSourceJar(sourceJar, libraries);
+ resultWrapper.setDone();
+ });
+ }
+
+ @Override
+ public void onFailure() {
+ resultWrapper.setRejected();
+ String message = ("Sources not found for: " + artifactCoordinates) + "";
+ NotificationData notification = new NotificationData(
+ "Sources download failed", message, NotificationCategory.WARNING, NotificationSource.PROJECT_SYNC);
+ ExternalSystemNotificationManager.getInstance(project).showNotification(GradleConstants.SYSTEM_ID, notification);
+ }
+ }, ProgressExecutionMode.IN_BACKGROUND_ASYNC, false, userData);
+
+ return resultWrapper;
+ }
+ });
+ }
+
+ @NotNull
+ private static File getSourceFile(String artifactCoordinates, VirtualFile classesFile, Project project) {
+ LibraryData data = new LibraryData(GradleConstants.SYSTEM_ID, artifactCoordinates);
+ data.addPath(LibraryPathType.BINARY, PathUtil.getLocalFile(classesFile).getPath());
+ String serviceDirectory = GradleSettings.getInstance(project).getServiceDirectoryPath();
+ File gradleUserHome =
+ serviceDirectory != null ? new File(serviceDirectory) : new BuildLayoutParameters().getGradleUserHomeDir();
+ attachSourcesAndJavadocFromGradleCacheIfNeeded(gradleUserHome, data);
+ return new File(data.getPaths(LibraryPathType.SOURCE).iterator().next());
+ }
+
+ private static Map getGradleModules(List libraryOrderEntries) {
+ Map result = ContainerUtil.newHashMap();
+ for (LibraryOrderEntry entry : libraryOrderEntries) {
+ if (entry.isModuleLevel()) continue;
+ Module module = entry.getOwnerModule();
+ if (ExternalSystemApiUtil.isExternalSystemAwareModule(GradleConstants.SYSTEM_ID, module)) {
+ result.put(entry, module);
+ }
+ }
+ return result;
+ }
+}