blob: 1d162f95a3a4db0ba22436b648786ef5319f2cc6 [file] [log] [blame]
/*
* Copyright (C) 2013 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.util;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.builder.model.*;
import com.android.ide.common.repository.GradleCoordinate;
import com.android.sdklib.repository.FullRevision;
import com.android.sdklib.repository.PreciseRevision;
import com.android.tools.idea.gradle.IdeaAndroidProject;
import com.android.tools.idea.gradle.eclipse.GradleImport;
import com.android.tools.idea.gradle.facet.AndroidGradleFacet;
import com.android.tools.idea.gradle.project.AndroidGradleNotification;
import com.android.tools.idea.gradle.project.ChooseGradleHomeDialog;
import com.android.tools.idea.sdk.IdeSdks;
import com.android.tools.idea.templates.TemplateManager;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.CharMatcher;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.extensions.ExtensionPoint;
import com.intellij.openapi.externalSystem.model.DataNode;
import com.intellij.openapi.externalSystem.model.ExternalProjectInfo;
import com.intellij.openapi.externalSystem.model.ProjectSystemId;
import com.intellij.openapi.externalSystem.model.project.ProjectData;
import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.options.ConfigurableEP;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.Function;
import com.intellij.util.Processor;
import com.intellij.util.containers.SmartHashSet;
import icons.AndroidIcons;
import org.gradle.StartParameter;
import org.gradle.wrapper.PathAssembler;
import org.gradle.wrapper.WrapperConfiguration;
import org.gradle.wrapper.WrapperExecutor;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.service.GradleInstallationManager;
import org.jetbrains.plugins.gradle.settings.DistributionType;
import org.jetbrains.plugins.gradle.settings.GradleExecutionSettings;
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings;
import org.jetbrains.plugins.gradle.settings.GradleSettings;
import org.jetbrains.plugins.gradle.util.GradleConstants;
import org.jetbrains.plugins.groovy.lang.lexer.GroovyLexer;
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
import javax.swing.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.android.SdkConstants.*;
import static com.android.ide.common.repository.GradleCoordinate.parseCoordinateString;
import static com.android.tools.idea.gradle.util.BuildMode.ASSEMBLE_TRANSLATE;
import static com.android.tools.idea.gradle.util.EmbeddedDistributionPaths.findAndroidStudioLocalMavenRepoPath;
import static com.android.tools.idea.gradle.util.EmbeddedDistributionPaths.findEmbeddedGradleDistributionPath;
import static com.android.tools.idea.gradle.util.GradleBuilds.ENABLE_TRANSLATION_JVM_ARG;
import static com.android.tools.idea.gradle.util.Projects.*;
import static com.android.tools.idea.gradle.util.PropertiesUtil.getProperties;
import static com.android.tools.idea.gradle.util.PropertiesUtil.savePropertiesToFile;
import static com.android.tools.idea.startup.AndroidStudioSpecificInitializer.GRADLE_DAEMON_TIMEOUT_MS;
import static com.android.tools.idea.startup.AndroidStudioSpecificInitializer.isAndroidStudio;
import static com.google.common.base.Splitter.on;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.intellij.notification.NotificationType.ERROR;
import static com.intellij.notification.NotificationType.WARNING;
import static com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil.getExecutionSettings;
import static com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil.getSettings;
import static com.intellij.openapi.util.SystemInfo.isWindows;
import static com.intellij.openapi.util.io.FileUtil.*;
import static com.intellij.openapi.util.io.FileUtil.join;
import static com.intellij.openapi.util.io.FileUtil.notNullize;
import static com.intellij.openapi.util.text.StringUtil.*;
import static com.intellij.openapi.vfs.VfsUtil.findFileByIoFile;
import static com.intellij.openapi.vfs.VfsUtil.processFileRecursivelyWithoutIgnored;
import static com.intellij.openapi.vfs.VfsUtilCore.virtualToIoFile;
import static com.intellij.util.ArrayUtil.toStringArray;
import static com.intellij.util.SystemProperties.getUserHome;
import static com.intellij.util.containers.ContainerUtil.addAll;
import static com.intellij.util.containers.ContainerUtil.getFirstItem;
import static com.intellij.util.ui.UIUtil.invokeAndWaitIfNeeded;
import static java.util.Collections.sort;
import static org.gradle.wrapper.WrapperExecutor.DISTRIBUTION_URL_PROPERTY;
import static org.gradle.wrapper.WrapperExecutor.forWrapperPropertiesFile;
import static org.jetbrains.plugins.gradle.settings.DistributionType.DEFAULT_WRAPPED;
import static org.jetbrains.plugins.gradle.settings.DistributionType.LOCAL;
import static org.jetbrains.plugins.gradle.util.GradleUtil.getLastUsedGradleHome;
/**
* Utilities related to Gradle.
*/
public final class GradleUtil {
public static final ProjectSystemId GRADLE_SYSTEM_ID = GradleConstants.SYSTEM_ID;
@NonNls public static final String BUILD_DIR_DEFAULT_NAME = "build";
@NonNls public static final String GRADLEW_PROPERTIES_PATH = join(FD_GRADLE_WRAPPER, FN_GRADLE_WRAPPER_PROPERTIES);
private static final Logger LOG = Logger.getInstance(GradleUtil.class);
private static final Pattern GRADLE_JAR_NAME_PATTERN = Pattern.compile("gradle-(.*)-(.*)\\.jar");
/**
* Finds characters that shouldn't be used in the Gradle path.
* <p/>
* I was unable to find any specification for Gradle paths. In my experiments, Gradle only failed with slashes. This list may grow if
* we find any other unsupported characters.
*/
private static final CharMatcher ILLEGAL_GRADLE_PATH_CHARS_MATCHER = CharMatcher.anyOf("\\/");
private static final Pattern GRADLE_DISTRIBUTION_URL_PATTERN = Pattern.compile(".*-([^-]+)-([^.]+).zip");
private GradleUtil() {
}
public static void clearStoredGradleJvmArgs(@NotNull final Project project) {
GradleSettings settings = GradleSettings.getInstance(project);
final String existingJvmArgs = settings.getGradleVmOptions();
settings.setGradleVmOptions("");
if (!isEmptyOrSpaces(existingJvmArgs)) {
invokeAndWaitIfNeeded(new Runnable() {
@Override
public void run() {
String jvmArgs = existingJvmArgs.trim();
final String msg =
String.format("Starting with version 1.3, Android Studio no longer supports IDE-specific Gradle JVM arguments.\n\n" +
"Android Studio will now remove any stored Gradle JVM arguments.\n\n" +
"Would you like to copy these JVM arguments:\n%1$s\n" +
"to the project's gradle.properties file?\n\n" +
"(Any existing JVM arguments in the gradle.properties file will be overwritten.)", jvmArgs);
int result = Messages.showYesNoDialog(project, msg, "Gradle Settings", Messages.getQuestionIcon());
if (result == Messages.YES) {
try {
GradleProperties gradleProperties = new GradleProperties(project);
gradleProperties.setJvmArgs(jvmArgs);
gradleProperties.save();
}
catch (IOException e) {
String err = String.format("Failed to copy JVM arguments '%1$s' to the project's gradle.properties file.", existingJvmArgs);
LOG.info(err, e);
String cause = e.getMessage();
if (isNotEmpty(cause)) {
err += String.format("<br>\nCause: %1$s", cause);
}
AndroidGradleNotification.getInstance(project).showBalloon("Gradle Settings", err, ERROR);
}
}
else {
String text =
String.format("JVM arguments<br>\n'%1$s'<br>\nwere not copied to the project's gradle.properties file.", existingJvmArgs);
AndroidGradleNotification.getInstance(project).showBalloon("Gradle Settings", text, WARNING);
}
}
});
}
}
public static boolean isSupportedGradleVersion(@NotNull FullRevision gradleVersion) {
FullRevision supported = FullRevision.parseRevision(GRADLE_MINIMUM_VERSION);
return supported.compareTo(gradleVersion) <= 0;
}
/**
* This is temporary, until the model returns more outputs per artifact.
* Deprecating since the model 0.13 provides multiple outputs per artifact if split apks are enabled.
*/
@Deprecated
@NotNull
public static AndroidArtifactOutput getOutput(@NotNull AndroidArtifact artifact) {
Collection<AndroidArtifactOutput> outputs = artifact.getOutputs();
assert !outputs.isEmpty();
AndroidArtifactOutput output = getFirstItem(outputs);
assert output != null;
return output;
}
@NotNull
public static Icon getModuleIcon(@NotNull Module module) {
AndroidProject androidProject = getAndroidProject(module);
if (androidProject != null) {
return androidProject.isLibrary() ? AndroidIcons.LibraryModule : AndroidIcons.AppModule;
}
return requiresAndroidModel(module.getProject()) ? AllIcons.Nodes.PpJdk : AllIcons.Nodes.Module;
}
@Nullable
public static AndroidProject getAndroidProject(@NotNull Module module) {
AndroidFacet facet = AndroidFacet.getInstance(module);
if (facet != null) {
IdeaAndroidProject androidModel = facet.getAndroidModel();
if (androidModel != null) {
return androidModel.getAndroidProject();
}
}
return null;
}
/**
* Returns the Gradle "logical" path (using colons as separators) if the given module represents a Gradle project or sub-project.
*
* @param module the given module.
* @return the Gradle path for the given module, or {@code null} if the module does not represent a Gradle project or sub-project.
*/
@Nullable
public static String getGradlePath(@NotNull Module module) {
AndroidGradleFacet facet = AndroidGradleFacet.getInstance(module);
return facet != null ? facet.getConfiguration().GRADLE_PROJECT_PATH : null;
}
/**
* Returns whether the given module is the module corresponding to the project root (i.e. gradle path of ":") and has no source roots.
* <p/>
* The default Android Studio projects create an empty module at the root level. In theory, users could add sources to that module, but
* we expect that most don't and keep that as a module simply to tie together other modules.
*/
public static boolean isRootModuleWithNoSources(@NotNull Module module) {
if (ModuleRootManager.getInstance(module).getSourceRoots().length == 0) {
String gradlePath = getGradlePath(module);
if (gradlePath == null || gradlePath.equals(":")) {
return true;
}
}
return false;
}
/**
* Returns the library dependencies in the given variant. This method checks dependencies in the main and test (as currently selected
* in the UI) artifacts. The dependency lookup is not transitive (only direct dependencies are returned.)
*/
@NotNull
public static List<AndroidLibrary> getDirectLibraryDependencies(@NotNull Variant variant,
@NotNull IdeaAndroidProject androidModel) {
List<AndroidLibrary> libraries = Lists.newArrayList();
libraries.addAll(variant.getMainArtifact().getDependencies().getLibraries());
BaseArtifact testArtifact = androidModel.findSelectedTestArtifact(variant);
if (testArtifact != null) {
libraries.addAll(testArtifact.getDependencies().getLibraries());
}
return libraries;
}
@Nullable
public static Module findModuleByGradlePath(@NotNull Project project, @NotNull String gradlePath) {
ModuleManager moduleManager = ModuleManager.getInstance(project);
for (Module module : moduleManager.getModules()) {
AndroidGradleFacet gradleFacet = AndroidGradleFacet.getInstance(module);
if (gradleFacet != null) {
if (gradlePath.equals(gradleFacet.getConfiguration().GRADLE_PROJECT_PATH)) {
return module;
}
}
}
return null;
}
@NotNull
public static List<String> getPathSegments(@NotNull String gradlePath) {
return on(GRADLE_PATH_SEPARATOR).omitEmptyStrings().splitToList(gradlePath);
}
/**
* Returns the build.gradle file in the given module. This method first checks if the Gradle model has the path of the build.gradle
* file for the given module. If it doesn't find it, it tries to find a build.gradle inside the module's root directory.
*
* @param module the given module.
* @return the build.gradle file in the given module, or {@code null} if it cannot be found.
*/
@Nullable
public static VirtualFile getGradleBuildFile(@NotNull Module module) {
AndroidGradleFacet gradleFacet = AndroidGradleFacet.getInstance(module);
if (gradleFacet != null && gradleFacet.getGradleProject() != null) {
return gradleFacet.getGradleProject().getBuildFile();
}
// At the time we're called, module.getModuleFile() may be null, but getModuleFilePath returns the path where it will be created.
File moduleFilePath = new File(module.getModuleFilePath());
return getGradleBuildFile(moduleFilePath.getParentFile());
}
/**
* Returns the build.gradle file that is expected right in the directory at the given path. For example, if the directory path is
* '~/myProject/myModule', this method will look for the file '~/myProject/myModule/build.gradle'.
* <p>
* <b>Note:</b> Only use this method if you do <b>not</b> have a reference to a {@link Module}. Otherwise use
* {@link #getGradleBuildFile(Module)}.
* </p>
*
* @param dirPath the given directory path.
* @return the build.gradle file in the directory at the given path, or {@code null} if there is no build.gradle file in the given
* directory path.
*/
@Nullable
public static VirtualFile getGradleBuildFile(@NotNull File dirPath) {
File gradleBuildFilePath = getGradleBuildFilePath(dirPath);
return findFileByIoFile(gradleBuildFilePath, true);
}
/**
* Returns the path of a build.gradle file in the directory at the given path. For example, if the directory path is
* '~/myProject/myModule', this method will return the path '~/myProject/myModule/build.gradle'. Please note that a build.gradle file
* may not exist at the returned path.
* <p>
* <b>Note:</b> Only use this method if you do <b>not</b> have a reference to a {@link Module}. Otherwise use
* {@link #getGradleBuildFile(Module)}.
* </p>
*
* @param dirPath the given directory path.
* @return the path of a build.gradle file in the directory at the given path.
*/
@NotNull
public static File getGradleBuildFilePath(@NotNull File dirPath) {
return new File(dirPath, FN_BUILD_GRADLE);
}
@Nullable
public static VirtualFile getGradleSettingsFile(@NotNull File dirPath) {
File gradleSettingsFilePath = getGradleSettingsFilePath(dirPath);
return findFileByIoFile(gradleSettingsFilePath, true);
}
@NotNull
public static File getGradleSettingsFilePath(@NotNull File dirPath) {
return new File(dirPath, FN_SETTINGS_GRADLE);
}
@NotNull
public static File getGradleWrapperPropertiesFilePath(@NotNull File projectRootDir) {
return new File(projectRootDir, GRADLEW_PROPERTIES_PATH);
}
/**
* Updates the 'distributionUrl' in the given Gradle wrapper properties file.
*
* @param gradleVersion the Gradle version to update the property to.
* @param propertiesFile the given Gradle wrapper properties file.
* @return {@code true} if the property was updated, or {@code false} if no update was necessary because the property already had the
* correct value.
* @throws IOException if something goes wrong when saving the file.
*/
public static boolean updateGradleDistributionUrl(@NotNull String gradleVersion, @NotNull File propertiesFile) throws IOException {
Properties properties = getProperties(propertiesFile);
String gradleDistributionUrl = getGradleDistributionUrl(gradleVersion, false);
String property = properties.getProperty(DISTRIBUTION_URL_PROPERTY);
if (property != null && (property.equals(gradleDistributionUrl) || property.equals(getGradleDistributionUrl(gradleVersion, true)))) {
return false;
}
properties.setProperty(DISTRIBUTION_URL_PROPERTY, gradleDistributionUrl);
savePropertiesToFile(properties, propertiesFile, null);
return true;
}
@Nullable
public static String getGradleWrapperVersion(@NotNull File propertiesFile) throws IOException {
Properties properties = getProperties(propertiesFile);
String url = properties.getProperty(DISTRIBUTION_URL_PROPERTY);
if (url != null) {
Matcher m = GRADLE_DISTRIBUTION_URL_PATTERN.matcher(url);
if (m.matches()) {
return m.group(1);
}
}
return null;
}
@NotNull
private static String getGradleDistributionUrl(@NotNull String gradleVersion, boolean binOnly) {
String suffix = binOnly ? "bin" : "all";
return String.format("https://services.gradle.org/distributions/gradle-%1$s-" + suffix + ".zip", gradleVersion);
}
@Nullable
public static GradleExecutionSettings getGradleExecutionSettings(@NotNull Project project) {
GradleProjectSettings projectSettings = getGradleProjectSettings(project);
if (projectSettings == null) {
File baseDirPath = getBaseDirPath(project);
String msg = String
.format("Unable to obtain Gradle project settings for project '%1$s', located at '%2$s'", project.getName(), baseDirPath.getPath());
LOG.info(msg);
return null;
}
// Set the JDK again, just in case it changed name after creating a project.
// See https://code.google.com/p/android/issues/detail?id=156747
Sdk jdk = IdeSdks.getJdk();
if (jdk != null) {
projectSettings.setGradleJvm(jdk.getName());
}
try {
GradleExecutionSettings settings = getExecutionSettings(project, projectSettings.getExternalProjectPath(), GRADLE_SYSTEM_ID);
if (settings != null) {
// By setting the Gradle daemon timeout to -1, we don't allow IDEA to set it to 1 minute. Gradle daemons need to be reused as
// much as possible. The default timeout is 3 hours.
settings.setRemoteProcessIdleTtlInMs(GRADLE_DAEMON_TIMEOUT_MS);
}
return settings;
}
catch (IllegalArgumentException e) {
LOG.info("Failed to obtain Gradle execution settings", e);
return null;
}
}
@Nullable
public static File findWrapperPropertiesFile(@NotNull Project project) {
String basePath = project.getBasePath();
if (basePath == null) {
return null;
}
File baseDir = new File(basePath);
File wrapperPropertiesFile = getGradleWrapperPropertiesFilePath(baseDir);
return wrapperPropertiesFile.isFile() ? wrapperPropertiesFile : null;
}
@Nullable
public static GradleProjectSettings getGradleProjectSettings(@NotNull Project project) {
GradleSettings settings = (GradleSettings)getSettings(project, GRADLE_SYSTEM_ID);
GradleSettings.MyState state = settings.getState();
assert state != null;
Set<GradleProjectSettings> allProjectsSettings = state.getLinkedExternalProjectsSettings();
return getFirstNotNull(allProjectsSettings);
}
@Nullable
private static GradleProjectSettings getFirstNotNull(@Nullable Set<GradleProjectSettings> allProjectSettings) {
if (allProjectSettings != null) {
for (GradleProjectSettings settings : allProjectSettings) {
if (settings != null) {
return settings;
}
}
}
return null;
}
@VisibleForTesting
@Nullable
static String getGradleInvocationJvmArg(@Nullable BuildMode buildMode) {
if (ASSEMBLE_TRANSLATE == buildMode) {
return AndroidGradleSettings.createJvmArg(ENABLE_TRANSLATION_JVM_ARG, true);
}
return null;
}
public static void stopAllGradleDaemons(boolean interactive) throws IOException {
File gradleHome = findAnyGradleHome(interactive);
if (gradleHome == null) {
throw new FileNotFoundException("Unable to find path to Gradle home directory");
}
File gradleExecutable = new File(gradleHome, join("bin", isWindows ? FN_GRADLE_WIN : FN_GRADLE_UNIX));
if (!gradleExecutable.isFile()) {
throw new FileNotFoundException("Unable to find Gradle executable: " + gradleExecutable.getPath());
}
new ProcessBuilder(gradleExecutable.getPath(), "--stop").start();
}
@Nullable
public static File findAnyGradleHome(boolean interactive) {
// Try cheapest option first:
String lastUsedGradleHome = getLastUsedGradleHome();
if (!lastUsedGradleHome.isEmpty()) {
File path = new File(lastUsedGradleHome);
if (isValidGradleHome(path)) {
return path;
}
}
ProjectManager projectManager = ProjectManager.getInstance();
for (Project project : projectManager.getOpenProjects()) {
File gradleHome = findGradleHome(project);
if (gradleHome != null) {
return gradleHome;
}
}
if (interactive) {
ChooseGradleHomeDialog chooseGradleHomeDialog = new ChooseGradleHomeDialog();
chooseGradleHomeDialog.setTitle("Choose Gradle Installation");
String description = "A Gradle installation is necessary to stop all daemons.\n" +
"Please select the home directory of a Gradle installation, otherwise the project won't be closed.";
chooseGradleHomeDialog.setDescription(description);
if (!chooseGradleHomeDialog.showAndGet()) {
return null;
}
String enteredPath = chooseGradleHomeDialog.getEnteredGradleHomePath();
File gradleHomePath = new File(enteredPath);
if (isValidGradleHome(gradleHomePath)) {
chooseGradleHomeDialog.storeLastUsedGradleHomePath();
return gradleHomePath;
}
}
return null;
}
@Nullable
private static File findGradleHome(@NotNull Project project) {
GradleExecutionSettings settings = getGradleExecutionSettings(project);
if (settings != null) {
String gradleHome = settings.getGradleHome();
if (isNotEmpty(gradleHome)) {
File path = new File(gradleHome);
if (isValidGradleHome(path)) {
return path;
}
}
}
File wrapperPropertiesFile = findWrapperPropertiesFile(project);
if (wrapperPropertiesFile != null) {
WrapperExecutor wrapperExecutor = forWrapperPropertiesFile(wrapperPropertiesFile, new StringBuilder());
WrapperConfiguration configuration = wrapperExecutor.getConfiguration();
File gradleHome = getGradleHome(project, configuration);
if (gradleHome != null) {
return gradleHome;
}
}
return null;
}
@Nullable
private static File getGradleHome(@NotNull Project project, @NotNull WrapperConfiguration configuration) {
File systemHomePath = StartParameter.DEFAULT_GRADLE_USER_HOME;
if ("PROJECT".equals(configuration.getDistributionBase())) {
systemHomePath = new File(getBaseDirPath(project), DOT_GRADLE);
}
if (!systemHomePath.isDirectory()) {
return null;
}
PathAssembler.LocalDistribution localDistribution = new PathAssembler(systemHomePath).getDistribution(configuration);
File distributionPath = localDistribution.getDistributionDir();
if (distributionPath != null) {
File[] children = notNullize(distributionPath.listFiles());
for (File child : children) {
if (child.isDirectory() && child.getName().startsWith("gradle-") && isValidGradleHome(child)) {
return child;
}
}
}
return null;
}
private static boolean isValidGradleHome(@NotNull File path) {
return path.isDirectory() && ServiceManager.getService(GradleInstallationManager.class).isGradleSdkHome(path);
}
/**
* Converts a Gradle project name into a system dependent path relative to root project. Please note this is the default mapping from a
* Gradle "logical" path to a physical path. Users can override this mapping in settings.gradle and this mapping may not always be
* accurate.
* <p/>
* E.g. ":module" becomes "module" and ":directory:module" is converted to "directory/module"
*/
@NotNull
public static String getDefaultPhysicalPathFromGradlePath(@NotNull String gradlePath) {
List<String> segments = getPathSegments(gradlePath);
return join(toStringArray(segments));
}
/**
* Obtains the default path for the module (Gradle sub-project) with the given name inside the given directory.
*/
@NotNull
public static File getModuleDefaultPath(@NotNull VirtualFile parentDir, @NotNull String gradlePath) {
assert gradlePath.length() > 0;
String relativePath = getDefaultPhysicalPathFromGradlePath(gradlePath);
return new File(virtualToIoFile(parentDir), relativePath);
}
/**
* Tests if the Gradle path is valid and return index of the offending character or -1 if none.
*/
public static int isValidGradlePath(@NotNull String gradlePath) {
return ILLEGAL_GRADLE_PATH_CHARS_MATCHER.indexIn(gradlePath);
}
/**
* Checks if the project already has a module with given Gradle path.
*/
public static boolean hasModule(@Nullable Project project, @NotNull String gradlePath, boolean checkProjectFolder) {
if (project == null) {
return false;
}
for (Module module : ModuleManager.getInstance(project).getModules()) {
if (gradlePath.equals(getGradlePath(module))) {
return true;
}
}
if (checkProjectFolder) {
File location = getModuleDefaultPath(project.getBaseDir(), gradlePath);
if (location.isFile()) {
return true;
}
else if (location.isDirectory()) {
File[] children = location.listFiles();
return children == null || children.length > 0;
}
else {
return false;
}
}
else {
return false;
}
}
public static void cleanUpPreferences(@NotNull ExtensionPoint<ConfigurableEP<Configurable>> preferences,
@NotNull List<String> bundlesToRemove) {
List<ConfigurableEP<Configurable>> nonStudioExtensions = Lists.newArrayList();
ConfigurableEP<Configurable>[] extensions = preferences.getExtensions();
for (ConfigurableEP<Configurable> extension : extensions) {
if (bundlesToRemove.contains(extension.instanceClass)) {
nonStudioExtensions.add(extension);
}
}
for (ConfigurableEP<Configurable> toRemove : nonStudioExtensions) {
preferences.unregisterExtension(toRemove);
}
}
@Nullable
public static FullRevision getGradleVersion(@NotNull Project project) {
String gradleVersion = getGradleVersionUsed(project);
if (isNotEmpty(gradleVersion)) {
// The version of Gradle used is retrieved one of the Gradle models. If that fails, we try to deduce it from the project's Gradle
// settings.
FullRevision revision = parseRevision(removeTimestampFromGradleVersion(gradleVersion));
if (revision != null) {
return revision;
}
}
GradleProjectSettings gradleSettings = getGradleProjectSettings(project);
if (gradleSettings != null) {
DistributionType distributionType = gradleSettings.getDistributionType();
if (distributionType == DEFAULT_WRAPPED) {
File wrapperPropertiesFile = findWrapperPropertiesFile(project);
if (wrapperPropertiesFile != null) {
try {
String wrapperVersion = getGradleWrapperVersion(wrapperPropertiesFile);
if (wrapperVersion != null) {
return parseRevision(removeTimestampFromGradleVersion(wrapperVersion));
}
}
catch (IOException e) {
LOG.info("Failed to read Gradle version in wrapper", e);
}
}
}
else if (distributionType == LOCAL) {
String gradleHome = gradleSettings.getGradleHome();
if (isNotEmpty(gradleHome)) {
File gradleHomePath = new File(gradleHome);
return getGradleVersion(gradleHomePath);
}
}
}
return null;
}
/**
* Attempts to figure out the Gradle version of the given distribution.
*
* @param gradleHomePath the path of the directory containing the Gradle distribution.
* @return the Gradle version of the given distribution, or {@code null} if it was not possible to obtain the version.
*/
@Nullable
public static FullRevision getGradleVersion(@NotNull File gradleHomePath) {
File libDirPath = new File(gradleHomePath, "lib");
for (File child : notNullize(libDirPath.listFiles())) {
FullRevision version = getGradleVersionFromJar(child);
if (version != null) {
return version;
}
}
return null;
}
@VisibleForTesting
@Nullable
static FullRevision getGradleVersionFromJar(@NotNull File libraryJarFile) {
String fileName = libraryJarFile.getName();
Matcher matcher = GRADLE_JAR_NAME_PATTERN.matcher(fileName);
if (matcher.matches()) {
// Obtain the version of Gradle from a library name (e.g. "gradle-core-2.0.jar")
String version = matcher.group(2);
try {
return PreciseRevision.parseRevision(removeTimestampFromGradleVersion(version));
}
catch (NumberFormatException e) {
LOG.warn(String.format("Unable to parse version '%1$s' (obtained from file '%2$s')", version, fileName));
}
}
return null;
}
@NotNull
private static String removeTimestampFromGradleVersion(@NotNull String gradleVersion) {
int dashIndex = gradleVersion.indexOf('-');
if (dashIndex != -1) {
// in case this is a nightly (e.g. "2.4-20150409092851+0000").
return gradleVersion.substring(0, dashIndex);
}
return gradleVersion;
}
/**
* Creates the Gradle wrapper, using the latest supported version of Gradle, in the project at the given directory.
*
* @param projectDirPath the project's root directory.
* @return {@code true} if the project already has the wrapper or the wrapper was successfully created; {@code false} if the wrapper was
* not created (e.g. the template files for the wrapper were not found.)
* @throws IOException any unexpected I/O error.
* @see SdkConstants#GRADLE_LATEST_VERSION
*/
public static boolean createGradleWrapper(@NotNull File projectDirPath) throws IOException {
return createGradleWrapper(projectDirPath, GRADLE_LATEST_VERSION);
}
/**
* Creates the Gradle wrapper in the project at the given directory.
*
* @param projectDirPath the project's root directory.
* @param gradleVersion the version of Gradle to use.
* @return {@code true} if the project already has the wrapper or the wrapper was successfully created; {@code false} if the wrapper was
* not created (e.g. the template files for the wrapper were not found.)
* @throws IOException any unexpected I/O error.
* @see SdkConstants#GRADLE_LATEST_VERSION
*/
@VisibleForTesting
public static boolean createGradleWrapper(@NotNull File projectDirPath, @NotNull String gradleVersion) throws IOException {
File projectWrapperDirPath = new File(projectDirPath, FD_GRADLE_WRAPPER);
if (!projectWrapperDirPath.isDirectory()) {
File wrapperSrcDirPath = new File(TemplateManager.getTemplateRootFolder(), FD_GRADLE_WRAPPER);
if (!wrapperSrcDirPath.exists()) {
for (File root : TemplateManager.getExtraTemplateRootFolders()) {
wrapperSrcDirPath = new File(root, FD_GRADLE_WRAPPER);
if (wrapperSrcDirPath.exists()) {
break;
}
else {
wrapperSrcDirPath = null;
}
}
}
if (wrapperSrcDirPath == null) {
return false;
}
copyDirContent(wrapperSrcDirPath, projectDirPath);
}
File wrapperPropertiesFile = getGradleWrapperPropertiesFilePath(projectDirPath);
updateGradleDistributionUrl(gradleVersion, wrapperPropertiesFile);
return true;
}
/**
* Determines version of the Android gradle plugin (and model) used by the project. The result can be absent if there are no android
* modules in the project or if the last sync has failed.
*
* @see #getAndroidGradleModelVersionFromBuildFile(Project)
*/
@Nullable
public static FullRevision getAndroidGradleModelVersionInUse(@NotNull Project project) {
Set<String> pluginVersionsUsedInProject = new SmartHashSet<String>();
for (Module module : ModuleManager.getInstance(project).getModules()) {
AndroidProject androidProject = getAndroidProject(module);
if (androidProject == null) {
continue;
}
pluginVersionsUsedInProject.add(androidProject.getModelVersion());
}
// This should pretty much always be the case, but let's be safe here.
if (pluginVersionsUsedInProject.size() == 1) {
return parseRevision(getOnlyElement(pluginVersionsUsedInProject));
}
return null;
}
/**
* Tries to find the version of the Android Gradle plug-in declared in build.gradle files. If the project contains complicated build
* logic, this may be incorrect.
* <p/>
* <p>
* The version is returned as it is specified in build files if it does not use "+" notation.
* </p>
* <p>
* If the version is using "+" notation for the "micro" portion, this method replaces the "+" with a zero. For example: "0.13.+" will be
* returned as "0.13.0". In practice, the micro portion of the version is not used.
* </p>
* <p>
* If the version in build files is "+" or uses "+" for the major or minor portions, this method will find the latest version in the local
* Gradle cache.
* </p>
*
* @param project the given project.
* @return the version of the Android Gradle plug-in being used in the given project. (or an approximation.)
* @see #getAndroidGradleModelVersionInUse(Project)
*/
@Nullable
public static FullRevision getAndroidGradleModelVersionFromBuildFile(@NotNull final Project project) {
VirtualFile baseDir = project.getBaseDir();
if (baseDir == null) {
// This is default project.
return null;
}
final Ref<FullRevision> modelVersionRef = new Ref<FullRevision>();
processFileRecursivelyWithoutIgnored(baseDir, new Processor<VirtualFile>() {
@Override
public boolean process(VirtualFile virtualFile) {
if (FN_BUILD_GRADLE.equals(virtualFile.getName())) {
File fileToCheck = virtualToIoFile(virtualFile);
try {
String contents = loadFile(fileToCheck);
FullRevision version = getAndroidGradleModelVersionFromBuildFile(contents, project);
if (version != null) {
modelVersionRef.set(version);
return false; // we found the model version. Stop.
}
}
catch (IOException e) {
LOG.warn("Failed to read contents of " + fileToCheck.getPath());
}
}
return true;
}
});
return modelVersionRef.get();
}
@VisibleForTesting
@Nullable
static FullRevision getAndroidGradleModelVersionFromBuildFile(@NotNull String fileContents, @Nullable Project project) {
GradleCoordinate found = getPluginDefinition(fileContents, GRADLE_PLUGIN_NAME);
if (found != null) {
String revision = getAndroidGradleModelVersion(found, project);
if (isNotEmpty(revision)) {
return parseRevision(revision);
}
}
return null;
}
@Nullable
private static FullRevision parseRevision(@NotNull String revision) {
try {
return PreciseRevision.parseRevision(revision);
}
catch (NumberFormatException e) {
LOG.info("Failed to parse revision '" + revision + "'", e);
}
return null;
}
/**
* Returns target plugin's definition.
*
* @param fileContents target Gradle build file contents
* @param pluginName target plugin's name in a form {@code group-id:artifact-id:}
* @return target plugin's definition if found; {@code null} otherwise
*/
@Nullable
public static GradleCoordinate getPluginDefinition(@NotNull String fileContents, @NotNull String pluginName) {
String definition = findStringLiteral(pluginName, fileContents, new Function<Pair<String, GroovyLexer>, String>() {
@Override
public String fun(Pair<String, GroovyLexer> pair) {
return pair.getFirst();
}
});
return isNotEmpty(definition) ? parseCoordinateString(definition) : null;
}
/**
* Updates the version of a Gradle dependency used in a build.gradle file.
*
* @param project the project containing the build.gradle file.
* @param buildFileDocument document of the build.gradle file, which declares the version of the dependency.
* @param dependencyName the name of the dependency to look for.
* @param versionTask returns the version of the dependency to update the file to.
* @return {@code true} if the build.gradle file was updated; {@code false} otherwise.
*/
public static boolean updateGradleDependencyVersion(@NotNull Project project,
@NotNull final Document buildFileDocument,
@NotNull final String dependencyName,
@NotNull final Computable<String> versionTask) {
String contents = buildFileDocument.getText();
final TextRange range = findStringLiteral(dependencyName, contents, new Function<Pair<String, GroovyLexer>, TextRange>() {
@Override
public TextRange fun(Pair<String, GroovyLexer> pair) {
GroovyLexer lexer = pair.getSecond();
return TextRange.create(lexer.getTokenStart() + 1 + dependencyName.length(), lexer.getTokenEnd() - 1);
}
});
if (range != null) {
WriteCommandAction.runWriteCommandAction(project, new Runnable() {
@Override
public void run() {
buildFileDocument.replaceString(range.getStartOffset(), range.getEndOffset(), versionTask.compute());
}
});
return true;
}
return false;
}
@Nullable
public static TextRange findDependency(@NotNull final String dependency, @NotNull String contents) {
return findStringLiteral(dependency, contents, new Function<Pair<String, GroovyLexer>, TextRange>() {
@Override
public TextRange fun(Pair<String, GroovyLexer> pair) {
GroovyLexer lexer = pair.getSecond();
return TextRange.create(lexer.getTokenStart() + 1, lexer.getTokenEnd() - 1);
}
});
}
@Nullable
private static <T> T findStringLiteral(@NotNull String textToSearchPrefix,
@NotNull String fileContents,
@NotNull Function<Pair<String, GroovyLexer>, T> consumer) {
GroovyLexer lexer = new GroovyLexer();
lexer.start(fileContents);
while (lexer.getTokenType() != null) {
IElementType type = lexer.getTokenType();
if (type == GroovyTokenTypes.mSTRING_LITERAL) {
String text = unquoteString(lexer.getTokenText());
if (text.startsWith(textToSearchPrefix)) {
return consumer.fun(Pair.create(text, lexer));
}
}
lexer.advance();
}
return null;
}
/**
* Delegates to the {@link #forPluginDefinition(String, String, Function)} and just returns target plugin's definition string (unquoted).
*
* @param fileContents target gradle config text
* @param pluginName target plugin's name in a form <code>'group-id:artifact-id:'</code>
* @return target plugin's definition string if found (unquoted); <code>null</code> otherwise
* @see #forPluginDefinition(String, String, Function)
*/
@Nullable
public static String getPluginDefinitionString(@NotNull String fileContents, @NotNull String pluginName) {
return forPluginDefinition(fileContents, pluginName, new Function<Pair<String, GroovyLexer>, String>() {
@Override
public String fun(Pair<String, GroovyLexer> pair) {
return pair.getFirst();
}
});
}
/**
* Checks given file contents (assuming that it's build.gradle config) and finds target plugin's definition (given the plugin
* name in a form <code>'group-id:artifact-id:'</code>. Supplies given callback with the plugin definition string (unquoted) and
* a {@link GroovyLexer} which state points to the plugin definition string (quoted).
* <p/>
* Example:
* <pre>
* buildscript {
* repositories {
* mavenCentral()
* }
* dependencies {
* classpath 'com.google.appengine:gradle-appengine-plugin:1.9.4'
* }
* }
* </pre>
* Suppose that this method is called for the given build script content and
* <code>'com.google.appengine:gradle-appengine-plugin:'</code> as a plugin name argument. Given callback is supplied by a
* string <code>'com.google.appengine:gradle-appengine-plugin:1.9.4'</code> (without quotes) and a {@link GroovyLexer} which
* {@link GroovyLexer#getTokenStart() points} to the string <code>'com.google.appengine:gradle-appengine-plugin:1.9.4'</code>
* (with quotes), i.e. we can get exact text range for the target string in case we need to do something like replacing plugin's
* version.
*
* @param fileContents target gradle config text
* @param pluginName target plugin's name in a form <code>'group-id:artifact-id:'</code>
* @param consumer a callback to be notified for the target plugin's definition string
* @param <T> given callback's return type
* @return given callback's call result if target plugin definition is found; <code>null</code> otherwise
*/
@Nullable
public static <T> T forPluginDefinition(@NotNull String fileContents,
@NotNull String pluginName,
@NotNull Function<Pair<String, GroovyLexer>, T> consumer) {
GroovyLexer lexer = new GroovyLexer();
lexer.start(fileContents);
while (lexer.getTokenType() != null) {
IElementType type = lexer.getTokenType();
if (type == GroovyTokenTypes.mSTRING_LITERAL) {
String text = unquoteString(lexer.getTokenText());
if (text.startsWith(pluginName)) {
return consumer.fun(Pair.create(text, lexer));
}
}
lexer.advance();
}
return null;
}
@Nullable
private static String getAndroidGradleModelVersion(@NotNull GradleCoordinate coordinate, @Nullable Project project) {
String revision = coordinate.getFullRevision();
if (isNotEmpty(revision)) {
if (!coordinate.acceptsGreaterRevisions()) {
return revision;
}
// For the Android plug-in we don't care about the micro version. Major and minor only matter.
int major = coordinate.getMajorVersion();
int minor = coordinate.getMinorVersion();
if (coordinate.getMicroVersion() == -1 && major >= 0 && minor > 0) {
return major + "." + minor + "." + 0;
}
}
GradleCoordinate latest = findLatestVersionInGradleCache(coordinate, null, project);
return latest != null ? latest.getFullRevision() : null;
}
@Nullable
public static GradleCoordinate findLatestVersionInGradleCache(@NotNull GradleCoordinate original,
@Nullable String filter,
@Nullable Project project) {
for (File gradleServicePath : getGradleServicePaths(project)) {
GradleCoordinate version = findLatestVersionInGradleCache(gradleServicePath, original, filter);
if (version != null) {
return version;
}
}
return null;
}
@Nullable
private static GradleCoordinate findLatestVersionInGradleCache(@NotNull File gradleServicePath,
@NotNull GradleCoordinate original,
@Nullable String filter) {
File gradleCache = new File(gradleServicePath, "caches");
if (gradleCache.exists()) {
List<GradleCoordinate> coordinates = Lists.newArrayList();
String groupId = original.getGroupId();
String artifactId = original.getArtifactId();
for (File moduleDir : notNullize(gradleCache.listFiles())) {
if (!moduleDir.getName().startsWith("modules-") || !moduleDir.isDirectory()) {
continue;
}
for (File metadataDir : notNullize(moduleDir.listFiles())) {
if (!metadataDir.getName().startsWith("metadata-") || !metadataDir.isDirectory()) {
continue;
}
File versionDir = new File(metadataDir, join("descriptors", groupId, artifactId));
if (!versionDir.isDirectory()) {
continue;
}
for (File version : notNullize(versionDir.listFiles())) {
String name = version.getName();
if ((filter == null || name.startsWith(filter)) && !name.isEmpty() && Character.isDigit(name.charAt(0))) {
GradleCoordinate found = parseCoordinateString(groupId + ":" + artifactId + ":" + name);
if (found != null) {
coordinates.add(found);
}
}
}
}
}
if (!coordinates.isEmpty()) {
sort(coordinates, GradleCoordinate.COMPARE_PLUS_LOWER);
return coordinates.get(coordinates.size() - 1);
}
}
return null;
}
public static void addLocalMavenRepoInitScriptCommandLineOption(@NotNull List<String> args) {
if (isAndroidStudio() || ApplicationManager.getApplication().isUnitTestMode()) {
File repoPath = findAndroidStudioLocalMavenRepoPath();
if (repoPath != null && repoPath.isDirectory()) {
addLocalMavenRepoInitScriptCommandLineOption(args, repoPath);
}
}
}
@VisibleForTesting
@Nullable
static File addLocalMavenRepoInitScriptCommandLineOption(@NotNull List<String> args, @NotNull File repoPath) {
try {
File file = createTempFile("asLocalRepo", DOT_GRADLE);
file.deleteOnExit();
String contents = "allprojects {\n" +
" buildscript {\n" +
" repositories {\n" +
" maven { url '" + GradleImport.escapeGroovyStringLiteral(repoPath.getPath()) + "'}\n" +
" }\n" +
" }\n" +
"}\n";
writeToFile(file, contents);
addAll(args, GradleConstants.INIT_SCRIPT_CMD_OPTION, file.getAbsolutePath());
return file;
}
catch (IOException e) {
LOG.warn("Failed to set up 'local repo' Gradle init script", e);
}
return null;
}
public static void attemptToUseEmbeddedGradle(@NotNull Project project) {
if (isAndroidStudio()) {
File wrapperPropertiesFile = findWrapperPropertiesFile(project);
if (wrapperPropertiesFile != null) {
String gradleVersion = null;
try {
Properties properties = getProperties(wrapperPropertiesFile);
String url = properties.getProperty(DISTRIBUTION_URL_PROPERTY);
gradleVersion = getGradleWrapperVersionOnlyIfComingForGradleDotOrg(url);
}
catch (IOException e) {
LOG.warn("Failed to read file " + wrapperPropertiesFile.getPath());
}
if (gradleVersion != null &&
isCompatibleWithEmbeddedGradleVersion(gradleVersion) &&
!isWrapperInGradleCache(project, gradleVersion)) {
File embeddedGradlePath = findEmbeddedGradleDistributionPath();
if (embeddedGradlePath != null) {
GradleProjectSettings gradleSettings = getGradleProjectSettings(project);
if (gradleSettings != null) {
gradleSettings.setDistributionType(LOCAL);
gradleSettings.setGradleHome(embeddedGradlePath.getPath());
}
}
}
}
}
}
@VisibleForTesting
@Nullable
static String getGradleWrapperVersionOnlyIfComingForGradleDotOrg(@Nullable String url) {
if (url != null) {
int foundIndex = url.indexOf("://");
if (foundIndex != -1) {
String protocol = url.substring(0, foundIndex);
if (protocol.equals("http") || protocol.equals("https")) {
String expectedPrefix = protocol + "://services.gradle.org/distributions/gradle-";
if (url.startsWith(expectedPrefix)) {
// look for "-" before "bin" or "all"
foundIndex = url.indexOf('-', expectedPrefix.length());
if (foundIndex != -1) {
String version = url.substring(expectedPrefix.length(), foundIndex);
if (isNotEmpty(version)) {
return version;
}
}
}
}
}
}
return null;
}
// Currently, the latest Gradle version is 2.2.1, and we consider 2.2 and 2.2.1 as compatible.
private static boolean isCompatibleWithEmbeddedGradleVersion(@NotNull String gradleVersion) {
return gradleVersion.equals(GRADLE_MINIMUM_VERSION) || gradleVersion.equals(GRADLE_LATEST_VERSION);
}
private static boolean isWrapperInGradleCache(@NotNull Project project, @NotNull String gradleVersion) {
String distFolderDirName = "gradle-" + gradleVersion;
String wrapperDirNamePrefix = distFolderDirName + "-";
// Try both distributions "all" and "bin".
String[] wrapperDirNames = {wrapperDirNamePrefix + "all", wrapperDirNamePrefix + "bin"};
for (File gradleServicePath : getGradleServicePaths(project)) {
for (String wrapperDirName : wrapperDirNames) {
File wrapperDirPath = new File(gradleServicePath, join("wrapper", "dists", wrapperDirName));
if (wrapperDirPath.isDirectory()) {
// There is a folder that contains the actual distribution
// Example: // ~/.gradle/wrapper/dists/gradle-2.1-all/27drb4udbjf4k88eh2ffdc0n55/gradle-2.1
for (File mayBeDistParent : notNullize(wrapperDirPath.listFiles())) {
if (mayBeDistParent.isDirectory()) {
for (File mayBeDistFolder : notNullize(mayBeDistParent.listFiles())) {
if (mayBeDistFolder.isDirectory() && distFolderDirName.equals(mayBeDistFolder.getName())) {
return true;
}
}
}
}
}
}
}
return false;
}
@NotNull
private static Collection<File> getGradleServicePaths(@Nullable Project project) {
Set<File> paths = Sets.newLinkedHashSet();
if (project != null) {
// Use the one set in the IDE
GradleSettings settings = GradleSettings.getInstance(project);
String path = settings.getServiceDirectoryPath();
if (isNotEmpty(path)) {
File file = new File(path);
if (file.isDirectory()) {
paths.add(file);
}
}
}
// The default location: ~/.gradle
File path = new File(getUserHome(), DOT_GRADLE);
if (path.isDirectory()) {
paths.add(path);
}
return paths;
}
/**
* Returns {@code true} if the given Android model depends on the given artifact, which consists of a group id and an artifact id, such as
* {@link SdkConstants#APPCOMPAT_LIB_ARTIFACT}.
*
* @param androidModel the Android model to check
* @param artifact the artifact
* @return {@code true} if the project depends on the given artifact (including transitively)
*/
public static boolean dependsOn(@NonNull IdeaAndroidProject androidModel, @NonNull String artifact) {
Dependencies dependencies = androidModel.getMainArtifact().getDependencies();
return dependsOn(dependencies, artifact);
}
/**
* Returns {@code true} if the given dependencies include the given artifact, which consists of a group id and an artifact id, such as
* {@link SdkConstants#APPCOMPAT_LIB_ARTIFACT}.
*
* @param dependencies the Gradle dependencies object to check
* @param artifact the artifact
* @return {@code true} if the dependencies include the given artifact (including transitively)
*/
private static boolean dependsOn(@NonNull Dependencies dependencies, @NonNull String artifact) {
for (AndroidLibrary library : dependencies.getLibraries()) {
if (dependsOn(library, artifact, true)) {
return true;
}
}
return false;
}
/**
* Returns {@code true} if the given library depends on the given artifact, which consists a group id and an artifact id, such as
* {@link SdkConstants#APPCOMPAT_LIB_ARTIFACT}.
*
* @param library the Gradle library to check
* @param artifact the artifact
* @param transitively if {@code false}, checks only direct dependencies, otherwise checks transitively
* @return {@code true} if the project depends on the given artifact
*/
public static boolean dependsOn(@NonNull AndroidLibrary library, @NonNull String artifact, boolean transitively) {
MavenCoordinates resolvedCoordinates = library.getResolvedCoordinates();
if (resolvedCoordinates != null) {
String s = resolvedCoordinates.getGroupId() + ':' + resolvedCoordinates.getArtifactId();
if (artifact.equals(s)) {
return true;
}
}
if (transitively) {
for (AndroidLibrary dependency : library.getLibraryDependencies()) {
if (dependsOn(dependency, artifact, true)) {
return true;
}
}
}
return false;
}
public static boolean hasCause(@NotNull Throwable e, @NotNull Class<?> causeClass) {
// We want to ignore class loader difference, that's why we just compare fully-qualified class names here.
String causeClassName = causeClass.getName();
for (Throwable ex = e; ex != null; ex = ex.getCause()) {
if (causeClassName.equals(ex.getClass().getName())) {
return true;
}
}
return false;
}
@Nullable
public static File getGradleUserSettingsFile() {
String homePath = getUserHome();
if (homePath == null) {
return null;
}
return new File(homePath, join(DOT_GRADLE, FN_GRADLE_PROPERTIES));
}
@Nullable
public static DataNode<ProjectData> getCachedProjectData(@NotNull Project project) {
ProjectDataManager dataManager = ProjectDataManager.getInstance();
ExternalProjectInfo projectInfo = dataManager.getExternalProjectData(project, GRADLE_SYSTEM_ID, getBaseDirPath(project).getPath());
return projectInfo != null ? projectInfo.getExternalProjectStructure() : null;
}
/**
* Indicates whether <a href="https://code.google.com/p/android/issues/detail?id=170841">a known layout rendering issue</a> is present in
* the given model.
*
* @param model the given model.
* @return {@true} if the model has the layout rendering issue; {@code false} otherwise.
*/
public static boolean hasLayoutRenderingIssue(@NotNull AndroidProject model) {
String modelVersion = model.getModelVersion();
return modelVersion.startsWith("1.2.0") || modelVersion.equals("1.2.1") || modelVersion.equals("1.2.2");
}
}