| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * 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.android.tools.idea.gradle.project; |
| |
| import com.android.SdkConstants; |
| import com.android.sdklib.repository.FullRevision; |
| import com.android.tools.idea.gradle.messages.ProjectSyncMessages; |
| import com.android.tools.idea.gradle.util.GradleProperties; |
| import com.intellij.CommonBundle; |
| import com.intellij.ide.util.PropertiesComponent; |
| import com.intellij.notification.NotificationType; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.DialogWrapper.DoNotAskOption; |
| import com.intellij.openapi.ui.Messages; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.util.net.HttpConfigurable; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.plugins.gradle.settings.DistributionType; |
| import org.jetbrains.plugins.gradle.settings.GradleProjectSettings; |
| |
| import java.io.File; |
| import java.io.IOException; |
| |
| import static com.android.tools.idea.gradle.util.GradleUtil.*; |
| import static com.android.tools.idea.gradle.util.Projects.getBaseDirPath; |
| import static com.android.tools.idea.startup.AndroidStudioSpecificInitializer.isAndroidStudio; |
| import static com.intellij.openapi.ui.Messages.*; |
| import static com.intellij.openapi.util.io.FileUtil.delete; |
| import static com.intellij.openapi.util.io.FileUtil.toSystemDependentName; |
| import static com.intellij.openapi.util.text.StringUtil.isEmpty; |
| import static com.intellij.openapi.util.text.StringUtil.isNotEmpty; |
| import static com.intellij.util.ExceptionUtil.getRootCause; |
| import static org.jetbrains.plugins.gradle.settings.DistributionType.DEFAULT_WRAPPED; |
| import static org.jetbrains.plugins.gradle.settings.DistributionType.LOCAL; |
| |
| final class PreSyncChecks { |
| private static final Logger LOG = Logger.getInstance(PreSyncChecks.class); |
| private static final String GRADLE_SYNC_MSG_TITLE = "Gradle Sync"; |
| private static final String PROJECT_SYNCING_ERROR_GROUP = "Project syncing error"; |
| |
| @NonNls private static final String SHOW_DO_NOT_ASK_TO_COPY_PROXY_SETTINGS_PROPERTY_NAME = |
| "show.do.not.copy.http.proxy.settings.to.gradle"; |
| |
| private PreSyncChecks() { |
| } |
| |
| @NotNull |
| static PreSyncCheckResult canSync(@NotNull Project project) { |
| VirtualFile baseDir = project.getBaseDir(); |
| if (baseDir == null) { |
| // Unlikely to happen because it would mean this is the default project. |
| return PreSyncCheckResult.success(); |
| } |
| |
| if (isAndroidStudio()) { |
| // We only check proxy settings for Studio, because Studio does not pass the IDE's proxy settings to Gradle. |
| // See https://code.google.com/p/android/issues/detail?id=169743 |
| checkHttpProxySettings(project); |
| } |
| |
| ProjectSyncMessages syncMessages = ProjectSyncMessages.getInstance(project); |
| syncMessages.removeMessages(PROJECT_SYNCING_ERROR_GROUP); |
| |
| if (!ApplicationManager.getApplication().isUnitTestMode()) { |
| attemptToUseEmbeddedGradle(project); |
| } |
| |
| GradleProjectSettings gradleSettings = getGradleProjectSettings(project); |
| File wrapperPropertiesFile = findWrapperPropertiesFile(project); |
| |
| DistributionType distributionType = gradleSettings != null ? gradleSettings.getDistributionType() : null; |
| boolean usingWrapper = (distributionType == null || distributionType == DEFAULT_WRAPPED) && wrapperPropertiesFile != null; |
| if (usingWrapper && gradleSettings != null) { |
| // Do this just to ensure that the right distribution type is set. If this is not set, build.gradle editor will not have code |
| // completion (see BuildClasspathModuleGradleDataService, line 119). |
| gradleSettings.setDistributionType(DEFAULT_WRAPPED); |
| } |
| else if (!ApplicationManager.getApplication().isUnitTestMode()) { |
| if (wrapperPropertiesFile == null && gradleSettings != null) { |
| createWrapperIfNecessary(project, gradleSettings, distributionType); |
| } |
| } |
| |
| return PreSyncCheckResult.success(); |
| } |
| |
| // If the IDE is configured to use proxies, we ask the user if she would like to have those settings copied to gradle.properties, if such |
| // files does not include them already. |
| // Gradle may need those settings to access the Internet to download dependencies. |
| // See https://code.google.com/p/android/issues/detail?id=65325 |
| private static void checkHttpProxySettings(@NotNull Project project) { |
| boolean performCheck = PropertiesComponent.getInstance().getBoolean(SHOW_DO_NOT_ASK_TO_COPY_PROXY_SETTINGS_PROPERTY_NAME, true); |
| if (!performCheck) { |
| // User already checked the "do not ask me" option. |
| return; |
| } |
| |
| HttpConfigurable ideProxySettings = HttpConfigurable.getInstance(); |
| if (ideProxySettings.USE_HTTP_PROXY && isNotEmpty(ideProxySettings.PROXY_HOST)) { |
| GradleProperties properties; |
| try { |
| properties = new GradleProperties(project); |
| } |
| catch (IOException e) { |
| LOG.info("Failed to read gradle.properties file", e); |
| // Let sync continue, even though it may fail. |
| return; |
| } |
| GradleProperties.ProxySettings proxySettings = properties.getProxySettings(); |
| if (!ideProxySettings.PROXY_HOST.equals(proxySettings.getHost())) { |
| String msg = "Android Studio is configured to use a HTTP proxy. " + |
| "Gradle may need these proxy settings to access the Internet (e.g. for downloading dependencies.)\n\n" + |
| "Would you like to have the IDE's proxy configuration be set in the project's gradle.properties file?"; |
| DoNotAskOption doNotAskOption = new PropertyDoNotAskOption(SHOW_DO_NOT_ASK_TO_COPY_PROXY_SETTINGS_PROPERTY_NAME); |
| int result = Messages.showYesNoDialog(project, msg, "Proxy Settings", Messages.getQuestionIcon(), doNotAskOption); |
| if (result == YES) { |
| proxySettings.copyFrom(ideProxySettings); |
| properties.setProxySettings(proxySettings); |
| try { |
| properties.save(); |
| } |
| catch (IOException e) { |
| //noinspection ThrowableResultOfMethodCallIgnored |
| Throwable root = getRootCause(e); |
| |
| String cause = root.getMessage(); |
| String errMsg = "Failed to save changes to gradle.properties file."; |
| if (isNotEmpty(cause)) { |
| if (!cause.endsWith(".")) { |
| cause += "."; |
| } |
| errMsg += String.format("\nCause: %1$s", cause); |
| } |
| AndroidGradleNotification notification = AndroidGradleNotification.getInstance(project); |
| notification.showBalloon("Proxy Settings", errMsg, NotificationType.ERROR); |
| |
| LOG.info("Failed to save changes to gradle.properties file", e); |
| } |
| } |
| } |
| } |
| } |
| |
| private static boolean createWrapperIfNecessary(@NotNull Project project, |
| @NotNull GradleProjectSettings gradleSettings, |
| @Nullable DistributionType distributionType) { |
| boolean createWrapper = false; |
| boolean chooseLocalGradleHome = false; |
| |
| if (distributionType == null) { |
| String msg = createUseWrapperQuestion("Gradle settings for this project are not configured yet."); |
| int answer = showOkCancelDialog(project, msg, GRADLE_SYNC_MSG_TITLE, getQuestionIcon()); |
| createWrapper = answer == Messages.OK; |
| } |
| else if (distributionType == DEFAULT_WRAPPED) { |
| createWrapper = true; |
| } |
| else if (distributionType == LOCAL) { |
| String gradleHome = gradleSettings.getGradleHome(); |
| String msg = null; |
| if (isEmpty(gradleHome)) { |
| msg = createUseWrapperQuestion("The path of the local Gradle distribution to use is not set."); |
| } |
| else { |
| File gradleHomePath = new File(toSystemDependentName(gradleHome)); |
| if (!gradleHomePath.isDirectory()) { |
| String reason = String.format("The path\n'%1$s'\n, set as a local Gradle distribution, does not belong to an existing directory.", |
| gradleHomePath.getPath()); |
| msg = createUseWrapperQuestion(reason); |
| } |
| else { |
| FullRevision gradleVersion = getGradleVersion(gradleHomePath); |
| if (gradleVersion == null) { |
| String reason = String.format("The path\n'%1$s'\n, does not belong to a Gradle distribution.", gradleHomePath.getPath()); |
| msg = createUseWrapperQuestion(reason); |
| } |
| else if (!isSupportedGradleVersion(gradleVersion)) { |
| String reason = String.format("Gradle version %1$s is not supported.", gradleHomePath.getPath()); |
| msg = createUseWrapperQuestion(reason); |
| } |
| } |
| } |
| if (msg != null) { |
| int answer = showOkCancelDialog(project, msg, GRADLE_SYNC_MSG_TITLE, getQuestionIcon()); |
| createWrapper = answer == Messages.OK; |
| chooseLocalGradleHome = !createWrapper; |
| } |
| } |
| |
| if (createWrapper) { |
| File projectDirPath = getBaseDirPath(project); |
| |
| // attempt to delete the whole gradle wrapper folder. |
| File gradleDirPath = new File(projectDirPath, SdkConstants.FD_GRADLE); |
| if (!delete(gradleDirPath)) { |
| // deletion failed. Let sync continue. |
| return true; |
| } |
| |
| try { |
| createGradleWrapper(projectDirPath); |
| if (distributionType == null) { |
| gradleSettings.setDistributionType(DEFAULT_WRAPPED); |
| } |
| return true; |
| } |
| catch (IOException e) { |
| LOG.info("Failed to create Gradle wrapper for project '" + project.getName() + "'", e); |
| } |
| } |
| else if (distributionType == null || chooseLocalGradleHome) { |
| ChooseGradleHomeDialog dialog = new ChooseGradleHomeDialog(); |
| if (dialog.showAndGet()) { |
| String enteredGradleHomePath = dialog.getEnteredGradleHomePath(); |
| gradleSettings.setGradleHome(enteredGradleHomePath); |
| gradleSettings.setDistributionType(LOCAL); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @NotNull |
| private static String createUseWrapperQuestion(@NotNull String reason) { |
| return reason + "\n\n" + |
| "Would you like the project to use the Gradle wrapper?\n" + |
| "(The wrapper will automatically download the latest supported Gradle version).\n\n" + |
| "Click 'OK' to use the Gradle wrapper, or 'Cancel' to manually set the path of a local Gradle distribution."; |
| } |
| |
| static class PreSyncCheckResult { |
| private final boolean mySuccess; |
| @Nullable private final String myFailureCause; |
| |
| @NotNull |
| private static PreSyncCheckResult success() { |
| return new PreSyncCheckResult(true, null); |
| } |
| |
| @NotNull |
| private static PreSyncCheckResult failure(@NotNull String cause) { |
| return new PreSyncCheckResult(false, cause); |
| } |
| |
| private PreSyncCheckResult(boolean success, @Nullable String failureCause) { |
| mySuccess = success; |
| myFailureCause = failureCause; |
| } |
| |
| public boolean isSuccess() { |
| return mySuccess; |
| } |
| |
| @Nullable |
| public String getFailureCause() { |
| return myFailureCause; |
| } |
| } |
| |
| /** |
| * Implementation of "Do not show this dialog in the future" option. This option is displayed as a checkbox in a {@code Messages} dialog. |
| * The state of such checkbox is stored in the IDE's {@code PropertiesComponent} under the name passed in the constructor. |
| */ |
| private static class PropertyDoNotAskOption implements DoNotAskOption { |
| /** The name of the property storing the value of the "Do not show this dialog in the future" option. */ |
| @NotNull private final String myProperty; |
| |
| PropertyDoNotAskOption(@NotNull String property) { |
| myProperty = property; |
| } |
| |
| @Override |
| public boolean isToBeShown() { |
| // Read the stored value. If none is found, return "true" to display the checkbox the first time. |
| return PropertiesComponent.getInstance().getBoolean(myProperty, true); |
| } |
| |
| @Override |
| public void setToBeShown(boolean toBeShown, int exitCode) { |
| // Stores the state of the checkbox into the property. |
| PropertiesComponent.getInstance().setValue(myProperty, String.valueOf(toBeShown)); |
| } |
| |
| @Override |
| public boolean canBeHidden() { |
| // By returning "true", the Messages dialog can hide the checkbox if the user previously set the checkbox as "selected". |
| return true; |
| } |
| |
| @Override |
| public boolean shouldSaveOptionsOnCancel() { |
| // We always want to save the value of the checkbox, regardless of the button pressed in the Messages dialog. |
| return true; |
| } |
| |
| @NotNull |
| @Override |
| public String getDoNotShowMessage() { |
| // This is the text to set in the checkbox. |
| return CommonBundle.message("dialog.options.do.not.show"); |
| } |
| } |
| } |