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; + } +}