| /* |
| * 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); |
| } |
| } |
| } |
| } |
| } |