/* * Copyright 2000-2010 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.android.SdkConstants; import com.android.sdklib.IAndroidTarget; import com.intellij.facet.FacetManager; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.compiler.*; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.roots.CompilerModuleExtension; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.containers.HashSet; import org.jetbrains.android.compiler.tools.AndroidDxWrapper; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.android.facet.AndroidFacetConfiguration; import org.jetbrains.android.facet.AndroidRootUtil; import org.jetbrains.android.maven.AndroidMavenProvider; import org.jetbrains.android.maven.AndroidMavenUtil; import org.jetbrains.android.sdk.AndroidPlatform; import org.jetbrains.android.util.AndroidBundle; import org.jetbrains.android.util.AndroidCommonUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.util.*; /** * Android Dex compiler. * * @author Alexey Efimov */ public class AndroidDexCompiler implements ClassPostProcessingCompiler { @Override @NotNull public ProcessingItem[] getProcessingItems(CompileContext context) { return ApplicationManager.getApplication().runReadAction(new PrepareAction(context)); } @Override public ProcessingItem[] process(CompileContext context, ProcessingItem[] items) { if (!AndroidCompileUtil.isFullBuild(context)) { return ProcessingItem.EMPTY_ARRAY; } if (items != null && items.length > 0) { context.getProgressIndicator().setText("Generating " + AndroidCommonUtils.CLASSES_FILE_NAME + "..."); return new ProcessAction(context, items).compute(); } return ProcessingItem.EMPTY_ARRAY; } @Override @NotNull public String getDescription() { return FileUtil.getNameWithoutExtension(SdkConstants.FN_DX); } @Override public boolean validateConfiguration(CompileScope scope) { return true; } @Override public ValidityState createValidityState(DataInput in) throws IOException { return new MyValidityState(in); } public static VirtualFile getOutputDirectoryForDex(@NotNull Module module) { if (AndroidMavenUtil.isMavenizedModule(module)) { AndroidMavenProvider mavenProvider = AndroidMavenUtil.getMavenProvider(); if (mavenProvider != null) { String buildDirPath = mavenProvider.getBuildDirectory(module); if (buildDirPath != null) { VirtualFile buildDir = LocalFileSystem.getInstance().findFileByPath(FileUtil.toSystemIndependentName(buildDirPath)); if (buildDir != null) { return buildDir; } } } } return CompilerModuleExtension.getInstance(module).getCompilerOutputPath(); } static void addModuleOutputDir(Collection<VirtualFile> files, VirtualFile dir) { // only include files inside packages for (VirtualFile child : dir.getChildren()) { if (child.isDirectory()) { files.add(child); } } } private static final class PrepareAction implements Computable<ProcessingItem[]> { private final CompileContext myContext; public PrepareAction(CompileContext context) { myContext = context; } @Override public ProcessingItem[] compute() { final AndroidDexCompilerConfiguration dexConfig = AndroidDexCompilerConfiguration.getInstance(myContext.getProject()); Module[] modules = ModuleManager.getInstance(myContext.getProject()).getModules(); List<ProcessingItem> items = new ArrayList<ProcessingItem>(); for (Module module : modules) { AndroidFacet facet = FacetManager.getInstance(module).getFacetByType(AndroidFacet.ID); if (facet != null && !facet.isLibraryProject()) { final VirtualFile dexOutputDir = getOutputDirectoryForDex(module); Collection<VirtualFile> files; final boolean shouldRunProguard = AndroidCompileUtil.getProguardConfigFilePathIfShouldRun(facet, myContext) != null; if (shouldRunProguard) { final VirtualFile obfuscatedSourcesJar = dexOutputDir.findChild(AndroidCommonUtils.PROGUARD_OUTPUT_JAR_NAME); if (obfuscatedSourcesJar == null) { myContext.addMessage(CompilerMessageCategory.INFORMATION, "Dex won't be launched for module " + module.getName() + " because file " + AndroidCommonUtils.PROGUARD_OUTPUT_JAR_NAME + " doesn't exist", null, -1, -1); continue; } files = Collections.singleton(obfuscatedSourcesJar); } else { CompilerModuleExtension extension = CompilerModuleExtension.getInstance(module); VirtualFile outputDir = extension.getCompilerOutputPath(); if (outputDir == null) { myContext.addMessage(CompilerMessageCategory.INFORMATION, "Dex won't be launched for module " + module.getName() + " because it doesn't contain compiled files", null, -1, -1); continue; } files = new HashSet<VirtualFile>(); addModuleOutputDir(files, outputDir); files.addAll(AndroidRootUtil.getExternalLibraries(module)); for (VirtualFile file : AndroidRootUtil.getDependentModules(module, outputDir)) { if (file.isDirectory()) { addModuleOutputDir(files, file); } else { files.add(file); } } if (facet.getProperties().PACK_TEST_CODE) { VirtualFile outputDirForTests = extension.getCompilerOutputPathForTests(); if (outputDirForTests != null) { addModuleOutputDir(files, outputDirForTests); } } } final AndroidFacetConfiguration configuration = facet.getConfiguration(); final AndroidPlatform platform = configuration.getAndroidPlatform(); if (platform == null) { myContext.addMessage(CompilerMessageCategory.ERROR, AndroidBundle.message("android.compilation.error.specify.platform", module.getName()), null, -1, -1); continue; } items.add(new DexItem(module, dexOutputDir, platform.getTarget(), files, dexConfig.VM_OPTIONS, dexConfig.MAX_HEAP_SIZE, dexConfig.OPTIMIZE)); } } return items.toArray(new ProcessingItem[items.size()]); } } private final static class ProcessAction implements Computable<ProcessingItem[]> { private final CompileContext myContext; private final ProcessingItem[] myItems; public ProcessAction(CompileContext context, ProcessingItem[] items) { myContext = context; myItems = items; } @Override public ProcessingItem[] compute() { List<ProcessingItem> results = new ArrayList<ProcessingItem>(myItems.length); for (ProcessingItem item : myItems) { if (item instanceof DexItem) { DexItem dexItem = (DexItem)item; if (!AndroidCompileUtil.isModuleAffected(myContext, dexItem.myModule)) { continue; } String outputDirPath = FileUtil.toSystemDependentName(dexItem.myClassDir.getPath()); String[] files = new String[dexItem.myFiles.size()]; int i = 0; for (VirtualFile file : dexItem.myFiles) { files[i++] = FileUtil.toSystemDependentName(file.getPath()); } Map<CompilerMessageCategory, List<String>> messages = AndroidCompileUtil.toCompilerMessageCategoryKeys(AndroidDxWrapper.execute( dexItem.myModule, dexItem.myAndroidTarget, outputDirPath, files, dexItem.myAdditionalVmParams, dexItem.myMaxHeapSize, dexItem.myOptimize)); addMessages(messages, dexItem.myModule); if (messages.get(CompilerMessageCategory.ERROR).isEmpty()) { results.add(dexItem); } } } return results.toArray(new ProcessingItem[results.size()]); } private void addMessages(Map<CompilerMessageCategory, List<String>> messages, Module module) { for (CompilerMessageCategory category : messages.keySet()) { List<String> messageList = messages.get(category); for (String message : messageList) { myContext.addMessage(category, '[' + module.getName() + "] " + message, null, -1, -1); } } } } private final static class DexItem implements ProcessingItem { final Module myModule; final VirtualFile myClassDir; final IAndroidTarget myAndroidTarget; final Collection<VirtualFile> myFiles; final String myAdditionalVmParams; final int myMaxHeapSize; final boolean myOptimize; public DexItem(@NotNull Module module, @NotNull VirtualFile classDir, @NotNull IAndroidTarget target, Collection<VirtualFile> files, @NotNull String additionalVmParams, int maxHeapSize, boolean optimize) { myModule = module; myClassDir = classDir; myAndroidTarget = target; myFiles = files; myAdditionalVmParams = additionalVmParams; myMaxHeapSize = maxHeapSize; myOptimize = optimize; } @Override @NotNull public VirtualFile getFile() { return myClassDir; } @Override @Nullable public ValidityState getValidityState() { return new MyValidityState(myFiles, myAdditionalVmParams, myMaxHeapSize, myOptimize); } } private static class MyValidityState extends ClassesAndJarsValidityState { private final String myAdditionalVmParams; private final int myMaxHeapSize; private final boolean myOptimize; public MyValidityState(@NotNull Collection<VirtualFile> files, @NotNull String additionalVmParams, int maxHeapSize, boolean optimize) { super(files); myAdditionalVmParams = additionalVmParams; myMaxHeapSize = maxHeapSize; myOptimize = optimize; } public MyValidityState(@NotNull DataInput in) throws IOException { super(in); myAdditionalVmParams = in.readUTF(); myMaxHeapSize = in.readInt(); myOptimize = in.readBoolean(); } @Override public void save(DataOutput out) throws IOException { super.save(out); out.writeUTF(myAdditionalVmParams); out.writeInt(myMaxHeapSize); out.writeBoolean(myOptimize); } @Override public boolean equalsTo(ValidityState otherState) { if (!super.equalsTo(otherState)) { return false; } if (!(otherState instanceof MyValidityState)) { return false; } final MyValidityState state = (MyValidityState)otherState; return state.myAdditionalVmParams.equals(myAdditionalVmParams) && state.myMaxHeapSize == myMaxHeapSize && state.myOptimize == myOptimize; } } }