| /* |
| * Copyright 2000-2012 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.tools; |
| |
| import com.android.SdkConstants; |
| import com.android.sdklib.BuildToolInfo; |
| import com.android.sdklib.IAndroidTarget; |
| import com.android.sdklib.internal.build.SymbolLoader; |
| import com.android.sdklib.internal.build.SymbolWriter; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.util.io.FileUtilRt; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.containers.HashMap; |
| import com.intellij.util.containers.HashSet; |
| import com.intellij.util.containers.MultiMap; |
| import com.intellij.util.execution.ParametersListUtil; |
| import org.jetbrains.android.util.AndroidCommonUtils; |
| import org.jetbrains.android.util.AndroidCompilerMessageKind; |
| import org.jetbrains.android.util.AndroidExecutionUtil; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.io.File; |
| import java.io.FileFilter; |
| import java.io.IOException; |
| import java.util.*; |
| |
| /** |
| * AndroidApt decorator. |
| * |
| * @author Alexey Efimov |
| */ |
| public final class AndroidApt { |
| private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.compiler.tools.AndroidApt"); |
| |
| @NonNls private static final String COMMAND_CRUNCH = "crunch"; |
| @NonNls private static final String COMMAND_PACKAGE = "package"; |
| |
| private static final FileFilter PNG_FILES_FILTER = new FileFilter() { |
| @Override |
| public boolean accept(File file) { |
| return file.isDirectory() || FileUtilRt.extensionEquals(file.getName(), AndroidCommonUtils.PNG_EXTENSION); |
| } |
| }; |
| |
| private AndroidApt() { |
| } |
| |
| public static Map<AndroidCompilerMessageKind, List<String>> compile(@NotNull IAndroidTarget target, |
| int platformToolsRevision, |
| @NotNull String manifestFileOsPath, |
| @NotNull String aPackage, |
| @NotNull String outDirOsPath, |
| @NotNull String[] resourceDirsOsPaths, |
| @NotNull String[] extraPackages, |
| boolean nonConstantFields, |
| @Nullable String proguardCfgOutputFileOsPath) throws IOException { |
| final List<Pair<String, String>> libRTxtFilesAndPackages = new ArrayList<Pair<String, String>>(); |
| |
| for (String extraPackage : extraPackages) { |
| libRTxtFilesAndPackages.add(Pair.create((String)null, extraPackage)); |
| } |
| return compile(target, platformToolsRevision, manifestFileOsPath, aPackage, outDirOsPath, resourceDirsOsPaths, |
| libRTxtFilesAndPackages, nonConstantFields, proguardCfgOutputFileOsPath, null, false); |
| } |
| |
| public static Map<AndroidCompilerMessageKind, List<String>> compile(@NotNull IAndroidTarget target, |
| int platformToolsRevision, |
| @NotNull String manifestFileOsPath, |
| @NotNull String aPackage, |
| @NotNull String outDirOsPath, |
| @NotNull String[] resourceDirsOsPaths, |
| @NotNull List<Pair<String, String>> libRTxtFilesAndPackages, |
| boolean nonConstantFields, |
| @Nullable String proguardCfgOutputFileOsPath, |
| @Nullable String rTxtOutDirOsPath, |
| boolean optimizeRFile) throws IOException { |
| final Map<AndroidCompilerMessageKind, List<String>> messages = new HashMap<AndroidCompilerMessageKind, List<String>>(); |
| messages.put(AndroidCompilerMessageKind.ERROR, new ArrayList<String>()); |
| messages.put(AndroidCompilerMessageKind.INFORMATION, new ArrayList<String>()); |
| |
| final File outOsDir = new File(outDirOsPath); |
| if (!outOsDir.exists()) { |
| if (!outOsDir.mkdirs()) { |
| messages.get(AndroidCompilerMessageKind.ERROR).add("Unable to create directory " + outDirOsPath); |
| } |
| } |
| |
| final String packageFolderOsPath = FileUtil.toSystemDependentName(outDirOsPath + '/' + aPackage.replace('.', '/')); |
| |
| /* We actually need to delete the manifest.java as it may become empty and |
| in this case aapt doesn't generate an empty one, but instead doesn't |
| touch it */ |
| final File manifestJavaFile = new File(packageFolderOsPath + File.separatorChar + AndroidCommonUtils.MANIFEST_JAVA_FILE_NAME); |
| if (manifestJavaFile.exists()) { |
| if (!FileUtil.delete(manifestJavaFile)) { |
| messages.get(AndroidCompilerMessageKind.ERROR).add("Unable to delete " + manifestJavaFile.getPath()); |
| } |
| } |
| |
| final File rJavaFile = new File(packageFolderOsPath + File.separatorChar + AndroidCommonUtils.R_JAVA_FILENAME); |
| if (rJavaFile.exists()) { |
| if (!FileUtil.delete(rJavaFile)) { |
| messages.get(AndroidCompilerMessageKind.ERROR).add("Unable to delete " + rJavaFile.getPath()); |
| } |
| } |
| |
| final File[] extraRJavaFiles = new File[libRTxtFilesAndPackages.size()]; |
| |
| for (int i = 0, n = libRTxtFilesAndPackages.size(); i < n; i++) { |
| final String libPackage = libRTxtFilesAndPackages.get(i).getSecond(); |
| final String libPackageFolderOsPath = FileUtil.toSystemDependentName(outDirOsPath + '/' + libPackage.replace('.', '/')); |
| extraRJavaFiles[i] = new File(libPackageFolderOsPath + File.separatorChar + AndroidCommonUtils.R_JAVA_FILENAME); |
| } |
| |
| for (File extraRJavaFile : extraRJavaFiles) { |
| if (extraRJavaFile.exists()) { |
| if (!FileUtil.delete(extraRJavaFile)) { |
| messages.get(AndroidCompilerMessageKind.ERROR).add("Unable to delete " + extraRJavaFile.getPath()); |
| } |
| } |
| } |
| |
| if (platformToolsRevision < 0 || platformToolsRevision > 7) { |
| Map<AndroidCompilerMessageKind, List<String>> map = |
| doCompile(target, manifestFileOsPath, outDirOsPath, resourceDirsOsPaths, libRTxtFilesAndPackages, |
| null, nonConstantFields, proguardCfgOutputFileOsPath, rTxtOutDirOsPath, optimizeRFile); |
| |
| if (map.get(AndroidCompilerMessageKind.ERROR).isEmpty()) { |
| makeFieldsNotFinal(extraRJavaFiles); |
| } |
| |
| AndroidExecutionUtil.addMessages(messages, map); |
| return messages; |
| } |
| else { |
| messages.get(AndroidCompilerMessageKind.ERROR).add( |
| "'Platform Tools' package is out of date. Please update it through Android SDK manager"); |
| return messages; |
| } |
| } |
| |
| private static void makeFieldsNotFinal(@NotNull File[] libRJavaFiles) throws IOException { |
| for (File file : libRJavaFiles) { |
| if (file.isFile()) { |
| final String fileContent = AndroidCommonUtils.readFile(file); |
| FileUtil.writeToFile(file, fileContent.replace("public static final int ", "public static int ")); |
| } |
| } |
| } |
| |
| private static Map<AndroidCompilerMessageKind, List<String>> doCompile(@NotNull IAndroidTarget target, |
| @NotNull String manifestFileOsPath, |
| @NotNull String outDirOsPath, |
| @NotNull String[] resourceDirsOsPaths, |
| @NotNull List<Pair<String, String>> libRTxtFilesAndPackages, |
| @Nullable String customPackage, |
| boolean nonConstantIds, |
| @Nullable String proguardCfgOutputFileOsPath, |
| @Nullable String rTxtOutDirOsPath, |
| boolean optimizeRFile) |
| throws IOException { |
| final List<String> args = new ArrayList<String>(); |
| |
| BuildToolInfo buildToolInfo = target.getBuildToolInfo(); |
| if (buildToolInfo == null) { |
| return Collections.singletonMap(AndroidCompilerMessageKind.ERROR, Collections.singletonList("No Build Tools in the Android SDK.")); |
| } |
| |
| args.add(buildToolInfo.getPath(BuildToolInfo.PathId.AAPT)); |
| args.add("package"); |
| args.add("-m"); |
| |
| if (nonConstantIds) { |
| args.add("--non-constant-id"); |
| } |
| |
| if (resourceDirsOsPaths.length > 1) { |
| args.add("--auto-add-overlay"); |
| } |
| final Set<String> extraPackages = new HashSet<String>(); |
| |
| for (Pair<String, String> pair : libRTxtFilesAndPackages) { |
| extraPackages.add(pair.getSecond()); |
| } |
| if (extraPackages.size() > 0) { |
| args.add("--extra-packages"); |
| args.add(toPackagesString(ArrayUtil.toStringArray(extraPackages))); |
| } |
| |
| if (customPackage != null) { |
| args.add("--custom-package"); |
| args.add(customPackage); |
| } |
| |
| if (rTxtOutDirOsPath != null) { |
| args.add("--output-text-symbols"); |
| args.add(rTxtOutDirOsPath); |
| } |
| args.add("-J"); |
| args.add(outDirOsPath); |
| args.add("-M"); |
| args.add(manifestFileOsPath); |
| |
| for (String libResFolderOsPath : resourceDirsOsPaths) { |
| args.add("-S"); |
| args.add(libResFolderOsPath); |
| } |
| |
| args.add("-I"); |
| args.add(target.getPath(IAndroidTarget.ANDROID_JAR)); |
| |
| if (proguardCfgOutputFileOsPath != null) { |
| args.add("-G"); |
| args.add(proguardCfgOutputFileOsPath); |
| } |
| final Map<AndroidCompilerMessageKind, List<String>> messages = AndroidExecutionUtil.doExecute(ArrayUtil.toStringArray(args)); |
| LOG.info(AndroidCommonUtils.command2string(args)); |
| |
| if (messages.get(AndroidCompilerMessageKind.ERROR).size() > 0) { |
| return messages; |
| } |
| |
| if (optimizeRFile && !libRTxtFilesAndPackages.isEmpty() && rTxtOutDirOsPath != null) { |
| final File rFile = new File(rTxtOutDirOsPath, SdkConstants.FN_RESOURCE_TEXT); |
| // if the project has no resources the file could not exist. |
| if (rFile.isFile()) { |
| final SymbolLoader fullSymbolValues = new SymbolLoader(rFile); |
| fullSymbolValues.load(); |
| final MultiMap<String, SymbolLoader> libMap = new MultiMap<String, SymbolLoader>(); |
| |
| for (Pair<String, String> pair : libRTxtFilesAndPackages) { |
| final File rTextFile = new File(pair.getFirst()); |
| final String libPackage = pair.getSecond(); |
| |
| if (rTextFile.isFile()) { |
| final SymbolLoader libSymbols = new SymbolLoader(rTextFile); |
| libSymbols.load(); |
| libMap.putValue(libPackage, libSymbols); |
| } |
| } |
| |
| for (Map.Entry<String, Collection<SymbolLoader>> entry : libMap.entrySet()) { |
| final String libPackage = entry.getKey(); |
| final Collection<SymbolLoader> symbols = entry.getValue(); |
| final SymbolWriter writer = new SymbolWriter(outDirOsPath, libPackage, fullSymbolValues); |
| |
| for (SymbolLoader symbolLoader : symbols) { |
| writer.addSymbolsToWrite(symbolLoader); |
| } |
| writer.write(); |
| } |
| } |
| } |
| return messages; |
| } |
| |
| @NotNull |
| private static String toPackagesString(@NotNull String[] packages) { |
| final StringBuilder builder = new StringBuilder(); |
| for (int i = 0, n = packages.length; i < n; i++) { |
| if (i > 0) { |
| builder.append(':'); |
| } |
| builder.append(packages[i]); |
| } |
| return builder.toString(); |
| } |
| |
| public static Map<AndroidCompilerMessageKind, List<String>> crunch(@NotNull IAndroidTarget target, |
| @NotNull List<String> resPaths, |
| @NotNull String outputPath) throws IOException { |
| BuildToolInfo buildToolInfo = target.getBuildToolInfo(); |
| if (buildToolInfo == null) { |
| return Collections.singletonMap(AndroidCompilerMessageKind.ERROR, Collections.singletonList("No Build Tools in the Android SDK.")); |
| } |
| |
| final ArrayList<String> args = new ArrayList<String>(); |
| |
| args.add(buildToolInfo.getPath(BuildToolInfo.PathId.AAPT)); |
| |
| args.add(COMMAND_CRUNCH); |
| File tempDir = null; |
| try { |
| if (resPaths.size() > 0) { |
| if (resPaths.size() == 1) { |
| args.add("-S"); |
| args.add(resPaths.get(0)); |
| } |
| else { |
| tempDir = FileUtil.createTempDirectory("android_combined_resources", "tmp"); |
| |
| for (int i = resPaths.size() - 1; i >= 0; i--) { |
| final String resDirPath = resPaths.get(i); |
| final File resDir = new File(resDirPath); |
| |
| if (resDir.exists()) { |
| FileUtil.copyDir(resDir, tempDir, PNG_FILES_FILTER); |
| } |
| } |
| args.add("-S"); |
| args.add(tempDir.getPath()); |
| } |
| } |
| |
| args.add("-C"); |
| args.add(outputPath); |
| |
| LOG.info(AndroidCommonUtils.command2string(args)); |
| return AndroidExecutionUtil.doExecute(ArrayUtil.toStringArray(args)); |
| } |
| finally { |
| if (tempDir != null) { |
| FileUtil.delete(tempDir); |
| } |
| } |
| } |
| |
| public static Map<AndroidCompilerMessageKind, List<String>> packageResources(@NotNull IAndroidTarget target, |
| int platformToolsRevision, |
| @NotNull String manifestPath, |
| @NotNull String[] resPaths, |
| @NotNull String[] osAssetDirPaths, |
| @NotNull String outputPath, |
| @Nullable String configFilter, |
| boolean debugMode, |
| int versionCode, |
| @Nullable String customManifestPackage, |
| @Nullable String additionalParameters, |
| FileFilter assetsFilter) throws IOException { |
| for (String resDirPath : resPaths) { |
| if (FileUtil.isAncestor(resDirPath, outputPath, false)) { |
| throw new IOException("Resource directory " + |
| FileUtil.toSystemDependentName(resDirPath) + |
| " contains output " + |
| FileUtil.toSystemDependentName(outputPath)); |
| } |
| } |
| |
| for (String assetsDirPath : osAssetDirPaths) { |
| if (FileUtil.isAncestor(assetsDirPath, outputPath, false)) { |
| throw new IOException("Assets directory " + |
| FileUtil.toSystemDependentName(assetsDirPath) + |
| " contains output " + |
| FileUtil.toSystemDependentName(outputPath)); |
| } |
| } |
| |
| BuildToolInfo buildToolInfo = target.getBuildToolInfo(); |
| if (buildToolInfo == null) { |
| return Collections.singletonMap(AndroidCompilerMessageKind.ERROR, Collections.singletonList("No Build Tools in the Android SDK.")); |
| } |
| |
| final ArrayList<String> args = new ArrayList<String>(); |
| |
| args.add(buildToolInfo.getPath(BuildToolInfo.PathId.AAPT)); |
| |
| args.add(COMMAND_PACKAGE); |
| |
| for (String path : resPaths) { |
| args.add("-S"); |
| args.add(path); |
| } |
| |
| args.add("-f"); |
| |
| if (platformToolsRevision < 0 || platformToolsRevision > 7) { |
| args.add("--no-crunch"); |
| } |
| |
| if (resPaths.length > 1) { |
| args.add("--auto-add-overlay"); |
| } |
| |
| if (debugMode) { |
| args.add("--debug-mode"); |
| } |
| |
| if (versionCode > 0) { |
| args.add("--version-code"); |
| args.add(Integer.toString(versionCode)); |
| } |
| |
| if (configFilter != null) { |
| args.add("-c"); |
| args.add(configFilter); |
| } |
| args.add("-M"); |
| args.add(manifestPath); |
| |
| File tempDir = null; |
| try { |
| if (osAssetDirPaths.length > 0) { |
| final String[] nonEmptyAssetDirs = getNonEmptyExistingDirs(osAssetDirPaths); |
| |
| if (nonEmptyAssetDirs.length > 0) { |
| if (nonEmptyAssetDirs.length == 1) { |
| args.add("-A"); |
| args.add(nonEmptyAssetDirs[0]); |
| } |
| else { |
| tempDir = FileUtil.createTempDirectory("android_combined_assets", "tmp"); |
| for (int i = nonEmptyAssetDirs.length - 1; i >= 0; i--) { |
| final String assetDir = nonEmptyAssetDirs[i]; |
| FileUtil.copyDir(new File(assetDir), tempDir, assetsFilter); |
| } |
| args.add("-A"); |
| args.add(tempDir.getPath()); |
| } |
| } |
| } |
| |
| args.add("-I"); |
| args.add(target.getPath(IAndroidTarget.ANDROID_JAR)); |
| |
| if (customManifestPackage != null) { |
| args.add("--rename-manifest-package"); |
| args.add(customManifestPackage); |
| } |
| if (additionalParameters != null && additionalParameters.length() > 0) { |
| args.addAll(ParametersListUtil.parse(additionalParameters)); |
| } |
| args.add("-F"); |
| args.add(outputPath); |
| LOG.info(AndroidCommonUtils.command2string(args)); |
| return AndroidExecutionUtil.doExecute(ArrayUtil.toStringArray(args)); |
| } |
| finally { |
| if (tempDir != null) { |
| FileUtil.delete(tempDir); |
| } |
| } |
| } |
| |
| @NotNull |
| private static String[] getNonEmptyExistingDirs(@NotNull String[] dirs) { |
| final List<String> result = new ArrayList<String>(); |
| for (String dirPath : dirs) { |
| final File dir = new File(dirPath); |
| |
| if (dir.isDirectory()) { |
| final File[] children = dir.listFiles(); |
| |
| if (children != null && children.length > 0) { |
| result.add(dirPath); |
| } |
| } |
| } |
| return ArrayUtil.toStringArray(result); |
| } |
| |
| |
| } |