blob: 4f4ceeeb3c7a5bd286153277648367800262b644 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.gradle;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.SimpleJavaParameters;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.externalSystem.ExternalSystemAutoImportAware;
import com.intellij.openapi.externalSystem.ExternalSystemConfigurableAware;
import com.intellij.openapi.externalSystem.ExternalSystemManager;
import com.intellij.openapi.externalSystem.ExternalSystemUiAware;
import com.intellij.openapi.externalSystem.model.DataNode;
import com.intellij.openapi.externalSystem.model.ProjectSystemId;
import com.intellij.openapi.externalSystem.model.execution.ExternalSystemTaskExecutionSettings;
import com.intellij.openapi.externalSystem.model.execution.ExternalTaskExecutionInfo;
import com.intellij.openapi.externalSystem.model.execution.ExternalTaskPojo;
import com.intellij.openapi.externalSystem.model.project.ExternalProjectPojo;
import com.intellij.openapi.externalSystem.model.project.ProjectData;
import com.intellij.openapi.externalSystem.service.execution.ProgressExecutionMode;
import com.intellij.openapi.externalSystem.service.project.ExternalProjectRefreshCallback;
import com.intellij.openapi.externalSystem.service.project.ExternalSystemProjectResolver;
import com.intellij.openapi.externalSystem.service.project.autoimport.CachingExternalSystemAutoImportAware;
import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataManager;
import com.intellij.openapi.externalSystem.service.ui.DefaultExternalSystemUiAware;
import com.intellij.openapi.externalSystem.task.ExternalSystemTaskManager;
import com.intellij.openapi.externalSystem.util.DisposeAwareProjectChange;
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil;
import com.intellij.openapi.externalSystem.util.ExternalSystemConstants;
import com.intellij.openapi.externalSystem.util.ExternalSystemUtil;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
import com.intellij.openapi.startup.StartupActivity;
import com.intellij.openapi.util.AtomicNotNullLazyValue;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.Function;
import com.intellij.util.PathUtil;
import com.intellij.util.PathsList;
import com.intellij.util.containers.ContainerUtilRt;
import com.intellij.util.messages.MessageBusConnection;
import icons.GradleIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.config.GradleSettingsListenerAdapter;
import org.jetbrains.plugins.gradle.remote.GradleJavaHelper;
import org.jetbrains.plugins.gradle.service.GradleInstallationManager;
import org.jetbrains.plugins.gradle.service.project.GradleAutoImportAware;
import org.jetbrains.plugins.gradle.service.project.GradleProjectResolver;
import org.jetbrains.plugins.gradle.service.project.GradleProjectResolverExtension;
import org.jetbrains.plugins.gradle.service.settings.GradleConfigurable;
import org.jetbrains.plugins.gradle.service.task.GradleTaskManager;
import org.jetbrains.plugins.gradle.settings.*;
import org.jetbrains.plugins.gradle.util.GradleConstants;
import org.jetbrains.plugins.gradle.util.GradleUtil;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.*;
/**
* @author Denis Zhdanov
* @since 4/10/13 1:19 PM
*/
public class GradleManager
implements ExternalSystemConfigurableAware, ExternalSystemUiAware, ExternalSystemAutoImportAware, StartupActivity, ExternalSystemManager<
GradleProjectSettings,
GradleSettingsListener,
GradleSettings,
GradleLocalSettings,
GradleExecutionSettings> {
private static final Logger LOG = Logger.getInstance("#" + GradleManager.class.getName());
@NotNull private final ExternalSystemAutoImportAware myAutoImportDelegate =
new CachingExternalSystemAutoImportAware(new GradleAutoImportAware());
@NotNull
private final GradleInstallationManager myInstallationManager;
@NotNull private static final NotNullLazyValue<List<GradleProjectResolverExtension>> RESOLVER_EXTENSIONS =
new AtomicNotNullLazyValue<List<GradleProjectResolverExtension>>() {
@NotNull
@Override
protected List<GradleProjectResolverExtension> compute() {
List<GradleProjectResolverExtension> result = ContainerUtilRt.newArrayList();
Collections.addAll(result, GradleProjectResolverExtension.EP_NAME.getExtensions());
ExternalSystemApiUtil.orderAwareSort(result);
return result;
}
};
public GradleManager(@NotNull GradleInstallationManager manager) {
myInstallationManager = manager;
}
@NotNull
@Override
public ProjectSystemId getSystemId() {
return GradleConstants.SYSTEM_ID;
}
@NotNull
@Override
public Function<Project, GradleSettings> getSettingsProvider() {
return new Function<Project, GradleSettings>() {
@Override
public GradleSettings fun(Project project) {
return GradleSettings.getInstance(project);
}
};
}
@NotNull
@Override
public Function<Project, GradleLocalSettings> getLocalSettingsProvider() {
return new Function<Project, GradleLocalSettings>() {
@Override
public GradleLocalSettings fun(Project project) {
return GradleLocalSettings.getInstance(project);
}
};
}
@NotNull
@Override
public Function<Pair<Project, String>, GradleExecutionSettings> getExecutionSettingsProvider() {
return new Function<Pair<Project, String>, GradleExecutionSettings>() {
private final GradleJavaHelper myJavaHelper = new GradleJavaHelper();
@Override
public GradleExecutionSettings fun(Pair<Project, String> pair) {
GradleSettings settings = GradleSettings.getInstance(pair.first);
File gradleHome = myInstallationManager.getGradleHome(pair.first, pair.second);
String localGradlePath = null;
if (gradleHome != null) {
try {
// Try to resolve symbolic links as there were problems with them at the gradle side.
localGradlePath = gradleHome.getCanonicalPath();
}
catch (IOException e) {
localGradlePath = gradleHome.getAbsolutePath();
}
}
GradleProjectSettings projectLevelSettings = settings.getLinkedProjectSettings(pair.second);
final DistributionType distributionType;
if (projectLevelSettings == null) {
distributionType =
GradleUtil.isGradleDefaultWrapperFilesExist(pair.second) ? DistributionType.DEFAULT_WRAPPED : DistributionType.LOCAL;
}
else {
distributionType =
projectLevelSettings.getDistributionType() == null ? DistributionType.LOCAL : projectLevelSettings.getDistributionType();
}
GradleExecutionSettings result = new GradleExecutionSettings(localGradlePath,
settings.getServiceDirectoryPath(),
distributionType,
settings.getGradleVmOptions(),
settings.isOfflineWork());
for (GradleProjectResolverExtension extension : RESOLVER_EXTENSIONS.getValue()) {
result.addResolverExtensionClass(ClassHolder.from(extension.getClass()));
}
String javaHome = myJavaHelper.getJdkHome(pair.first);
if (!StringUtil.isEmpty(javaHome)) {
LOG.info("Instructing gradle to use java from " + javaHome);
}
result.setJavaHome(javaHome);
return result;
}
};
}
@Override
public void enhanceRemoteProcessing(@NotNull SimpleJavaParameters parameters) throws ExecutionException {
final Set<String> additionalEntries = ContainerUtilRt.newHashSet();
for (GradleProjectResolverExtension extension : RESOLVER_EXTENSIONS.getValue()) {
ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(extension.getClass()));
for (Class aClass : extension.getExtraProjectModelClasses()) {
ContainerUtilRt.addIfNotNull(additionalEntries, PathUtil.getJarPathForClass(aClass));
}
extension.enhanceRemoteProcessing(parameters);
}
final PathsList classPath = parameters.getClassPath();
for (String entry : additionalEntries) {
classPath.add(entry);
}
parameters.getVMParametersList().addProperty(
ExternalSystemConstants.EXTERNAL_SYSTEM_ID_KEY, GradleConstants.SYSTEM_ID.getId());
}
@Override
public void enhanceLocalProcessing(@NotNull List<URL> urls) {
}
@NotNull
@Override
public Class<? extends ExternalSystemProjectResolver<GradleExecutionSettings>> getProjectResolverClass() {
return GradleProjectResolver.class;
}
@Override
public Class<? extends ExternalSystemTaskManager<GradleExecutionSettings>> getTaskManagerClass() {
return GradleTaskManager.class;
}
@NotNull
@Override
public Configurable getConfigurable(@NotNull Project project) {
return new GradleConfigurable(project);
}
@Nullable
@Override
public FileChooserDescriptor getExternalProjectConfigDescriptor() {
return GradleUtil.getGradleProjectFileChooserDescriptor();
}
@Nullable
@Override
public Icon getProjectIcon() {
return GradleIcons.Gradle;
}
@Nullable
@Override
public Icon getTaskIcon() {
return DefaultExternalSystemUiAware.INSTANCE.getTaskIcon();
}
@NotNull
@Override
public String getProjectRepresentationName(@NotNull String targetProjectPath, @Nullable String rootProjectPath) {
return ExternalSystemApiUtil.getProjectRepresentationName(targetProjectPath, rootProjectPath);
}
@Nullable
@Override
public String getAffectedExternalProjectPath(@NotNull String changedFileOrDirPath, @NotNull Project project) {
return myAutoImportDelegate.getAffectedExternalProjectPath(changedFileOrDirPath, project);
}
@NotNull
@Override
public FileChooserDescriptor getExternalProjectDescriptor() {
return GradleUtil.getGradleProjectFileChooserDescriptor();
}
@Override
public void runActivity(@NotNull final Project project) {
// We want to automatically refresh linked projects on gradle service directory change.
MessageBusConnection connection = project.getMessageBus().connect(project);
connection.subscribe(GradleSettings.getInstance(project).getChangesTopic(), new GradleSettingsListenerAdapter() {
@Override
public void onServiceDirectoryPathChange(@Nullable String oldPath, @Nullable String newPath) {
ensureProjectsRefresh();
}
@Override
public void onGradleHomeChange(@Nullable String oldPath, @Nullable String newPath, @NotNull String linkedProjectPath) {
ensureProjectsRefresh();
}
@Override
public void onGradleDistributionTypeChange(DistributionType currentValue, @NotNull String linkedProjectPath) {
ensureProjectsRefresh();
}
@Override
public void onProjectsLinked(@NotNull Collection<GradleProjectSettings> settings) {
final ProjectDataManager projectDataManager = ServiceManager.getService(ProjectDataManager.class);
for (GradleProjectSettings gradleProjectSettings : settings) {
ExternalSystemUtil.refreshProject(
project, GradleConstants.SYSTEM_ID, gradleProjectSettings.getExternalProjectPath(),
new ExternalProjectRefreshCallback() {
@Override
public void onSuccess(@Nullable final DataNode<ProjectData> externalProject) {
if (externalProject == null) {
return;
}
ExternalSystemApiUtil.executeProjectChangeAction(true, new DisposeAwareProjectChange(project) {
@Override
public void execute() {
ProjectRootManagerEx.getInstanceEx(project).mergeRootsChangesDuring(new Runnable() {
@Override
public void run() {
projectDataManager.importData(externalProject.getKey(), Collections.singleton(externalProject), project, true);
}
});
}
});
}
@Override
public void onFailure(@NotNull String errorMessage, @Nullable String errorDetails) {
}
}, false, ProgressExecutionMode.MODAL_SYNC);
}
}
private void ensureProjectsRefresh() {
ExternalSystemUtil.refreshProjects(project, GradleConstants.SYSTEM_ID, true);
}
});
// We used to assume that gradle scripts are always named 'build.gradle' and kept path to that build.gradle file at ide settings.
// However, it was found out that that is incorrect assumption (IDEA-109064). Now we keep paths to gradle script's directories
// instead. However, we don't want to force old users to re-import gradle projects because of that. That's why we check gradle
// config and re-point it from build.gradle to the parent dir if necessary.
Map<String, String> adjustedPaths = patchLinkedProjects(project);
if (adjustedPaths == null) {
return;
}
GradleLocalSettings localSettings = GradleLocalSettings.getInstance(project);
patchRecentTasks(adjustedPaths, localSettings);
patchAvailableProjects(adjustedPaths, localSettings);
patchAvailableTasks(adjustedPaths, localSettings);
}
@Nullable
private static Map<String, String> patchLinkedProjects(@NotNull Project project) {
GradleSettings settings = GradleSettings.getInstance(project);
Collection<GradleProjectSettings> correctedSettings = ContainerUtilRt.newArrayList();
Map<String/* old path */, String/* new path */> adjustedPaths = ContainerUtilRt.newHashMap();
for (GradleProjectSettings projectSettings : settings.getLinkedProjectsSettings()) {
String oldPath = projectSettings.getExternalProjectPath();
if (oldPath != null && new File(oldPath).isFile() && FileUtilRt.extensionEquals(oldPath, GradleConstants.EXTENSION)) {
try {
String newPath = new File(oldPath).getParentFile().getCanonicalPath();
projectSettings.setExternalProjectPath(newPath);
adjustedPaths.put(oldPath, newPath);
}
catch (IOException e) {
LOG.warn(String.format(
"Unexpected exception occurred on attempt to re-point linked gradle project path from build.gradle to its parent dir. Path: %s",
oldPath
), e);
}
}
correctedSettings.add(projectSettings);
}
if (adjustedPaths.isEmpty()) {
return null;
}
settings.setLinkedProjectsSettings(correctedSettings);
return adjustedPaths;
}
private static void patchAvailableTasks(@NotNull Map<String, String> adjustedPaths, @NotNull GradleLocalSettings localSettings) {
Map<String, Collection<ExternalTaskPojo>> adjustedAvailableTasks = ContainerUtilRt.newHashMap();
for (Map.Entry<String, Collection<ExternalTaskPojo>> entry : localSettings.getAvailableTasks().entrySet()) {
String newPath = adjustedPaths.get(entry.getKey());
if (newPath == null) {
adjustedAvailableTasks.put(entry.getKey(), entry.getValue());
}
else {
for (ExternalTaskPojo task : entry.getValue()) {
String newTaskPath = adjustedPaths.get(task.getLinkedExternalProjectPath());
if (newTaskPath != null) {
task.setLinkedExternalProjectPath(newTaskPath);
}
}
adjustedAvailableTasks.put(newPath, entry.getValue());
}
}
localSettings.setAvailableTasks(adjustedAvailableTasks);
}
private static void patchAvailableProjects(@NotNull Map<String, String> adjustedPaths, @NotNull GradleLocalSettings localSettings) {
Map<ExternalProjectPojo, Collection<ExternalProjectPojo>> adjustedAvailableProjects = ContainerUtilRt.newHashMap();
for (Map.Entry<ExternalProjectPojo, Collection<ExternalProjectPojo>> entry : localSettings.getAvailableProjects().entrySet()) {
String newPath = adjustedPaths.get(entry.getKey().getPath());
if (newPath == null) {
adjustedAvailableProjects.put(entry.getKey(), entry.getValue());
}
else {
adjustedAvailableProjects.put(new ExternalProjectPojo(entry.getKey().getName(), newPath), entry.getValue());
}
}
localSettings.setAvailableProjects(adjustedAvailableProjects);
}
private static void patchRecentTasks(@NotNull Map<String, String> adjustedPaths, @NotNull GradleLocalSettings localSettings) {
for (ExternalTaskExecutionInfo taskInfo : localSettings.getRecentTasks()) {
ExternalSystemTaskExecutionSettings s = taskInfo.getSettings();
String newPath = adjustedPaths.get(s.getExternalProjectPath());
if (newPath != null) {
s.setExternalProjectPath(newPath);
}
}
}
}