/*
* 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.variant.view;
import com.android.builder.model.AndroidLibrary;
import com.android.builder.model.Variant;
import com.android.tools.idea.gradle.IdeaAndroidProject;
import com.android.tools.idea.gradle.customizer.ModuleCustomizer;
import com.android.tools.idea.gradle.customizer.android.CompilerOutputModuleCustomizer;
import com.android.tools.idea.gradle.customizer.android.ContentRootModuleCustomizer;
import com.android.tools.idea.gradle.customizer.android.DependenciesModuleCustomizer;
import com.android.tools.idea.gradle.util.GradleUtil;
import com.android.tools.idea.gradle.util.ProjectBuilder;
import com.android.tools.idea.gradle.variant.conflict.ConflictSet;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.externalSystem.util.DisposeAwareProjectChange;
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import static com.android.tools.idea.gradle.variant.conflict.ConflictSet.findConflicts;
/**
* Updates the contents/settings of a module when a build variant changes.
*/
class BuildVariantUpdater {
private static final Logger LOG = Logger.getInstance(BuildVariantUpdater.class);
private final List<ModuleCustomizer<IdeaAndroidProject>> myAndroidModuleCustomizers =
ImmutableList.of(new ContentRootModuleCustomizer(), new DependenciesModuleCustomizer(), new CompilerOutputModuleCustomizer());
/**
* Updates a module's structure when the user selects a build variant from the tool window.
*
* @param project the module's project.
* @param moduleName the module's name.
* @param buildVariantName the name of the selected build variant.
* @return the facets affected by the build variant selection, if the module update was successful; an empty list otherwise.
*/
@NotNull
List<AndroidFacet> updateModule(@NotNull final Project project, @NotNull final String moduleName, @NotNull final String buildVariantName) {
final List<AndroidFacet> facets = Lists.newArrayList();
ExternalSystemApiUtil.executeProjectChangeAction(true /*synchronous*/, new DisposeAwareProjectChange(project) {
@Override
public void execute() {
Module updatedModule = doUpdate(project, moduleName, buildVariantName, facets);
if (updatedModule != null) {
ConflictSet conflicts = findConflicts(project);
conflicts.showSelectionConflicts();
}
if (!facets.isEmpty()) {
// We build only the selected variant. If user changes variant, we need to re-generate sources since the generated sources may not
// be there.
if (!ApplicationManager.getApplication().isUnitTestMode()) {
ProjectBuilder.getInstance(project).generateSourcesOnly();
}
}
}
});
return facets;
}
@Nullable
private Module doUpdate(@NotNull Project project,
@NotNull String moduleName,
@NotNull String variant,
@NotNull List<AndroidFacet> affectedFacets) {
Module moduleToUpdate = findModule(project, moduleName);
if (moduleToUpdate == null) {
logAndShowUpdateFailure(variant, String.format("Cannot find module '%1$s'.", moduleName));
return null;
}
AndroidFacet facet = getAndroidFacet(moduleToUpdate, variant);
if (facet == null) {
return null;
}
IdeaAndroidProject androidProject = getAndroidProject(facet, variant);
if (androidProject == null) {
return null;
}
if (!updateSelectedVariant(facet, androidProject, variant, affectedFacets)) {
return null;
}
affectedFacets.add(facet);
return moduleToUpdate;
}
@Nullable
private static Module findModule(@NotNull Project project, @NotNull String moduleName) {
ModuleManager moduleManager = ModuleManager.getInstance(project);
return moduleManager.findModuleByName(moduleName);
}
private boolean updateSelectedVariant(@NotNull AndroidFacet androidFacet,
@NotNull IdeaAndroidProject androidProject,
@NotNull String variantToSelect,
@NotNull List<AndroidFacet> affectedFacets) {
Variant selectedVariant = androidProject.getSelectedVariant();
if (variantToSelect.equals(selectedVariant.getName())) {
return false;
}
androidProject.setSelectedVariantName(variantToSelect);
androidFacet.syncSelectedVariant();
Module module = androidFacet.getModule();
Project project = module.getProject();
for (ModuleCustomizer<IdeaAndroidProject> customizer : myAndroidModuleCustomizers) {
customizer.customizeModule(module, project, androidProject);
}
selectedVariant = androidProject.getSelectedVariant();
for (AndroidLibrary library : selectedVariant.getMainArtifact().getDependencies().getLibraries()) {
String gradlePath = library.getProject();
if (StringUtil.isEmpty(gradlePath)) {
continue;
}
String projectVariant = library.getProjectVariant();
if (StringUtil.isNotEmpty(projectVariant)) {
ensureVariantIsSelected(project, gradlePath, projectVariant, affectedFacets);
}
}
return true;
}
private void ensureVariantIsSelected(@NotNull Project project,
@NotNull String moduleGradlePath,
@NotNull String variant,
@NotNull List<AndroidFacet> affectedFacets) {
Module module = GradleUtil.findModuleByGradlePath(project, moduleGradlePath);
if (module == null) {
logAndShowUpdateFailure(variant, String.format("Cannot find module with Gradle path '%1$s'.", moduleGradlePath));
return;
}
AndroidFacet facet = getAndroidFacet(module, variant);
if (facet == null) {
return;
}
IdeaAndroidProject androidProject = getAndroidProject(facet, variant);
if (androidProject == null) {
return;
}
if (!updateSelectedVariant(facet, androidProject, variant, affectedFacets)) {
return;
}
affectedFacets.add(facet);
}
@Nullable
private static AndroidFacet getAndroidFacet(@NotNull Module module, @NotNull String variantToSelect) {
AndroidFacet facet = AndroidFacet.getInstance(module);
if (facet == null) {
logAndShowUpdateFailure(variantToSelect, String.format("Cannot find 'Android' facet in module '%1$s'.", module.getName()));
}
return facet;
}
@Nullable
private static IdeaAndroidProject getAndroidProject(@NotNull AndroidFacet facet, @NotNull String variantToSelect) {
IdeaAndroidProject androidProject = facet.getIdeaAndroidProject();
if (androidProject == null) {
logAndShowUpdateFailure(variantToSelect, String.format("Cannot find AndroidProject for module '%1$s'.", facet.getModule().getName()));
}
return androidProject;
}
private static void logAndShowUpdateFailure(@NotNull String buildVariantName, @NotNull String reason) {
String prefix = String.format("Unable to select build variant '%1$s':\n", buildVariantName);
String msg = prefix + reason;
LOG.error(msg);
msg += ".\n\nConsult IDE log for more details (Help | Show Log)";
Messages.showErrorDialog(msg, "Error");
}
}