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