com.android.tools.idea.gradle.project.AndroidGradleProjectResolver Maven / Gradle / Ivy
/*
* 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.project;
import com.android.SdkConstants;
import com.android.builder.model.AndroidProject;
import com.android.builder.model.Variant;
import com.android.sdklib.repository.FullRevision;
import com.android.tools.idea.gradle.*;
import com.android.tools.idea.gradle.service.notification.errors.UnsupportedModelVersionErrorHandler;
import com.android.tools.idea.gradle.util.AndroidGradleSettings;
import com.android.tools.idea.gradle.util.LocalProperties;
import com.android.tools.idea.sdk.DefaultSdks;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.intellij.execution.configurations.SimpleJavaParameters;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.externalSystem.model.DataNode;
import com.intellij.openapi.externalSystem.model.ExternalSystemException;
import com.intellij.openapi.externalSystem.model.project.ModuleData;
import com.intellij.openapi.externalSystem.model.project.ProjectData;
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil;
import com.intellij.openapi.externalSystem.util.ExternalSystemConstants;
import com.intellij.openapi.externalSystem.util.Order;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.util.KeyValue;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import org.gradle.tooling.model.GradleTask;
import org.gradle.tooling.model.gradle.GradleScript;
import org.gradle.tooling.model.idea.IdeaModule;
import org.jetbrains.android.AndroidPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.model.ModuleExtendedModel;
import org.jetbrains.plugins.gradle.service.project.AbstractProjectResolverExtension;
import org.jetbrains.plugins.gradle.util.GradleConstants;
import java.io.File;
import java.io.IOException;
import java.util.*;
import static com.android.tools.idea.gradle.IdeaGradleProject.newIdeaGradleProject;
import static com.android.tools.idea.gradle.facet.JavaGradleFacet.COMPILE_JAVA_TASK_NAME;
import static com.android.tools.idea.gradle.service.notification.hyperlink.SyncProjectWithExtraCommandLineOptionsHyperlink.EXTRA_GRADLE_COMMAND_LINE_OPTIONS_KEY;
import static com.android.tools.idea.gradle.util.AndroidGradleSettings.createProjectProperty;
import static com.android.tools.idea.gradle.util.GradleBuilds.BUILD_SRC_FOLDER_NAME;
import static com.android.tools.idea.gradle.util.GradleUtil.addLocalMavenRepoInitScriptCommandLineOption;
import static com.android.tools.idea.startup.AndroidStudioSpecificInitializer.isAndroidStudio;
import static com.intellij.openapi.util.io.FileUtil.toSystemDependentName;
import static com.intellij.openapi.util.text.StringUtil.isNotEmpty;
import static com.intellij.util.ExceptionUtil.getRootCause;
import static com.intellij.util.PathUtil.getJarPathForClass;
import static com.intellij.util.containers.ContainerUtil.addIfNotNull;
import static java.util.Collections.sort;
/**
* Imports Android-Gradle projects into IDEA.
*/
@Order(ExternalSystemConstants.UNORDERED)
public class AndroidGradleProjectResolver extends AbstractProjectResolverExtension {
/** Default test artifact selected when importing a project. */
private static final String DEFAULT_TEST_ARTIFACT = AndroidProject.ARTIFACT_ANDROID_TEST;
@NotNull private final ProjectImportErrorHandler myErrorHandler;
@SuppressWarnings("UnusedDeclaration")
public AndroidGradleProjectResolver() {
this(new ProjectImportErrorHandler());
}
@VisibleForTesting
AndroidGradleProjectResolver(@NotNull ProjectImportErrorHandler errorHandler) {
myErrorHandler = errorHandler;
}
@NotNull
@Override
public ModuleData createModule(@NotNull IdeaModule gradleModule, @NotNull ProjectData projectData) {
AndroidProject androidProject = resolverCtx.getExtraProject(gradleModule, AndroidProject.class);
if (androidProject != null && !GradleModelVersionCheck.isSupportedVersion(androidProject)) {
String msg = getUnsupportedModelVersionErrorMsg(GradleModelVersionCheck.getModelVersion(androidProject));
throw new IllegalStateException(msg);
}
return nextResolver.createModule(gradleModule, projectData);
}
@Override
public void populateModuleContentRoots(@NotNull IdeaModule gradleModule, @NotNull DataNode ideModule) {
ImportedModule importedModule = new ImportedModule(gradleModule);
ideModule.createChild(AndroidProjectKeys.IMPORTED_MODULE, importedModule);
GradleScript buildScript = null;
try {
buildScript = gradleModule.getGradleProject().getBuildScript();
} catch (UnsupportedOperationException ignore) {}
if (buildScript == null || !inAndroidGradleProject(gradleModule)) {
nextResolver.populateModuleContentRoots(gradleModule, ideModule);
return;
}
File moduleFilePath = new File(toSystemDependentName(ideModule.getData().getModuleFilePath()));
File moduleRootDirPath = moduleFilePath.getParentFile();
AndroidProject androidProject = resolverCtx.getExtraProject(gradleModule, AndroidProject.class);
if (androidProject != null) {
Variant selectedVariant = getVariantToSelect(androidProject);
IdeaAndroidProject ideaAndroidProject =
new IdeaAndroidProject(GradleConstants.SYSTEM_ID,
gradleModule.getName(),
moduleRootDirPath,
androidProject,
selectedVariant.getName(),
DEFAULT_TEST_ARTIFACT);
ideModule.createChild(AndroidProjectKeys.IDE_ANDROID_PROJECT, ideaAndroidProject);
}
File gradleSettingsFile = new File(moduleRootDirPath, SdkConstants.FN_SETTINGS_GRADLE);
if (gradleSettingsFile.isFile() && androidProject == null) {
// This is just a root folder for a group of Gradle projects. We don't set an IdeaGradleProject so the JPS builder won't try to
// compile it using Gradle. We still need to create the module to display files inside it.
createJavaProject(gradleModule, ideModule);
return;
}
File buildFilePath = buildScript.getSourceFile();
IdeaGradleProject gradleProject = newIdeaGradleProject(gradleModule.getName(), gradleModule.getGradleProject(), buildFilePath);
ideModule.createChild(AndroidProjectKeys.IDE_GRADLE_PROJECT, gradleProject);
if (androidProject == null && isJavaProject(gradleModule)) {
// This is a Java lib module.
createJavaProject(gradleModule, ideModule);
}
}
private static boolean isJavaProject(@NotNull IdeaModule gradleModule) {
for (GradleTask task : gradleModule.getGradleProject().getTasks()) {
if (COMPILE_JAVA_TASK_NAME.equals(task.getName())) {
return true;
}
}
return false;
}
private void createJavaProject(@NotNull IdeaModule gradleModule, @NotNull DataNode ideModule) {
ModuleExtendedModel model = resolverCtx.getExtraProject(gradleModule, ModuleExtendedModel.class);
IdeaJavaProject javaProject = new IdeaJavaProject(gradleModule, model);
ideModule.createChild(AndroidProjectKeys.IDE_JAVA_PROJECT, javaProject);
}
@Override
public void populateModuleCompileOutputSettings(@NotNull IdeaModule gradleModule, @NotNull DataNode ideModule) {
if (!inAndroidGradleProject(gradleModule)) {
nextResolver.populateModuleCompileOutputSettings(gradleModule, ideModule);
}
}
@Override
public void populateModuleDependencies(@NotNull IdeaModule gradleModule,
@NotNull DataNode ideModule,
@NotNull DataNode ideProject) {
if (!inAndroidGradleProject(gradleModule)) {
// For plain Java projects (non-Gradle) we let the framework populate dependencies
nextResolver.populateModuleDependencies(gradleModule, ideModule, ideProject);
}
}
// Indicates it is an "Android" project if at least one module has an AndroidProject.
private boolean inAndroidGradleProject(@NotNull IdeaModule gradleModule) {
if (!resolverCtx.findModulesWithModel(AndroidProject.class).isEmpty()) {
return true;
}
if (BUILD_SRC_FOLDER_NAME.equals(gradleModule.getGradleProject().getName()) && isAndroidStudio()) {
// For now, we will "buildSrc" to be considered part of an Android project. We need changes in IDEA to make this distinction better.
// Currently, when processing "buildSrc" we don't have access to the rest of modules in the project, making it impossible to tell
// if the project has at least one Android module.
return true;
}
return false;
}
@Override
@NotNull
public Set getExtraProjectModelClasses() {
return Sets.newHashSet(AndroidProject.class);
}
@Override
public void preImportCheck() {
if (AndroidPlugin.isGuiTestingMode()) {
// We use this task in GUI tests to simulate errors coming from Gradle project sync.
Application application = ApplicationManager.getApplication();
Runnable task = application.getUserData(AndroidPlugin.EXECUTE_BEFORE_PROJECT_SYNC_TASK_IN_GUI_TEST_KEY);
if (task != null) {
application.putUserData(AndroidPlugin.EXECUTE_BEFORE_PROJECT_SYNC_TASK_IN_GUI_TEST_KEY, null);
task.run();
}
}
if (isAndroidStudio()) {
LocalProperties localProperties = getLocalProperties();
// Ensure that Android Studio and the project (local.properties) point to the same Android SDK home. If they are not the same, we'll
// ask the user to choose one and updates either the IDE's default SDK or project's SDK based on the user's choice.
SdkSync.syncIdeAndProjectAndroidHomes(localProperties);
}
}
@Override
@NotNull
public List> getExtraJvmArgs() {
if (ExternalSystemApiUtil.isInProcessMode(GradleConstants.SYSTEM_ID)) {
List> args = Lists.newArrayList();
if (!isAndroidStudio()) {
LocalProperties localProperties = getLocalProperties();
if (localProperties.getAndroidSdkPath() == null) {
File androidHomePath = DefaultSdks.getDefaultAndroidHome();
// In Android Studio, the Android SDK home path will never be null. It may be null when running in IDEA.
if (androidHomePath != null) {
args.add(KeyValue.create(AndroidGradleSettings.ANDROID_HOME_JVM_ARG, androidHomePath.getPath()));
}
}
}
return args;
}
return Collections.emptyList();
}
@NotNull
@Override
public List getExtraCommandLineArgs() {
List args = Lists.newArrayList();
Project project = findProject();
if (project != null) {
String[] commandLineOptions = project.getUserData(EXTRA_GRADLE_COMMAND_LINE_OPTIONS_KEY);
if (commandLineOptions != null) {
project.putUserData(EXTRA_GRADLE_COMMAND_LINE_OPTIONS_KEY, null);
Collections.addAll(args, commandLineOptions);
}
}
args.add(createProjectProperty(AndroidProject.PROPERTY_BUILD_MODEL_ONLY, true));
args.add(createProjectProperty(AndroidProject.PROPERTY_BUILD_MODEL_ONLY_ADVANCED, true));
args.add(createProjectProperty(AndroidProject.PROPERTY_INVOKED_FROM_IDE, true));
if (AndroidPlugin.isGuiTestingMode()) {
// We store the command line args, the GUI test will later on verify that the correct values were passed to the sync process.
ApplicationManager.getApplication().putUserData(AndroidPlugin.GRADLE_SYNC_COMMAND_LINE_OPTIONS_KEY, ArrayUtil.toStringArray(args));
}
addLocalMavenRepoInitScriptCommandLineOption(args);
return args;
}
@Nullable
private Project findProject() {
String projectDir = resolverCtx.getProjectPath();
if (isNotEmpty(projectDir)) {
File projectDirPath = new File(toSystemDependentName(projectDir));
Project[] projects = ProjectManager.getInstance().getOpenProjects();
for (Project project : projects) {
File currentPath = new File(project.getBasePath());
if (FileUtil.filesEqual(projectDirPath, currentPath)) {
return project;
}
}
}
return null;
}
@NotNull
private LocalProperties getLocalProperties() {
File projectDir = new File(toSystemDependentName(resolverCtx.getProjectPath()));
try {
return new LocalProperties(projectDir);
}
catch (IOException e) {
String msg = String.format("Unable to read local.properties file in project '%1$s'", projectDir.getPath());
throw new ExternalSystemException(msg, e);
}
}
@SuppressWarnings("ThrowableResultOfMethodCallIgnored") // Studio complains that the exceptions created by this method are never thrown.
@NotNull
@Override
public ExternalSystemException getUserFriendlyError(@NotNull Throwable error,
@NotNull String projectPath,
@Nullable String buildFilePath) {
String msg = error.getMessage();
if (msg != null && !msg.contains(UnsupportedModelVersionErrorHandler.UNSUPPORTED_MODEL_VERSION_ERROR_PREFIX)) {
Throwable rootCause = getRootCause(error);
if (rootCause instanceof ClassNotFoundException) {
msg = rootCause.getMessage();
// Project is using an old version of Gradle (and most likely an old version of the plug-in.)
if ("org.gradle.api.artifacts.result.ResolvedComponentResult".equals(msg) ||
"org.gradle.api.artifacts.result.ResolvedModuleVersionResult".equals(msg)) {
return new ExternalSystemException("The project is using an unsupported version of Gradle.");
}
}
}
ExternalSystemException userFriendlyError = myErrorHandler.getUserFriendlyError(error, projectPath, buildFilePath);
return userFriendlyError != null ? userFriendlyError : nextResolver.getUserFriendlyError(error, projectPath, buildFilePath);
}
@NotNull
private static String getUnsupportedModelVersionErrorMsg(@Nullable FullRevision modelVersion) {
StringBuilder builder = new StringBuilder();
builder.append(UnsupportedModelVersionErrorHandler.UNSUPPORTED_MODEL_VERSION_ERROR_PREFIX);
String recommendedVersion = String.format("The recommended version is %1$s.", SdkConstants.GRADLE_PLUGIN_RECOMMENDED_VERSION);
if (modelVersion != null) {
builder.append(String.format(" (%1$s).", modelVersion.toString()))
.append(" ")
.append(recommendedVersion);
if (modelVersion.getMajor() == 0 && modelVersion.getMinor() <= 8) {
builder.append("\n\nStarting with version 0.9.0 incompatible changes were introduced in the build language.\n")
.append(UnsupportedModelVersionErrorHandler.READ_MIGRATION_GUIDE_MSG)
.append(" to learn how to update your project.");
}
}
else {
builder.append(". ")
.append(recommendedVersion);
}
return builder.toString();
}
@NotNull
private static Variant getVariantToSelect(@NotNull AndroidProject androidProject) {
Collection variants = androidProject.getVariants();
if (variants.size() == 1) {
Variant variant = ContainerUtil.getFirstItem(variants);
assert variant != null;
return variant;
}
// look for "debug" variant. This is just a little convenience for the user that has not created any additional flavors/build types.
// trying to match something else may add more complexity for little gain.
for (Variant variant : variants) {
if ("debug".equals(variant.getName())) {
return variant;
}
}
List sortedVariants = Lists.newArrayList(variants);
sort(sortedVariants, new Comparator() {
@Override
public int compare(Variant o1, Variant o2) {
return o1.getName().compareTo(o2.getName());
}
});
return sortedVariants.get(0);
}
@Override
public void enhanceRemoteProcessing(@NotNull SimpleJavaParameters parameters) {
List classPath = Lists.newArrayList();
// Android module jars
addIfNotNull(getJarPathForClass(getClass()), classPath);
// Android sdklib jar
addIfNotNull(getJarPathForClass(FullRevision.class), classPath);
// Android common jar
addIfNotNull(getJarPathForClass(AndroidGradleSettings.class), classPath);
// Android gradle model jar
addIfNotNull(getJarPathForClass(AndroidProject.class), classPath);
parameters.getClassPath().addAll(classPath);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy