blob: 1130a8fb6268c86ca01cd0aa08fd621d8d1f7bb8 [file] [log] [blame]
/*
* Copyright 2000-2011 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.android.compiler;
import com.intellij.compiler.CompilerConfiguration;
import com.intellij.compiler.CompilerConfigurationImpl;
import com.intellij.compiler.options.CompileStepBeforeRun;
import com.intellij.compiler.server.BuildManager;
import com.intellij.facet.ProjectFacetManager;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.compiler.*;
import com.intellij.openapi.compiler.options.ExcludeEntryDescription;
import com.intellij.openapi.compiler.options.ExcludesConfiguration;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.DependencyScope;
import com.intellij.openapi.roots.ModuleOrderEntry;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.OrderEntry;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.packaging.artifacts.Artifact;
import com.intellij.packaging.artifacts.ArtifactProperties;
import com.intellij.packaging.impl.compiler.ArtifactCompileScope;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.hash.HashSet;
import org.jetbrains.android.compiler.artifact.AndroidApplicationArtifactProperties;
import org.jetbrains.android.compiler.artifact.AndroidArtifactPropertiesProvider;
import org.jetbrains.android.compiler.artifact.AndroidArtifactSigningMode;
import org.jetbrains.android.compiler.artifact.AndroidArtifactUtil;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.facet.AndroidRootUtil;
import org.jetbrains.android.maven.AndroidMavenUtil;
import org.jetbrains.android.sdk.AndroidPlatform;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.*;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidPrecompileTask implements CompileTask {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.compiler.AndroidPrecompileTask");
@Override
public boolean execute(CompileContext context) {
final Project project = context.getProject();
if (!ProjectFacetManager.getInstance(project).hasFacets(AndroidFacet.ID)) {
return true;
}
BuildManager.forceModelLoading(context);
// in out-of-process mode gen roots will be excluded by AndroidExcludedJavaSourceRootProvider
// we do it here for internal mode and also to make there roots 'visibly excluded' in IDE settings
createGenModulesAndSourceRoots(project);
return true;
}
private static boolean prepareForCompilation(CompileContext context) {
final Project project = context.getProject();
if (!checkArtifacts(context)) {
return false;
}
checkAndroidDependencies(context);
ExcludesConfiguration configuration =
CompilerConfiguration.getInstance(project).getExcludedEntriesConfiguration();
Set<ExcludeEntryDescription> addedEntries = new HashSet<ExcludeEntryDescription>();
for (Module module : ModuleManager.getInstance(project).getModules()) {
final AndroidFacet facet = AndroidFacet.getInstance(module);
if (facet == null) {
continue;
}
if (context.isRebuild()) {
clearResCache(facet, context);
}
final AndroidPlatform platform = facet.getConfiguration().getAndroidPlatform();
final int platformToolsRevision = platform != null ? platform.getSdkData().getPlatformToolsRevision() : -1;
LOG.debug("Platform-tools revision for module " + module.getName() + " is " + platformToolsRevision);
if (facet.isLibraryProject()) {
if (platformToolsRevision >= 0 && platformToolsRevision <= 7) {
LOG.debug("Excluded sources of module " + module.getName());
excludeAllSourceRoots(module, configuration, addedEntries);
}
else {
// todo: support this by project converter to use on compile-server
unexcludeAllSourceRoots(facet, configuration);
}
}
}
if (addedEntries.size() > 0) {
LOG.debug("Files excluded by Android: " + addedEntries.size());
CompilerManager.getInstance(project).addCompilationStatusListener(new MyCompilationStatusListener(project, addedEntries), project);
}
return true;
}
private static void createGenModulesAndSourceRoots(final Project project) {
final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
final List<AndroidFacet> facets = new ArrayList<AndroidFacet>();
for (Module module : ModuleManager.getInstance(project).getModules()) {
final AndroidFacet facet = AndroidFacet.getInstance(module);
if (facet != null) {
facets.add(facet);
}
}
if (facets.size() > 0) {
ApplicationManager.getApplication().invokeAndWait(new Runnable() {
@Override
public void run() {
AndroidCompileUtil.createGenModulesAndSourceRoots(project, facets);
}
}, indicator != null ? indicator.getModalityState() : ModalityState.NON_MODAL);
}
}
private static boolean checkArtifacts(@NotNull CompileContext context) {
final Project project = context.getProject();
final CompileScope scope = context.getCompileScope();
final Set<Artifact> artifacts = ApplicationManager.getApplication().runReadAction(new Computable<Set<Artifact>>() {
@Override
public Set<Artifact> compute() {
return ArtifactCompileScope.getArtifactsToBuild(project, scope, false);
}
});
if (artifacts == null) {
return true;
}
final Set<Artifact> debugArtifacts = new HashSet<Artifact>();
final Set<Artifact> releaseArtifacts = new HashSet<Artifact>();
final Map<AndroidFacet, List<Artifact>> facet2artifacts = new HashMap<AndroidFacet, List<Artifact>>();
for (final Artifact artifact : artifacts) {
final ArtifactProperties<?> properties = artifact.getProperties(AndroidArtifactPropertiesProvider.getInstance());
if (properties instanceof AndroidApplicationArtifactProperties) {
final AndroidArtifactSigningMode mode = ((AndroidApplicationArtifactProperties)properties).getSigningMode();
if (mode == AndroidArtifactSigningMode.DEBUG || mode == AndroidArtifactSigningMode.DEBUG_WITH_CUSTOM_CERTIFICATE) {
debugArtifacts.add(artifact);
}
else {
releaseArtifacts.add(artifact);
}
}
final AndroidFacet facet = ApplicationManager.getApplication().runReadAction(new Computable<AndroidFacet>() {
@Nullable
@Override
public AndroidFacet compute() {
return AndroidArtifactUtil.getPackagedFacet(project, artifact);
}
});
if (facet != null) {
List<Artifact> list = facet2artifacts.get(facet);
if (list == null) {
list = new ArrayList<Artifact>();
facet2artifacts.put(facet, list);
}
list.add(artifact);
}
}
boolean success = true;
if (debugArtifacts.size() > 0 && releaseArtifacts.size() > 0) {
final String message = "Cannot build debug and release Android artifacts in the same session\n" +
"Debug artifacts: " + toString(debugArtifacts) + "\n" +
"Release artifacts: " + toString(releaseArtifacts);
context.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1);
success = false;
}
if (releaseArtifacts.size() > 0 &&
CompileStepBeforeRun.getRunConfiguration(context) != null) {
final String message = "Cannot build release Android artifacts in the 'make before run' session\n" +
"Release artifacts: " + toString(releaseArtifacts);
context.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1);
success = false;
}
for (Map.Entry<AndroidFacet, List<Artifact>> entry : facet2artifacts.entrySet()) {
final List<Artifact> list = entry.getValue();
final String moduleName = entry.getKey().getModule().getName();
if (list.size() > 1) {
final Artifact firstArtifact = list.get(0);
final Object[] firstArtifactProGuardOptions = getProGuardOptions(firstArtifact);
for (int i = 1; i < list.size(); i++) {
final Artifact artifact = list.get(i);
if (!Arrays.equals(getProGuardOptions(artifact), firstArtifactProGuardOptions)) {
context.addMessage(CompilerMessageCategory.ERROR, "Artifacts related to the same module '" +
moduleName +
"' have different ProGuard options: " +
firstArtifact.getName() +
", " +
artifact.getName(), null, -1, -1);
success = false;
break;
}
}
}
}
return success;
}
@NotNull
private static Object[] getProGuardOptions(@NotNull Artifact artifact) {
final ArtifactProperties<?> properties = artifact.getProperties(AndroidArtifactPropertiesProvider.getInstance());
if (properties instanceof AndroidApplicationArtifactProperties) {
final AndroidApplicationArtifactProperties p = (AndroidApplicationArtifactProperties)properties;
return new Object[] {p.isRunProGuard(), p.getProGuardCfgFiles()};
}
return ArrayUtil.EMPTY_OBJECT_ARRAY;
}
private static String toString(Collection<Artifact> artifacts) {
final StringBuilder result = new StringBuilder();
for (Artifact artifact : artifacts) {
if (result.length() > 0) {
result.append(", ");
}
result.append(artifact.getName());
}
return result.toString();
}
private static void checkAndroidDependencies(@NotNull CompileContext context) {
for (Module module : context.getCompileScope().getAffectedModules()) {
final AndroidFacet facet = AndroidFacet.getInstance(module);
if (facet == null) {
continue;
}
final Pair<String, VirtualFile> manifestMergerProp =
AndroidRootUtil.getProjectPropertyValue(module, AndroidUtils.ANDROID_MANIFEST_MERGER_PROPERTY);
if (manifestMergerProp != null && Boolean.parseBoolean(manifestMergerProp.getFirst())) {
context.addMessage(CompilerMessageCategory.WARNING,
"[" + module.getName() + "] " + AndroidBundle.message("android.manifest.merger.not.supported.error"),
manifestMergerProp.getSecond().getUrl(), -1, -1);
}
if (!facet.isLibraryProject()) {
for (OrderEntry entry : ModuleRootManager.getInstance(module).getOrderEntries()) {
if (entry instanceof ModuleOrderEntry) {
final ModuleOrderEntry moduleOrderEntry = (ModuleOrderEntry)entry;
if (moduleOrderEntry.getScope() == DependencyScope.COMPILE) {
final Module depModule = moduleOrderEntry.getModule();
if (depModule != null) {
final AndroidFacet depFacet = AndroidFacet.getInstance(depModule);
if (depFacet != null && !depFacet.isLibraryProject()) {
String message = "Suspicious module dependency " +
module.getName() +
" -> " +
depModule.getName() +
": Android application module depends on other application module. Possibly, you should ";
if (AndroidMavenUtil.isMavenizedModule(depModule)) {
message += "change packaging type of module " + depModule.getName() + " to 'apklib' in pom.xml file or ";
}
message += "change dependency scope to 'Provided'.";
context.addMessage(CompilerMessageCategory.WARNING, message, null, -1, -1);
}
}
}
}
}
}
}
}
private static void clearResCache(@NotNull AndroidFacet facet, @NotNull CompileContext context) {
final Module module = facet.getModule();
final String dirPath = AndroidCompileUtil.findResourcesCacheDirectory(module, false, null);
if (dirPath != null) {
final File dir = new File(dirPath);
if (dir.exists()) {
FileUtil.delete(dir);
}
}
}
private static void unexcludeAllSourceRoots(AndroidFacet facet,
ExcludesConfiguration configuration) {
final VirtualFile[] sourceRoots = ModuleRootManager.getInstance(facet.getModule()).getSourceRoots();
final Set<VirtualFile> sourceRootSet = new HashSet<VirtualFile>();
sourceRootSet.addAll(Arrays.asList(sourceRoots));
final String aidlGenSourceRootPath = AndroidRootUtil.getAidlGenSourceRootPath(facet);
if (aidlGenSourceRootPath != null) {
final VirtualFile aidlGenSourceRoot = LocalFileSystem.getInstance().findFileByPath(aidlGenSourceRootPath);
if (aidlGenSourceRoot != null) {
sourceRootSet.remove(aidlGenSourceRoot);
}
}
final String aptGenSourceRootPath = AndroidRootUtil.getAptGenSourceRootPath(facet);
if (aptGenSourceRootPath != null) {
final VirtualFile aptGenSourceRoot = LocalFileSystem.getInstance().findFileByPath(aptGenSourceRootPath);
if (aptGenSourceRoot != null) {
sourceRootSet.remove(aptGenSourceRoot);
}
}
final VirtualFile rsGenRoot = AndroidRootUtil.getRenderscriptGenDir(facet);
if (rsGenRoot != null) {
sourceRootSet.remove(rsGenRoot);
}
final VirtualFile buildconfigGenDir = AndroidRootUtil.getBuildconfigGenDir(facet);
if (buildconfigGenDir != null) {
sourceRootSet.remove(buildconfigGenDir);
}
final ExcludeEntryDescription[] descriptions = configuration.getExcludeEntryDescriptions();
configuration.removeAllExcludeEntryDescriptions();
for (ExcludeEntryDescription description : descriptions) {
final VirtualFile file = description.getVirtualFile();
if (file == null || !sourceRootSet.contains(file)) {
configuration.addExcludeEntryDescription(description);
}
}
}
private static void excludeAllSourceRoots(Module module,
ExcludesConfiguration configuration,
Collection<ExcludeEntryDescription> addedEntries) {
Project project = module.getProject();
VirtualFile[] sourceRoots = ModuleRootManager.getInstance(module).getSourceRoots();
for (VirtualFile sourceRoot : sourceRoots) {
ExcludeEntryDescription description = new ExcludeEntryDescription(sourceRoot, true, false, project);
if (!configuration.containsExcludeEntryDescription(description)) {
configuration.addExcludeEntryDescription(description);
addedEntries.add(description);
}
}
}
private static class MyCompilationStatusListener extends CompilationStatusAdapter {
private final Project myProject;
private final Set<ExcludeEntryDescription> myEntriesToRemove;
public MyCompilationStatusListener(Project project, Set<ExcludeEntryDescription> entriesToRemove) {
myProject = project;
myEntriesToRemove = entriesToRemove;
}
@Override
public void compilationFinished(boolean aborted, int errors, int warnings, CompileContext compileContext) {
CompilerManager.getInstance(myProject).removeCompilationStatusListener(this);
ExcludesConfiguration configuration =
((CompilerConfigurationImpl)CompilerConfiguration.getInstance(myProject)).getExcludedEntriesConfiguration();
ExcludeEntryDescription[] descriptions = configuration.getExcludeEntryDescriptions();
configuration.removeAllExcludeEntryDescriptions();
for (ExcludeEntryDescription description : descriptions) {
if (!myEntriesToRemove.contains(description)) {
configuration.addExcludeEntryDescription(description);
}
}
}
}
}