| /* |
| * 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 com.intellij.openapi.externalSystem.util; |
| |
| import com.intellij.execution.*; |
| import com.intellij.execution.configurations.ConfigurationType; |
| import com.intellij.execution.executors.DefaultDebugExecutor; |
| import com.intellij.execution.executors.DefaultRunExecutor; |
| import com.intellij.execution.process.ProcessAdapter; |
| import com.intellij.execution.process.ProcessEvent; |
| import com.intellij.execution.process.ProcessHandler; |
| import com.intellij.execution.rmi.RemoteUtil; |
| import com.intellij.execution.runners.ExecutionEnvironment; |
| import com.intellij.execution.runners.ProgramRunner; |
| import com.intellij.openapi.Disposable; |
| import com.intellij.openapi.actionSystem.DataKey; |
| import com.intellij.openapi.actionSystem.DataProvider; |
| import com.intellij.openapi.application.Application; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.application.ModalityState; |
| import com.intellij.openapi.components.ServiceManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.extensions.Extensions; |
| import com.intellij.openapi.externalSystem.ExternalSystemManager; |
| import com.intellij.openapi.externalSystem.importing.ImportSpec; |
| import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder; |
| import com.intellij.openapi.externalSystem.model.*; |
| import com.intellij.openapi.externalSystem.model.execution.ExternalSystemTaskExecutionSettings; |
| import com.intellij.openapi.externalSystem.model.execution.ExternalTaskExecutionInfo; |
| import com.intellij.openapi.externalSystem.model.project.ModuleData; |
| import com.intellij.openapi.externalSystem.model.project.ProjectData; |
| import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener; |
| import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType; |
| import com.intellij.openapi.externalSystem.service.ImportCanceledException; |
| import com.intellij.openapi.externalSystem.service.execution.AbstractExternalSystemTaskConfigurationType; |
| import com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfiguration; |
| import com.intellij.openapi.externalSystem.service.execution.ProgressExecutionMode; |
| import com.intellij.openapi.externalSystem.service.internal.ExternalSystemProcessingManager; |
| import com.intellij.openapi.externalSystem.service.internal.ExternalSystemResolveProjectTask; |
| import com.intellij.openapi.externalSystem.service.notification.ExternalSystemNotificationManager; |
| import com.intellij.openapi.externalSystem.service.notification.NotificationSource; |
| import com.intellij.openapi.externalSystem.service.project.ExternalProjectRefreshCallback; |
| import com.intellij.openapi.externalSystem.service.project.PlatformFacade; |
| import com.intellij.openapi.externalSystem.service.project.ProjectStructureHelper; |
| import com.intellij.openapi.externalSystem.service.project.manage.ModuleDataService; |
| import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataManager; |
| import com.intellij.openapi.externalSystem.service.settings.ExternalSystemConfigLocator; |
| import com.intellij.openapi.externalSystem.service.task.ui.ExternalSystemRecentTasksList; |
| import com.intellij.openapi.externalSystem.settings.AbstractExternalSystemLocalSettings; |
| import com.intellij.openapi.externalSystem.settings.AbstractExternalSystemSettings; |
| import com.intellij.openapi.externalSystem.settings.ExternalProjectSettings; |
| import com.intellij.openapi.externalSystem.task.TaskCallback; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.progress.PerformInBackgroundOption; |
| import com.intellij.openapi.progress.ProgressIndicator; |
| import com.intellij.openapi.progress.Task; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.roots.ex.ProjectRootManagerEx; |
| import com.intellij.openapi.roots.libraries.Library; |
| import com.intellij.openapi.roots.libraries.LibraryTable; |
| import com.intellij.openapi.ui.DialogWrapper; |
| import com.intellij.openapi.util.Disposer; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.LocalFileSystem; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.openapi.wm.ToolWindow; |
| import com.intellij.openapi.wm.ToolWindowEP; |
| import com.intellij.openapi.wm.ToolWindowManager; |
| import com.intellij.openapi.wm.ex.ToolWindowManagerEx; |
| import com.intellij.openapi.wm.impl.ToolWindowImpl; |
| import com.intellij.ui.CheckBoxList; |
| import com.intellij.ui.IdeBorderFactory; |
| import com.intellij.ui.components.JBScrollPane; |
| import com.intellij.ui.content.Content; |
| import com.intellij.ui.content.ContentManager; |
| import com.intellij.util.Consumer; |
| import com.intellij.util.Function; |
| import com.intellij.util.concurrency.Semaphore; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.containers.ContainerUtilRt; |
| import com.intellij.util.ui.UIUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import java.awt.*; |
| import java.io.File; |
| import java.util.*; |
| import java.util.List; |
| |
| /** |
| * @author Denis Zhdanov |
| * @since 4/22/13 9:36 AM |
| */ |
| public class ExternalSystemUtil { |
| |
| private static final Logger LOG = Logger.getInstance("#" + ExternalSystemUtil.class.getName()); |
| |
| @NotNull private static final Map<String, String> RUNNER_IDS = ContainerUtilRt.newHashMap(); |
| |
| static { |
| RUNNER_IDS.put(DefaultRunExecutor.EXECUTOR_ID, ExternalSystemConstants.RUNNER_ID); |
| RUNNER_IDS.put(DefaultDebugExecutor.EXECUTOR_ID, ExternalSystemConstants.DEBUG_RUNNER_ID); |
| } |
| |
| private ExternalSystemUtil() { |
| } |
| |
| public static void ensureToolWindowInitialized(@NotNull Project project, @NotNull ProjectSystemId externalSystemId) { |
| ToolWindowManager manager = ToolWindowManager.getInstance(project); |
| if (!(manager instanceof ToolWindowManagerEx)) { |
| return; |
| } |
| ToolWindowManagerEx managerEx = (ToolWindowManagerEx)manager; |
| String id = externalSystemId.getReadableName(); |
| ToolWindow window = manager.getToolWindow(id); |
| if (window != null) { |
| return; |
| } |
| ToolWindowEP[] beans = Extensions.getExtensions(ToolWindowEP.EP_NAME); |
| for (final ToolWindowEP bean : beans) { |
| if (id.equals(bean.id)) { |
| managerEx.initToolWindow(bean); |
| } |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Nullable |
| public static <T> T getToolWindowElement(@NotNull Class<T> clazz, |
| @NotNull Project project, |
| @NotNull DataKey<T> key, |
| @NotNull ProjectSystemId externalSystemId) { |
| if (project.isDisposed() || !project.isOpen()) { |
| return null; |
| } |
| final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project); |
| if (toolWindowManager == null) { |
| return null; |
| } |
| final ToolWindow toolWindow = ensureToolWindowContentInitialized(project, externalSystemId); |
| if (toolWindow == null) { |
| return null; |
| } |
| |
| final ContentManager contentManager = toolWindow.getContentManager(); |
| if (contentManager == null) { |
| return null; |
| } |
| |
| for (Content content : contentManager.getContents()) { |
| final JComponent component = content.getComponent(); |
| if (component instanceof DataProvider) { |
| final Object data = ((DataProvider)component).getData(key.getName()); |
| if (data != null && clazz.isInstance(data)) { |
| return (T)data; |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static ToolWindow ensureToolWindowContentInitialized(@NotNull Project project, @NotNull ProjectSystemId externalSystemId) { |
| final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project); |
| if (toolWindowManager == null) return null; |
| |
| final ToolWindow toolWindow = toolWindowManager.getToolWindow(externalSystemId.getReadableName()); |
| if (toolWindow == null) return null; |
| |
| if (toolWindow instanceof ToolWindowImpl) { |
| ((ToolWindowImpl)toolWindow).ensureContentInitialized(); |
| } |
| return toolWindow; |
| } |
| |
| /** |
| * Asks to refresh all external projects of the target external system linked to the given ide project. |
| * <p/> |
| * 'Refresh' here means 'obtain the most up-to-date version and apply it to the ide'. |
| * |
| * @param project target ide project |
| * @param externalSystemId target external system which projects should be refreshed |
| * @param force flag which defines if external project refresh should be performed if it's config is up-to-date |
| * @deprecated use {@link ExternalSystemUtil#refreshProjects(com.intellij.openapi.externalSystem.importing.ImportSpecBuilder)} |
| */ |
| @Deprecated |
| public static void refreshProjects(@NotNull final Project project, @NotNull final ProjectSystemId externalSystemId, boolean force) { |
| refreshProjects(project, externalSystemId, force, ProgressExecutionMode.IN_BACKGROUND_ASYNC); |
| } |
| |
| /** |
| * Asks to refresh all external projects of the target external system linked to the given ide project. |
| * <p/> |
| * 'Refresh' here means 'obtain the most up-to-date version and apply it to the ide'. |
| * |
| * @param project target ide project |
| * @param externalSystemId target external system which projects should be refreshed |
| * @param force flag which defines if external project refresh should be performed if it's config is up-to-date |
| * |
| * @deprecated use {@link ExternalSystemUtil#refreshProjects(com.intellij.openapi.externalSystem.importing.ImportSpecBuilder)} |
| */ |
| @Deprecated |
| public static void refreshProjects(@NotNull final Project project, @NotNull final ProjectSystemId externalSystemId, boolean force, @NotNull final ProgressExecutionMode progressExecutionMode) { |
| refreshProjects( |
| new ImportSpecBuilder(project, externalSystemId) |
| .forceWhenUptodate(force) |
| .use(progressExecutionMode) |
| ); |
| } |
| |
| /** |
| * Asks to refresh all external projects of the target external system linked to the given ide project based on provided spec |
| * |
| * @param specBuilder import specification builder |
| */ |
| public static void refreshProjects(@NotNull final ImportSpecBuilder specBuilder) { |
| ImportSpec spec = specBuilder.build(); |
| |
| ExternalSystemManager<?, ?, ?, ?, ?> manager = ExternalSystemApiUtil.getManager(spec.getExternalSystemId()); |
| if (manager == null) { |
| return; |
| } |
| AbstractExternalSystemSettings<?, ?, ?> settings = manager.getSettingsProvider().fun(spec.getProject()); |
| final Collection<? extends ExternalProjectSettings> projectsSettings = settings.getLinkedProjectsSettings(); |
| if (projectsSettings.isEmpty()) { |
| return; |
| } |
| |
| final ProjectDataManager projectDataManager = ServiceManager.getService(ProjectDataManager.class); |
| final int[] counter = new int[1]; |
| |
| ExternalProjectRefreshCallback callback = |
| new MyMultiExternalProjectRefreshCallback(spec.getProject(), projectDataManager, counter, spec.getExternalSystemId()); |
| |
| Map<String, Long> modificationStamps = |
| manager.getLocalSettingsProvider().fun(spec.getProject()).getExternalConfigModificationStamps(); |
| Set<String> toRefresh = ContainerUtilRt.newHashSet(); |
| for (ExternalProjectSettings setting : projectsSettings) { |
| |
| // don't refresh project when auto-import is disabled if such behavior needed (e.g. on project opening when auto-import is disabled) |
| if (!setting.isUseAutoImport() && spec.isWhenAutoImportEnabled()) continue; |
| |
| if (spec.isForceWhenUptodate()) { |
| toRefresh.add(setting.getExternalProjectPath()); |
| } |
| else { |
| Long oldModificationStamp = modificationStamps.get(setting.getExternalProjectPath()); |
| long currentModificationStamp = getTimeStamp(setting, spec.getExternalSystemId()); |
| if (oldModificationStamp == null || oldModificationStamp < currentModificationStamp) { |
| toRefresh.add(setting.getExternalProjectPath()); |
| } |
| } |
| } |
| |
| if (!toRefresh.isEmpty()) { |
| ExternalSystemNotificationManager.getInstance(spec.getProject()) |
| .clearNotifications(null, NotificationSource.PROJECT_SYNC, spec.getExternalSystemId()); |
| |
| counter[0] = toRefresh.size(); |
| for (String path : toRefresh) { |
| refreshProject( |
| spec.getProject(), spec.getExternalSystemId(), path, callback, false, spec.getProgressExecutionMode()); |
| } |
| } |
| } |
| |
| private static long getTimeStamp(@NotNull ExternalProjectSettings externalProjectSettings, @NotNull ProjectSystemId externalSystemId) { |
| long timeStamp = 0; |
| for (ExternalSystemConfigLocator locator : ExternalSystemConfigLocator.EP_NAME.getExtensions()) { |
| if (!externalSystemId.equals(locator.getTargetExternalSystemId())) { |
| continue; |
| } |
| for (VirtualFile virtualFile : locator.findAll(externalProjectSettings)) { |
| timeStamp += virtualFile.getTimeStamp(); |
| } |
| } |
| return timeStamp; |
| } |
| |
| /** |
| * There is a possible case that an external module has been un-linked from ide project. There are two ways to process |
| * ide modules which correspond to that external project: |
| * <pre> |
| * <ol> |
| * <li>Remove them from ide project as well;</li> |
| * <li>Keep them at ide project as well;</li> |
| * </ol> |
| * </pre> |
| * This method handles that situation, i.e. it asks a user what should be done and acts accordingly. |
| * |
| * @param orphanModules modules which correspond to the un-linked external project |
| * @param project current ide project |
| * @param externalSystemId id of the external system which project has been un-linked from ide project |
| */ |
| public static void ruleOrphanModules(@NotNull final List<Module> orphanModules, |
| @NotNull final Project project, |
| @NotNull final ProjectSystemId externalSystemId) |
| { |
| UIUtil.invokeLaterIfNeeded(new Runnable() { |
| @Override |
| public void run() { |
| |
| final JPanel content = new JPanel(new GridBagLayout()); |
| content.add(new JLabel(ExternalSystemBundle.message("orphan.modules.text", externalSystemId.getReadableName())), |
| ExternalSystemUiUtil.getFillLineConstraints(0)); |
| |
| final CheckBoxList<Module> orphanModulesList = new CheckBoxList<Module>(); |
| orphanModulesList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); |
| orphanModulesList.setItems(orphanModules, new Function<Module, String>() { |
| @Override |
| public String fun(Module module) { |
| return module.getName(); |
| } |
| }); |
| for (Module module : orphanModules) { |
| orphanModulesList.setItemSelected(module, true); |
| } |
| orphanModulesList.setBorder(IdeBorderFactory.createEmptyBorder(8)); |
| content.add(orphanModulesList, ExternalSystemUiUtil.getFillLineConstraints(0)); |
| content.setBorder(IdeBorderFactory.createEmptyBorder(0, 0, 8, 0)); |
| |
| DialogWrapper dialog = new DialogWrapper(project) { |
| |
| { |
| setTitle(ExternalSystemBundle.message("import.title", externalSystemId.getReadableName())); |
| init(); |
| } |
| |
| @Nullable |
| @Override |
| protected JComponent createCenterPanel() { |
| return new JBScrollPane(content); |
| } |
| }; |
| boolean ok = dialog.showAndGet(); |
| if (!ok) { |
| return; |
| } |
| |
| List<Module> toRemove = ContainerUtilRt.newArrayList(); |
| for (int i = 0; i < orphanModules.size(); i++) { |
| Module module = orphanModules.get(i); |
| if (orphanModulesList.isItemSelected(i)) { |
| toRemove.add(module); |
| } |
| else { |
| ModuleDataService.unlinkModuleFromExternalSystem(module); |
| } |
| } |
| |
| if (!toRemove.isEmpty()) { |
| ServiceManager.getService(ProjectDataManager.class).removeData(ProjectKeys.MODULE, toRemove, project, true); |
| } |
| } |
| }); |
| } |
| |
| @SuppressWarnings("ThrowableResultOfMethodCallIgnored") |
| @Nullable |
| private static String extractDetails(@NotNull Throwable e) { |
| final Throwable unwrapped = RemoteUtil.unwrap(e); |
| if (unwrapped instanceof ExternalSystemException) { |
| return ((ExternalSystemException)unwrapped).getOriginalReason(); |
| } |
| return null; |
| } |
| |
| /** |
| * TODO[Vlad]: refactor the method to use {@link com.intellij.openapi.externalSystem.importing.ImportSpecBuilder} |
| * |
| * Queries slave gradle process to refresh target gradle project. |
| * |
| * @param project target intellij project to use |
| * @param externalProjectPath path of the target gradle project's file |
| * @param callback callback to be notified on refresh result |
| * @param isPreviewMode flag that identifies whether gradle libraries should be resolved during the refresh |
| * @return the most up-to-date gradle project (if any) |
| */ |
| public static void refreshProject(@NotNull final Project project, |
| @NotNull final ProjectSystemId externalSystemId, |
| @NotNull final String externalProjectPath, |
| @NotNull final ExternalProjectRefreshCallback callback, |
| final boolean isPreviewMode, |
| @NotNull final ProgressExecutionMode progressExecutionMode) { |
| refreshProject(project, externalSystemId, externalProjectPath, callback, isPreviewMode, progressExecutionMode, true); |
| } |
| |
| /** |
| * TODO[Vlad]: refactor the method to use {@link com.intellij.openapi.externalSystem.importing.ImportSpecBuilder} |
| * |
| * Queries slave gradle process to refresh target gradle project. |
| * |
| * @param project target intellij project to use |
| * @param externalProjectPath path of the target gradle project's file |
| * @param callback callback to be notified on refresh result |
| * @param isPreviewMode flag that identifies whether gradle libraries should be resolved during the refresh |
| * @param reportRefreshError prevent to show annoying error notification, e.g. if auto-import mode used |
| * @return the most up-to-date gradle project (if any) |
| */ |
| public static void refreshProject(@NotNull final Project project, |
| @NotNull final ProjectSystemId externalSystemId, |
| @NotNull final String externalProjectPath, |
| @NotNull final ExternalProjectRefreshCallback callback, |
| final boolean isPreviewMode, |
| @NotNull final ProgressExecutionMode progressExecutionMode, |
| final boolean reportRefreshError) |
| { |
| File projectFile = new File(externalProjectPath); |
| final String projectName; |
| if (projectFile.isFile()) { |
| projectName = projectFile.getParentFile().getName(); |
| } |
| else { |
| projectName = projectFile.getName(); |
| } |
| final TaskUnderProgress refreshProjectStructureTask = new TaskUnderProgress() { |
| @SuppressWarnings({"ThrowableResultOfMethodCallIgnored", "IOResourceOpenedButNotSafelyClosed"}) |
| @Override |
| public void execute(@NotNull ProgressIndicator indicator) { |
| if(project.isDisposed()) return; |
| |
| ExternalSystemProcessingManager processingManager = ServiceManager.getService(ExternalSystemProcessingManager.class); |
| if (processingManager.findTask(ExternalSystemTaskType.RESOLVE_PROJECT, externalSystemId, externalProjectPath) != null) { |
| callback.onFailure(ExternalSystemBundle.message("error.resolve.already.running", externalProjectPath), null); |
| return; |
| } |
| |
| if (!(callback instanceof MyMultiExternalProjectRefreshCallback)) { |
| ExternalSystemNotificationManager.getInstance(project) |
| .clearNotifications(null, NotificationSource.PROJECT_SYNC, externalSystemId); |
| } |
| |
| ExternalSystemResolveProjectTask task |
| = new ExternalSystemResolveProjectTask(externalSystemId, project, externalProjectPath, isPreviewMode); |
| |
| task.execute(indicator, ExternalSystemTaskNotificationListener.EP_NAME.getExtensions()); |
| if(project.isDisposed()) return; |
| |
| final Throwable error = task.getError(); |
| if (error == null) { |
| ExternalSystemManager<?, ?, ?, ?, ?> manager = ExternalSystemApiUtil.getManager(externalSystemId); |
| assert manager != null; |
| DataNode<ProjectData> externalProject = task.getExternalProject(); |
| |
| if(externalProject != null) { |
| Set<String> externalModulePaths = ContainerUtil.newHashSet(); |
| Collection<DataNode<ModuleData>> moduleNodes = ExternalSystemApiUtil.findAll(externalProject, ProjectKeys.MODULE); |
| for (DataNode<ModuleData> node : moduleNodes) { |
| externalModulePaths.add(node.getData().getLinkedExternalProjectPath()); |
| } |
| |
| String projectPath = externalProject.getData().getLinkedExternalProjectPath(); |
| ExternalProjectSettings linkedProjectSettings = manager.getSettingsProvider().fun(project).getLinkedProjectSettings(projectPath); |
| if (linkedProjectSettings != null) { |
| linkedProjectSettings.setModules(externalModulePaths); |
| |
| long stamp = getTimeStamp(linkedProjectSettings, externalSystemId); |
| if (stamp > 0) { |
| manager.getLocalSettingsProvider().fun(project).getExternalConfigModificationStamps().put(externalProjectPath, stamp); |
| } |
| } |
| } |
| |
| callback.onSuccess(externalProject); |
| return; |
| } |
| if(error instanceof ImportCanceledException) { |
| // stop refresh task |
| return; |
| } |
| String message = ExternalSystemApiUtil.buildErrorMessage(error); |
| if (StringUtil.isEmpty(message)) { |
| message = String.format( |
| "Can't resolve %s project at '%s'. Reason: %s", externalSystemId.getReadableName(), externalProjectPath, message |
| ); |
| } |
| |
| callback.onFailure(message, extractDetails(error)); |
| |
| ExternalSystemManager<?, ?, ?, ?, ?> manager = ExternalSystemApiUtil.getManager(externalSystemId); |
| if(manager == null) { |
| return; |
| } |
| AbstractExternalSystemSettings<?, ?, ?> settings = manager.getSettingsProvider().fun(project); |
| ExternalProjectSettings projectSettings = settings.getLinkedProjectSettings(externalProjectPath); |
| if (projectSettings == null || !reportRefreshError) { |
| return; |
| } |
| |
| ExternalSystemNotificationManager.getInstance(project).processExternalProjectRefreshError(error, projectName, externalSystemId); |
| } |
| }; |
| |
| UIUtil.invokeAndWaitIfNeeded(new Runnable() { |
| @Override |
| public void run() { |
| final String title; |
| switch (progressExecutionMode) { |
| case MODAL_SYNC: |
| title = ExternalSystemBundle.message("progress.import.text", projectName, externalSystemId.getReadableName()); |
| new Task.Modal(project, title, true) { |
| @Override |
| public void run(@NotNull ProgressIndicator indicator) { |
| refreshProjectStructureTask.execute(indicator); |
| } |
| }.queue(); |
| break; |
| case IN_BACKGROUND_ASYNC: |
| title = ExternalSystemBundle.message("progress.refresh.text", projectName, externalSystemId.getReadableName()); |
| new Task.Backgroundable(project, title) { |
| @Override |
| public void run(@NotNull ProgressIndicator indicator) { |
| refreshProjectStructureTask.execute(indicator); |
| } |
| }.queue(); |
| break; |
| case START_IN_FOREGROUND_ASYNC: |
| title = ExternalSystemBundle.message("progress.refresh.text", projectName, externalSystemId.getReadableName()); |
| new Task.Backgroundable(project, title, true, PerformInBackgroundOption.DEAF) { |
| @Override |
| public void run(@NotNull ProgressIndicator indicator) { |
| refreshProjectStructureTask.execute(indicator); |
| } |
| }.queue(); |
| } |
| } |
| }); |
| } |
| |
| public static void runTask(@NotNull ExternalSystemTaskExecutionSettings taskSettings, |
| @NotNull String executorId, |
| @NotNull Project project, |
| @NotNull ProjectSystemId externalSystemId) { |
| runTask(taskSettings, executorId, project, externalSystemId, null, ProgressExecutionMode.IN_BACKGROUND_ASYNC); |
| } |
| |
| 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) { |
| final Pair<ProgramRunner, ExecutionEnvironment> pair = createRunner(taskSettings, executorId, project, externalSystemId); |
| if (pair == null) return; |
| |
| final ProgramRunner runner = pair.first; |
| final ExecutionEnvironment environment = pair.second; |
| |
| final TaskUnderProgress task = new TaskUnderProgress() { |
| @Override |
| public void execute(@NotNull ProgressIndicator indicator) { |
| final Semaphore targetDone = new Semaphore(); |
| final Ref<Boolean> result = new Ref<Boolean>(false); |
| final Disposable disposable = Disposer.newDisposable(); |
| |
| project.getMessageBus().connect(disposable).subscribe(ExecutionManager.EXECUTION_TOPIC, new ExecutionAdapter() { |
| public void processStartScheduled(final String executorIdLocal, final ExecutionEnvironment environmentLocal) { |
| if (executorId.equals(executorIdLocal) && environment.equals(environmentLocal)) { |
| targetDone.down(); |
| } |
| } |
| |
| public void processNotStarted(final String executorIdLocal, @NotNull final ExecutionEnvironment environmentLocal) { |
| if (executorId.equals(executorIdLocal) && environment.equals(environmentLocal)) { |
| targetDone.up(); |
| } |
| } |
| |
| public void processStarted(final String executorIdLocal, |
| @NotNull final ExecutionEnvironment environmentLocal, |
| @NotNull final ProcessHandler handler) { |
| if (executorId.equals(executorIdLocal) && environment.equals(environmentLocal)) { |
| handler.addProcessListener(new ProcessAdapter() { |
| public void processTerminated(ProcessEvent event) { |
| result.set(event.getExitCode() == 0); |
| targetDone.up(); |
| } |
| }); |
| } |
| } |
| }); |
| |
| try { |
| ApplicationManager.getApplication().invokeAndWait(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| runner.execute(environment); |
| } |
| catch (ExecutionException e) { |
| targetDone.up(); |
| LOG.error(e); |
| } |
| } |
| }, ModalityState.NON_MODAL); |
| } |
| catch (Exception e) { |
| LOG.error(e); |
| Disposer.dispose(disposable); |
| return; |
| } |
| |
| targetDone.waitFor(); |
| Disposer.dispose(disposable); |
| |
| if (callback != null) { |
| if (result.get()) { |
| callback.onSuccess(); |
| } |
| else { |
| callback.onFailure(); |
| } |
| } |
| } |
| }; |
| |
| UIUtil.invokeAndWaitIfNeeded(new Runnable() { |
| @Override |
| public void run() { |
| final String title = AbstractExternalSystemTaskConfigurationType.generateName(project, taskSettings); |
| switch (progressExecutionMode) { |
| case MODAL_SYNC: |
| new Task.Modal(project, title, true) { |
| @Override |
| public void run(@NotNull ProgressIndicator indicator) { |
| task.execute(indicator); |
| } |
| }.queue(); |
| break; |
| case IN_BACKGROUND_ASYNC: |
| new Task.Backgroundable(project, title) { |
| @Override |
| public void run(@NotNull ProgressIndicator indicator) { |
| task.execute(indicator); |
| } |
| }.queue(); |
| break; |
| case START_IN_FOREGROUND_ASYNC: |
| new Task.Backgroundable(project, title, true, PerformInBackgroundOption.DEAF) { |
| @Override |
| public void run(@NotNull ProgressIndicator indicator) { |
| task.execute(indicator); |
| } |
| }.queue(); |
| } |
| } |
| }); |
| } |
| |
| @Nullable |
| public static Pair<ProgramRunner, ExecutionEnvironment> createRunner(@NotNull ExternalSystemTaskExecutionSettings taskSettings, |
| @NotNull String executorId, |
| @NotNull Project project, |
| @NotNull ProjectSystemId externalSystemId) { |
| Executor executor = ExecutorRegistry.getInstance().getExecutorById(executorId); |
| if (executor == null) return null; |
| |
| String runnerId = getRunnerId(executorId); |
| if (runnerId == null) return null; |
| |
| ProgramRunner runner = RunnerRegistry.getInstance().findRunnerById(runnerId); |
| if (runner == null) return null; |
| |
| AbstractExternalSystemTaskConfigurationType configurationType = findConfigurationType(externalSystemId); |
| if (configurationType == null) return null; |
| |
| String name = AbstractExternalSystemTaskConfigurationType.generateName(project, taskSettings); |
| RunnerAndConfigurationSettings settings = RunManager.getInstance(project).createRunConfiguration(name, configurationType.getFactory()); |
| ExternalSystemRunConfiguration runConfiguration = (ExternalSystemRunConfiguration)settings.getConfiguration(); |
| runConfiguration.getSettings().setExternalProjectPath(taskSettings.getExternalProjectPath()); |
| runConfiguration.getSettings().setTaskNames(ContainerUtil.newArrayList(taskSettings.getTaskNames())); |
| runConfiguration.getSettings().setTaskDescriptions(ContainerUtil.newArrayList(taskSettings.getTaskDescriptions())); |
| runConfiguration.getSettings().setVmOptions(taskSettings.getVmOptions()); |
| runConfiguration.getSettings().setScriptParameters(taskSettings.getScriptParameters()); |
| runConfiguration.getSettings().setExecutionName(taskSettings.getExecutionName()); |
| |
| return Pair.create(runner, new ExecutionEnvironment(executor, runner, settings, project)); |
| } |
| |
| @Nullable |
| public static AbstractExternalSystemTaskConfigurationType findConfigurationType(@NotNull ProjectSystemId externalSystemId) { |
| for (ConfigurationType type : Extensions.getExtensions(ConfigurationType.CONFIGURATION_TYPE_EP)) { |
| if (type instanceof AbstractExternalSystemTaskConfigurationType) { |
| AbstractExternalSystemTaskConfigurationType candidate = (AbstractExternalSystemTaskConfigurationType)type; |
| if (externalSystemId.equals(candidate.getExternalSystemId())) { |
| return candidate; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Is expected to be called when given task info is about to be executed. |
| * <p/> |
| * Basically, this method updates recent tasks list at the corresponding external system tool window and |
| * persists new recent tasks state. |
| * |
| * @param taskInfo task which is about to be executed |
| * @param project target project |
| */ |
| public static void updateRecentTasks(@NotNull ExternalTaskExecutionInfo taskInfo, @NotNull Project project) { |
| ProjectSystemId externalSystemId = taskInfo.getSettings().getExternalSystemId(); |
| ExternalSystemRecentTasksList recentTasksList = getToolWindowElement(ExternalSystemRecentTasksList.class, |
| project, |
| ExternalSystemDataKeys.RECENT_TASKS_LIST, |
| externalSystemId); |
| if (recentTasksList == null) { |
| return; |
| } |
| recentTasksList.setFirst(taskInfo); |
| |
| ExternalSystemManager<?, ?, ?, ?, ?> manager = ExternalSystemApiUtil.getManager(externalSystemId); |
| assert manager != null; |
| AbstractExternalSystemLocalSettings settings = manager.getLocalSettingsProvider().fun(project); |
| settings.setRecentTasks(recentTasksList.getModel().getTasks()); |
| } |
| |
| @Nullable |
| public static String getRunnerId(@NotNull String executorId) { |
| return RUNNER_IDS.get(executorId); |
| } |
| |
| /** |
| * Allows to answer if given ide project has 1-1 mapping with the given external project, i.e. the ide project has been |
| * imported from external system and no other external projects have been added. |
| * <p/> |
| * This might be necessary in a situation when project-level setting is changed (e.g. project name). We don't want to rename |
| * ide project if it doesn't completely corresponds to the given ide project then. |
| * |
| * @param ideProject target ide project |
| * @param externalProject target external project |
| * @return <code>true</code> if given ide project has 1-1 mapping to the given external project; |
| * <code>false</code> otherwise |
| */ |
| public static boolean isOneToOneMapping(@NotNull Project ideProject, @NotNull DataNode<ProjectData> externalProject) { |
| String linkedExternalProjectPath = null; |
| for (ExternalSystemManager<?, ?, ?, ?, ?> manager : ExternalSystemApiUtil.getAllManagers()) { |
| ProjectSystemId externalSystemId = manager.getSystemId(); |
| AbstractExternalSystemSettings systemSettings = ExternalSystemApiUtil.getSettings(ideProject, externalSystemId); |
| Collection projectsSettings = systemSettings.getLinkedProjectsSettings(); |
| int linkedProjectsNumber = projectsSettings.size(); |
| if (linkedProjectsNumber > 1) { |
| // More than one external project of the same external system type is linked to the given ide project. |
| return false; |
| } |
| else if (linkedProjectsNumber == 1) { |
| if (linkedExternalProjectPath == null) { |
| // More than one external project of different external system types is linked to the current ide project. |
| linkedExternalProjectPath = ((ExternalProjectSettings)projectsSettings.iterator().next()).getExternalProjectPath(); |
| } |
| else { |
| return false; |
| } |
| } |
| } |
| |
| ProjectData projectData = externalProject.getData(); |
| if (linkedExternalProjectPath != null && !linkedExternalProjectPath.equals(projectData.getLinkedExternalProjectPath())) { |
| // New external project is being linked. |
| return false; |
| } |
| |
| Set<String> externalModulePaths = ContainerUtilRt.newHashSet(); |
| for (DataNode<ModuleData> moduleNode : ExternalSystemApiUtil.findAll(externalProject, ProjectKeys.MODULE)) { |
| externalModulePaths.add(moduleNode.getData().getLinkedExternalProjectPath()); |
| } |
| externalModulePaths.remove(linkedExternalProjectPath); |
| |
| PlatformFacade platformFacade = ServiceManager.getService(PlatformFacade.class); |
| for (Module module : platformFacade.getModules(ideProject)) { |
| String path = module.getOptionValue(ExternalSystemConstants.LINKED_PROJECT_PATH_KEY); |
| if (!StringUtil.isEmpty(path) && !externalModulePaths.remove(path)) { |
| return false; |
| } |
| } |
| return externalModulePaths.isEmpty(); |
| } |
| |
| /** |
| * Tries to obtain external project info implied by the given settings and link that external project to the given ide project. |
| * |
| * @param externalSystemId target external system |
| * @param projectSettings settings of the external project to link |
| * @param project target ide project to link external project to |
| * @param executionResultCallback it might take a while to resolve external project info, that's why it's possible to provide |
| * a callback to be notified on processing result. It receives <code>true</code> if an external |
| * project has been successfully linked to the given ide project; |
| * <code>false</code> otherwise (note that corresponding notification with error details is expected |
| * to be shown to the end-user then) |
| * @param isPreviewMode flag which identifies if missing external project binaries should be downloaded |
| * @param progressExecutionMode identifies how progress bar will be represented for the current processing |
| */ |
| @SuppressWarnings("UnusedDeclaration") |
| public static void linkExternalProject(@NotNull final ProjectSystemId externalSystemId, |
| @NotNull final ExternalProjectSettings projectSettings, |
| @NotNull final Project project, |
| @Nullable final Consumer<Boolean> executionResultCallback, |
| boolean isPreviewMode, |
| @NotNull final ProgressExecutionMode progressExecutionMode) |
| { |
| ExternalProjectRefreshCallback callback = new ExternalProjectRefreshCallback() { |
| @SuppressWarnings("unchecked") |
| @Override |
| public void onSuccess(@Nullable final DataNode<ProjectData> externalProject) { |
| if (externalProject == null) { |
| if (executionResultCallback != null) { |
| executionResultCallback.consume(false); |
| } |
| return; |
| } |
| AbstractExternalSystemSettings systemSettings = ExternalSystemApiUtil.getSettings(project, externalSystemId); |
| Set<ExternalProjectSettings> projects = ContainerUtilRt.newHashSet(systemSettings.getLinkedProjectsSettings()); |
| projects.add(projectSettings); |
| systemSettings.setLinkedProjectsSettings(projects); |
| ensureToolWindowInitialized(project, externalSystemId); |
| ExternalSystemApiUtil.executeProjectChangeAction(new DisposeAwareProjectChange(project) { |
| @Override |
| public void execute() { |
| ProjectRootManagerEx.getInstanceEx(project).mergeRootsChangesDuring(new Runnable() { |
| @Override |
| public void run() { |
| ProjectDataManager dataManager = ServiceManager.getService(ProjectDataManager.class); |
| dataManager.importData(externalProject.getKey(), Collections.singleton(externalProject), project, true); |
| } |
| }); |
| } |
| }); |
| if (executionResultCallback != null) { |
| executionResultCallback.consume(true); |
| } |
| } |
| |
| @Override |
| public void onFailure(@NotNull String errorMessage, @Nullable String errorDetails) { |
| if (executionResultCallback != null) { |
| executionResultCallback.consume(false); |
| } |
| } |
| }; |
| refreshProject(project, externalSystemId, projectSettings.getExternalProjectPath(), callback, isPreviewMode, progressExecutionMode); |
| } |
| |
| @Nullable |
| public static VirtualFile waitForTheFile(@Nullable final String path) { |
| if (path == null) return null; |
| |
| final VirtualFile[] file = new VirtualFile[1]; |
| final Application app = ApplicationManager.getApplication(); |
| Runnable action = new Runnable() { |
| public void run() { |
| app.runWriteAction(new Runnable() { |
| public void run() { |
| file[0] = LocalFileSystem.getInstance().refreshAndFindFileByPath(path); |
| } |
| }); |
| } |
| }; |
| if (app.isDispatchThread()) { |
| action.run(); |
| } |
| else { |
| app.invokeAndWait(action, ModalityState.defaultModalityState()); |
| } |
| return file[0]; |
| } |
| |
| private interface TaskUnderProgress { |
| void execute(@NotNull ProgressIndicator indicator); |
| } |
| |
| private static class MyMultiExternalProjectRefreshCallback implements ExternalProjectRefreshCallback { |
| |
| @NotNull |
| private final Set<String> myExternalModulePaths; |
| private final Project myProject; |
| private final ProjectDataManager myProjectDataManager; |
| private final int[] myCounter; |
| private final ProjectSystemId myExternalSystemId; |
| |
| public MyMultiExternalProjectRefreshCallback(Project project, |
| ProjectDataManager projectDataManager, |
| int[] counter, |
| ProjectSystemId externalSystemId) { |
| myProject = project; |
| myProjectDataManager = projectDataManager; |
| myCounter = counter; |
| myExternalSystemId = externalSystemId; |
| myExternalModulePaths = ContainerUtilRt.newHashSet(); |
| } |
| |
| @Override |
| public void onSuccess(@Nullable final DataNode<ProjectData> externalProject) { |
| if (externalProject == null) { |
| return; |
| } |
| Collection<DataNode<ModuleData>> moduleNodes = ExternalSystemApiUtil.findAll(externalProject, ProjectKeys.MODULE); |
| for (DataNode<ModuleData> node : moduleNodes) { |
| myExternalModulePaths.add(node.getData().getLinkedExternalProjectPath()); |
| } |
| ExternalSystemApiUtil.executeProjectChangeAction(true, new DisposeAwareProjectChange(myProject) { |
| @Override |
| public void execute() { |
| ProjectRootManagerEx.getInstanceEx(myProject).mergeRootsChangesDuring(new Runnable() { |
| @Override |
| public void run() { |
| myProjectDataManager.importData(externalProject.getKey(), Collections.singleton(externalProject), myProject, true); |
| } |
| }); |
| |
| processOrphanProjectLibraries(); |
| } |
| }); |
| if (--myCounter[0] <= 0) { |
| processOrphanModules(); |
| } |
| } |
| |
| @Override |
| public void onFailure(@NotNull String errorMessage, @Nullable String errorDetails) { |
| myCounter[0] = Integer.MAX_VALUE; // Don't process orphan modules if there was an error on refresh. |
| } |
| |
| private void processOrphanModules() { |
| if(myProject.isDisposed()) return; |
| if(ExternalSystemDebugEnvironment.DEBUG_ORPHAN_MODULES_PROCESSING) { |
| LOG.info(String.format( |
| "Checking for orphan modules. External paths returned by external system: '%s'", myExternalModulePaths |
| )); |
| } |
| PlatformFacade platformFacade = ServiceManager.getService(PlatformFacade.class); |
| List<Module> orphanIdeModules = ContainerUtilRt.newArrayList(); |
| String externalSystemIdAsString = myExternalSystemId.toString(); |
| |
| for (Module module : platformFacade.getModules(myProject)) { |
| String s = module.getOptionValue(ExternalSystemConstants.EXTERNAL_SYSTEM_ID_KEY); |
| String p = module.getOptionValue(ExternalSystemConstants.LINKED_PROJECT_PATH_KEY); |
| if(ExternalSystemDebugEnvironment.DEBUG_ORPHAN_MODULES_PROCESSING) { |
| LOG.info(String.format( |
| "IDE module: EXTERNAL_SYSTEM_ID_KEY - '%s', LINKED_PROJECT_PATH_KEY - '%s'.", s, p |
| )); |
| } |
| if (externalSystemIdAsString.equals(s) && !myExternalModulePaths.contains(p)) { |
| orphanIdeModules.add(module); |
| if(ExternalSystemDebugEnvironment.DEBUG_ORPHAN_MODULES_PROCESSING) { |
| LOG.info(String.format( |
| "External paths doesn't contain IDE module LINKED_PROJECT_PATH_KEY anymore => add to orphan IDE modules." |
| )); |
| } |
| } |
| } |
| |
| if (!orphanIdeModules.isEmpty()) { |
| ruleOrphanModules(orphanIdeModules, myProject, myExternalSystemId); |
| } |
| } |
| |
| private void processOrphanProjectLibraries() { |
| PlatformFacade platformFacade = ServiceManager.getService(PlatformFacade.class); |
| List<Library> orphanIdeLibraries = ContainerUtilRt.newArrayList(); |
| |
| LibraryTable projectLibraryTable = platformFacade.getProjectLibraryTable(myProject); |
| for (Library library : projectLibraryTable.getLibraries()) { |
| if (!ExternalSystemApiUtil.isExternalSystemLibrary(library, myExternalSystemId)) continue; |
| if (ProjectStructureHelper.isOrphanProjectLibrary(library, platformFacade.getModules(myProject))) { |
| orphanIdeLibraries.add(library); |
| } |
| } |
| for (Library orphanIdeLibrary : orphanIdeLibraries) { |
| projectLibraryTable.removeLibrary(orphanIdeLibrary); |
| } |
| } |
| } |
| } |